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

news2025/1/31 0:24:22

目录

一、二段跳、蹬墙跳 

二、扶墙下滑


一、二段跳、蹬墙跳

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.coloradmin.cn/o/2286663.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mybatis(134/134)完结

一级缓存&#xff08;默认情况下开启&#xff09;同一个sqlsession中执行相同的查询语句走一级缓存 二级缓存 &#xff1a;同一个sqlsessionfactory&#xff0c;sqlsession关闭了才会将一级缓存提交到二级缓存中 外部编写的缓存 PageHelper插件&#xff1a;方便进行分页&#x…

PaddleSeg 从配置文件和模型 URL 自动化运行预测任务

git clone https://github.com/PaddlePaddle/PaddleSeg.git# 在ipynb里面运行 cd PaddleSegimport sys sys.path.append(/home/aistudio/work/PaddleSeg)import os# 配置文件夹路径 folder_path "/home/aistudio/work/PaddleSeg/configs"# 遍历文件夹&#xff0c;寻…

BLE透传方案,IoT短距无线通信的“中坚力量”

在物联网&#xff08;IoT&#xff09;短距无线通信生态系统中&#xff0c;低功耗蓝牙&#xff08;BLE&#xff09;数据透传是一种无需任何网络或基础设施即可完成双向通信的技术。其主要通过简单操作串口的方式进行无线数据传输&#xff0c;最高能满足2Mbps的数据传输速率&…

苍穹外卖—订单模块

该模块分为地址表的增删改查、用户下单、订单支付三个部分。 第一部分地址表的增删改查无非就是对于单表的增删改查&#xff0c;较基础&#xff0c;因此直接导入代码。 地址表 一个用户可以有多个地址&#xff0c;同时有一个地址为默认地址。用户还可为地址添加例如&q…

openeuler 22.03 lts sp4 使用 cri-o 和 静态 pod 的方式部署 k8s-v1.32.0 高可用集群

前情提要 整篇文章会非常的长…可以选择性阅读,另外,这篇文章是自己学习使用的,用于生产,还请三思和斟酌 静态 pod 的部署方式和二进制部署的方式是差不多的,区别在于 master 组件的管理方式是 kubectl 还是 systemctl有 kubeadm 工具,为什么还要用静态 pod 的方式部署?…

MySQL分表自动化创建的实现方案(存储过程、事件调度器)

《MySQL 新年度自动分表创建项目方案》 一、项目目的 在数据库应用场景中&#xff0c;随着数据量的不断增长&#xff0c;单表存储数据可能会面临性能瓶颈&#xff0c;例如查询、插入、更新等操作的效率会逐渐降低。分表是一种有效的优化策略&#xff0c;它将数据分散存储在多…

接口技术-第6次作业

目录 作业内容 解答 1.假设在一个系统中&#xff0c;8255A的端口地址为184H-187H&#xff0c;A口工作于方式1输出&#xff0c;B口工作于方式1输入&#xff0c;禁止中断&#xff0c;C口剩余的两根线PC5&#xff0c;PC4位输入&#xff0c;如下图所示&#xff0c;试编写初始化…

(1)Linux高级命令简介

Linux高级命令简介 在安装好linux环境以后第一件事情就是去学习一些linux的基本指令&#xff0c;我在这里用的是CentOS7作演示。 首先在VirtualBox上装好Linux以后&#xff0c;启动我们的linux&#xff0c;输入账号密码以后学习第一个指令 简介 Linux高级命令简介ip addrtou…

网络直播时代的营销新策略:基于受众分析与开源AI智能名片2+1链动模式S2B2C商城小程序源码的探索

摘要&#xff1a;随着互联网技术的飞速发展&#xff0c;网络直播作为一种新兴的、极具影响力的媒体形式&#xff0c;正逐渐改变着人们的娱乐方式、消费习惯乃至社交模式。据中国互联网络信息中心数据显示&#xff0c;网络直播用户规模已达到3.25亿&#xff0c;占网民总数的45.8…

CSS(快速入门)

欢迎大家来到我的博客~欢迎大家对我的博客提出指导&#xff0c;有错误的地方会改进的哦~点击这里了解更多内容 目录 一、什么是CSS?二、基本语法规范三、CSS选择器3.1 标签选择器3.2 id选择器3.3 class选择器3.4 通配符选择器3.5 复合选择器 四、常用CSS样式4.1 color4.2 font…

对顾客行为的数据分析:融入2+1链动模式、AI智能名片与S2B2C商城小程序的新视角

摘要&#xff1a;随着互联网技术的飞速发展&#xff0c;企业与顾客之间的交互方式变得日益多样化&#xff0c;移动设备、社交媒体、门店、电子商务网站等交互点应运而生。这些交互点不仅为顾客提供了便捷的服务体验&#xff0c;同时也为企业积累了大量的顾客行为数据。本文旨在…

MySQL查询优化(三):深度解读 MySQL客户端和服务端协议

如果需要从 MySQL 服务端获得很高的性能&#xff0c;最佳的方式就是花时间研究 MySQL 优化和执行查询的机制。一旦理解了这些&#xff0c;大部分的查询优化是有据可循的&#xff0c;从而使得整个查询优化的过程更有逻辑性。下图展示了 MySQL 执行查询的过程&#xff1a; 客户端…

UE AController

定义和功能 AController是一种特定于游戏的控制器&#xff0c;在UE框架中用于定义玩家和AI的控制逻辑。AController负责处理玩家输入&#xff0c;并根据这些输入驱动游戏中的角色或其他实体的行为。设计理念 AController设计用于分离控制逻辑与游戏角色&#xff0c;增强游戏设计…

Git进阶之旅:Git 配置信息 Config

Git 配置级别&#xff1a; 仓库级别&#xff1a;local [ 优先级最高 ]用户级别&#xff1a;global [ 优先级次之 ]系统级别&#xff1a;system [ 优先级最低 ] 配置文件位置&#xff1a; git 仓库级别对应的配置文件是当前仓库下的 .git/configgit 用户级别对应的配置文件时用…

51单片机开发:定时器中断

目标&#xff1a;利用定时器中断&#xff0c;每隔1s开启/熄灭LED1灯。 外部中断结构图如下图所示&#xff0c;要使用定时器中断T0&#xff0c;须开启TE0、ET0。&#xff1a; 系统中断号如下图所示&#xff1a;定时器0的中断号为1。 定时器0的工作方式1原理图如下图所示&#x…

深度学习框架应用开发:基于 TensorFlow 的函数求导分析

深度学习框架应用开发&#xff1a;基于 TensorFlow 的函数求导分析 在深度学习的世界里&#xff0c;梯度计算是优化算法的核心。而 TensorFlow 作为一款强大的深度学习框架&#xff0c;为我们提供了简洁而强大的工具来进行自动求导操作&#xff0c;这极大地简化了深度学习模型的…

2025春晚刘谦魔术揭秘魔术过程

2025春晚刘谦魔术揭秘魔术过程 首先来看全过程 将杯子&#xff0c;筷子&#xff0c;勺子以任意顺序摆成一排 1.筷子和左边物体交换位置 2.杯子和右边物体交换位置 3.勺子和左边物体交换位置 最终魔术的结果是右手出现了杯子 这个就是一个简单的分类讨论的问题。 今年的魔术…

上海亚商投顾:沪指冲高回落 大金融板块全天强势 上海亚商投

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一&#xff0e;市场情绪 市场全天冲高回落&#xff0c;深成指、创业板指午后翻绿。大金融板块全天强势&#xff0c;天茂集团…

01学习预热篇(D6_正式踏入JVM深入学习前的铺垫)

目录 学习前言 一、虚拟机的结构 1. Java虚拟机参数设置 2. java 堆 3. 出入栈 4. 局部变量表 1> 局部变量的剖析 2> 局部变量的回收 5. 操作数栈 1> 常量入栈指令 2> 局部变量值转载到栈中指令 3> 将栈顶值保存到局部变量中指令 6. 帧数据区 7. 栈…

【漫话机器学习系列】068.网格搜索(GridSearch)

网格搜索&#xff08;Grid Search&#xff09; 网格搜索&#xff08;Grid Search&#xff09;是一种用于优化机器学习模型超参数的技术。它通过系统地遍历给定的参数组合&#xff0c;找出使模型性能达到最优的参数配置。 网格搜索的核心思想 定义参数网格 创建一个包含超参数值…