创建第三人称角色控制器
本教程的宗旨不是指导读者如何使用Unity中的第三人称摄像机,而是教读者如何自己开发一个简单的第三人称角色控制器。
准备角色
首先,需要一个包含3个动画循环的角色,即:空闲循环、行走循环、跑动循环。
创建工程和场景
创建一个虚拟的游戏场景,以便角色在其中走动。
1.创建一个工程
\"File -> New Project\"
导入标准资源包(standard asset package),以便能够获得需要的天空盒(sky box)和地形纹理。
2. 创建一个场景,其中包含纹理地形,天空盒和平行光:
- 第一,创建一个地形:
\"Terrain -> Create Terrain\"。
- 第二,创建一个平行光,并对它进行旋转,从而使之照亮地形:
\"GameObject -> Create Other -> Directional Light\"
- 第三,在Hierarchy中选中地形(Terrain),在Inspector中选中“Paint Terrain Texture”按钮,并添加一个默认的纹理。
- 第四,\"Edit -> Render Settings\",然后在Inspector中设置Skybox的材质。
最终的效果,如下图:
导入角色
在Project Panel中右键单击,选择“Import New Asset”,从弹出的对话框中选择需要导入的角色。当角色导入后,你会在Project panel中看到角色prefab。
输入设置
需要创建的角色控制器很简单,它有一个行走和跑动选项,因此我们需要设置输入选项,以便控制向前,后退,向左,向右,以及在走动和跑动之间切换等。当然,如果你添加了其他的动画,如跳动,攻击等,你还需要更多输入选项。
执行:
\"Edit -> Project Settings -> Input\".
在Inspector上会变成如下形式:
展开Axis部分,其中有17个不同类型的输入形式。在顶部,有“Horizontal”和“Vertical” ,它包含了left, right, forward和backwards输入选项。他们有positive和negative两个值,分别控制不同的方向,支持键盘以及其他类似输入设备,如游戏手柄等。
不需要17个输入选项,因此进行如下改变:
1.将17设置成3。
2.保留“Horizontal”和“Vertical”选项,可以改变其中的关于按键的设置,默认情况下,up, down, left, right对应着键W, A, S and D。
3.选择第三个选项(默认为Fire1),展开它的设置:
- 重新命名 \"Run\"。
- 将Positive 设置为\"left ctrl\"。
- 将Negative设置为空。
- 将alternative Positive和Negative options 设置为空。
下面让我们来看一下Horizontal和Vertical轴,Horizontal指的是你的左右控制,Vertical指的是前后控制。
需要设置一个正值和一个负值,另外还可以提供另外一套按键,设置的时候一定注意
不用发生冲突。
动画周期
在执行角色控制之前,需要对动画进行配置。导入的角色中包含了3个动画循环,如果你打开3ds Max,你会在时间线上发现这些动画,首先是静止动画,然后是走动,接着是跑,当我们导入此角色的时候,这些动画同样会一起导入,但我们需要告诉Unity,这里有3个动画,分别指明每一个动画循环的起始帧和终止帧,并给每一个动画循环进行命名,以便于我们在控制脚本中进行调用。
动画的时间线按照下面的形式进行设计:
Idle循环:0-180帧
Walk循环:200-230帧
Run循环:250-274帧
在Project面板上选择此角色,在Inspector中查看它的设置。从中找到FBX Importer部分,如果你展开了这些设置,你将在下面看到动画序列选项。
我们需要创建3个动画序列,点击右侧的+号,创建3个不同的内容,对他们进行命名,然后设置起始和终止帧,如下:
1. idle (0-180)
2. walk (200-230)
3. run (250-274)
完成上述设置后,点击Apply,Unity会自动打断此动画循环,形成3个不同的序列,我们可以在脚本中使用动画序列的名称。
最终的动画序列如下图:
你或许已经注意到了一个blend模式和一个loop选项,你可以设置动画混合模式和序列循环模式,但我们后面将使用脚本进行控制,所以这里使用默认设置。
当这些设置完成后,我们开始执行动画控制器了(character controller)。
放置角色
将角色从project中拖放到scene视图中,此时角色显示在scene中,同时在hierarchy中也会显示出来。
你或许需要在场景中对角色进行定位,一定要保证它的脚接触到地面(但要保证不要穿过地面,否则对象会掉下去)。
你可以使用窗口右上角的视角控制手柄来切换top,down,side, front和 perspective模式,最终角色的放置方式如下:
添加角色控制器组件(Character Controller)
现在我们需要在角色上添加一个\"Character Controller\" 以便Unity知道此对象为一个可运行的角色,在Hierarchy中选择角色:
Component -> Physics -> Character Controller
当Prefab警告窗口出现后,点击Add,接收这种改变,当添加角色控制组件后,我们需要缩放它的胶囊型碰撞器,以便它的尺寸和角色的尺寸相当,并将它放置在角色的周围(包裹在角色上)。
在Inspector中你可以看到角色控制的设置,调节到front和side视图,然后调整高度和半径,以便与它和角色相匹配,调整胶囊的中心值在正确位置。
对于我们的角色,设置高度为1.85,半径为0.4,Y中心值为0.93;如果你使用自己的角色,设置会有所不同。
调整胶囊的大小使之匹配角色的尺寸,调整它的位置,以便不穿越地形,如下图:
放置摄像机
接下来我们来设置这个摄像机,选择Main Camera,调整它的位置,以便于摄像机在角色的后面调整摄像机的角度,以便于稍微偏转一点,从另外一个角度上观察角色。
如果你此时运行游戏,你可以看到角色,处于闲置状态。你不能移动角色,我们需要写一个脚本来接收用户的输入,然后来掌握控制器,然后告诉角色模型来改变不同的动画循环。
创建并应用脚本(Script)
(1)首要的是创建一个脚本script,
在Project Panel上单击Create按钮,然后选择\"JavaScript\",这将在Project Panel中创建一个新的JavaScript。
(2)对脚本进行重命名\"PlayerCharacter\" 然后将其从Project Panel中拖放到角色上,以便使它们之间建立联系,现在脚本中的内容就应用在了角色上。
(3)打开脚本编辑器,修改其中的代码。
编写脚本
如果你曾经看过其他的3rd person控制器脚本,会发现它们很长,如果你是unity的初学者则会发现这些代码是很复杂的,下面的介绍会尽量的突出重点,从而使之更加容易理解。
(1)首先定义一些控制器要用到的非常重要的变量,
private var walkSpeed : float = 1.0;
private var gravity = 100.0;
private var moveDirection : Vector3 = Vector3.zero;
private var charController : CharacterController;
下面我们对上面的4条代码进行一下解释:
walkSpeed是一个浮点型,存储了移动的速度
gravity是另外一个浮点型,其中包含了重力加速度,用于保持角色位于地面上。
moveDirection是一个Vector3(包含xyz坐标的变量)类型,存储了角色运动的方向。
charController是一个角色控制器变量(character controller variable),我们将在此变量中设置前面添加的控制器,以便于我们能够在脚本中直接引用它。
(2)创建一个Start()函数,他将在游戏运行的时候,自动被调用,此函数会被用于初始化运行时。
function Start()
{
charController = GetComponent(CharacterController);
animation.wrapMode = WrapMode.Loop;
}
Start()函数类似于main() / winmain() 函数,它在脚本加载时自动被调用, Start() 只能被调用一次,不能多次执行。
上面脚本中的第一行,将前面创建的character controller赋给controller变量,第二行设置动画wrapMode 模式为\"loop\"。此语句和前面设置动画的loop选项框有相同的作用. ,但通过此语句,可以得到所有的动画的loop属性。
(3)接下来创建一个Update()函数,它时另一个重要的函数, start()和update() 的区别是,update()函数在每一帧都会自动调用。因此,所以应用在事件列表和实时行为脚本是很有用的。
function Update ()
{
}
我们的行为代码都添加在此函数中。
创建基本行为代码
首先,我们不需要角色能够在空中移动或转身。因此我们必须检测角色是否在地面上,如果角色在地面上,我们运行玩家移动角色,否则,角色将下落,直到接触地面为止。
因此在Update()函数的顶部添加一个if语句:
if(charController.isGrounded == true)
{
}
上面的代码的含义是,当角色控制器接触到地面后,我们可以执行if中的功能\"isGrounded\" 是一个条件,Unity可以自动的理解,当控制器角色接触到地面后,它的值变为true,如果没有接触到地面,则它的值为false。
如果角色没有在地面上,我们需要告诉Unity将角色降落到地面上,因此需要在if后面添加一些代码,提供一些重力的影响效果。
moveDirection.y -= gravity * Time.deltaTime;
上面这行代码将起到重力的作用效果,它将使角色下降,
将下面的代码放置在重力代码的后面:
charController.Move(moveDirection * (Time.deltaTime * walkSpeed));
上面的这一行新代码使用Move函数来移动角色,他是Unity中的函数,用于移动对象,我们将移动方向movedirection这个vector3赋给此函数,此函数中包含了xyz位置,并根据时间值来调整下降的速度。
换句话说,我们在movedirection的方向上以每秒walkspeed的速度移动对象。
如果你保存了脚本,并通过平移工具将角色拉高一定距离,那么当你松开鼠标的时候,你会发现,你的角色会自动向下降落,指导它触碰到地面。如果你的角色穿过地形,可以将角色拉高一些,从而保证角色的脚在地面上或高于地面。如果角色仍旧会穿过地面,则需要检查你事先设置好的碰撞体(胶囊),查看它的底部是不是和角色的脚相匹配,如果它们不匹配则,此碰撞体有可能已经穿过了地面。
因为我们已经施加了重力,下面让我们添加一些代码,以便于角色能够向前行走。在此之前,我们需要检查输入的Vertical axis,然后使之控制角色向前移动。
将下面的代码添加到isGrounded的IF中。
if(Input.GetAxis(\"Vertical\") > .1)
{
}
我们使用Input。GetAxis(“name”)来读取任何vertical axis轴的输入。如果输入值大于0.1,那么就会有一个向前的输入需求。Axes具有*。*值的原因是,模拟控制器有可能有一个读取的范围(控制速度会根据操作杆按下的距离而定)。我们也可以通过它的负值来控制相反的运动。
当按下向前的按钮后,if中的语句就会执行。
接下来,在我们告诉角色向前移动之前,我们需要有一个行走/跑动的切换选项,通常,当Ctrl被按下后,角色开始跑动,否则角色会行走。如果你只有一个行走速度,你可以跳
过此设置,只添加向前行走的代码。但很多游戏都至少具有2个不同的移动速度。因此,让我们创建另外一个if语句,它可以检测是否我们需要行走和跑动了。
将下面的代码放置在GetAxis(\"Vertical\")的if 语句
if(Input.GetButton(\"Run\"))
{
}
else
{
}
这个新的if语句使用GetButton监听Run按钮,如果run按钮按下的时候,我们可以执行角色的跑动,否则我们的角色只会行走。
因此,让我们在if中添加行走和跑动的代码。
animation.CrossFade(\"run\");
walkSpeed = 4;
上面的代码中放入到第一个if中(跑动)。第一行使用
animation.CrossFade(\"animation_name\") 来混合需要的动画,第二行设置行走速度(4是跑动的速度)。我们将下面的代码放置在else中:animation.CrossFade(\"walk\");
walkSpeed = 1;
上面的代码执行相同的功能,但混合仅行走动画,并且设置速度为1.
接下来我们向添加一些代码,使之能够在前进键释放后时角色返回空闲动画。
else
{
animation.CrossFade(\"idle\");
}
上面的代码中我们检测了向前、跑动,行走速度和空闲状态,我们现在想检测后退的运动,在if中添加下面的代码:
else if(Input.GetAxis(\"Vertical\") < -.1)
{
animation[\"walk\"].speed = -1;
animation.CrossFade(\"walk\");
walkSpeed = 1;
}
上面的代码检测负值(控制后退),然后将动画速度设置为-1,向后移动角色,混和动画为行走,速度为1.
此时如果按下play,你能够控制角色向前、向后行走,切换行走和跑动,然而在角色真的在执行动画时,它不能真的向前和向后运动。
将下面的代码放在IsGrounded的if中,放置在最后。
moveDirection = Vector3(0,0, Input.GetAxis(\"Vertical\"));
moveDirection = transform.TransformDirection(moveDirection);
上面的两行代码将设置垂直运动和运动方向,以便于角色真的向前移动。
我们现在还不能进行旋转,因此需要检测水平控制,来进行旋转操作,添加下面的代码,到上面代码的前面。
transform.eulerAngles.y += Input.GetAxis(\"Horizontal\");
上面的代码设置旋转方向,点击play,现在角色可以旋转了,但你可能会发现两个瑕
疵:
(1)如果你执行了后退操作后,当再次执行向前操作时,角色仍旧会执行向后的动画。
(2)If you turn on the spot without moving the character doesnt step.
(3)接下来让我们来修改这两个小bug。
(4)将下面的代码添加到行走的if中的最顶部。
(5)animation[\"walk\"].speed = 1;
(6)这将保证行走动画在你向前走的时候执行向前的操作。
(7)下面在IsGrounded的if中的陈述放置在设置旋转的eulerAngles前面:
(8)// Create an animation cycle for when the character is turning on the spot
(9)if(Input.GetAxis(\"Horizontal\") && !Input.GetAxis(\"Vertical\"))
(10){
(11)animation.CrossFade(\"walk\");
(12)}
(13)if语句检测如果角色在没有向前行走时,进行旋转,在旋转的时候播放行走动画。
(14)现在可以比较完美的执行动画了。
下面需要让摄像机跟踪角色了。
粘贴代码
下面是完整的代码:
private var walkSpeed : float = 1.0;
private var gravity = 100.0;
private var moveDirection : Vector3 = Vector3.zero;
private var charController : CharacterController;
function Start()
{
charController = GetComponent(CharacterController);
animation.wrapMode = WrapMode.Loop;
}
function Update ()
{
if(charController.isGrounded == true)
{
if(Input.GetAxis(\"Vertical\") > .1)
{
if(Input.GetButton(\"Run\"))
{
animation.CrossFade(\"run\");
walkSpeed = 4;
}
else
{
animation[\"walk\"].speed = 1;
animation.CrossFade(\"walk\");
walkSpeed = 1;
}
}
else if(Input.GetAxis(\"Vertical\") < -.1)
{
animation[\"walk\"].speed = -1;
animation.CrossFade(\"walk\");
walkSpeed = 1;
}
else
{
animation.CrossFade(\"idle\");
}
// Create an animation cycle for when the character is turning on the spot
if(Input.GetAxis(\"Horizontal\") && !Input.GetAxis(\"Vertical\"))
{
animation.CrossFade(\"walk\");
}
transform.eulerAngles.y += Input.GetAxis(\"Horizontal\");
// Calculate the movement direction (forward motion)
moveDirection = Vector3(0,0, Input.GetAxis(\"Vertical\"));
moveDirection = transform.TransformDirection(moveDirection);
}
moveDirection.y -= gravity * Time.deltaTime;
charController.Move(moveDirection * (Time.deltaTime * walkSpeed));
按照上面的步骤可以创建一个可以在场景运行的角色,然而,在我们创建的系统中有两个瑕疵:
1.摄像机不能跟踪角色。
2.角色可以在十分陡峭的地形上可以行走。
摄像机跟踪
当角色在走动的时候,我们希望摄像机在后面跟随角色运动,有3种方法可以完成此功能:
(1)从Hierarchy中拖动Main Camer到character prefab上,这样将摄像机绑定到控制器上。
(2)创建一个光滑跟着摄像机(smooth follower camera),它将绑定在角色上。
(3)创建一个轨道摄像机(orbital camera)。
第一种方法是最简单的,它可以将摄像机作为角色的一个子集(child),如果想测试场景运行的时候,可以采用此方法,但这种方法设置的摄像机运动有一些粗糙,它并不是一个最好的跟踪器。
第二种方法,我们施加一个光滑跟踪组件(smooth follow component)到摄像机上,然后将其与角色相关联,从而获得一个更好的跟踪器。
第三种方法稍微有一些复杂,可以参看另外其他的一些指导文档。
在这里我们使用第二种方法,执行下面的基本步骤。
1.在hierarchy中选择摄像机。
2. 选择【Component 】/【Camera Control】/【Smooth Follow】。
这样可以将一个光滑跟踪组件(smooth follow component)添加到摄像机上,我们接下来需要将它和角色连接起来。
1. 在Hierarchy中选择Main Camera。
2. 在Inspector中查看Smooth Follow script部分,如下图。
3. 设置目标(target)为角色的transform。
如果你测试此游戏,摄像机将跟随角色,但将会脱离位置,因此我们需要设置摄像机的位置。
设置Height和Distance,以便于它在希望的位置。当你在恰当的位置上测试游戏,你会发现摄像机会跟随角色运动,并光滑的配合角色运动,没有粗糙的感觉。
坡度的限制
如果你曾经构造了一些地形,如山坡,悬崖等,那么你会发现,及时坡度很陡峭,角色仍旧可以走上去,这是不可能的,因此,我们需要采取技术防止这一情况,设置一个坡度限制可以很方便的防止角色走上一个斜面,因为我们连接在角色上的角色控制器组件(character controller component)中包含了一个坡度限制。
1. 在Hierarchy中选择角色。
2. 在Inspector的角色控制器部分处查看“Slope Limit”。
默认为90度,这是很陡峭了,将它们设置为40-50.
其他说明:
伴随摄像机
当伴随摄像机工作的时候,必须考虑到摄像机碰撞检测问题,但是要做到这些需要掌握其他一些机能,这已经超出了本文的范畴,很快我们要提供一个关于高级摄像机的应用文章。
读取动画时间
知道怎样读取动画的时间是非常有必要的,很简单,只需要下面的代码:
animationTime = animation[\"name_of_animation\"].clip.length;
在角色上添加纹理
我们没有考虑到怎样在角色上添加纹理,但这是很简单的,和很多模型导入方法一样,单独导入纹理,然后在Inspector 中打开材质,从中为每一个材质选择纹理。
因篇幅问题不能全部显示,请点此查看更多更全内容