http://blog.csdn.net/kakashi8841/article/details/39451739
文章适合人群:对Unity基础组件有一些了解的,想知道怎么在项目中具体应用各种组件。
这篇文章以一个Asset Store上面的例子“Unity Projects Stealth”来讲解Unity的一些知识。所以可能你要对Unity一些概念有个了解。另外,这个例子"Unity Projects Stealth.unitypackage"我已经分享到http://pan.baidu.com/s/1gd6A0L1了,大家也可以去下载下来参照着文章看。
这篇项目会大概分成以下部分来讲(这个目录在后续文章编写中可能会根据需要稍作修改):
1、项目大致了解
2、碰撞器基础
3、控制角色
4、碰撞器及其使用
5、怪物AI
6、事件通知
7、渲染特效
OK。
在此之前先说下本文运行项目时的环境:
系统 :Window 7 X64
Unity:4.3.3f1
1、项目大致了解:
我们打开unity,新建项目,然后导入上面下载的"Unity Projects Stealth.unitypackage"。等导入完毕后就可以看到“Project”视图(如果你没打开,可以从菜单栏“Window”-“Project”打开)里面的结构如下:
里面每个目录大家都可以随便打开看看:
Animations | 一些角色动画 |
Animator | 动画控制器,实际里面啥都没有-_- |
Audio | 音频文件 |
Fonts | 字体 |
Gizmos | 在scene视图用于调试的一些图标 |
Materials | 材质 |
Models | 模型 |
Prefabs | 预设 |
Scenes | 场景文件,实际里面啥都没有-_- |
Scripts | 脚本,实际里面啥都没有-_- |
Shaders | 着色器 |
Textures | 一些贴图纹理 |
Done | 这里面才包含了上面缺少的动画控制器、场景文件和脚本〜· |
大家平常做项目也可以参考上面的做法,对不同的资源放不同目录进行归类整理,而且大家一看到这些文件夹就都知道里面有什么资源。可以减少很多沟通成本,也为自己查找资源带来便利。
好了,如果没什么问题,点击播放按钮,应该就能运行项目了。
使用键盘的WASD或上下左右箭头就可以控制人物走动了。屏幕左下方也有文本给出其它操作的按键。比如"Z"可以打开开关等。
2、碰撞器基础
一般情况下,你想让物体直接有体积,能产生碰撞,那么你需要给这个物体增加一个碰撞器“Collider”。在Unity中,你只需要选择一个对象,然后点击菜单栏的”Component“-“Physics”,里面就有各种各样的碰撞器,你根据自己模型选择一个比较接近的就OK了。Unity里面的碰撞器区分为两种,一种为静态的,另一种动态的。
静态的意思是这个碰撞器在游戏过程中不会发生位移、旋转、缩放。
动态的意思则是说这个碰撞器可能会在游戏过程中发生位移、旋转、缩放。动态碰撞器有两种:
一种是CharacterController。
另一种是普通的碰撞器+刚体组件。
第二种一定要增加刚体组件。不然可能会导致碰撞失效、性能开销增加。(比如Unity一个UI组件NGUI,它新版本的Panel会检测当前对象是否带了Rigibody,如果没有则自动增加一个。就是为了防止开发者做一些界面动画,忘记修改添加Rigibody,导致UI按钮点击失效。)
3、控制角色
控制一个角色在场景中运动,最简单的做法是把一个对象拖到场景中,然后根据按键。设置这个对象transform的position值。这样对象就可以在场景中运动起来。
获取按键可以用Input.GetAxis方法。获取X和Z轴的按键分别可以使用:Input.GetAxis("Horizontal") 和 Input.GetAxis("Vertical")方法。“Horizontal”和“Vertical”其实是在“Edit”-“Project Settings”-“Input”里面配置的。
那么我们这个例子是使用什么方法来让玩家角色运动的呢?
先在“Hierarchy”视图(如果没有这个视图,可以在“Window”-“Hierarchy”打开它)中找到“char_ethan”对象,选中它。
我们先看看它的“Inspector”视图(如果没有可以在“Window”-“Inspector”打开它)。
注意这里除了有基本的Transform组件外,还有“Animator”,“Capsule Collider”,“Rigidbody”组件。这三个组件其实用于做可移动物体其实是黄金组合(当然也有用“Character Controller”的)。
还有“Audio Source”、“Audio Listener”两个分别是播放声音和监听声音的组件。
然后"Done Player Health (Script)","Done Player Inventory (Script)","Done Player Movement (Script)"分别是对角色做控制的脚本。下面我们简单说说每个组件的用途:
A、Animator
其实是Unity内置的一个动画控制器,原理是状态机。比如双击上面的“Animator”面板里面的“Controller”属性
就会出现状态机的编辑窗口
B、Capsule Collider
胶囊体碰撞器。比较简单,没什么好说的。
C、Rigidbody
刚刚上面也说了,动态碰撞器的需要。
关于A、B、C这几个组件上面就只简单介绍到这里,这里不展开讨论。如果有需要,我后面会再单独写文章介绍。
下面开始看看控制代码部分。
D、DonePlayerMovement
这里有个“DonePlayerMovement(Script)”组件,从名字上看就知道应该是控制移动的(所以说命名很重要)。我们打开这个组件的源码,看看。
这个脚本整体来说:
在Awake的时候,先找到脚本需要用到的Animator组件和DoneHashIds组件,然后缓存它们(在后面用到就不需要频繁查找了,节省CPU)。
在FixedUpdate的时候,获取玩家按下的移动键,并处理移动、角色朝向问题。
在Update的时候,获取玩家是否按下“Attract”键,并确定是否播放动画以及声音。
OK,我们主要来看看MovementManagement这个方法:
我们可以发现,除了Rotating方法,基本都是对anim做一些状态设置的方法。那么Rotating方法里面是什么呢?
上面代码其实看出,他也没有实现玩家的位置移动。那是在其它组件里面实现的还是什么其它方式处理的呢?且听下回分解。
---------------------------------------------------------------------------------------------------------------------
好的,这次距离上次时间有点久,本来说一周更新一次,但实在没办法。在公司客户端、服务端都要搞,而且战斗系统重新重构优化,也花了不少时间。望见谅。
我们接上次的话题:代码没有实现玩家的位置移动的代码,但是实际运行的时候却可以看到角色会移动。这是为什么?
我们一起看到“Hierarchy”视图中的“char_ethan”。注意里面的Animator组件(它勾选了“Apply Root Motion”):
我们看到官方文档对于这个选项是这么介绍的:
Root motion is the effect where an object's entire mesh moves away from its starting point but that motion is created by the animation itself rather than by changing the Transform position.
这里大致的意思其实就是:这个选项会在由动画产生移动时,使得物体的mesh会偏离起点。
那也就是物体的移动是做在动画中的,然后勾选Animator的ApplyRootMotion选项来实现的。
关于角色控制中anim.SetFloat的方法的。我们在这里暂时不讲。这个涉及到动画混合,以后会专门讲。
那么角色的移动、旋转就都讲了。接下来我们看看碰撞器。
4、碰撞器及其使用
前面我们说了碰撞器分静态和动态两种。
那么角色“char_ethan”身上的肯定就是动态碰撞器了,注意到“char_ethan”对象有个“Capsule Collider”组件。这就是一个胶囊体碰撞器组件。然后角色还有个“rigidbody”组件,这个是刚体组件。也就是采用前面说的普通碰撞器+刚体组件。
关于各种内置碰撞器的知识。大家不了解的可以看看官网http://docs.unity3d.com/Manual/Physics3DReference.html(我不会告诉你我大部分U3D的知识都在官网学习的。)
这里只要知道碰撞器一个重要的选项“Is Trigger”
只要两个相互碰撞的碰撞器,有任何一个勾选了IsTrigger,那么就会触发这两个碰撞器上面的OnTriggerEnter方法。
如果两个相互碰撞的碰撞器,都没勾选IsTrigger,那么会触发这两个碰撞器上面的OnCollisionEnter,OnCollisionStay,OnCollisionExit方法。
只有在不勾选IsTrigger的时候,才能使用Physics.Raycast进行射线检测。
所以当你的碰撞方法没有调用的时候,请确保IsTrigger和方法是符合的。
注意:这个游戏中玩家走路的时候不会穿墙,因为墙的碰撞器和玩家身上的碰撞器都没有勾选IsTrigger。
5、怪物AI
在“Hierarchy”视图中找到三个怪物“char_robotGuard_001”、“char_robotGuard_002”、“char_robotGuard_003”,这三个怪物上面的组件其实都是一样的。我们来看看“char_robotGuard_001”就可以了。看看怪物是怎么巡逻、然后发现敌人进行追踪的。
怪物身上有如下组件:
A、Animator
这里不再赘述。
B、碰撞器
两个碰撞器?为什么这里要用两个碰撞器?我们先看看两个碰撞器的属性:
一个是胶囊体,没有勾选IsTrigger。这个应该是配合走路,为了不让角色穿过建筑物的。
第二个是球体碰撞器,勾选了IsTrigger。干嘛用的,别急,我们还是先来看那4个脚本吧。
C、DoneEnemySight
二话不说我们先上代码
Awake | 主要是获取一些组件,在后面的逻辑中可以使用。 |
Update | 检查玩家最后一次被发现的位置是否一致,不是则同步位置。 把playerInSight(是否发现玩家)这个状态设置到Animator的状态机里面。 |
OnTriggerStay | 没错,前面说到的第二个碰撞器正是这里要用到的。第二个碰撞器勾选了IsTrigger,就是为了触发这个方法。 当玩家进入这个碰撞器(也就是怪物的视野)触发。 检查玩家是否在怪物前方,而且玩家和怪物中间没有遮挡物。那么设置playerInSight为true。 |
OnTriggerExit | 当玩家离开怪物的视野(碰撞器)。那么设置playerInSight为false。 |
CalculatePathLength | 计算怪物和玩家当前的距离。 |
我们接着看DoneEnemyAI这个组件
D、DoneEnemyAI
还是上代码:
这个组件主要是:如果判断玩家在视野中,则对玩家进行射击。如果玩家最后一次被发现的点不是默认点,则进行追击。否则就进行巡逻。
追击、巡逻,主要都是对NavMeshAgent进行设置来实现控制怪物的移动。关于NavMeshAgent我们以后再专门来讲解。
而射击调用的Shooting方法里面只有一行代码:nav.Stop。那么射击具体在哪里实现的?接着看DoneEnemyShooting组件。
E、DoneEnemyShooting
你知道我肯定会说,看代码。。。没错:
Update的时候可以发现它检查动画的shotFloat属性。然后判断是否应该射击或取消射击。
而射击的激光,这里可以看到是采用LineRenderer实现的。
6、事件通知
以上关于怪物的AI我们就讲完了。但这只是单个怪物的AI。实际这个游戏里面还有用于监控玩家的摄像头、还有红外线墙检测玩家是否通过。但是原理都一样。都是通过监听碰撞器的OnTriggerStay来设置玩家被发现的全局位置。也就是DoneLastPlayerSighting。
个人关于这个demo代码设计的看法:
我个人觉得这种做法不好。大家都自己去获取DoneLastPlayerSighting,然后自己检查里面的状态。然后还可以设置里面的状态。这样会导致程序变量跟踪困难。
如果是我的话,我会把DoneLastPlayerSighting做成是一个消息中心。然后每个敌人订阅这个消息中心的消息(即玩家被发现的位置发生变化的消息),然后由消息中心通知所有订阅的对象。然后订阅的对象也可以提交自己发现的目标给消息中心,如果消息中心确认通过,再通知已经订阅的其它对象。这样做起来代码会更清晰,更容易维护。
下一篇我会专门来讲关于这部分代码的一些优化。
7、渲染特效
A、雾效
你可以通过点击Unity的菜单栏“Edit”-“Render Settings”。这时候在右边的Inspector面板可以通过勾选“fog”选项来开启雾效。“Fog Color”可以调整雾的颜色。
B、游戏中监视器投射到地上的亮点
在“Hiererchy”中找到监视器物体“prop_cctvCam_001”,你会发现它下面有个子物体“cam_frustum_collision”,它上面有个Light组件,Light上的Cookie属性就是产生亮点的贴图。
C、激光墙的激光
其实就是用了一个"Self-Illumin/Diffuse"的shader,这是一个自发光shader,你可以尝试一下,在相同的灯光条件下,采用自发光shader的物体总会比Diffuse的亮。