【Unity3D】实现横版2D游戏角色二段跳、蹬墙跳、扶墙下滑

目录

一、二段跳、蹬墙跳

二、扶墙下滑


一、二段跳、蹬墙跳

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,可自行调整这些参数来达到理想效果

修改部分代码:

cs 复制代码
    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");
			}
		}
    }

完整代码:

cs 复制代码
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

cs 复制代码
		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,仅适用于本案例不会在平地滚动。

可做辅助射线查看是否正常射线检测到墙体

cs 复制代码
//朝着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是为了让射线延伸到碰撞盒外面一点点(皮肤厚度)从而才能检测到其他物体

相关推荐
wonder135793 分钟前
UGUI重建流程和优化
unity·游戏开发·ugui
那个村的李富贵4 小时前
Unity打包Webgl后 本地运行测试
unity·webgl
nnsix5 小时前
Unity OpenXR开发HTC Vive Cosmos
unity·游戏引擎
nnsix6 小时前
Unity OpenXR,扳机键交互UI时,必须按下扳机才触发
unity·游戏引擎
nnsix6 小时前
Unity XR 编辑器VR设备模拟功能
unity·编辑器·xr
老朱佩琪!6 小时前
Unity访问者模式
unity·游戏引擎·访问者模式
不定时总结的那啥6 小时前
Unity实现点击Console消息自动选中预制体的方法
unity·游戏引擎
nnsix7 小时前
Unity OpenXR 关闭手柄的震动
unity·游戏引擎
CreasyChan7 小时前
Unity 中的反射使用详解
unity·c#·游戏引擎·游戏开发
Jessica巨人7 小时前
Shader显示为黑色
unity·shader