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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/962226.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

讯飞智作 AI 配音技术浅析(二):深度学习与神经网络

讯飞智作 AI 配音技术依赖于深度学习与神经网络&#xff0c;特别是 Tacotron、WaveNet 和 Transformer-TTS 模型。这些模型通过复杂的神经网络架构和数学公式&#xff0c;实现了从文本到自然语音的高效转换。 一、Tacotron 模型 Tacotron 是一种端到端的语音合成模型&#xff…

初始化mysql报错cannot open shared object file: No such file or directory

报错展示 我在初始化msyql的时候报错&#xff1a;mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory 解读&#xff1a; libaio包的作用是为了支持同步I/O。对于数据库之类的系统特别重要&#xff0c;因此…

DeepSeek介绍

目录 前言 1.介绍一下你自己 2.什么是CUDA CUDA的核心特点&#xff1a; CUDA的工作原理&#xff1a; CUDA的应用场景&#xff1a; CUDA的开发工具&#xff1a; CUDA的局限性&#xff1a; 3.在AI领域&#xff0c;PTX是指什么 1. PTX 的作用 2. PTX 与 AI 的关系 3. …

python学opencv|读取图像(五十一)使用修改图像像素点上BGR值实现图像覆盖效果

【1】引言 前序学习了图像的得加方法&#xff0c;包括使用add()函数直接叠加BGR值、使用bitwise()函数对BGR值进行按位计算叠加和使用addWeighted()函数实现图像加权叠加至少三种方法。文章链接包括且不限于&#xff1a; python学opencv|读取图像&#xff08;四十二&#xff…

【硬件介绍】三极管工作原理(图文+典型电路设计)

什么是三极管&#xff1f; 三极管&#xff0c;全称为双极型晶体三极管&#xff0c;是一种广泛应用于电子电路中的半导体器件。它是由三个掺杂不同的半导体材料区域组成的&#xff0c;这三个区域分别是发射极&#xff08;E&#xff09;、基极&#xff08;B&#xff09;和集电极&…

【解决方案】MuMu模拟器移植系统进度条卡住98%无法打开

之前在Vmware虚拟机里配置了mumu模拟器&#xff0c;现在想要移植到宿主机中 1、虚拟机中的MuMu模拟器12-1是目标系统&#xff0c;对应的目录如下 C:\Program Files\Netease\MuMu Player 12\vms\MuMuPlayer-12.0-1 2、Vmware-虚拟机-设置-选项&#xff0c;启用共享文件夹 3、复…

C++中常用的十大排序方法之1——冒泡排序

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于C中常用的排序方法之——冒泡排序的相关…

开源2+1链动模式AI智能名片S2B2C商城小程序:利用用户争强好胜心理促进分享行为的策略研究

摘要&#xff1a;随着互联网技术的快速发展和社交媒体的普及&#xff0c;用户分享行为在企业营销中的作用日益凸显。本文旨在探讨如何利用用户的争强好胜心理&#xff0c;通过开源21链动模式AI智能名片S2B2C商城小程序&#xff08;以下简称“小程序”&#xff09;促进用户分享行…

DeepSeek-R1环境搭建推理测试

引子 这两天国货之光DeepSeek-R1火爆出圈&#xff0c;凑个热闹。过来看看 aha moment&#xff08;顿悟时刻&#xff09;的神奇&#xff0c;OK&#xff0c;我们开始吧。 一、模型介绍 1月20日&#xff0c;中国AI公司深度求索&#xff08;DeepSeek&#xff09;发布的DeepSeek-…

【深度分析】微软全球裁员计划不影响印度地区,将继续增加当地就业机会

当微软的裁员刀锋掠过全球办公室时&#xff0c;班加罗尔的键盘声却愈发密集——这场资本迁徙背后&#xff0c;藏着数字殖民时代最锋利的生存法则。 表面是跨国公司的区域战略调整&#xff0c;实则是全球人才市场的地壳运动。微软一边在硅谷裁撤年薪20万美金的高级工程师&#x…

架构技能(六):软件设计(下)

我们知道&#xff0c;软件设计包括软件的整体架构设计和模块的详细设计。 在上一篇文章&#xff08;见 《架构技能&#xff08;五&#xff09;&#xff1a;软件设计&#xff08;上&#xff09;》&#xff09;谈了软件的整体架构设计&#xff0c;今天聊一下模块的详细设计。 模…

unity使用内置videoplayer打包到安卓手机进行视频播放

1.新建UI&#xff0c;新建RawImage在画布当作视频播放的显示载体 2.新建VideoPlayer 3.新建Render Texture作为连接播放器视频显示和幕布的渲染纹理 将Render Texture同时挂载在VideoPlayer播放器和RawImage上。这样就可以将显示的视频内容在RawImage上显示出来了。 问题在于&a…

LLMs之RAG:解读RAG主流的七类架构(Naive RAG/Retrieve-and-rerank/Multimodal RAG/GraphRAG/HybridRAG/Agentic RAG(Ro

LLMs之RAG&#xff1a;解读RAG主流的七类架构(Naive RAG/Retrieve-and-rerank/Multimodal RAG/GraphRAG/HybridRAG/Agentic RAG(Router)/Agentic RAG(Multi-Agent)) 目录 解读RAG主流的七类架构(Naive RAG/Retrieve-and-rerank/Multimodal RAG/GraphRAG/HybridRAG/Agentic RAG…

99.20 金融难点通俗解释:中药配方比喻马科维茨资产组合模型(MPT)

目录 0. 承前1. 核心知识点拆解2. 中药搭配比喻方案分析2.1 比喻的合理性 3. 通俗易懂的解释3.1 以中药房为例3.2 配方原理 4. 实际应用举例4.1 基础配方示例4.2 效果说明 5. 注意事项5.1 个性化配置5.2 定期调整 6. 总结7. 代码实现 0. 承前 本文主旨&#xff1a; 本文通过中…

python算法和数据结构刷题[1]:数组、矩阵、字符串

一画图二伪代码三写代码 LeetCode必刷100题&#xff1a;一份来自面试官的算法地图&#xff08;题解持续更新中&#xff09;-CSDN博客 算法通关手册&#xff08;LeetCode&#xff09; | 算法通关手册&#xff08;LeetCode&#xff09; (itcharge.cn) 面试经典 150 题 - 学习计…

EWM 变更库存类型

目录 1 简介 2 配置 3 业务操作 1 简介 一般情况下 EWM 标准收货流程是 ROD(Ready on Dock) --> AFS(Avaiable for Sale),对应 AG 001 --> AG 002,对应库存类型 F1 --> F2。 因业务需要反向进行的时候,AFS --> ROD,AG 002 --> AG 001,库存类型 F2…

B站吴恩达机器学习笔记

机器学习视频地址&#xff1a; 4.5 线性回归中的梯度下降_哔哩哔哩_bilibili 损失函数学习地址&#xff1a; 损失函数选择 选凸函数的话&#xff0c;会收敛到全局最小值。证明凸函数用Hessian矩阵。凸函数定义&#xff1a;两点连线比线上所有点都大。 batch理解&#xff1…

SpringBoot 数据访问(MyBatis)

SpringBoot 数据访问&#xff08;MyBatis) 向 SQL 语句传参 #{} 形式 #{}&#xff1a;如果传过来的值是字符串类型。那两边会自动加上 单引号。当传递给 #{} 的参数值是非字符串类型&#xff08;如整数、浮点数、布尔值等&#xff09;&#xff0c;MyBatis 不会为这些值添加引…

【游戏设计原理】93 - 节奏

与“序-破-急”类似的节奏概念广泛存在于全球不同文化和创意领域中。以下是一些常见的节奏框架和理论&#xff0c;它们与“序-破-急”在本质上有相似之处&#xff0c;但体现出不同的风格和应用&#xff1a; 1. 三幕式结构&#xff08;Three-Act Structure&#xff09; 来源&a…

蓝桥云客 三羊献瑞

三羊献瑞 题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 观察下面的加法算式&#xff1a; 祥 瑞 生 辉 三 羊 献 瑞 -------------------三 羊 生 瑞 气其中&#xff0c;相同的汉字代表相同的数字&#xff0c;…