20230118-【UNITY 学习】增加攀登系统

替换脚本PlayerMovement_04.cs

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class PlayerMovement_05 : MonoBehaviour
{private float moveSpeed; // 玩家移动速度public float walkSpeed = 7; // 行走速度public float sprintSpeed = 10; // 冲刺速度public float slideSpeed = 30; // 滑动速度public float wallrunSpeed = 8.5f;public float climbSpeed = 3;private float desiredMoveSpeed; // 期望的移动速度private float lastDesiredMoveSpeed; // 上一次的期望移动速度public float speedIncreaseMultiplier = 1.5f; // 速度增加倍数public float slopeIncreaseMultiplier = 2.5f; // 斜坡增加倍数public float groundDrag = 5; // 地面时的阻力public float playerHeight = 2; // 玩家身高public LayerMask whatIsGround; // 地面的LayerMaskpublic bool grounded; // 是否在地面上public float jumpForce = 6; // 跳跃力度public float jumpCooldown = 0.25f; // 跳跃冷却时间public float airMultiplier = 0.4f; // 空中移动速度衰减private bool readyToJump = true; // 是否可以跳跃public float crouchSpeed = 3.5f; // 蹲伏时的移动速度public float crouchYScale = 0.5f; // 蹲伏时的Y轴缩放比例private float startYScale; // 初始Y轴缩放比例public float maxSlopAngle = 40; // 最大坡度角度private RaycastHit slopeHit; // 坡度检测的射线信息private bool exitingSlope = true; // 是否正在离开坡度public KeyCode jumpKey = KeyCode.Space; // 跳跃键public KeyCode sprintKey = KeyCode.LeftShift; // 冲刺键public KeyCode crouchKey = KeyCode.LeftControl; // 下蹲键public Climbing climbingScript;public Transform orientation; // 玩家朝向的Transformprivate float h; // 水平输入private float v; // 垂直输入private Vector3 moveDirection; // 移动方向private Rigidbody rb; // 玩家刚体public MovementState state; // 当前玩家的移动状态public enum MovementState{walking,    // 行走sprinting,  // 冲刺wallrunning,//墙跑climbing,crouching,  // 蹲伏sliding,    // 滑动air         // 空中}public bool sliding; // 是否正在滑动public bool wallrunning;public bool climbing;private void Start(){rb = GetComponent<Rigidbody>();rb.freezeRotation = true; // 防止刚体旋转startYScale = transform.localScale.y;  // 记录初始的Y轴缩放}private void Update(){grounded = Physics.Raycast(transform.position, Vector3.down, playerHeight * 0.5f + 0.2f, whatIsGround);MyInput();SpeedControl();StateHandler();if (grounded)rb.drag = groundDrag;elserb.drag = 0;}private void FixedUpdate(){MovePlayer();}private void MyInput(){// 获取水平和垂直输入h = Input.GetAxisRaw("Horizontal");v = Input.GetAxisRaw("Vertical");// 如果按下跳跃键且准备好跳,并且在地面上if (Input.GetKey(jumpKey) && readyToJump && grounded){readyToJump = false;Jump();Invoke(nameof(ResetJump), jumpCooldown);}if (Input.GetKeyDown(crouchKey)){// 调整玩家缩放以模拟蹲下效果transform.localScale = new Vector3(transform.localScale.x, crouchYScale, transform.localScale.z);rb.AddForce(Vector3.down * 5f, ForceMode.Impulse);}// 如果释放下蹲键if (Input.GetKeyUp(crouchKey)){// 恢复到原始Y轴缩放transform.localScale = new Vector3(transform.localScale.x, startYScale, transform.localScale.z);}}private void MovePlayer(){if (climbingScript.exitingWall) return;// 根据朝向计算移动方向moveDirection = orientation.forward * v + orientation.right * h;// 如果在斜坡上并且不是即将离开斜坡if (OnSlope() && !exitingSlope){// 在斜坡上施加力,以便更好地移动rb.AddForce(GetSlopeMoveDirection(moveDirection) * moveSpeed * 20f, ForceMode.Force);// 如果垂直速度为正(上升),则额外施加向下的力,以克服斜坡引起的垂直速度变慢if (rb.velocity.y > 0){rb.AddForce(Vector3.down * 80f, ForceMode.Force);}}else if (grounded) // 如果在地面上{rb.AddForce(moveDirection.normalized * moveSpeed * 10f, ForceMode.Force); // 在地面上施加移动力}else if (!grounded) // 如果在空中{// 在空中施加移动力,乘以空中移动速度衰减系数rb.AddForce(moveDirection.normalized * moveSpeed * 10f * airMultiplier, ForceMode.Force);}// 根据是否在斜坡上决定是否启用重力if (!wallrunning)rb.useGravity = !OnSlope();}private void SpeedControl(){// 如果在斜坡上并且不是即将离开斜坡if (OnSlope() && !exitingSlope){// 如果速度的大小超过了设定的移动速度if (rb.velocity.magnitude > moveSpeed){// 将速度归一化,并乘以设定的移动速度,以限制速度在设定范围内rb.velocity = rb.velocity.normalized * moveSpeed;}}// 如果不在斜坡上else{// 获取水平方向的速度Vector3 flatVel = new Vector3(rb.velocity.x, 0f, rb.velocity.z);// 如果水平速度的大小超过了设定的移动速度if (flatVel.magnitude > moveSpeed){// 限制水平速度在设定范围内Vector3 limitedVel = flatVel.normalized * moveSpeed;// 更新刚体的速度,保持垂直速度不变rb.velocity = new Vector3(limitedVel.x, rb.velocity.y, limitedVel.z);}}}private void Jump(){exitingSlope = true;//rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z);rb.velocity = Vector3.zero;// 添加向上的力以实现跳跃rb.AddForce(transform.up * jumpForce, ForceMode.Impulse);}private void ResetJump(){readyToJump = true;exitingSlope = false;}private void StateHandler(){if (climbing){state = MovementState.climbing;desiredMoveSpeed = climbSpeed;}else if (wallrunning){state = MovementState.wallrunning;desiredMoveSpeed = wallrunSpeed;}else if (sliding){state = MovementState.sliding;  // 设置当前状态为滑动状态if (OnSlope() && rb.velocity.y < 0.1f){desiredMoveSpeed = slideSpeed;  // 如果在斜坡上并且垂直速度小于0.1,则设置期望移动速度为滑动速度}else{desiredMoveSpeed = sprintSpeed;  // 否则,设置期望移动速度为冲刺速度}}// 如果按住蹲伏键else if (Input.GetKey(crouchKey)){// 设置当前状态为蹲伏状态state = MovementState.crouching;// 设置移动速度为蹲伏速度desiredMoveSpeed = crouchSpeed;}// 如果在地面上并且按住冲刺键else if (grounded && Input.GetKey(sprintKey)){// 设置当前状态为冲刺状态state = MovementState.sprinting;// 设置移动速度为冲刺速度desiredMoveSpeed = sprintSpeed;}// 如果在地面上但没有按住冲刺键else if (grounded){// 设置当前状态为行走状态state = MovementState.walking;// 设置移动速度为行走速度desiredMoveSpeed = walkSpeed;}// 如果不在地面上else{// 设置当前状态为空中状态state = MovementState.air;}if (Mathf.Abs(desiredMoveSpeed - lastDesiredMoveSpeed) > 4f && moveSpeed != 0){StopAllCoroutines();  // 停止所有协程StartCoroutine(SmoothlyLerpMoveSpeed());  // 启动平滑插值移动速度的协程}else{moveSpeed = desiredMoveSpeed;  // 否则,直接将移动速度设置为期望移动速度}lastDesiredMoveSpeed = desiredMoveSpeed;  // 更新上一次的期望移动速度}public bool OnSlope(){// 使用射线检测当前位置向下,获取击中信息存储在slopeHit中if (Physics.Raycast(transform.position, Vector3.down, out slopeHit, playerHeight * 0.5f + 0.3f)){// 计算斜坡的角度float angle = Vector3.Angle(Vector3.up, slopeHit.normal);// 如果角度小于最大允许斜坡角度且不等于0,表示在斜坡上return angle < maxSlopAngle && angle != 0;}// 如果没有击中信息,或者角度不符合条件,表示不在斜坡上return false;}public Vector3 GetSlopeMoveDirection(Vector3 direction){// 使用Vector3.ProjectOnPlane将移动方向投影到斜坡法线上,然后进行归一化return Vector3.ProjectOnPlane(direction, slopeHit.normal).normalized;}private IEnumerator SmoothlyLerpMoveSpeed(){float time = 0;  // 记录经过的时间float difference = Mathf.Abs(desiredMoveSpeed - moveSpeed);  // 计算期望移动速度与当前移动速度的差值float startValue = moveSpeed;  // 记录开始时的移动速度while (time < difference){moveSpeed = Mathf.Lerp(startValue, desiredMoveSpeed, time / difference);  // 使用插值平滑地改变移动速度if (OnSlope()){float slopeAngle = Vector3.Angle(Vector3.up, slopeHit.normal);  // 计算当前坡度的角度float slopeAngleIncrease = 1 + (slopeAngle / 90f);  // 根据坡度角度增加速度// 根据时间、速度增加倍数、坡度增加倍数进行平滑插值time += Time.deltaTime * speedIncreaseMultiplier * slopeIncreaseMultiplier * slopeAngleIncrease;}else{// 在平地上,只考虑时间和速度增加倍数time += Time.deltaTime * speedIncreaseMultiplier;}yield return null;  // 等待下一帧}moveSpeed = desiredMoveSpeed;  // 最终将移动速度设置为期望移动速度}
}

新增脚本Climbing.cs

在这里插入图片描述

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Climbing : MonoBehaviour
{public Transform orientation; // 角色朝向public Rigidbody rb; // 角色刚体public PlayerMovement_05 pm_05; // 角色基本运动脚本public LayerMask whatIsWall; // 定义哪些是墙的图层public float climbSpeed = 10; // 攀爬速度public float maxClimbTime = 0.75f; // 最大攀爬时间private float climbTimer; // 攀爬计时器private bool climbing; // 是否正在攀爬public float climbJumpUpForce = 14; // 攀爬跳跃向上的力public float climbJumpBackForce = 12; // 攀爬跳跃向后的力public KeyCode jumpKey = KeyCode.Space; // 跳跃键public int climbJumps = 1; // 攀爬跳跃次数private int climbJumpsLeft; // 剩余可用的攀爬跳跃次数public float detectionLength = 0.7f; // 检测墙的射线长度public float sphereCastRadius = 0.25f; // 射线球的半径public float maxWallLookAngle = 30; // 最大墙面角度private float wallLookAngle; // 当前墙面角度private RaycastHit frontWallHit; // 射线检测到的前方墙面信息private bool wallFront; // 是否在墙面前方private Transform lastWall; // 上一次攀爬的墙面private Vector3 lastWallNormal; // 上一次攀爬的墙面法线方向public float minWallNormalAngleChange = 5; // 最小墙面法线方向变化角度public bool exitingWall; // 是否正在退出墙面public float exitWallTime = 0.2f; // 退出墙面的时间private float exitWallTimer; // 退出墙面计时器private void Update(){WallCheck();StateMachine();if (climbing && !exitingWall){ClimbingMovement();}}private void StateMachine(){// 检查是否在墙面前方,按下攀爬键,墙面角度小于最大角度,且正在退出墙面if (wallFront && Input.GetKey(KeyCode.W) && wallLookAngle < maxWallLookAngle && !exitingWall){// 如果尚未开始攀爬且攀爬时间尚未用完if (!climbing && climbTimer > 0){Debug.Log("开始攀爬");StartClimbing(); // 开始攀爬}// 如果攀爬时间尚未用完if (climbTimer > 0){climbTimer -= Time.deltaTime;}// 如果攀爬时间用完if (climbTimer < 0){Debug.Log("攀爬时间用完,停止攀爬!");StopClimbing(); // 停止攀爬}}// 如果正在退出墙面else if (exitingWall){// 如果正在攀爬if (climbing){StopClimbing(); // 停止攀爬}// 如果退出墙面计时尚未用完if (exitWallTimer > 0)exitWallTimer -= Time.deltaTime;// 如果退出墙面计时用完if (exitWallTimer < 0)exitingWall = false;}else{// 如果正在攀爬if (climbing){StopClimbing(); // 停止攀爬}}// 如果在墙面前方,按下跳跃键,且剩余可用的攀爬跳跃次数大于0if (wallFront && Input.GetKeyDown(jumpKey) && climbJumpsLeft > 0){Debug.Log("进行攀爬跳跃");ClimbJump(); // 进行攀爬跳跃}}private void WallCheck(){// 发出前方射线,检测是否有墙wallFront = Physics.SphereCast(transform.position, sphereCastRadius, orientation.forward, out frontWallHit, detectionLength, whatIsWall);// 计算墙面角度wallLookAngle = Vector3.Angle(orientation.forward, -frontWallHit.normal);// 判断是否是新的墙面,或者墙面法线方向变化大于最小变化角度,或者角色在地面上bool newWall = frontWallHit.transform != lastWall || Mathf.Abs(Vector3.Angle(lastWallNormal, frontWallHit.normal)) > minWallNormalAngleChange;// 如果在墙面前方且是新的墙面if ((wallFront && newWall) || pm_05.grounded){climbTimer = maxClimbTime; // 重置攀爬时间climbJumpsLeft = climbJumps; // 重置可用的攀爬跳跃次数}}private void StartClimbing(){climbing = true; // 设置正在攀爬pm_05.climbing = true; // 触发攀爬状态lastWall = frontWallHit.transform; // 记录上一次攀爬的墙面lastWallNormal = frontWallHit.normal; // 记录上一次攀爬的墙面法线方向}private void ClimbingMovement(){rb.velocity = new Vector3(rb.velocity.x, climbSpeed, rb.velocity.z); // 应用攀爬速度}private void StopClimbing(){climbing = false; // 设置停止攀爬pm_05.climbing = false; // 触发停止攀爬状态}private void ClimbJump(){exitingWall = true; // 设置正在退出墙面exitWallTimer = exitWallTime; // 重置退出墙面计时器// 计算施加到角色身上的力,包括向上的力和沿墙的后退力Vector3 forceToApply = transform.up * climbJumpUpForce + frontWallHit.normal * climbJumpBackForce;rb.velocity = new Vector3(rb.velocity.x, 0f, rb.velocity.z); // 将垂直速度置为0,确保在墙面上的水平移动rb.AddForce(forceToApply, ForceMode.Impulse); // 应用力到刚体,实现攀爬跳跃效果climbJumpsLeft--; // 减少可用的攀爬跳跃次数}
}

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

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

相关文章

[笔记]深度学习入门 基于Python的理论与实现(一)

代码仓库 gitee 1. python 入门 1.5之前是python安装和基础语法, 我直接跳过了 1.5 Numpy 深度学习中经常出现数组和矩阵运算&#xff0c;Numpy 的数组类 numpy.array 提供了很多便捷的方法 1.5.1 导入 Numpy import numpy as np1.5.2 生成 Numpy 数组 np.array()&#xf…

C++ 设计模式之策略模式

【声明】本题目来源于卡码网&#xff08;题目页面 (kamacoder.com)&#xff09; 【提示&#xff1a;如果不想看文字介绍&#xff0c;可以直接跳转到C编码部分】 【设计模式大纲】 【简介】什么是策略模式&#xff08;第14种模式&#xff09; 策略模式是⼀种⾏为型设计模式&…

西门子WINCC常用C脚本1

1.置位&#xff0c;复位&#xff0c;取反 获取变量值&#xff1a;GetTagBit(可以是位也可以是字节&#xff0c;字&#xff0c;双字等具体字母不同) 设置变量值&#xff1a;SetTagBit 置位&#xff1a;SetTagBit&#xff08;"变量名",1&#xff09; 复位&#xff…

Node+Express编写接口---前端

前端页面 vue_node_admin: 第一个以node后端,vue为前端的后台管理项目https://gitee.com/ah-ah-bao/vue_node_admin.git

java-log4j日志冲突解决

一、概述 java日志框架较多&#xff0c;其中主流的slf4j和commons-logging是日志接口&#xff0c;log4j、log4j2和logback是真正的日志实现库。 二、具体库单独使用 2.1 log4j <dependency><groupId>log4j</groupId><artifactId>log4j</artifa…

Ceph分布式存储(1)

目录 一.ceph分布式存储 Ceph架构&#xff08;自上往下&#xff09; OSD的存储引擎&#xff1a; Ceph的存储过程&#xff1a; 二. 基于 ceph-deploy 部署 Ceph 集群 20-40节点上添加3块硬盘&#xff0c;一个网卡&#xff1a; 10节点为admin&#xff0c;20-40为node&…

指针理解C部分

目录 1.二级指针 2.指针数组 2.1指针数组的定义和表现形式 2.2指针数组模拟实现二维数组 2.2.1二维数组 2.2.2使用指针数组模拟实现二维数组 3.字符指针 2.数组指针 3.二维数组传参 4.函数指针 4.1函数指针变量的定义和创建 4.2函数指针变量的使用 4.3两段有趣的代码 4.…

Python自动化测试中APScheduler Flask的应用示例

使用背景 实际项目中&#xff0c;需要验证打点数据在各个系统中收集是否一致&#xff0c;而部分节点打点数据收集是通过异步任务实现的&#xff0c;等待时间比较久。为应对业务异步操作处理&#xff0c;实现异步数据的收集&#xff0c;经过调研后&#xff0c;选择了 APSchedule…

JMeter实操入门之登录

JMeter实操入门之登录 前言初级-无变量的登录线程组取样器-HTTP请求 进阶-定义变量的登录用户定义的变量获取JSON返回的数据-tokentoken设置全局变量 前言 安装及环境配置教程可移步&#xff1a;JMeter安装与配置环境 本篇文章针对小白进一步的认识及运用JMeter&#xff0c;围绕…

湖(岛屿)

from book&#xff1a;挑战程序设计竞赛

表的增删改查 进阶(二)

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;MySql&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 3.新增 4.查询 聚合查询 聚合函数 GROUP BY子句 HA…

shell编程学习

学习目标&#xff1a; 一周掌握 shell编程 变量的高级用法 变量替换 ##变量替换&#xff08;贪婪&#xff0c;从前往后匹配&#xff0c;匹配到进行删除&#xff09; test1I love you,you love me echo $test1 handletest1${test1##*ov} echo $handletest1##变量替换&#xff…

C#,入门教程(07)——软件项目的源文件与目录结构

上一篇&#xff1a; C#&#xff0c;入门教程(06)——解决方案资源管理器&#xff0c;代码文件与文件夹的管理工具https://blog.csdn.net/beijinghorn/article/details/124895033 创建新的 C# 项目后&#xff0c; Visual Studio 会自动创建一系列的目录与文件。 程序员后面的工…

使用 Kali Linux Hydra 工具进行攻击测试和警报生成

一、Hydra 工具和 Kali Linux 简介 在网络安全领域中&#xff0c;渗透测试是评估系统密码强度的重要组成部分。Hydra 是一款由黑客组织“The Hackers Choice”开发的开源登录破解工具&#xff0c;支持50多种协议。本教程将探索如何将 Hydra 与 Kali Linux 结合使用&#xff0c…

Android.mk和Android.bp的区别和转换详解

Android.mk和Android.bp的区别和转换详解 文章目录 Android.mk和Android.bp的区别和转换详解一、前言二、Android.mk和Android.bp的联系三、Android.mk和Android.bp的区别1、语法&#xff1a;2、灵活性&#xff1a;3、版本兼容性&#xff1a;4、向后兼容性&#xff1a;5、编译区…

新上线一个IT公司微信小程序

项目介绍 项目背景: 一家IT公司,业务包含以下六大块: 1、IT设备回收 2、IT设备租赁 3、IT设备销售 4、IT设备维修 5、IT外包 6、IT软件开发 通过小程序,提供在线下单,在线制单,在线销售,业务介绍,推广,会员 项目目的: 业务介绍: 包含企业业务介绍 客户需…

万字讲解新一代分布式任务调度框架Power-job

1、简介 Power-Job 的设计目标是成为企业级的分布式任务调度平台&#xff0c;整个公司统一部署调度中心 power-job-server&#xff0c;旗下所有业务线应用只需要依赖 power-job-worker 即可接入调度中心获取任务调度与分布式计算能力。 Power-job官方网址&#xff1a;http:/…

链动2+1模式:月流水6000万是怎么做到的?

一个好的企业往往只需要最简单的营销方式。当我们面对当今的商业市场&#xff0c;琳琅满目的商业模式&#xff0c;应接不暇的营销方案&#xff0c;我们一定会举足无措的不知道怎么选择。因为一个好的公司或企业&#xff0c;一定要有一个十分经得起推敲的模式来面对消费者。 那么…

失眠了,感谢技术人对“Spring Cloud Alibaba实战派的支持”

笔者从2015年开始接触Spring Boot&#xff0c;2017年开始接触Spring Cloud&#xff0c;到现在的Spring Cloud Alibaba已经整整快7个年头了&#xff0c;从2012年开始接触Java到现在已经整整10年了。 这里并没有倚老卖老的意思&#xff0c;只是想说作为一个纯碎的技术人&#xf…

Gin 框架之Cookie与Session

文章目录 一、Cookie和Session的由来二、Cookie简介1. 什么是Cookie2. Cookie规范3. 安全性4. Cookie 关键配置 三、Session简介1. 什么是Session2. Session 安全性3. 如何让客户端携带 sess_id 四、使用 Gin 的 Session 插件4.1 介绍4.2 基本使用 五、 session与store5.1 会话…