SAIMA/Assets//脚本/Horse.cs
Roman 7bbfa625a0 任务:搭建基本玩法
1.让马可以跳跃
(1.测试,如果我给马的刚体一个(1,1)的力代表跳跃,是否可行?
Good Job!通过测试,可以通过给力的方式使马整体起飞,而且控制点和马蹄都会随之起飞,是理想的状态。
(2.让马并拢每一组腿,当按住某键,使该组腿并拢,也就是消除输入偏差。当松开键,重新设置输入偏差,使双腿分开。
DONE
(3.从输入中获取信息,当两腿均并拢,进入跳跃模式,否则离开跳跃模式,在跳跃模式下,如何通过输入计算出应该给出多大、哪个方向的的跳跃力是核心和难点
(4.马脚和马脚的控制点很难控制,数据也经常抖动,我不想通过这两个变量来计算跳跃的力。我打算直接通过输入值判断
(5.尝试:首先,若处于跳跃模式,每帧记录上一帧输入,并计算一个瞬时速度,当瞬时速度的y大于0,也就代表此时腿在上升,在预备,不考虑。若瞬时速度的y小于0,说明腿开始下降,可能会跳起。此时判断此瞬时速度的模长是否大于某个阈值,若是,进入戒备状态,此后的若干帧后,有任何一帧再低于阈值,说明输入结束,输出最后的速度,并给予马刚体。若此时瞬时速度的模长小于阈值
,说明太慢,不记录。在这个戒备状态中的每一帧,需要记录瞬时速度,并累计影响,最终经过乘以调整值、取反(因为是相对作用)操作,得到需要赋予马的力,然后把力赋给马刚体,完成一次跳跃
(6.Good!初步尝试有效。在马着地后,重置跳跃状态,即可完成循环,开始下一次跳跃。

至此,马基本可以跳跃,但是手感极差,并且键鼠几乎没有操作空间,需要后续进一步优化……
2022-07-26 00:00:56 +08:00

570 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Sirenix.OdinInspector;
/// <summary>
/// 马类,用于接受来自玩家的指令、进行物理模拟
/// </summary>
public class Horse : MonoBehaviour
{
// _____ _ _ _
// | __ \ | | | (_)
// | |__) | _| |__ | |_ ___
// | ___/ | | | '_ \| | |/ __|
// | | | |_| | |_) | | | (__
// |_| \__,_|_.__/|_|_|\___|
/// <summary>
/// 马的前腿根部
/// </summary>
[BoxGroup("必须绑定的物体")][Header("马的前腿根部")]
public Transform frontLegRoot;
/// <summary>
/// 马的后腿根部
/// </summary>
[BoxGroup("必须绑定的物体")][Header("马的后腿根部")]
public Transform backLegRoot;
/// <summary>
/// 马伸直腿后前脚底部
/// </summary>
[BoxGroup("必须绑定的物体")][Header("马前脚伸直后的最深位置")]
public Transform frontFoot;
/// <summary>
/// 马伸直腿后后脚底部
/// </summary>
[BoxGroup("必须绑定的物体")][Header("马后脚伸直后的最深位置")]
public Transform backFoot;
/// <summary>
/// 马脚Transform
/// </summary>
[BoxGroup("必须绑定的物体")][Header("马脚Transform必须按照左前、右前、左后、右后的顺序绑定")]
public Transform[] footsTransform;
/// <summary>
/// 腿做圆周运动的半径
/// </summary>
[BoxGroup("马的基本运动信息")][Header("马脚圆周运动半径")]
public float footMoveRadius;
/// <summary>
/// 四条腿分别对输入偏差多少
/// </summary>
[ListDrawerSettings] [BoxGroup("马的基本运动信息")][Header("四条腿分别对输入偏差多少")]
public Vector2[] footOffestOfInput;
/// <summary>
/// 马足力量
/// </summary>
[BoxGroup("马的基本运动信息")][Header("马足力量")]
public float footForce;
/// <summary>
/// 马蹄推动马身的力度调整值
/// </summary>
[BoxGroup("马的基本运动信息")][Header("马前进的力度")]
public float strength;
/// <summary>
/// 马蹄供力有效范围
/// </summary>
[BoxGroup("马的基本运动信息")][Header("马蹄供力有效范围")]
public float effectiveDistance;
public enum FootType { Front, Back }
/// <summary>
/// 马状态Stand代表无输入Move为有输入着地行走Jump为跳跃当两对足均并拢时进入
/// </summary>
public enum HorseState { Stand, Move, Jump}
/// <summary>
/// 单次跳跃过程中的状态量,Ready表示还没开始累计最终力Charge表示已经开始蓄力jump表示已经触发跳跃
/// </summary>
public enum JumpState { Ready, Charging, Jump }
/// <summary>
/// 跳跃判定阈值,只有输入的变化大于该值才会触发跳跃的蓄力
/// </summary>
[BoxGroup("马的基本运动信息")][Header("马跳跃蓄力阈值")]
public float jumpChargeThreshold;
/// <summary>
/// 马跳跃力度
/// </summary>
[BoxGroup("马的基本运动信息")][Header("马跳跃力度")]
public float jumpForce;
// _____ _ _
// | __ \ (_) | |
// | |__) | __ ___ ____ _| |_ ___
// | ___/ '__| \ \ / / _` | __/ _ \
// | | | | | |\ V / (_| | || __/
// |_| |_| |_| \_/ \__,_|\__\___|
struct foot
{
/// <summary>
/// 腿对输入偏差多少
/// </summary>
public Vector2 footOffestOfInput;
/// <summary>
/// 马脚控制点通过transform找到
/// </summary>
public Transform footControlPoint;
/// <summary>
/// 真实的马脚的Transform
/// </summary>
public Transform footRealTransform;
/// <summary>
/// 马脚此时目标位置,每帧计算
/// </summary>
public Vector3 footTargetPosition;
/// <summary>
/// 马蹄刚体组件
/// </summary>
public Rigidbody2D footRigidbody2D;
/// <summary>
/// 马蹄的碰撞体,但是不绑在马蹄上,而是为了物理计算绑在马身上
/// </summary>
public BoxCollider2D footBoxCollider2D;
/// <summary>
/// 马蹄前一帧的位置
/// </summary>
public Vector3 footPreviousPosition;
}
private foot[] foots;
/// <summary>
/// 马前腿长度
/// </summary>
private float frontLegLength;
/// <summary>
/// 马后腿长度
/// </summary>
private float backLegLength;
/// <summary>
/// 从前腿根骨骼到马脚运动圆心的距离,用来推算最终马脚位置
/// </summary>
private float frontRootDistanceToCenterOfCircle;
/// <summary>
/// 从后腿根骨骼到马脚运动圆心的距离,用来推算最终马脚位置
/// </summary>
private float backRootDistanceToCenterOfCircle;
/// <summary>
/// 输入的马前脚信息
/// </summary>
private Vector2 inputFrontVector;
/// <summary>
/// 输入的马后脚信息
/// </summary>
private Vector2 inputBackVector;
/// <summary>
/// 前一帧的前马脚输入向量
/// </summary>
private Vector2 previousInputFrontVector;
/// <summary>
/// 前一帧的后马脚输入向量
/// </summary>
private Vector2 previousInputBackVector;
/// <summary>
/// 原重力尺度在Start中通过随意一条腿的刚体获取
/// </summary>
private float oriGravityScale;
/// <summary>
/// 马蹄碰撞体数组
/// </summary>
private BoxCollider2D[] footBoxColliders;
/// <summary>
/// 马的刚体组件
/// </summary>
private Rigidbody2D horseRig;
/// <summary>
/// 四条腿分别对输入偏差多少,但是是原始值,不会改变,用于还原并拢的脚
/// </summary>
private Vector2[] oriFootOffestOfInput;
/// <summary>
/// 马此时的状态
/// </summary>
private HorseState horseState;
/// <summary>
/// 此时前腿并拢着吗?
/// </summary>
private bool isFrontFootMerged = false;
/// <summary>
/// 此时后腿并拢着吗?
/// </summary>
private bool isBackFootMerged = false;
/// <summary>
/// 此时的跳跃状态
/// </summary>
private JumpState jumpState = JumpState.Ready;
/// <summary>
/// 跳跃蓄力的速度
/// </summary>
private Vector2 jumpChargeVelocity;
void Start()
{
//初始化
Init();
//找到必要的物体和组件
FindSth();
//计算必要的量
CaculateSth();
}
void Update()
{
//更新一些数据
UpdateDataEarly();
//计算马脚目标位置
CaculateTargetPosition();
//让马脚朝着目标移动
MoveFoot();
//让马整体运动
MoveHorse();
//跳跃检测和物理实现
if(horseState == HorseState.Jump) CheckJump();
//更新一些数据
UpdateDataLate();
}
private void Jump()
{
jumpState = JumpState.Jump;
Vector2 jumpVelocity = jumpChargeVelocity * jumpForce * -1 * new Vector2(1.5f,1.5f);
if(jumpVelocity.magnitude > 30f) jumpVelocity *= 0.1f;
Debug.Log("Jump Velocity: " + jumpVelocity);
horseRig.velocity += jumpVelocity;
jumpChargeVelocity = Vector2.zero;
}
/// <summary>
/// 在跳跃状态时每帧执行,用于检测跳跃和实现跳跃的物理
/// </summary>
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) Jump();
}
}
}
/// <summary>
/// 在Update之前更新一些数据
/// </summary>
private void UpdateDataEarly()
{
//判断是否两腿均并拢若是进入跳跃状态否则设置为Stand状态
if (isFrontFootMerged && isBackFootMerged) horseState = HorseState.Jump;
else horseState = HorseState.Stand;
Debug.Log("jumpState = " + jumpState);
}
/// <summary>
/// 使马并拢输入的双腿,
/// </summary>
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;
}
/// <summary>
/// 恢复输入的并拢的双腿至有偏移状态
/// </summary>
/// <param name="type">需要恢复的是哪一组腿</param>
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;
}
/// <summary>
/// 移动马身体
/// </summary>
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;
//取反,因为是模拟反作用力
v*=-1;
//减弱y轴的影响不要让马在平地上起飞
v *= new Vector2(1,0.2f);
// Debug.Log(v);
//如果模长超过5那必是触发了什么逆天Bug会导致马起飞需要避免这些错误
if(v.magnitude > 10f) 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;
}
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;
}
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<Rigidbody2D>();
//找到马蹄的真实Transform
for(int i = 0; i < 4; i++) foots[i].footRealTransform = footsTransform[i];
//找到马蹄的碰撞体
footBoxColliders = GetComponents<BoxCollider2D>();
//给foots的碰撞体赋值
for (int i = 0; i < 4; i++)
{
foots[i].footBoxCollider2D = footBoxColliders[i];
footBoxColliders[i].offset = foots[i].footControlPoint.localPosition ;
}
//找到马的刚体
horseRig = GetComponent<Rigidbody2D>();
}
/// <summary>
/// 计算一些不会变的必要的量
/// </summary>
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;
}
/// <summary>
/// 根据记录的输入向量计算马脚本帧的目标位置保存到footTargetPoints数组中
/// </summary>
private void CaculateTargetPosition()
{
//循环4次
for(int i = 0; i < 4; i++)
{
//整体公式为Target = 根位置 - V0L - 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;
}
}
/// <summary>
/// 把马脚朝着算好的目标点移动
/// </summary>
private void MoveFoot()
{
//循环4次
for(int i = 0; i < 4; i++)
{
//看此时该脚是否有输入
bool hasInput = i < 2 ? inputFrontVector.magnitude > 0.1f : inputBackVector.magnitude > 0.1f;
//如果有输入,则把马脚朝着目标移动
// 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.5f + 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].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;
//撞击地面后,修改跳跃状态
void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.tag == "Ground")
{
jumpState = JumpState.Ready;
}
}
}