目录
一、二段跳、蹬墙跳
二、扶墙下滑
一、二段跳、蹬墙跳
GitHub - prime31/CharacterController2D
下载工程后直接打开demo场景:DemoScene(Unity 2019.4.0f1项目环境)
Player物体上的CharacterController2D,Mask添加Wall层(自定义墙体层)
将场景里其中一个障碍物设置为Wall层 Wall标签 并拉伸为墙体高度
Player物体上的Demo Scene脚本控制玩家移动 二段跳 蹬墙跳
蹬墙跳要调整好Jump On Wall H Force 横向力 和 Jump On Wall V Force 纵向力 数值才能表现正常,其中 V Force 是在 1的基础上的增量值,这里的力并非物理力实际是速度增量倍率。
跳跃对Y轴速度影响是用公式:根号2gh
代码则是:Mathf.Sqrt(2f * jumpHeight * -gravity),加速度是重力反方向,跳跃高度固定,则计算出了速度增量,之后用它乘以(1+V Force)得出的一个对Y轴速度影响的增量。
上例子中速度增量根号2gh是8.48,因此每次蹬墙跳Y速度增量是8.48*1.335=11.32
代码默认有重力对Y轴速度影响:_velocity.y += gravity * Time.deltaTime; 即每秒Y轴速度会减去重力加速度(墙上为-24,地面为-25)若帧数是30,则每帧会减少0.8。具体可以将_velocity参数公开查看变化,实际蹬墙跳会离开墙体,重力加速度为-25,可自行调整这些参数来达到理想效果
修改部分代码:
private float rawGravity;
private int jumpLevel;//跳跃阶段 1段跳 2段跳
private int dir; //朝向 -1左 1右
public LayerMask jumpOnWallMask = 0;//墙体Layer层遮罩
private bool isHoldWall; //是否在墙上
public float jumpOnWallHForce = 1; //墙上跳跃横向力度
public float jumpOnWallVForce = 2; //墙上跳跃纵向力度
public float gravityOnWall = -24f;
void Awake()
{
//... ...
rawGravity = gravity;
}
void Update()
{
if (_controller.isGrounded)
{
gravity = rawGravity;
_velocity.y = 0;
jumpLevel = 0;
}
//朝着dir方向发射长度为(碰撞体宽度+自身皮肤厚度)的射线
RaycastHit2D hit = Physics2D.Linecast(playerBottomTrans.position, playerBottomTrans.position +
new Vector3(dir * (Mathf.Abs(transform.localScale.x) * _controller.boxCollider.size.x / 2f + _controller.skinWidth), 0, 0), jumpOnWallMask);
if (hit && hit.collider.tag == "Wall")
{
isHoldWall = true;
gravity = gravityOnWall; //可调整由rawGravity随着时间降低到gravityOnWall
}
else
{
isHoldWall = false;
gravity = rawGravity;
}
if ( Input.GetKey( KeyCode.RightArrow ) )
{
//... ...
dir = 1;
}
else if( Input.GetKey( KeyCode.LeftArrow ) )
{
//... ...
dir = -1;
}
else
{
//... ...
}
//原点击UpArrow代码删除,改为如下
//点击向上
if (Input.GetKeyDown(KeyCode.UpArrow))
{
//未在墙上
if (!isHoldWall)
{
//在地面起跳 (1级跳)
if (_controller.isGrounded)
{
jumpLevel = 1;
_velocity.y = Mathf.Sqrt(2f * jumpHeight * -gravity);
_animator.Play(Animator.StringToHash("Jump"));
}
else
{
//1级跳途中,再次起跳(2级跳)
if(jumpLevel == 1)
{
jumpLevel = 2;
_velocity.y = Mathf.Sqrt(2f * jumpHeight * -gravity);
_animator.Play("Jump");
}
}
}
else
{
//墙上可连续起跳,若想限制只能2段跳,则要类似上面代码写法
//在墙上
_velocity.x += -dir * jumpOnWallHForce;
//仅在墙上会受到重力因此想再次起跳上升 必须比重力还要大的力 1+jumpOnWallForce
//若在墙体上且在地面上,则不要加这个jumpOnWallVForce力,否则贴墙就起跳会让你飞起来!
_velocity.y += Mathf.Sqrt(2f * jumpHeight * -gravity) * (1 + (_controller.isGrounded ? 0 : jumpOnWallVForce));
_animator.Play("Jump");
}
}
}
完整代码:
using UnityEngine;
using System.Collections;
using Prime31;
public class DemoScene : MonoBehaviour
{
// movement config
private float rawGravity;
public float gravity = -25f;
public float runSpeed = 8f;
public float groundDamping = 20f; // how fast do we change direction? higher means faster
public float inAirDamping = 5f;
public float jumpHeight = 3f;
[HideInInspector]
private float normalizedHorizontalSpeed = 0;
private CharacterController2D _controller;
private Animator _animator;
private RaycastHit2D _lastControllerColliderHit;
private Vector3 _velocity;
private int jumpLevel;//跳跃阶段 1段跳 2段跳
private int dir; //朝向 -1左 1右
public LayerMask jumpOnWallMask = 0;//墙体Layer层遮罩
private bool isHoldWall; //是否在墙上
public float jumpOnWallHForce = 1; //墙上跳跃横向力度
public float jumpOnWallVForce = 2; //墙上跳跃纵向力度
public float gravityOnWall = -24f;
void Awake()
{
_animator = GetComponent<Animator>();
_controller = GetComponent<CharacterController2D>();
// listen to some events for illustration purposes
_controller.onControllerCollidedEvent += onControllerCollider;
_controller.onTriggerEnterEvent += onTriggerEnterEvent;
_controller.onTriggerExitEvent += onTriggerExitEvent;
rawGravity = gravity;
}
#region Event Listeners
void onControllerCollider( RaycastHit2D hit )
{
// bail out on plain old ground hits cause they arent very interesting
if( hit.normal.y == 1f )
return;
// logs any collider hits if uncommented. it gets noisy so it is commented out for the demo
//Debug.Log( "flags: " + _controller.collisionState + ", hit.normal: " + hit.normal );
}
void onTriggerEnterEvent( Collider2D col )
{
Debug.Log( "onTriggerEnterEvent: " + col.gameObject.name );
}
void onTriggerExitEvent( Collider2D col )
{
Debug.Log( "onTriggerExitEvent: " + col.gameObject.name );
}
#endregion
// the Update loop contains a very simple example of moving the character around and controlling the animation
void Update()
{
if (_controller.isGrounded)
{
gravity = rawGravity;
_velocity.y = 0;
jumpLevel = 0;
}
//朝着dir方向发射长度为(碰撞体一半宽度+自身皮肤厚度)的射线
RaycastHit2D hit = Physics2D.Linecast(playerBottomTrans.position, playerBottomTrans.position +
new Vector3(dir * (Mathf.Abs(transform.localScale.x) * _controller.boxCollider.size.x / 2f + _controller.skinWidth), 0, 0), jumpOnWallMask);
if (hit && hit.collider.tag == "Wall")
{
isHoldWall = true;
gravity = gravityOnWall; //可调整由rawGravity随着时间降低到gravityOnWall
}
else
{
isHoldWall = false;
gravity = rawGravity;
}
if ( Input.GetKey( KeyCode.RightArrow ) )
{
normalizedHorizontalSpeed = 1;
dir = 1;
if( transform.localScale.x < 0f )
transform.localScale = new Vector3( -transform.localScale.x, transform.localScale.y, transform.localScale.z );
if( _controller.isGrounded )
_animator.Play( Animator.StringToHash( "Run" ) );
}
else if( Input.GetKey( KeyCode.LeftArrow ) )
{
normalizedHorizontalSpeed = -1;
dir = -1;
if( transform.localScale.x > 0f )
transform.localScale = new Vector3( -transform.localScale.x, transform.localScale.y, transform.localScale.z );
if( _controller.isGrounded )
_animator.Play( Animator.StringToHash( "Run" ) );
}
else
{
normalizedHorizontalSpeed = 0;
if( _controller.isGrounded )
_animator.Play( Animator.StringToHash( "Idle" ) );
}
//点击向上
if (Input.GetKeyDown(KeyCode.UpArrow))
{
//未在墙上
if (!isHoldWall)
{
//在地面起跳 (1级跳)
if (_controller.isGrounded)
{
jumpLevel = 1;
_velocity.y = Mathf.Sqrt(2f * jumpHeight * -gravity);
_animator.Play(Animator.StringToHash("Jump"));
}
else
{
//1级跳途中,再次起跳(2级跳)
if(jumpLevel == 1)
{
jumpLevel = 2;
_velocity.y = Mathf.Sqrt(2f * jumpHeight * -gravity);
_animator.Play("Jump");
}
}
}
else
{
//墙上可连续起跳,若想限制只能2段跳,则要类似上面代码写法
//在墙上
_velocity.x += -dir * jumpOnWallHForce;
//仅在墙上会受到重力因此想再次起跳上升 必须比重力还要大的力 1+jumpOnWallForce
//若在墙体上且在地面上,则不要加这个jumpOnWallVForce力,否则贴墙就起跳会让你飞起来!
_velocity.y += Mathf.Sqrt(2f * jumpHeight * -gravity) * (1 + (_controller.isGrounded ? 0 : jumpOnWallVForce));
_animator.Play("Jump");
}
}
// apply horizontal speed smoothing it. dont really do this with Lerp. Use SmoothDamp or something that provides more control
var smoothedMovementFactor = _controller.isGrounded ? groundDamping : inAirDamping; // how fast do we change direction?
_velocity.x = Mathf.Lerp( _velocity.x, normalizedHorizontalSpeed * runSpeed, Time.deltaTime * smoothedMovementFactor );
// apply gravity before moving
_velocity.y += gravity * Time.deltaTime;
//在地面上,按住下键不松开会蓄力将起跳速度*3倍
// if holding down bump up our movement amount and turn off one way platform detection for a frame.
// this lets us jump down through one way platforms
if( _controller.isGrounded && Input.GetKey( KeyCode.DownArrow ) )
{
_velocity.y *= 3f;
_controller.ignoreOneWayPlatformsThisFrame = true;
}
_controller.move( _velocity * Time.deltaTime );
// grab our current _velocity to use as a base for all calculations
_velocity = _controller.velocity;
}
}
蹬墙跳问题:
因此你要将重力、X Force 、Y Force、JumpHeight都要调整好才能呈现出正常的蹬墙跳,目前来看仅靠简单调整Y Force是不行的,要么力度太大 要么力度太小。
二、扶墙下滑
Asset Store使用免费资源:Hero Knight - Pixel Art
if(!_controller.isGrounded)
{
if (isHoldWall)
{
//必须是坠落时
if (_velocity.y < 0)
{
//人物顶点发起射线检测到墙体 才算是完整在墙体上 播放扶墙动画
RaycastHit2D hit2 = Physics2D.Linecast(playerTopTrans.position, playerTopTrans.position +
new Vector3(dir * (Mathf.Abs(transform.localScale.x) * _controller.boxCollider.size.x / 2f + _controller.skinWidth), 0, 0), jumpOnWallMask);
if (hit2 && hit2.collider.tag == "Wall")
{
_animator.Play(Animator.StringToHash("WallSlide"));
}
}
}
else
{
//避免影响1级跳(离地后)以及2级跳时立即切到Fall动画,代码里没有主动将jumpLevel在1级跳或2级跳结束后将jumpLevel改为0的操作,仅在蹬墙跳重置为0
if (jumpLevel != 2 && jumpLevel != 1)
{
_animator.Play(Animator.StringToHash("Fall"));
}
}
}
蹬墙跳时进行重置jumpLevel为0状态
Animator如上所示,Roll和Jump是无条件直接结束时回到Fall,仅适用于本案例不会在平地滚动。
可做辅助射线查看是否正常射线检测到墙体
//朝着dir方向发射长度为(碰撞体宽度+自身皮肤厚度)的射线
Debug.DrawRay(playerTopTrans.position, new Vector3(dir * (Mathf.Abs(transform.localScale.x) * _controller.boxCollider.size.x / 2 + _controller.skinWidth), 0, 0), Color.red);
Debug.DrawRay(playerBottomTrans.position, new Vector3(dir * (Mathf.Abs(transform.localScale.x) *_controller.boxCollider.size.x / 2 + _controller.skinWidth), 0, 0), Color.red);
skinWidth是为了让射线延伸到碰撞盒外面一点点(皮肤厚度)从而才能检测到其他物体