当前位置: 首页 > article >正文

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

修改部分代码:

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


http://www.kler.cn/a/527660.html

相关文章:

  • (即插即用模块-特征处理部分) 十九、(NeurIPS 2023) Prompt Block 提示生成 / 交互模块
  • 合并2个排序的链表
  • 音视频多媒体编解码器基础-codec
  • 【视频+图文详解】HTML基础4-html标签的基本使用
  • 01-时间与管理
  • VS2008 - debug版 - 由于应用程序配置不正确,应用程序未能启动。重新安装应用程序可能会纠正这个问题。
  • Linux Vim编辑器:快捷键与高效编辑技巧
  • C语言指针专题一 -- 指针基础原理
  • 【Linux】使用管道实现一个简易版本的进程池
  • Pandas 常用函数
  • 【PLL】杂散生成和调制
  • (动态规划基础 打家劫舍)leetcode 198
  • 简要介绍C++中的 max 和 min 函数以及返回值
  • TensorFlow 简单的二分类神经网络的训练和应用流程
  • Git 常用命令汇总
  • 3.Spring-事务
  • 冯诺依曼结构和进程概念及其相关的内容的简单介绍
  • 99.23 金融难点通俗解释:小卖部经营比喻PPI(生产者物价指数)vsCPI(消费者物价指数)
  • 谈谈你所了解的AR技术吧!
  • 本地部署 DeepSeek 模型并使用 WebUI 调用
  • 32. C 语言 安全函数( _s 尾缀)
  • 1.31 实现五个线程的同步
  • 前端知识速记—JS篇:箭头函数
  • 力扣hot100--2
  • 比较器使用
  • FIDL:Flutter与原生通讯的新姿势,不局限于基础数据类型