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.運行效果