关于Unity中ARPG游戏人物移动(专题十一)
ARPG:动作型角色扮演类游戏
大多数的ARPG游戏都是使用摇杆操作,以第三人称摄像机的方式来跟随主角,实际上人物只走八个方向,上,下,左,右,左上,左下,右下,右上
控制角色移动的思路
1: 在ARPG游戏中,主角人物在摇杆下控制行走;
2: 主角人物遇到障碍物(碰撞器)将不会穿越过去;
3: 摇杆控制主角人物8个方向的行走;
4: 使用CharacterController 角色控制器组件: 让你在受制于碰撞的情况下很容易的进行运动,而不用处理刚体,实际上没有刚体的物理特性。角色控制器不受力的影响,仅当你调用Move函数时才运动。它执行运动,但是受制于其他碰撞器。
本来以前都是在角色上面挂载刚体(用里面的重力)和碰撞器组件,如果碰到其他的刚体还会受力会受到一些不好的影响,用了CharacterController 就不会有这种不相关的物理力的影响了。
5: 调用角色控制器的Move函数移动角色;
6: 根据摇杆的方向旋转人物动画;
CharacterController组件
1: 属性面板属性:
Slope Limit: 角色碰撞器只能爬比这个指定角度低的斜坡:(单位是degree)
Step Offset: 上楼梯模式,小于Step Offset 的台阶,可以直接上去;
Skin Width: 两个碰撞器可以互相渗透深入皮肤宽度,一般设置成radius的10%;
Min Move Distance: 调用Move函数移动的最小移动量,如果移动距离比这个小,将不移动;
center: 相对与transform的位置角色叫胶囊体中心;
height: 胶囊体高度;
Radius: 胶囊体的半径;
2: 碰撞检测:
void OnControllerColliderHit(ControllerColliderHit hit) {},和一般的碰撞器一样。有碰撞后会调用这个接口。只会在和其他带有CharacterController组件的物体发生碰撞时才调用。
目前已知这个角色控制器的碰撞接口只有这个,如果要持续碰撞,我会加一个BoxCollider组件或者Capsule之类的碰撞器,调用三个碰撞接口
3: 重要方法:
Move(Vec3 offset): 移动的距离;
如果一个角色挂载了CharacterController组件。那么要控制这个角色的移动,其实不必改变角色节点的位置,只需对这个组件进行Move操作,角色就会跟着走。
我把这个组件理解为一个可以牵动节点的可以设置特殊移动属性的胶囊体碰撞器组件Capsule Collider
遥杆编写的基本思路与原理
1:以8个方向为例,将整个圆分为 下,右下,右,右上,上,左上, 左, 左下;
2:当我们的遥感中心点位于某一个方向的范围内,那么就属于这个方向;
3.排除一个无效的摇杆区域,在这个区域内主角不会跟着移动
4.角色移动的距离是一个标量,等于速度乘于时间,而角色移动的整个行为是一个向量,所以还要考虑方向,获得摇杆的角度得知运动方向后还要把角色到最终目的地的标量距离分解成X和Z轴方向上的距离。
根据摇杆的方向算出当前距离所对应的向量的分解的系数
5.前面都是摇杆的坐标轴解析,这里是从人物的Y轴的角度看各个欧拉角,也就是编辑器上的Transform组件的Rotation里面的Y的值对应的该主角节点的旋转方向角度,0度是角色面朝前方
实例
1.创建Unity工程项目和文件目录
2.导入人物模型资源和地图资源,以及摇杆包(79)
3.人物模型的材质球shader使用Mobile Diffuse,关联好贴图,设置模型---->Rig---->Animation Type---->Legacy---->Apply
4.在人物模型的Animation里添加跑动的动画,136-161帧是跑动的帧,Wrap Mode---->Loop---->Apply,角色配置完毕,拖进场景中
5.创建一个平面Plane,关联材质,放大10倍
6.不需要添加碰撞器,只要给主角添加CharacterController组件,调整组件的胶囊体到完全盖住主角,它有碰撞区域但是不受力的影响,千万别再加Rigidbody组件了,会使得CharacterController组件无效。
7.添加摇杆,Hedgehog Team---->Easy Touch---->Add Easy Touch For C#,Hedgehog Team---->Easy Touch---->Extension---->Adding a new joystick
8.调整摄像机的位置,到可以看见主角运动的最佳位置
9.在主角下面挂载一个脚本Person来通过摇杆控制角色移动
Person.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; enum DIR//定义一个枚举来区分角色移动的八个方向 { INVALID_DIR = -1, UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3, RU = 4, LU = 5, LD = 6, RD = 7, } public class person : MonoBehaviour { float move_speed = 8.0f;//角色移动速度 CharacterController c_ctrl;//角色控制器组件 public EasyJoystick joystick;//摇杆 float[] x_set;//八个方向在X轴上的分解系数 float[] z_set;//八个方向在Z轴上的分解系数 float[] rot_set;//人物面的朝向的角度表 Vector3 camera_offset; // Use this for initialization void Start() { this.c_ctrl = this.GetComponent<CharacterController>(); //按照上,下,左,右,右上,左上,左下,右下的顺序配置 this.x_set = new float[8] { 0, 0, -1, 1, 0.707f, -0.707f, -0.707f, 0.707f };//cos45=0.707,sin45=0.707 this.z_set = new float[8] { 1, -1, 0, 0, 0.707f, 0.707f, -0.707f, -0.707f }; this.rot_set = new float[8] { 0, 180, -90, 90, 45, -45, -135, 135 }; this.camera_offset = Camera.main.transform.position - this.transform.position;//获取当前摄像机和人物的三维距离 } int get_dir(float r) { if (r >= -Mathf.PI && r < -7 * Mathf.PI / 8) { // 左的一部分 return (int)DIR.LEFT; } else if (r >= -7 * Mathf.PI / 8 && r < -5 * Mathf.PI / 8) {//左下 return (int)DIR.LD; } else if (r >= -5 * Mathf.PI / 8 && r < -3 * Mathf.PI / 8) {//下 return (int)DIR.DOWN; } else if (r >= -3 * Mathf.PI / 8 && r < -1 * Mathf.PI / 8) {//右下 return (int)DIR.RD; } else if (r >= -1 * Mathf.PI / 8 && r < 1 * Mathf.PI / 8) {//右 return (int)DIR.RIGHT; } else if (r >= 1 * Mathf.PI / 8 && r < 3 * Mathf.PI / 8) {//右上 return (int)DIR.RU; } else if (r >= 3 * Mathf.PI / 8 && r < 5 * Mathf.PI / 8) {//上 return (int)DIR.UP; } else if (r >= 5 * Mathf.PI / 8 && r < 7 * Mathf.PI / 8) {//左上 return (int)DIR.LU; } else if (r >= 7 * Mathf.PI / 8 && r < 8 * Mathf.PI / 8) {//左的另一部分 return (int)DIR.LEFT; } return (int)DIR.INVALID_DIR;//无效的区域 } void walk_update() { float x = this.joystick.JoystickTouch.x;//摇杆坐标系的X坐标 float y = this.joystick.JoystickTouch.y;//摇杆坐标系的Y坐标 float len = (x * x + y * y);//不开根号是因为开销太大,而且是在Update里面,每帧都开根号受不了 if (len < (0.5f * 0.5f))//摇杆移动到这片区域是无效的 { return; } // 获取这个方向 float r = Mathf.Atan2(y, x); // 使用反三角函数, 获取向量的角度, [-PI, PI] int dir = this.get_dir(r); if (dir != (int)DIR.INVALID_DIR) { float s = this.move_speed * Time.deltaTime;//每一秒要移动的距离 Vector3 offset = new Vector3(s * this.x_set[dir], 0, s * this.z_set[dir]);//把这个距离分解到X和Z方向上 this.c_ctrl.Move(offset);//每一帧都移动 // 切换人物行走的朝向 Vector3 e_rot = this.transform.eulerAngles; e_rot.y = this.rot_set[dir]; this.transform.eulerAngles = e_rot; // end } // end } // Update is called once per frame void Update() { this.walk_update(); Camera.main.transform.position = this.transform.position + this.camera_offset;//保持人物和摄像机的距离不变 } }
10.运行效果