unity自帶的2D剛體在坡上會往下滑。而且也是會有可能穿牆,或碰撞到其他物體時鬼畜抖動。
設計思路
1、利用射線進行判斷碰撞防止穿牆:
2、在地面時往地面發射兩條射線,獲得的兩個點,兩個點形成的線,這個線是角色的移動向量。
下面是一個自定義的一個橫板2D角色控制器,包含有簡單的移動和跳躍。
代碼:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterController2D : MonoBehaviour
{
/// <summary>
/// 角色移動軸
/// </summary>
public Vector2 axis;
/// <summary>
/// 角色受到的重力
/// </summary>
public float gravity = 10;
/// <summary>
/// 角色限制的移動速度
/// </summary>
public float moveSpeed = 30;
/// <summary>
/// 中心點偏移
/// </summary>
public Vector3 offset;
/// <summary>
/// 最大速度向量
/// </summary>
public float maxSpeed = 100;
/// <summary>
/// 前長度:該值決定角色到前牆的碰撞距離
/// </summary>
public float forwardLength = 1;
/// <summary>
/// 底長度:該值決定角色到地面的距離
/// </summary>
public float bottonLnegth = 0.5f;
/// <summary>
/// 底下的先與旁邊的線偏移距離
/// </summary>
public float bottonForwardOffset = 0.25f;
/// <summary>
/// 上一次跳躍的到現在時間
/// </summary>
public float JumpTime { get => jumpTime; }
/// <summary>
/// 返回X軸的絕對值
/// </summary>
public float AxisAbs { get => axisAbs; }
/// <summary>
/// 角色是否進行了X反轉
/// </summary>
public bool ReversalX { get => reversalX; }
/// <summary>
/// 角色是否在地面
/// </summary>
public bool OnGround { get => onGround; }
/// <summary>
/// 角色的實際位移速度
/// </summary>
public Vector3 Velocity { get => velocity; }
private bool reversalX;
private float axisAbs;
private bool onGround;
private Vector3 velocity;
private float jumpTime;
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
Gizmos.DrawLine(transform.position + offset, transform.position + offset + Forward * (forwardLength + AxisAbs * Time.deltaTime));
Vector3 forward = transform.position + offset + Forward * bottonForwardOffset;
Gizmos.DrawLine(transform.position + offset, (transform.position + offset) + Vector3.down * bottonLnegth);
Gizmos.DrawLine(forward, forward + Vector3.down * bottonLnegth);
}
void Update()
{
if (axis.x < 0)
{
reversalX = true;
}
else if (axis.x > 0)
{
reversalX = false;
}
axisAbs = Mathf.Abs(axis.x);
IsOnGround();
velocity = Vector3.ClampMagnitude(velocity, maxSpeed);
transform.position += velocity * Time.deltaTime;
jumpTime += Time.deltaTime;
}
/// <summary>
/// 跳躍
/// </summary>
public void Jump()
{
if (OnGround)
{
onGround = false;
velocity += Vector3.up * 20;
jumpTime = 0;
}
}
/// <summary>
/// 獲取角色前面
/// </summary>
public Vector3 Forward
{
get
{
Vector3 forward = transform.right;
forward.x = ReversalX ? forward.x * -1 : forward.x;
return forward;
}
}
private void IsOnGround()
{
RaycastHit2D bottonHit, forwardHit;
GetBottonRaycastHit(out bottonHit, out forwardHit);
if (jumpTime > 0.2 && bottonHit && forwardHit)
{
onGround = true;
velocity.y = 0;
transform.position = new Vector3(
transform.position.x,
bottonHit.point.y + bottonLnegth - 0.1f - offset.y,
transform.position.z);
//速度方向
Vector2 speedDirection = (forwardHit.point - bottonHit.point).normalized;
//坡度
velocity = speedDirection * AxisAbs;
}
else
{
velocity.y -= gravity * Time.deltaTime * moveSpeed;
}
//判斷是否撞牆
RaycastHit2D rf = GetForwardHit();
if (rf)
{
velocity.x = 0;
transform.position = new Vector3(
rf.point.x + (axis.x < 0 ? forwardLength - 0.1f : -forwardLength + 0.1f) - offset.x,
transform.position.y,
transform.position.z);
}
}
private void GetBottonRaycastHit(out RaycastHit2D bottonHit, out RaycastHit2D forwardHit)
{
//前射線位置
Vector3 forward = transform.position + offset + Forward * bottonForwardOffset;
//發射射線
bottonHit = Physics2D.Raycast(transform.position + offset, Vector2.down, bottonLnegth, LayerMask.GetMask("Ground"));
forwardHit = Physics2D.Raycast(forward, Vector2.down, bottonLnegth, LayerMask.GetMask("Ground"));
}
public RaycastHit2D GetForwardHit()
{
float line = forwardLength + AxisAbs * Time.deltaTime;
//射線
RaycastHit2D rf = Physics2D.Raycast(transform.position + offset, Forward, line, LayerMask.GetMask("Ground"));
Debug.DrawRay(transform.position + offset, Forward * line, Color.green, 0.1f);
return rf;
}
}