using System.Collections; using System.Collections.Generic; using UnityEngine; using Sirenix.OdinInspector; /// /// 马类,用于接受来自玩家的指令、进行物理模拟 /// public class Horse : MonoBehaviour { // _____ _ _ _ // | __ \ | | | (_) // | |__) | _| |__ | |_ ___ // | ___/ | | | '_ \| | |/ __| // | | | |_| | |_) | | | (__ // |_| \__,_|_.__/|_|_|\___| [FoldoutGroup("SE")][Title("deadSE")] public AudioClip deadSE; [FoldoutGroup("SE")][Title("landSE")] public AudioClip landSE; [FoldoutGroup("SE")][Title("jumpSE")] public AudioClip jumpSE; private AudioSource audioSource; /// /// 马的前腿根部 /// [BoxGroup("必须绑定的物体")][Header("马的前腿根部")] public Transform frontLegRoot; /// /// 马的后腿根部 /// [BoxGroup("必须绑定的物体")][Header("马的后腿根部")] public Transform backLegRoot; /// /// 马伸直腿后前脚底部 /// [BoxGroup("必须绑定的物体")][Header("马前脚伸直后的最深位置")] public Transform frontFoot; /// /// 马伸直腿后后脚底部 /// [BoxGroup("必须绑定的物体")][Header("马后脚伸直后的最深位置")] public Transform backFoot; /// /// 马脚Transform /// [BoxGroup("必须绑定的物体")][Header("马脚Transform,必须按照左前、右前、左后、右后的顺序绑定")] public Transform[] footsTransform; /// /// 头部IK控制点 /// [BoxGroup("必须绑定的物体")][Header("头部IK控制点")] public Transform HeadControlPoint; /// /// 腿做圆周运动的半径 /// [BoxGroup("马的基本运动信息")][Header("马脚圆周运动半径")] public float footMoveRadius; /// /// 四条腿分别对输入偏差多少 /// [ListDrawerSettings] [BoxGroup("马的基本运动信息")][Header("四条腿分别对输入偏差多少")] public Vector2[] footOffestOfInput; /// /// 马足力量 /// [BoxGroup("马的基本运动信息")][Header("马足力量")] public float footForce; /// /// 马蹄推动马身的力度调整值 /// [BoxGroup("马的基本运动信息")][Header("马前进的力度")] public float strength; /// /// 马蹄供力有效范围 /// [BoxGroup("马的基本运动信息")][Header("马蹄供力有效范围")] public float effectiveDistance; public enum FootType { Front, Back } /// /// 马状态,Stand代表无输入,Move为有输入着地行走,Jump为跳跃,当两对足均并拢时进入 /// public enum HorseState { Stand, Move, Jump, Dead} /// /// 单次跳跃过程中的状态量,Ready表示还没开始累计最终力,Charge表示已经开始蓄力,jump表示已经触发跳跃 /// public enum JumpState { Ready, Charging, Jump } /// /// 跳跃判定阈值,只有输入的变化大于该值才会触发跳跃的蓄力 /// [BoxGroup("马的基本运动信息")][Header("马跳跃蓄力阈值")] public float jumpChargeThreshold; /// /// 马跳跃力度 /// [BoxGroup("马的基本运动信息")][Header("马跳跃力度")] public float jumpForce; [BoxGroup("马的其他信息")][Header("马被相机挤死后飞出有多大的力")] public float deadFlySpeed; /// /// 要统计多长时间内的输入变化以供加速?】 /// [BoxGroup("马的基本运动信息")][Header("要统计多长时间内的输入变化以供加速?")] public float accelerationTime; /// /// 加速倍率 /// [BoxGroup("马的基本运动信息")][Header("加速倍率")] public float accelerationRate = 1f; /// /// 最后一节马足的骨骼,必须按照左前、右前、左后、右后的顺序绑定 /// [ BoxGroup("必须绑定的物体")][Header("最后一节马足的骨骼,必须按照左前、右前、左后、右后的顺序绑定")] public Transform[] lastFoots; [BoxGroup("马的基本运动信息")][Header("最后一节马前脚的旋转区间")] public Vector2 lastFrontFootRotationRange; [BoxGroup("马的基本运动信息")][Header("最后一节马后脚的旋转区间")] public Vector2 lastBackFootRotationRange; [BoxGroup("马的基本运动信息")][Header("马头追踪速度")] public float headSpeed; [BoxGroup("马的基本运动信息")][Header("马尾追踪速度")] public float tailSpeed; [BoxGroup("马的基本运动信息")][Header("马尾旋转范围")] public Vector2 horseTailRotationRange; /// /// 马尾骨骼 /// [BoxGroup("必须绑定的物体")][Header("马尾IK控制点")] public Transform tailControlPoint; // _____ _ _ // | __ \ (_) | | // | |__) | __ ___ ____ _| |_ ___ // | ___/ '__| \ \ / / _` | __/ _ \ // | | | | | |\ V / (_| | || __/ // |_| |_| |_| \_/ \__,_|\__\___| struct foot { /// /// 腿对输入偏差多少 /// public Vector2 footOffestOfInput; /// /// 马脚控制点,通过transform找到 /// public Transform footControlPoint; /// /// 真实的马脚的Transform /// public Transform footRealTransform; /// /// 马脚此时目标位置,每帧计算 /// public Vector3 footTargetPosition; /// /// 马蹄刚体组件 /// public Rigidbody2D footRigidbody2D; /// /// 马蹄的碰撞体,但是不绑在马蹄上,而是为了物理计算绑在马身上 /// public BoxCollider2D footBoxCollider2D; /// /// 马蹄前一帧的位置 /// public Vector3 footPreviousPosition; /// /// 最后一节马足的骨骼Transform,需要模拟其弯曲 /// public Transform lastFootTransform; } private foot[] foots; /// /// 马前腿长度 /// private float frontLegLength; /// /// 马后腿长度 /// private float backLegLength; /// /// 从前腿根骨骼到马脚运动圆心的距离,用来推算最终马脚位置 /// private float frontRootDistanceToCenterOfCircle; /// /// 从后腿根骨骼到马脚运动圆心的距离,用来推算最终马脚位置 /// private float backRootDistanceToCenterOfCircle; /// /// 输入的马前脚信息 /// private Vector2 inputFrontVector; /// /// 上一帧马前脚信息 /// private Vector2 pInputFrontVector; /// /// 输入的马后脚信息 /// private Vector2 inputBackVector; /// /// 上一帧马后脚信息 /// private Vector2 pInputBackVector; /// /// 前一帧的前马脚输入向量 /// private Vector2 previousInputFrontVector; /// /// 前一帧的后马脚输入向量 /// private Vector2 previousInputBackVector; // /// // /// 一段时间内的变化总量 // /// // private float chargeCount = 0; /// /// 原重力尺度,在Start中通过随意一条腿的刚体获取 /// private float oriGravityScale; /// /// 马蹄碰撞体数组 /// private BoxCollider2D[] footBoxColliders; /// /// 马的刚体组件 /// private Rigidbody2D horseRig; /// /// 四条腿分别对输入偏差多少,但是是原始值,不会改变,用于还原并拢的脚 /// private Vector2[] oriFootOffestOfInput; /// /// 马此时的状态 /// private HorseState horseState; /// /// 此时前腿并拢着吗? /// private bool isFrontFootMerged = false; /// /// 此时后腿并拢着吗? /// private bool isBackFootMerged = false; /// /// 此时的跳跃状态 /// private JumpState jumpState = JumpState.Ready; /// /// 跳跃蓄力的速度 /// private Vector2 jumpChargeVelocity; /// /// 死亡后的旋转中心 /// private Transform rotateCenter; /// /// 保存了最近若干秒内的输入充能 /// private Queue footInputChargeQueue; /// /// 应有的输入加速累计队列的长度 /// private int footInputChargeQueueLength; /// /// 本帧中,累计的充能值 /// public float footInputCharge = 0; /// /// 马头控制点的Pos应同步于此 /// private Transform horseHeadDelay; /// /// 马头的默认位置 /// private Transform horseHeadDefault; /// /// 马尾的控制点的Pos应同步于此 /// private Transform horseTailDelay; /// /// 马尾的默认位置 /// private Transform horseTailDefault; private JumpState previousJumpState = JumpState.Ready; void Start() { //初始化 Init(); //找到必要的物体和组件 FindSth(); //计算必要的量 CaculateSth(); } void Update() { //更新一些数据 UpdateDataEarly(); if(horseState != HorseState.Dead) { //计算马脚目标位置 CaculateTargetPosition(); //让马脚朝着目标移动 MoveFoot(); //让马整体运动 MoveHorse(); //计算此时马脚在Y轴上的进程,并根据此进程对马蹄旋转插值,模拟最后一节马蹄的弯曲 CaculateLastFootRotation(); //跳跃检测和物理实现 if(horseState == HorseState.Jump) CheckJump(); //让马头控制点运动 MoveHorseHead(); //让马尾根据马刚体速度运动 MoveHorseTail(); // //防止马翻 // Debug.Log(transform.eulerAngles.z); // if(transform.eulerAngles.z > 30 && transform.eulerAngles.z < 330) // { // //看是离45度还是315度更近 // if(Mathf.Abs(transform.eulerAngles.z - 30) < Mathf.Abs(transform.eulerAngles.z - 330)) // { // transform.eulerAngles = new Vector3(0, 0, 30); // } // else // { // transform.eulerAngles = new Vector3(0, 0, 330); // } // } } //更新一些数据 UpdateDataLate(); } void FixedUpdate() { CaculateFootInputCharge(); } void MoveHorseTail(){ Vector2 dir = horseTailDefault.position - horseTailDelay.position; Vector2 horseV = horseRig.velocity; dir = new Vector2( dir.x * 0.05f, dir.y * 0.05f ); horseTailDelay.position += new Vector3( dir.x * Time.deltaTime * tailSpeed, dir.y * Time.deltaTime * tailSpeed, 0 ); tailControlPoint.position = horseTailDelay.position; } void MoveHorseHead(){ Vector2 dir = horseHeadDefault.position - horseHeadDelay.position; Vector2 horseV = horseRig.velocity; dir = new Vector2( dir.x += horseV.x * 0.05f, dir.y -= horseV.x*0.05f ); horseHeadDelay.position += new Vector3( dir.x * Time.deltaTime * headSpeed, dir.y * Time.deltaTime * headSpeed, 0 ); HeadControlPoint.position = horseHeadDelay.position; } void CaculateLastFootRotation(){ for(int i = 0; i < 4; i++){ //用脚底深度减去马足深度,ABS。得到一个Progress float footBottle = (i < 2) ? frontFoot.position.y : backFoot.position.y; float footY = foots[i].footRealTransform.position.y; float progress = Mathf.Abs(footBottle - footY) / (2*footMoveRadius); //if(i == 1) Debug.Log("左前脚Progress:" + progress); Vector2 lastFootRotationRange = (i < 2) ? lastFrontFootRotationRange : lastBackFootRotationRange; //插值,让最后一节马脚弯曲 foots[i].lastFootTransform.rotation = Quaternion.Euler( 0,0, Mathf.Lerp( lastFootRotationRange.x, lastFootRotationRange.y, progress) ); } } void CaculateFootInputCharge() { //计算上一帧和这一帧的输入的角度差 float angleDiff = Vector2.Angle(pInputFrontVector, inputFrontVector) + Vector2.Angle(pInputBackVector, inputBackVector); //把它存入队列 footInputChargeQueue.Enqueue(angleDiff); //如果队列的长度超过了一定的数量,就把最前面的一个去掉 if(footInputChargeQueue.Count > footInputChargeQueueLength) footInputChargeQueue.Dequeue(); //计算马脚的输入充能 footInputCharge = 0; foreach(float angle in footInputChargeQueue) footInputCharge += angle; //估算了一下,充能最快大概是每秒3000.这里/3000 * 时间,即可得到一个大约0-1的值 footInputCharge /= (3000 * accelerationTime); //Debug.Log("footInputCharge: " + footInputCharge); //更新上一帧信息 pInputFrontVector = inputFrontVector; pInputBackVector = inputBackVector; } public void Death(GameController.Death deathInfo) { //修改状态至死亡 horseState = HorseState.Dead; //关闭马蹄的碰撞体,并给一个右上角的速度和旋转,让马的模型掉出地图 foreach(foot foot in foots){ //foot.footBoxCollider2D.enabled = false; //马蹄刚体也需要给速度,否则会黏在地上飞不起来 foot.footRigidbody2D.velocity = Vector2.one * deadFlySpeed * 1.5f; } //马身体的碰撞体也要关 List temp = new List(); GetComponentsInChildren(temp); foreach(Collider2D box in temp) box.enabled = false; horseRig.velocity = Vector2.one * deadFlySpeed; //播放音效 audioSource.clip = deadSE; audioSource.Play(); } /// /// 一次跳跃的最终触发函数 /// private void Jump() { if(horseRig.velocity.y > 3f) return; //horseRig.velocity = Vector2.zero; //修改状态至跳跃 jumpState = JumpState.Jump; Vector2 jumpVelocity = jumpChargeVelocity * jumpForce * -1 * new Vector2(1.5f,1.5f); Vector2 fix = new Vector2( (jumpChargeVelocity.x > 0 ? -1 : 1) *12f, 33.5f); jumpVelocity = 1f * jumpVelocity + 0.8f * fix; //屏蔽出了问题的极大值 if(jumpVelocity.magnitude > 40f) jumpVelocity *= 0.1f; Debug.Log("Jump Velocity: " + jumpVelocity); horseRig.velocity = jumpVelocity; jumpChargeVelocity = Vector2.zero; //播放音效 audioSource.clip = jumpSE; audioSource.Play(); } private bool canJump = true; /// /// 在跳跃状态时每帧执行,用于检测跳跃和实现跳跃的物理 /// private void CheckJump() { //通过本帧输入向量减上一帧输入向量得到一个瞬时速度 Vector2 frontInstantaneousSpeed = inputFrontVector - previousInputFrontVector; Vector2 backInstantaneousSpeed = inputBackVector - previousInputBackVector; //如果任意一组的y大于0,说明在上升,不考虑它的跳跃 if(frontInstantaneousSpeed.y > 0 || backInstantaneousSpeed.y > 0) return; else { //此时双腿在下降,开始检测跳跃蓄力 //首先看前后腿输入变化的瞬时速度的模长和是否大于蓄力标准 if(frontInstantaneousSpeed.magnitude + backInstantaneousSpeed.magnitude > jumpChargeThreshold) { //若输入的瞬时速度大于蓄力标准时,看马的跳跃状态,若处于准备态,则转移至蓄力态 if(jumpState == JumpState.Ready) jumpState = JumpState.Charging; //若已处于蓄力态,累计力大小 if(jumpState == JumpState.Charging) { jumpChargeVelocity += (frontInstantaneousSpeed + backInstantaneousSpeed); } } else { //若输入的瞬时速度小于蓄力标准时,看马的跳跃状态,若处于蓄力态,则触发跳跃 if(jumpState == JumpState.Charging && canJump){ Jump(); canJump = false; ActionController.Instance.DelayToCall( () => { canJump = true; },3f ); } } } } /// /// 在Update之前更新一些数据 /// private void UpdateDataEarly() { //判断是否两腿均并拢,若是,进入跳跃状态,否则设置为Stand状态 if(horseState != HorseState.Dead) { if (isFrontFootMerged && isBackFootMerged) horseState = HorseState.Jump; else horseState = HorseState.Stand; } //Debug.Log("jumpState = " + jumpState); } /// /// 使马并拢输入的双腿, /// public void MergeFoot(FootType type) { //根据输入的组类型,判断需要更改偏移值的是那一个索引下的脚 int index = type == FootType.Front ? 1 : 3; footOffestOfInput[index] = Vector2.zero; if(type == FootType.Front) isFrontFootMerged = true; else isBackFootMerged = true; } /// /// 恢复输入的并拢的双腿至有偏移状态 /// /// 需要恢复的是哪一组腿 public void RecoverFoot(FootType type) { //根据输入的组类型,判断需要更改偏移值的是那一个索引下的脚 int index = type == FootType.Front ? 1 : 3; footOffestOfInput[index] = oriFootOffestOfInput[index]; if(type == FootType.Front) isFrontFootMerged = false; else isBackFootMerged = false; } /// /// 移动马身体 /// private void MoveHorse() { Vector2 v = new Vector2(); for(int i = 0; i < 4; i++) { //仅在有输入的情况下,会考虑给速度 //看此时该脚是否有输入 bool hasInput = i < 2 ? inputFrontVector.magnitude > 0.1f : inputBackVector.magnitude > 0.1f; if(!hasInput) continue; //从足底向正下方发射射线,获取碰撞到的物体 RaycastHit2D hit = Physics2D.Raycast( foots[i].footRealTransform.position, Vector2.down, 1000f, LayerMask.NameToLayer("foot") ); //如果击中物体的距离已经超过了有效范围,则不贡献速度 if(hit.distance > effectiveDistance) continue; //判断本帧中,马足位置是否低于圆心+调整值,仅低于时,需要计算影响 //根位置 Vector3 rootPosition = i < 2 ? frontLegRoot.position : backLegRoot.position; //L float L = i < 2 ? frontLegLength : backLegLength; //圆心y坐标 float centerY = L - footMoveRadius; centerY += effectiveDistance; //仅低于时,需要计算影响 if(foots[i].footRealTransform.position.y >= centerY) continue; //本帧位置减上帧位置 Vector3 trans = foots[i].footRealTransform.position - foots[i].footPreviousPosition; Vector2 v_ = trans; //把本足的影响加到速度中 v += v_; } //给速度乘以力量,得到最终理论速度 v *= strength * (1 + footInputCharge * accelerationRate); //取反,因为是模拟反作用力 v*=-1; //减弱y轴的影响,不要让马在平地上起飞 v *= new Vector2(1,0.2f); //如果模长超过5,那必是触发了什么逆天Bug,会导致马起飞,需要避免这些错误 if(v.magnitude > 20f){ v = Vector2.zero; Debug.Log("莫动"); } //如果马处于跳跃状态,则不让马蹄提供速度 if(horseState == HorseState.Jump) v = Vector2.zero; //把速度加给马的刚体 horseRig.velocity += v; } private void UpdateDataLate() { //更新四足的上帧位置 for(int i = 0; i < 4; i++) foots[i].footPreviousPosition = foots[i].footRealTransform.position; //更新上帧输入 previousInputFrontVector = inputFrontVector; previousInputBackVector = inputBackVector; previousJumpState = jumpState; } void Init() { foots = new foot[4]; oriFootOffestOfInput = new Vector2[4]; footBoxColliders = new BoxCollider2D[4]; for(int i = 0; i < 4; i++) foots[i].footPreviousPosition = transform.position; for(int i = 0; i < 4; i++) oriFootOffestOfInput[i] = footOffestOfInput[i]; horseState = HorseState.Stand; footInputChargeQueue = new Queue(); //初始化马背接收系统 GameObject.Find("接收人用的马背(不可改名)").AddComponent(); } private class HoseBack : MonoBehaviour{ private void OnTriggerEnter2D(Collider2D other) { //当马背接受到人,触发人的Recover(); if(other.TryGetComponent(out Person person) && person.state == Person.PersonState.FallingOff) person.Recover(); } } void FindSth() { //把四只脚的目标点找到,0:左前 1:右前 2:左后 3:右后 foots[0].footControlPoint = transform.Find("左前脚").GetChild(0); foots[1].footControlPoint = transform.Find("右前脚").GetChild(0); foots[2].footControlPoint = transform.Find("左后脚").GetChild(0); foots[3].footControlPoint = transform.Find("右后脚").GetChild(0); //同步四只脚的输入偏差 for (int i = 0; i < 4; i++) foots[i].footOffestOfInput = footOffestOfInput[i]; //找到马蹄刚体组件 for(int i = 0; i < 4; i++) foots[i].footRigidbody2D = foots[i].footControlPoint.GetComponent(); //找到马蹄的真实Transform for(int i = 0; i < 4; i++) foots[i].footRealTransform = footsTransform[i]; //找到马蹄的碰撞体 footBoxColliders = GetComponents(); //给foots的碰撞体赋值 for (int i = 0; i < 4; i++) { foots[i].footBoxCollider2D = footBoxColliders[i]; footBoxColliders[i].offset = foots[i].footControlPoint.localPosition ; } //找到马的刚体 horseRig = GetComponent(); // rotateCenter = transform.Find("马死后的旋转中心"); // for(int i = 0; i < 4; i++){ foots[i].lastFootTransform = lastFoots[i]; } // horseHeadDelay = GameObject.Find("马头IK延迟(不可改名)").transform; horseHeadDefault = transform.Find("马头默认位置(不可改名)"); // //tailBone = transform.Find("bone_10"); horseTailDelay = GameObject.Find("马尾IK延迟(不可改名)").transform; horseTailDefault = transform.Find("马尾默认位置(不可改名)"); // audioSource = GetComponent(); } /// /// 计算一些不会变的必要的量 /// void CaculateSth() { //计算前后腿马腿长度 frontLegLength = Vector3.Distance(frontLegRoot.position, frontFoot.position); backLegLength = Vector3.Distance(backLegRoot.position, backFoot.position); //计算从腿根骨骼到马脚运动圆心的距离 frontRootDistanceToCenterOfCircle = frontLegLength - footMoveRadius; backRootDistanceToCenterOfCircle = backLegLength - footMoveRadius; //取得原始重力尺度 oriGravityScale = foots[0].footRigidbody2D.gravityScale; //计算要记录多少帧的输入,用秒计算,每秒60次FixedUpdate footInputChargeQueueLength = (int)accelerationTime * 60; } /// /// 根据记录的输入向量计算马脚本帧的目标位置,保存到footTargetPoints数组中 /// private void CaculateTargetPosition() { //循环4次 for(int i = 0; i < 4; i++) { //整体公式为:Target = 根位置 - V(0,L - r) + inputdir * offest * r //根位置 Vector3 rootPosition = i < 2 ? frontLegRoot.position : backLegRoot.position; //L float L = i < 2 ? frontLegLength : backLegLength; //中间向量 Vector3 middleVector = new Vector3(0,L - footMoveRadius,0); //inputdir Vector2 inputVector = i < 2 ? inputFrontVector : inputBackVector; //inputTrans Vector3 inputTrans = new Vector3 ( inputVector.x * ( footOffestOfInput[i].x + 1), inputVector.y * ( footOffestOfInput[i].y + 1), 0 ) * footMoveRadius; //最终计算 //footTargetPoints[i] = rootPosition - middleVector + inputTrans; foots[i].footTargetPosition = rootPosition - middleVector + inputTrans; } } /// /// 把马脚朝着算好的目标点移动 /// private void MoveFoot() { //循环4次 for(int i = 0; i < 4; i++) { //看此时该脚是否有输入 bool hasInput = i < 2 ? inputFrontVector.magnitude > 0.1f : inputBackVector.magnitude > 0.1f; //如果有输入,则把马脚朝着目标移动 Vector3 targetPosition = hasInput ? foots[i].footTargetPosition : foots[i].footTargetPosition - new Vector3(0,footMoveRadius + 0.5f,0); // if(hasInput) if(true) { //若无输入,则把目标定在可以使马腿伸直的地方 // Vector3 targetPosition = hasInput ? // foots[i].footTargetPosition : // foots[i].footTargetPosition - new Vector3(0,footMoveRadius + 0.5f,0); //给刚体以target位置-自身位置作为力的方向 Vector2 Dir = targetPosition - foots[i].footControlPoint.position; //如果已经很接近目标点了,则不再给速度,同时解除重力,防止出现抖动问题 if(Dir.magnitude < 0.1f) { foots[i].footRigidbody2D.gravityScale = 0; foots[i].footRigidbody2D.velocity = Vector2.zero; continue; } //标准化马脚移动方向 Dir.Normalize(); //最终计算应有的速度 Vector2 force = Dir * footForce * (horseRig.velocity.magnitude * 0.1f + 1); //赋予脚的刚体以速度 foots[i].footRigidbody2D.velocity = foots[i].footRigidbody2D.velocity * 0.8f + force * 0.2f; //foots[i].footRigidbody2D.velocity = force; //foots[i].footRigidbody2D.AddForce(force); } // // 强制把马脚移到目标点 //foots[i].footControlPoint.position = foots[i].footTargetPosition; //foots[i].footControlPoint.position = targetPosition; // 恢复重力 foots[i].footRigidbody2D.gravityScale = oriGravityScale; // 刷新碰撞体位置 footBoxColliders[i].offset = foots[i].footRealTransform.position - transform.position; } } public void SetInputFrontVector(Vector2 input) => inputFrontVector = input; public void SetInputBackVector(Vector2 input) => inputBackVector = input; public bool IsHorseStillAlive() => horseState != HorseState.Dead; [FoldoutGroup("SE")][Title("马蹄音效")] public AudioSource[] footSEs; //撞击地面后,修改跳跃状态 void OnCollisionEnter2D(Collision2D collision) { if(collision.gameObject.tag == "Ground") { //播放马蹄点地音效 Random.Range(0,footSEs.Length); footSEs[Random.Range(0,footSEs.Length)].Play(); transform.Find("马蹄灰尘").GetComponent().Play(); jumpState = JumpState.Ready; //播放着地音效 if(previousJumpState == JumpState.Jump) { audioSource.clip = landSE; audioSource.Play(); } } } }