1 準備工作
我把cling錯看成了climb,所以做了個爬牆的功能。這裏把cling動畫就加入到動態切換裏面,然後添加一個參數Wall(Boolean)控制。
參數判斷:
- jump->cling:wall爲true,在牆上。
- cling->jump:wall爲false,不在牆上;ground爲false,不在地上。
- idle->cling:wall爲true,在牆上。
- cling->idle:wall爲false,不在牆上。
- run->cling:wall爲true,在牆上。
2 爬牆
總代碼的思路:把角色分爲在地上、在牆上、空中三種狀態。在地上的時候,可以移動、爬牆、跳躍;在空中的時候,可以爬牆、二段跳;在牆上的時候,可以上下爬、跳躍。至於爬牆的思路,只需要修改y軸的座標就好了。(總代碼會在本文最後給出)
遇到的問題:
- 判斷左右碰撞,且要和下面碰撞做區分:搜了很多資料,最終決定採用Physics2D.OverlapCircle來判斷碰撞的方向。
Physics2D.OverlapCircle介紹
作用:檢查對撞機是否落在圓形區域內(用來設置檢測碰撞點,檢測碰撞可以更詳細)。
public static Collider2D OverlapCircle(Vector2 point, float radius, int layerMask = DefaultRaycastLayers, float minDepth = -Mathf.Infinity, float maxDepth = Mathf.Infinity);
point | 圓心 |
radius | 半徑 |
layerMask | 檢測的特定Layer的對象 |
minDepth | z軸大於或等於此值的對象 |
maxDepth | z軸小於或等於此值的對象 |
Gizmos類介紹
Gizmos類:Gizmos用於Scene中給出一個可視化的調試或輔助設置。
作用:爲了讓設置碰撞點範圍可見,方便可視化調整。
注意:
- Gizmos繪製必須在OnDrawGizmos或OnDrawGizmosSelected函數中完成。
- OnDrawGizmos隨程序啓動運行,會在每一帖被調用;OnDrawGizmosSelected當鼠標點擊時候運行
這次代碼要用到的繪製函數:
- Gizmos.color = Color.red;//設置顏色
- Gizmos.DrawWireSphere((Vector2)向量, 半徑);//設置圓形
碰撞點繪製代碼
public Vector2 bottomOffset, rightOffset, leftOffset;//下左右相對於角色中心的二維向量座標
void OnDrawGizmos()//繪製輔助線
{
Gizmos.color = Color.red;//輔助線顏色
//繪製圓形輔助線
Gizmos.DrawWireSphere((Vector2)transform.position + bottomOffset, collisionRadius);
Gizmos.DrawWireSphere((Vector2)transform.position + rightOffset, collisionRadius);
Gizmos.DrawWireSphere((Vector2)transform.position + leftOffset, collisionRadius);
}
上面的座標參數設置成了public,這樣就可以在inspector裏面可以觀察到參數,根據效果來修改。
判斷碰撞方向代碼
bool isGround()//判斷是否碰地
{
return Physics2D.OverlapCircle((Vector2)transform.position + bottomOffset, collisionRadius, groundLayer);//判斷是否碰到地面
}
bool touchWall()//判斷是否碰到牆
{
return Physics2D.OverlapCircle((Vector2)transform.position + rightOffset, collisionRadius, groundLayer)
|| Physics2D.OverlapCircle((Vector2)transform.position + leftOffset, collisionRadius, groundLayer);
}
3 跳躍修改
問題:
- 跳躍後橫向速度不固定:之前寫的跳躍,跳躍起來之後還能夠左右移動,就算不能去掉左右移動,也還是會收到初始跳躍的橫向速度影響。
- 爬牆時跳躍後方向要修改:爬牆的方向如果是向右,那麼跳躍的話方向就會向左。
代碼思路:
- 跳躍後橫向速度固定:x方向的速度修改,按照之前跳躍y的速度修改方法就好了。
- 爬牆跳躍方向修改:用一個face參數(1表示右邊,-1表示左邊)記錄當前的角色朝向,然後如果跳躍就用face來改變角色朝向,速度=數值*face相反數值。至於face參數的獲取,在每次按下左右按鍵的時候獲取。
跳躍的速度變化核心代碼
rig.velocity = new Vector2(face*moveForce*Time.deltaTime, jumpForce * Time.deltaTime);
爬牆跳躍方向核心代碼
參數face
private float face=1;//記錄角色朝向,初始爲向右
參數face修改
float faceDirection = Input.GetAxisRaw("Horizontal");
if (faceDirection != 0)//按下按鍵,左邊爲-1,右邊爲1,不按就是0
{
face = faceDirection;//face參數的獲取,在每次按下左右按鍵的時候獲取
}
爬牆跳躍時候,進行的方向修改
face = (face == 1) ? -1 : 1;//在牆上跳出的話,動畫要和原來相反
transform.localScale = new Vector2(face, 1);
4 組織Inspector中的屬性顯示
涉及到的參數越來越多,需要來組織一下這些參數在Inspector上的顯示。
通過 “Header”、“Tooltip”、“Space”、“HideInInspector” 、“SerializeField”和屬性來組織Inspector中的屬性顯示。
[Header("hello")]:會出現一行字符串“hello”
[Tooltip("hello")]Tooltip:寫在某個變量上方,就可以在instpector面板上點擊到對應的變量後,提示註釋字符串"hello"
[Space]:會在Inspector上的顯示顯示空行
[HideInInspector]:公有變量可以在Inspector上的顯示,所以在定義變量前面加上就可以隱藏顯示了
[SerializeField]:私有變量不能在Inspector上的顯示,所以在定義變量前面加上就可以顯示了
5 最終完整代碼
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private Rigidbody2D rig;//剛體
private Animator Anim;//角色的Animator
[Header("Layers")]
public LayerMask groundLayer;//用來開啓layer
[Space]
[Header("Speed")]
[SerializeField] private float moveSpeed=360f;//移動速度
private float climbSpeed=100f;//爬行速度
[Space]
[Header("Force")]
private float moveForce=100f;//移動力
private float jumpForce=575f;//跳躍力
[Space]
[Header("Frequency")]
private int jumpMax=2;//跳躍次數的上線
[SerializeField] private int jumpNum=0;//當前跳躍的次數
[Space]
[Header("Booleans")]
[SerializeField] private bool falling = false;//用來標記是否是下落狀態
[SerializeField] private bool onWall;//是否在牆上
[SerializeField] private bool onJumping;//是否正在跳躍中
[SerializeField] private bool onGround;//是否正在地上
[Space]
[Header("Collision")]
private float collisionRadius = 0.15f;//碰撞半徑
public Vector2 bottomOffset, rightOffset, leftOffset;//下左右相對於角色中心的二維向量
private Collider2D coll;//角色的碰撞器
[Space]
private float face;//記錄角色朝向
//初始化
void Start()
{
rig = GetComponent<Rigidbody2D>();//獲取主角剛體組件
Anim = GetComponent<Animator>();//獲取主角動畫組件
coll = GetComponent<Collider2D>();//獲取角色碰撞器
groundLayer = 1 << 8;//開啓Ground的layer層,Ground在layer8
onWall = false;//初始不在牆上
onJumping = false;//初始不正在跳躍
onGround = true;//初始在地上
face = 1;//初始朝向向右邊
moveSpeed = 360f;//移動速度
}
void FixedUpdate()
{
Movement();
changeAnimator();
}
//控制移動
void Movement()
{
float moveMultiple = Input.GetAxis("Horizontal");
float faceDirection = Input.GetAxisRaw("Horizontal");
//獲取縱軸
float verticalMove = Input.GetAxis("Vertical");
if (onGround)//在地上
{
move(moveMultiple, faceDirection);//左右移動
if (touchWall())//是否可爬牆,碰到牆
{
if (verticalMove>0)//是否有爬牆的行爲,按向上鍵
{
onGround = false;
onWall = true;
Climb(verticalMove);
}
}
if (Input.GetButtonDown("Jump"))
{
onGround = false;
onJumping = true;
Jump();
}
}
else if (onJumping)//在空中跳躍
{
if (touchWall())//可以爬牆
{
onJumping = false;//關閉現在狀態
onWall = true;//開啓下一個狀態
Climb(verticalMove);//爬行
jumpNum = 0;//跳躍次數清零
}
else if (Input.GetButtonDown("Jump"))//二段跳
{
Jump();
}
}
else if (onWall)//在牆上
{
if(Input.GetButtonDown("Jump"))
{
onWall = false;
onJumping = true;
face = (face == 1) ? -1 : 1;//在牆上跳出的話,動畫要和原來相反
transform.localScale = new Vector2(face, 1);
Jump();
}
else if (touchWall())//如果碰到牆,就說明還在爬
{
Climb(verticalMove);
}
else//如果說爬到了末端,就可以有跳躍行爲,不然的話難爬到地面
{
onWall = false;
onJumping = true;
Jump();
}
}
}
bool isGround()//判斷是否碰地
{
return Physics2D.OverlapCircle((Vector2)transform.position + bottomOffset, collisionRadius, groundLayer);//判斷是否碰到地面
}
bool touchWall()//判斷是否碰到牆
{
return Physics2D.OverlapCircle((Vector2)transform.position + rightOffset, collisionRadius, groundLayer)
|| Physics2D.OverlapCircle((Vector2)transform.position + leftOffset, collisionRadius, groundLayer);
}
void move(float moveMultiple,float faceDirection)//移動代碼
{
//角色左右移動
if (moveMultiple != 0)
{
//velocity表示速度,Vector表示向量
rig.velocity = new Vector2(moveMultiple * moveSpeed * Time.deltaTime, rig.velocity.y);//輸入x,y向量,數值*方向
}
//角色朝向修改
if (faceDirection != 0)
{
//Scale,代表縮放,Localscale代表的是當前物體相對於父物體的縮放,通過正負不修改大小來實現左右朝向修改
transform.localScale = new Vector2(faceDirection, 1);
face = faceDirection;
}
}
void Jump()//跳躍代碼
{
if(jumpNum < jumpMax)
{
rig.velocity = new Vector2(face*moveForce*Time.deltaTime, jumpForce * Time.deltaTime);
jumpNum++;
}
}
void Climb(float verticalMove)//爬牆代碼
{
rig.velocity = new Vector2(rig.velocity.x,climbSpeed * verticalMove* Time.deltaTime);//輸入x,y向量,數值*方向
}
void changeAnimator()//動畫切換
{
if (onGround)//如果在地上
{
Anim.SetFloat("speed", Mathf.Abs(rig.velocity.x));//速度是向量
}
if (onWall)//如果在牆上
{
Anim.SetBool("wall", true);//肯定是碰到牆
if (Anim.GetBool("ground") == false)//如果是跳躍狀態下碰到牆,就把碰到地面設爲真,方便轉換到idle狀態
{
Anim.SetBool("ground", true);
}
}
else
{
Anim.SetBool("wall", false);
}
if (onJumping)//如果在跳躍中
{
if (Anim.GetBool("ground"))//第一次跳
{
falling = true;
Anim.SetBool("ground", false);
}
else
{
if (falling&&isGround())//如果下落碰地,意爲要轉變到落地狀態
{
Anim.SetBool("ground", true);
jumpNum = 0;//落地的話,當前跳躍次數清零
falling = false;
onJumping = false;
onGround = true;
}
else if(rig.velocity.y < 0)//如果速度爲下落,則設置下落狀態爲true
{
falling = true;
}
}
}
}
void OnDrawGizmos()//繪製輔助線
{
Gizmos.color = Color.red;//輔助線顏色
//繪製圓形輔助線
Gizmos.DrawWireSphere((Vector2)transform.position + bottomOffset, collisionRadius);
Gizmos.DrawWireSphere((Vector2)transform.position + rightOffset, collisionRadius);
Gizmos.DrawWireSphere((Vector2)transform.position + leftOffset, collisionRadius);
}
}