【Unity3D】实现横版2D游戏——攀爬绳索(简易版)

server/2025/2/2 15:56:02/

目录

GeneRope.cs 场景绳索生成类 

HeroColliderController.cs 控制角色与单向平台是否忽略碰撞

HeroClampController.cs 控制角色攀爬

OnTriggerEnter2D方法

OnTriggerStay2D方法

OnTriggerExit2D方法

Update方法

开始攀爬

结束攀爬

Sensor_HeroKnight.cs 角色触发器

HeroKnight.cs 角色类


  

基于【Unity3D】实现横版2D游戏——单向平台(简易版)-CSDN博客

实现了单向平台后才能进行攀爬绳索到平台,否则攀爬到平台后会被阻挡,以及无法从平台往下攀爬。

GeneRope.cs 场景绳索生成类 

GitHub - dbrizov/NaughtyAttributes: Attribute Extensions for Unity

上面是一个特性能够让脚本上出现一个按钮去点击执行我们的生成绳索方法   

用法:[Button("Generate Rope")] 写在生成绳索方法头部。

绳索标签Rope

using System.Collections;
using NaughtyAttributes;
using System.Collections.Generic;
using UnityEngine;[ExecuteInEditMode]
public class GeneRope : MonoBehaviour
{public Sprite top;public Sprite middle;public Sprite bottom;public int ropeNodeCount;public Vector2 ropeNodeSize;List<GameObject> nodeList;BoxCollider2D boxCollider2D;[Button("Generate Rope")]public void GenerateRope(){//清理if (nodeList == null){nodeList = new List<GameObject>();}else{for (int i = nodeList.Count - 1; i >= 0; i--){DestroyImmediate(nodeList[i]);}nodeList.Clear();}Transform[] childrens = transform.GetComponentsInChildren<Transform>();if (childrens != null && childrens.Length > 0){for (int i = childrens.Length - 1; i >= 0; i--){if (childrens[i].gameObject != this.gameObject){DestroyImmediate(childrens[i].gameObject);}}}//生成绳索节点图片for (int i = 0; i < ropeNodeCount; i++){Sprite sprite;if (i == 0){//头部sprite = top;}else if (i == ropeNodeCount - 1){//尾部sprite = bottom;}else{//中间sprite = middle;}SpriteRenderer sp = new GameObject("node_" + i, typeof(SpriteRenderer)).GetComponent<SpriteRenderer>();sp.sprite = sprite;nodeList.Add(sp.gameObject);sp.transform.SetParent(transform);//从根节点向下延伸的绳索sp.transform.localPosition = new Vector3(0, i * -ropeNodeSize.y - 0.5f, 0);}//生成绳索碰撞体(1个)boxCollider2D = GetComponent<BoxCollider2D>();if (boxCollider2D == null){boxCollider2D = gameObject.AddComponent<BoxCollider2D>();}boxCollider2D.size = new Vector2(ropeNodeSize.x, ropeNodeSize.y * ropeNodeCount);boxCollider2D.offset = new Vector2(0, boxCollider2D.size.y * -0.5f);boxCollider2D.isTrigger = true;//绳索标签(触发检测使用判定是否为绳索,处理攀爬逻辑)gameObject.tag = "Rope";}
}

HeroColliderController.cs 控制角色与单向平台是否忽略碰撞

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class HeroColliderController : MonoBehaviour
{BoxCollider2D heroCollider;public bool isOn;Collider2D m_Collision;public bool isIgnoreCollision = false;private void Awake(){heroCollider = GetComponent<BoxCollider2D>();}IEnumerator StartIgnoreCollision(Collider2D collider2D){//Debug.Log("忽略碰撞");//等待人物完整地达到平台上 或 平台下时 退出死循环while (isOn){yield return new WaitForEndOfFrame();}//Debug.Log("恢复");Physics2D.IgnoreCollision(heroCollider, collider2D, false); //恢复isIgnoreCollision = false;isOn = false;}private void OnTriggerEnter2D(Collider2D collision){//实现从下往上跳跃穿过单向平台效果if (collision != null && collision.gameObject.tag == "SingleDirCollider" && !isOn){//this.transform.position.y + 0.062f是人物minYif (collision.GetComponent<SingleDirCollider>().maxY > (this.transform.position.y + 0.062f)){IgnoreCollision(collision);}}}private void OnCollisionStay2D(Collision2D collision){//实现按下↓键位 让角色从单方向平台往下掉效果if (Input.GetKeyDown(KeyCode.DownArrow) && collision.gameObject.tag == "SingleDirCollider"){IgnoreCollision(collision.collider);}}public void IgnoreCollision(Collider2D collision){isOn = true;m_Collision = collision;Physics2D.IgnoreCollision(heroCollider, collision); //这2个碰撞体互相忽略碰撞isIgnoreCollision = true;StartCoroutine(StartIgnoreCollision(collision));}public void ResumeCollider(){Physics2D.IgnoreCollision(heroCollider, m_Collision, false); //恢复isOn = false;isIgnoreCollision = false;}private void OnTriggerExit2D(Collider2D collision){if (collision != null && collision.gameObject.tag == "SingleDirCollider" && isOn){var singleDirCollider = collision.GetComponent<SingleDirCollider>();//this.transform.position.y + 0.062f是人物minY, this.transform.position.y + 1.31f是人物maxY//当人物完整地位于平台之上 或 之下时if (singleDirCollider.maxY < (this.transform.position.y + 0.062f) || singleDirCollider.minY > (this.transform.position.y + 1.31f)){//协程死循环退出isOn = false;}}}
}

HeroClampController.cs 控制角色攀爬

OnTriggerEnter2D方法

当某个触发器检测到单向平台,持有它 并 triggerCount计数+1

OnTriggerStay2D方法

当角色检测到绳索时,角色底部在绳索顶部之上,需要按↓键才开始往下爬,此时需要立刻将角色底部瞬移到绳索顶部之下,避免与结束攀爬条件3冲突即

//3.接触单向平台 且人物在单向平台之上时 离开攀爬
lastIsTouchSingleDirPlatform = (singleDirCollider.maxY) < (heroKnight.transform.position.y + 0.062f);

角色底部在绳索顶部之下,需要按↑键才开始往上爬。 

OnTriggerExit2D方法

检测到某个触发器离开单向平台时,triggerCount计数-1,当triggerCount为0时,需要进行结束攀爬条件2判定

//2.接触单向平台 且人物在单向平台之上时 离开攀爬
lastIsTouchSingleDirPlatform = (singleDirCollider.maxY) <= (heroKnight.transform.position.y + 0.062f);

 并且将持有的单向平台脚本置空 singleDirCollider = null;

Update方法

检查到攀爬中,会使用角色类heroKnight.MoveY移动角色进行匀速运动上下攀爬。
进行结束攀爬条件1,2种情况:
1、isGrounds && !singleDirCollider :在地面且没有接触中的单向平台,结束攀爬
2、isGrounds && singleDirCollider && (singleDirCollider.maxY) <= (heroKnight.transform.position.y + 0.062f):在地面且接触单向平台中且角色在平台之上,结束攀爬。

开始攀爬

需要立即忽略重力影响以及接触中的单向平台碰撞体,否则会发生异常。

结束攀爬

需要立即恢复重力以及碰撞体

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class HeroClampController : MonoBehaviour
{//英雄类对象 控制移动HeroKnight heroKnight;//是否处于攀爬状态public bool isOn;//仅用于观察、调试参数public bool heroGround;//仅用于观察 绳索Y参数public float ropeY;//攀爬速度public float speed = 2f;//攀爬达到的单向平台private SingleDirCollider singleDirCollider;private int triggerCount = 0; //可能会有多个触发检测到平台,需要用计数形式来检测 是否完全离开平台//一个控制是否忽略角色与平台碰撞的控制器(攀爬开始 检测到平台会立刻忽略碰撞 防止角色和平台发生碰撞, 以及结束攀爬时立即恢复碰撞 让玩家站在上面)private HeroColliderController heroColliderController;//仅用于观测数据 调试 : 上次是否触碰到单向平台 并恰好离开了平台 标记public bool lastIsTouchSingleDirPlatform;void Start(){heroKnight = GetComponent<HeroKnight>();heroColliderController = GetComponent<HeroColliderController>();}public float InputY{get{return Input.GetAxis("Vertical");}}private void Update(){heroGround = heroKnight.IsGrounded();if (isOn){//攀爬移动float inputY = InputY;heroKnight.MoveY(inputY * speed * Time.deltaTime);//Debug.Log("攀爬:" + (inputY * speed * Time.deltaTime) + ", posY:" + (heroKnight.transform.position.y + 0.062f));bool isGrounds = heroKnight.IsGrounded();//1.接触地面 离开攀爬//2.接触单向平台 且人物在单向平台之上时 离开攀爬bool isTouchGround = isGrounds && !singleDirCollider;bool isTouchSingleDirPlatform = isGrounds && singleDirCollider && (singleDirCollider.maxY) <= (heroKnight.transform.position.y + 0.062f);if (isTouchGround || isTouchSingleDirPlatform){EndClamp();//Debug.LogError("攀爬结束 isOn:" + isOn + ", isTouchGround:" + isTouchGround + ", isTouchSingleDirPlatform:" + isTouchSingleDirPlatform//    + " singleDirCollider.maxY:" + singleDirCollider?.maxY + ", posY:" + (heroKnight.transform.position.y + 0.062f));}}}private void OnTriggerEnter2D(Collider2D collision){//触发检测到平台 持有它if (collision.tag == "SingleDirCollider"){triggerCount++;singleDirCollider = collision.transform.GetComponent<SingleDirCollider>();}}private void OnTriggerStay2D(Collider2D collision){if (isOn && singleDirCollider != null){//3.接触单向平台 且人物在单向平台之上时 离开攀爬lastIsTouchSingleDirPlatform = (singleDirCollider.maxY) < (heroKnight.transform.position.y + 0.062f);//Debug.LogError("离开时: " + (singleDirCollider.maxY) + ", HeroY:" + (heroKnight.transform.position.y + 0.062f));                if (lastIsTouchSingleDirPlatform){EndClamp();//Debug.LogError(">>>>> 3333 攀爬结束 isOn:" + isOn + ", isTouchSingleDirPlatform:" + lastIsTouchSingleDirPlatform//    + " singleDirCollider.maxY:" + singleDirCollider?.maxY + ", posY:" + (heroKnight.transform.position.y + 0.062f));}}if (collision.tag == "Rope" && !isOn){float inputY = InputY;bool hasInput = Mathf.Abs(inputY) > 0;bool isGrounds = heroKnight.IsGrounded();bool isValidInput = !isGrounds; //非站立地面则允许攀爬float heroY = heroKnight.transform.position.y + 0.062f;float ropeY = collision.transform.position.y;float deltaY = Mathf.Abs(heroY - ropeY); //绳索顶部和英雄底部的差值//若站立地面上if (hasInput && isGrounds){this.ropeY = ropeY;if (heroY > ropeY){isValidInput = inputY < 0; //角色位于绳索上方,按↓键才能攀爬}else{isValidInput = inputY > 0; //角色位于绳索下方,按↑键才能攀爬}}//Debug.Log("isValidInput:" + isValidInput + " inputY :"  + inputY + ", heroY:" + heroY + ", ropeY:" + ropeY);if (hasInput && isValidInput) //可以检测角色中心点x是否在绳索范围内才进入攀爬状态{var hero = heroKnight.transform;var rope = collision.transform;int dir = (inputY > 0 ? 0 : -1); //按↑时不需要偏移Y值,按↓时进行偏移//Debug.LogError("攀爬开始 heroY:" + heroY + ", ropeY:" + ropeY + ", inputY:" + inputY + ", isGrounds:" + isGrounds);//当角色从绳索顶部往下爬时,需要增加一个Y轴偏移 使得角色底部位于绳索顶部之下 避免发生临界点问题//(避免:开始攀爬 又会检测到物体在绳索上而结束攀爬的情况)float offsetY = 0f;if (heroY > ropeY){offsetY = -deltaY - 0.01f;}hero.position = new Vector3(rope.position.x, hero.position.y + offsetY, 0);isOn = true;heroKnight.isClamp = true;heroKnight.BlockGravity(true); //禁用重力影响if (singleDirCollider != null){heroColliderController.IgnoreCollision(singleDirCollider.GetComponent<BoxCollider2D>()); //主动忽略碰撞体}//heroKnight.MoveY(inputY * speed * Time.deltaTime);//Debug.LogError("攀爬开始 isOn:" + isOn + ", heroY:" + (hero.position.y + 0.062f) + ", dir * deltaY:" + dir * deltaY);}}//Debug.Log("heroKnight.transform.position.y + 0.062f:" + (heroKnight.transform.position.y + 0.062f));}private void OnTriggerExit2D(Collider2D collision){if (collision.tag == "SingleDirCollider" && singleDirCollider != null && collision.gameObject == singleDirCollider.gameObject){triggerCount--;if (triggerCount <= 0){if (isOn){//2.接触单向平台 且人物在单向平台之上时 离开攀爬lastIsTouchSingleDirPlatform = (singleDirCollider.maxY) <= (heroKnight.transform.position.y + 0.062f);//Debug.LogError("离开时: " + (singleDirCollider.maxY) + ", HeroY:" + (heroKnight.transform.position.y + 0.062f));                if (lastIsTouchSingleDirPlatform){EndClamp();//Debug.LogError(">>>>> 2222 攀爬结束 isOn:" + isOn + ", isTouchSingleDirPlatform:" + lastIsTouchSingleDirPlatform//    + " singleDirCollider.maxY:" + singleDirCollider?.maxY + ", posY:" + (heroKnight.transform.position.y + 0.062f));}}singleDirCollider = null;}}//还有其他的情况 退出攀爬状态的,比如攀爬中允许跳出去之类的 就还要写类似计数形式的 检测是否完全离开了绳索,再结束攀爬状态isOn = false//if (collision.tag == "Rope")//{//    isOn = false;//}}/// <summary>/// 结束攀爬/// </summary>private void EndClamp(){isOn = false;heroKnight.isClamp = false;heroKnight.BlockGravity(false); //恢复重力影响if (heroColliderController != null){heroColliderController.ResumeCollider(); //恢复碰撞体}}
}

Sensor_HeroKnight.cs 角色触发器

主要是忽略对Rope绳索层的检测,否则角色会认为绳索是地面或障碍物。

using UnityEngine;
using System.Collections;public class Sensor_HeroKnight : MonoBehaviour
{public int m_ColCount = 0;public float m_DisableTimer;public bool m_State;private void OnEnable(){m_ColCount = 0;}public bool State(){if (m_DisableTimer > 0){m_State = false;return m_State;}m_State = m_ColCount > 0;return m_State;}void OnTriggerEnter2D(Collider2D other){if (other.tag != "Rope"){m_ColCount++;}}void OnTriggerExit2D(Collider2D other){if (other.tag != "Rope"){m_ColCount--;}}void Update(){m_DisableTimer -= Time.deltaTime;}public void Disable(float duration){m_DisableTimer = duration;}
}

HeroKnight.cs 角色类

如下是部分代码新增isClamp是否攀爬中标记,Move和Roll判定要加上非攀爬中才允许进行,新增MoveY(攀爬时上下移动使用)、BlockGravity(禁用重力影响 攀爬时会禁用)、IsGrounded(获取是否真正在地面上)

    public bool isClamp;// Moveif (!m_rolling && !isClamp )m_body2d.velocity = new Vector2(inputX * m_speed, m_body2d.velocity.y);// Rollelse if (Input.GetKeyDown("left shift") && !m_rolling && !isClamp){m_rolling = true;m_animator.SetTrigger("Roll");m_body2d.velocity = new Vector2(m_facingDirection * m_rollForce, m_body2d.velocity.y);}public void MoveY(float y){m_body2d.MovePosition(new Vector2(transform.position.x, transform.position.y + y));}public void BlockGravity(bool isStatic){float oldScale = m_body2d.gravityScale;float newScale = isStatic ? 0f : 1f;if (Mathf.Abs(oldScale - newScale) > 0.0001){m_body2d.gravityScale = newScale;m_body2d.velocity = new Vector2(0, 0);}}public bool IsGrounded() { return m_groundSensor.State(); }

http://www.ppmy.cn/server/164374.html

相关文章

Linux进程控制:【进程创建】【进程终止】【进程等待】【进程程序替换】【自主shell命令行解释器】

目录 一.进程创建 1.1fork函数初识 1.2写时拷贝 1.3fork常规用法 1.4fork调用失败的原因 二.进程终止 2.1进程退出场景 2.2进程常见退出方法 2.2.1退出码 2.2.2_exit和exit 三.进程等待 3.1进程等待的必要性 3.2进程等待的方式 3.2.1wait 3.2.2waitpid 3.3获取子进…

实验四 XML

实验四 XML 目的&#xff1a; 1、安装和使用XML的开发环境 2、认识XML的不同类型 3、掌握XML文档的基本语法 4、了解DTD的作用 5、掌握DTD的语法 6、掌握Schema的语法 实验过程&#xff1a; 1、安装XML的编辑器&#xff0c;可以选择以下之一 a)XMLSpy b)VScode&#xff0c;Vs…

hive:基本数据类型,关于表和列语法

基本数据类型 Hive 的数据类型分为基本数据类型和复杂数据类型 加粗的是常用数据类型 BOOLEAN出现ture和false外的其他值会变成NULL值 没有number,decimal类似number 如果输入的数据不符合数据类型, 映射时会变成NULL, 但是数据本身并没有被修改 创建表 创建表的本质其实就是在…

Python GUI 开发 | Qt Designer — 工具介绍

关注这个框架的其他相关笔记&#xff1a;Python GUI 开发 | PySide6 & PyQt6 学习手册-CSDN博客 Qt Designer 即 Qt 设计师&#xff0c;是一个强大、灵活的可视化 GUI 设计工具&#xff0c;可以帮助用户加快开发 PySide6 程序的速度。 Qt Designer 是专门用来制作 PySide6…

3.攻防世界Web_php_unserialize

打开题目页面如下 是PHP源码&#xff0c;进行代码审计 <?php // 定义一个名为 Demo 的类 class Demo { // 定义一个私有属性 $file&#xff0c;初始值设置为 index.php// 该属性将用于指定要进行语法高亮显示的文件private $file index.php;public function __construct…

基于Spring Security 6的OAuth2 系列之七 - 授权服务器--自定义数据库客户端信息

之所以想写这一系列&#xff0c;是因为之前工作过程中使用Spring Security OAuth2搭建了网关和授权服务器&#xff0c;但当时基于spring-boot 2.3.x&#xff0c;其默认的Spring Security是5.3.x。之后新项目升级到了spring-boot 3.3.0&#xff0c;结果一看Spring Security也升级…

分布式微服务系统架构第89集:kafka消费者

那么消费者是如何提交偏移量的呢&#xff1f;消费者往一个叫作 _consumer_offset 的特殊主题发送 消息&#xff0c;消息里包含每个分区的偏移量。如果消费者一直处于运行状态&#xff0c;那么偏移量就没有 什么用处。不过&#xff0c;如果消费者发生崩溃或者有新的消费者加入群…

Verilog边沿检测

edge_check.v module edge_check(input clk,input in,output neg_edge,output pos_edge);reg r11d0;reg r21d0;assign neg_edge(~r1)&r2;assign pos_edger1&(~r2);always(posedge clk)beginr1<in;r2<r1;endendmodule tb.v timescale 1ns/1nsmodule tb; //被测…