Archive for the ‘Unity3D’ Category

【Unity3D】关于Vector3向量与Quaternion四元数的转换与应用

星期二, 四月 7th, 2015 3,652 views

在Unity中大家对Vector3应该说是很熟悉了,这里就简单带过一下,Vector3表示一个三维向量(x,y,z),例如Vector3.forward等价于new Vectory(0,0,1),即x=0,y=0,z=1的一个向量。图就不画了,大家高中都学过。
而Quaternion表示一个四元数,何为四元数,例如ai+bj+ck+d这样一个超复数,篇幅有限高数这里也不多说了,我们只关注一下Unity的四元数类的使用就好。在Unity中Quaternion(四元数类)主要用来处理物体的旋转,其实数变量由x,y,z,w四个参数构成,区间[-1,1]。
例如绕y轴旋转180°写做(0,1,0,0),调用公式计算过程如下:

由欧拉旋转(X,Y,Z)转换为四元数(x,y,z,w)
—————————————————->
x = sin(Y/2)sin(Z/2)cos(X/2)+cos(Y/2)cos(Z/2)sin(X/2)
y = sin(Y/2)cos(Z/2)cos(X/2)+cos(Y/2)sin(Z/2)sin(X/2)
z = cos(Y/2)sin(Z/2)cos(X/2)-sin(Y/2)cos(Z/2)sin(X/2)
w = cos(Y/2)cos(Z/2)cos(X/2)-sin(Y/2)sin(Z/2)sin(X/2)
q = (x, y, z, w)
—————————————————->
X = 0
Y = 180
Z = 0
—————————————————->
x = 0
y = 1
z = 0
w = 0

在Unity中这一套转换可以直接调用方法Quaternion.Euler(0, 180, 0)或者transform.Rotate(0, 180, 0)完成。euler即欧拉角的意思,欧拉角可以用Vector进行表示,表示在x,y,z三轴上的旋转角度。
同时transform.rotation即是一个四元数,表示物体的旋转角度方向,我们可以通过Quaternion.Euler和Quaternion.eulerAngles(但这个方法在Unity4之后的版本已经过时)将欧拉角转换为四元数。

因此假设需要让一个物体的顺时针方向以y轴旋转90°有以下几种方法:

1
2
3
4
5
6
7
8
//用四元数的欧拉方法 旋转到90°
transform.rotation = Quaternion.Euler(new Vector3(0, 90, 0));

//用四元数的向量方法 Vector3(1, 0, 0) 表示1个90°方向的向量
transform.rotation = Quaternion.LookRotation(new Vector3(1, 0, 0));

//每一帧自旋转90°
transform.Rotate(new Vector3(0, 90, 0));

以上方法均可在一帧之内旋转顺时针绕y轴旋转90°。但通常游戏中有一个缓慢转动的过程,如果需要一步到位,而是慢慢旋转到90°应该如何去做呢。我们可以使用以下方法:

1
2
3
4
5
//使用四元数的球形差值方法 在每一帧中调整与目标四元数的差值
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(new Vector3(1, 0, 0)), 10 * Time.deltaTime);

//使用Vector3的RotateTowards方法将一个向量转向另一个向量
transform.rotation = Quaternion.LookRotation(Vector3.RotateTowards(transform.forward, new Vector3(1, 0, 0), Time.deltaTime, Time.deltaTime));

从理论上来说球形差值Quaternion.Slerp的转向要平滑一些但相应的运算量大,每一帧都在出现误差值所以每一帧都在校调,而RotateTowards的方法要简洁但粗暴一些,但转向的平滑性不如前者好,特别是在多方向同时操作上,会出现一些不平滑的问题。
综上,建议在设计转向模块的时候,保留两种方案,在实际操作中对比两种方案的显示效果,择优选用。

本篇到此,欢迎大家补充新的方法,用以达到以上两端代码块同样的效果。

BeiTown
2015.04.07

Unity WebPlayer之间调用JS Post方法传参

星期六, 五月 24th, 2014 630 views

首先是功能需求的描述:我们需要在两个WebPlayer之间互相传递参数,其方法是在WebA中的Unity调用一个JS方法将参数进行一个post提交并跳转到WebB中,并在WebB的Unity中获取这个post的内容。
以上方案涉及到几个内容:
i 在Unity中调用Html中的JS方法并传参
ii Js的Post方法
iii Unity获取当前页面中的post内容
下面将逐一讲解:

①在Unity中调用Html中的JS方法并传参
代码如下
Unity中:

1
2
3
void Start () {
                Application.ExternalCall( "testFunction", "testMessage");
}

这里的testFunction是JS中的函数名,testMessage是我们传递的字符串内容,JS代码这样设计

1
2
3
4
5
function testFunction( arg )
{
    // show the message
    alert( arg );
}

此时将Unity打包成WebPlayer,命名为WebA.html,运行效果如下:

(更多…)

Protobuf-net在Unity中的序列化与反序列化

星期日, 五月 18th, 2014 1,310 views

本篇中我们只讲解如何在Unity中对Protobuf-net进行序列化(Serialize)与反序列化(Deserialize),关于Unity的socket网络通信部分我们后续开篇。

首先去Protobuf-net的Google下载点下载protobuf-net类库:https://code.google.com/p/protobuf-net/downloads/list
这里用的是目前最新的protobuf-net r668
下载完毕后打开压缩包,在Full\unity中找到protobuf-net.dll将其添加到项目中。
接下来过程其实类似于我之前的一文章《Protobuf在Java中的简单实例》

①创建proto文件,定义协议格式
首先我们需要创建一个.proto文件来进行测试:

1
2
3
4
5
package com.beitown.net.msg;//包名
message TestMsg {
    required int64 Id=1;
    required string Name=2;
}

每个字段后标记的“=1”、“=2”这里就不解释了,之前的一篇Protobuf文章中已经做过概述。

②编译Proto文件生成.cs文件
这里我们需要用到一个编译工具ProtoGen,在之前下载的protobuf-net r668.zip中就有。
为了方便管理.proto和.cs文件以及编译快捷,我们可以写一个bat脚本来实现自动编译.proto文件,脚本如下:

1
2
3
@echo off
for /f "delims=" %%i in ('dir /b proto "proto/*.proto"') do protogen -i:proto/%%i -o:cs/%%~ni.cs
pause

(更多…)

Protobuf-net在Unity WebPlayer中的兼容解决方案

星期四, 五月 15th, 2014 1,489 views

由于项目需要所以使用了Unity + Protobuf。
但是问题随之而来,因为Protobuf是JIT运行时动态编译的,而Unity不支持这种运行时动态编译,同时Protobuf是基于.net2.0框架的,因此当项目发布在PC时,出现了异常:

1
2
3
4
5
6
7
8
9
10
ArgumentOutOfRangeException: Argument is out of range.

Parameter name: options
at System.Text.RegularExpressions.Regex.validate_options (RegexOptions options) [0x0003a] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/System/System.Text.RegularExpressions/Regex.cs:248

at System.Text.RegularExpressions.Regex..ctor (System.String pattern, RegexOptions options) [0x00017] in /Users/builduser/buildslave/monoAndRuntimeClassLibs/build/mcs/class/System/System.Text.RegularExpressions/Regex.cs:214

at Google.ProtocolBuffers.Descriptors.DescriptorPool..cctor () [0x00000] in :0
Rethrow as TypeInitializationException: An exception was thrown by the type initializer for Google.ProtocolBuffers.Descriptors.DescriptorPool
......

这里使用的是Protobuf-Csharp包,通过排查,在发布时将PlayerSetting中Api Compatibility Level 设置成.Net 2.0即可解决。

如图

但当需要发布到WebPlayer的时候,发现一个基本无解的问题——WebPlayer PlayerSetting中的Api Compatibility Level按钮是灰色的!只支持.Net 2.0 subset模式,因此发布后依然无法解决protobuf异常问题。
(更多…)

Unity3D_TransForm相对世界坐标位移

星期四, 三月 14th, 2013 653 views

在Unity3D的简单位移教程里我们经常看到如下代码

1
2
3
4
function Update ()
{
        transform.Translate(new Vector3(1,0,0)*Time.deltaTime);
}

这里的Vector3 即创建一个方向向量,此时运行,物体会沿着其X轴方向位移,如图,即红色箭头方向

同理 修改Vector3的参数,即可使物体向任意一个方向进行位移,这里的Time.deltaTime在文档里的解释是:

“放在Update()函数中的代码是以帧来执行的.如果我们需要物体的移动以秒来执行.我们需要将物体移动的值乘以Time.deltaTime”

这样就确保了移动速度不至于过快

问题来了,这个物体的旋转方向已经改变,使用上文的方法,物体只会继续向着自身的x轴方向移动,如图,我们旋转角度已偏移,自身坐标角度与世界坐标角度已不重和

如果需要仍旧按照世界坐标的X轴方向运动
这里有两种方法,方法一,继续使用传统方法,但要进行一个向量的换算

方法二,查看API可以知道,translate这个方法其实还有第二个参数relativeTo(相对于)

function Translate (x : float, y : float, z : float, relativeTo : Space = Space.Self) : void

默认不写的话即为Space = Self,如果要相对于世界坐标的话,代码如下

1
2
3
4
function Update ()
{
        transform.Translate(new Vector3(1,0,0)*Time.deltaTime,Space.World);
}

此时即可相对于世界坐标的x轴方向运动了

同理如果希望相对于任意Gameobject的坐标进行运动,这里将Space.World 改成 GameObject.Transform 即可

虽然只是一个U3D函数拓展的使用方法,但常常容易被人们忽略,继而去自己编写向量转换或其他的方法。其实有时候多查一下API,看看是否已有可实现的方法,不失为一个好的习惯。

谢谢关注。共勉。

BeiTown
2013.03.14

Unity3D-FSM有限状态机的简单设计

星期三, 三月 13th, 2013 3,725 views

在之前的文章里介绍了一个基础U3D状态机框架(Unity3D游戏开发之状态流框架)即大Switch的枚举状态控制。这种方法虽然容易理解,编程方法也相对简单,但是弊端是当状态变得复杂之后,或需要添加一种新的状态时,会显得非常混乱并且难以下手。故我们需要引进一种更高级的状态机技术来避免这些问题。网上有一些讲述U3D-FSM状态机的文章,但都不针对基础讲解,而且大多带有冗余的与状态机不相关的代码,基础不好的读者容易看不清FSM状态机的核心所在。这里针对网上的一些文章和代码做了一个整理,意图使之简单易懂。

这里关于FSM有限状态机这类名词的解释这里就不再说明了,感兴趣的朋友可以自己去百度下(度娘链接),本文只说重点。

首先是状态机基类State.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * 状态基类
 */

public class State[entity_type>
{
    public entity_type Target;
    //Enter state  
    public virtual void Enter (entity_type entityType)
    {
       
    }
    //Execute state
    public virtual void Execute (entity_type entityType)
    {
       
    }
    //Exit state
    public virtual void Exit (entity_type entityType)
    {
       
    }

}

基类之所以设计成含有3个小的状态方法是因为,通常在游戏中有些行为都只是在进入或退出某个状态时出现的,并不会发生在通常的更新步骤中。这样设计就可以有效的将持续性调用语句和一次性调用语句有效的区分开来。(举例:发送技能时的特效,有些是持续性而有些又是一次性的)

接下来我们编写状态机代码,来使直接的这个基类的各个方法运作起来:
(更多…)

Unity3D协程闭包事件执行定时器

星期一, 三月 11th, 2013 549 views

介于U3D没有一个自带的事件执行定时器,所以打算自己动手做一个,基本思路如下,先用伪代码来写了

1
2
3
4
5
6
void timer(int time,Func func){

wait(time);//等待N秒
func();//执行传入的函数

}

最开始的思路是用闭包加线程来实现,之后和群里的朋友讨论了下,经过提示其实可以避免去碰线程这种bug多发产物,取而代之的是U3D包里自带的一个协程MonoBehaviour.StartCoroutine

先来完善下我们的事件定时器,惯例直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using UnityEngine;
using System.Collections;
using System;

public class EventTimer : MonoBehaviour {


    public IEnumerator Timer(float waitTime , Func[bool> func)
    {
        yield return new WaitForSeconds(waitTime);
        func();
   
    }

}

(更多…)

Unity3D摄像机高级控制

星期一, 三月 11th, 2013 1,194 views

在游戏场景中,通常摄像机如果需要进行一些不规则的固定运动,比如片头的场景摄像机的移动,此时如果通过代码来控制,虽然可以实现但调试过程并不直观,所以我们可以通过绑定摄像机动作来达到这一效果。
创建动作的过程这里不再赘述,前面的文章中有详细的讲解。

首先将创建好的动作绑定至摄像机,利用关键帧补间动画进行摄像机运动控制,如图

点击播放即可查看摄像机的实时运动效果,这里主要是针对xyz的位置及角度6个参赛进行调整,从而实现电影版的摄像机运动效果,这里我们且将动作命名为Move,并绑定至1号摄像机上

接下来我们创建一个摄像机管理脚本 CameraManger.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using UnityEngine;
using System.Collections;

public class CameraManger : MonoBehaviour {

    public GameObject Camera_1;
    public GameObject Camera_2;
    public GameObject Camera_3;
   

    // Use this for initialization
    void Start () {
   
    }
   
    // Update is called once per frame
    void Update () {


        switch (Main.state)
        {
            case Main.STATE.READY:
                onActiveFalse();
                Camera_1.SetActive(true);
                Camera_1.animation.Play("move");

                break;

            case Main.STATE.START:
                onActiveFalse();
                Camera_2.SetActive(true);                
                break;

            default:
                break;

        }
   
    }

    void onActiveFalse()
    {
        Camera_1.SetActive(false);
        Camera_2.SetActive(false);
        Camera_3.SetActive(false);
    }
}

(更多…)

Unity3D碰撞检测及Trigger触发器的使用及注意事项

星期六, 三月 9th, 2013 5,409 views

首先新建一个Cube,其本身就已自带了碰撞器Collider,如图

要使用Trigger检测物体进入,则需要勾选Is Trigger选项
碰撞检测触发器Trigger 有别于碰撞检测器 Collision,其可以不受物理效果影响,一些可穿透区域的碰撞检测,可以使用Trigger来进行碰撞检测
常用方法如下:
OnTriggerEnter 进入时
OnTriggerExit 离开时
OnTriggerStay 处于时

同上碰撞检测器 Collision 也有3个方法:
OnCollisionEnter
OnCollisionExit
OnCollisionStay

这里我们需要为跑道添加一个终点撞线的机制,所以使用可穿透的碰撞检测触发器Trigger

代码如下:

1
2
3
4
5
    void OnTriggerEnter(Collider collider)
    {
        print(collider.gameObject.name + ":" + Time.time);
     
    }

OnTriggerEnter传入的参数必须为Collider,故能触发该函数的对象必须具有Collider组建。这里我们用的是角色控制器Character Controller 自带了Collider组建
此时每当有角色撞线时即会打印角色名及系统时间
(更多…)

Unity3D角色控制器的使用及游戏对象移动速度控制

星期四, 三月 7th, 2013 1,489 views

首先是角色控制器的添加 选定模型对象 在其Inspector视图中选择Add Component -> Physics -> Character Controller 出现组建如下图

此时模型对象上会出现一个胶囊体,通过改变Center、Radius以及Hight的值来调整胶囊体的位置,使之恰好包裹住模型对象,如下图


(更多…)