SAIMA/Assets//脚本/Horse.cs
Roman 03b4c4d9f2 任务:针对上次答辩的反馈更新内容
子任务:恢复并更新马的操控模型
1.上一回在演示之前,操控模型都没有调好,所以我临时换到了一个比较容易展示的操控模型。应该把它换回并继续调教。
DONE
2.现在马的加速状态不理想,马的最大速度很小,想要统计过去一段时间内的摇杆输入的变化总量,以此来给速度加成。
(1.每Fixed记录本帧和上帧输入方向,求得夹角,累计若干时间内的夹角,就能得到这一段时间用户的输入总体的速度水平
(2.让这个速度水平乘以调整值后影响马的前进力度,即可解决加速问题
已将手感调至较为合适
DONE

子任务:调整镜头比例,上次说马太大了。
已稍微调小
DONE
2022-08-10 22:14:21 +08:00

706 lines
24 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, Dead}
/// <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;
[BoxGroup("马的其他信息")][Header("马被相机挤死后飞出有多大的力")]
public float deadFlySpeed;
/// <summary>
/// 要统计多长时间内的输入变化以供加速?】
/// </summary>
[BoxGroup("马的基本运动信息")][Header("要统计多长时间内的输入变化以供加速?")]
public float accelerationTime;
/// <summary>
/// 加速倍率
/// </summary>
[BoxGroup("马的基本运动信息")][Header("加速倍率")]
public float accelerationRate = 1f;
// _____ _ _
// | __ \ (_) | |
// | |__) | __ ___ ____ _| |_ ___
// | ___/ '__| \ \ / / _` | __/ _ \
// | | | | | |\ 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 pInputFrontVector;
/// <summary>
/// 输入的马后脚信息
/// </summary>
private Vector2 inputBackVector;
/// <summary>
/// 上一帧马后脚信息
/// </summary>
private Vector2 pInputBackVector;
/// <summary>
/// 前一帧的前马脚输入向量
/// </summary>
private Vector2 previousInputFrontVector;
/// <summary>
/// 前一帧的后马脚输入向量
/// </summary>
private Vector2 previousInputBackVector;
/// <summary>
/// 一段时间内的变化总量
/// </summary>
private float chargeCount = 0;
/// <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;
/// <summary>
/// 死亡后的旋转中心
/// </summary>
private Transform rotateCenter;
/// <summary>
/// 保存了最近若干秒内的输入充能
/// </summary>
private Queue <float> footInputChargeQueue;
/// <summary>
/// 应有的输入加速累计队列的长度
/// </summary>
private int footInputChargeQueueLength;
/// <summary>
/// 本帧中,累计的充能值
/// </summary>
private float footInputCharge = 0;
void Start()
{
//初始化
Init();
//找到必要的物体和组件
FindSth();
//计算必要的量
CaculateSth();
}
void Update()
{
//更新一些数据
UpdateDataEarly();
if(horseState != HorseState.Dead)
{
//计算马脚目标位置
CaculateTargetPosition();
//让马脚朝着目标移动
MoveFoot();
//让马整体运动
MoveHorse();
//跳跃检测和物理实现
if(horseState == HorseState.Jump) CheckJump();
}
//更新一些数据
UpdateDataLate();
}
void FixedUpdate() {
CaculateFootInputCharge();
}
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<Collider2D> temp = new List<Collider2D>();
GetComponentsInChildren<Collider2D>(temp);
foreach(Collider2D box in temp)
box.enabled = false;
horseRig.velocity = Vector2.one * deadFlySpeed;
horseRig.AddTorque(deadFlySpeed * 5f);
}
/// <summary>
/// 一次跳跃的最终触发函数
/// </summary>
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) *13f, 28f);
jumpVelocity = 0.2f * jumpVelocity + 0.8f * fix;
//屏蔽出了问题的极大值
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(horseState != HorseState.Dead)
{
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 * (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;
}
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<float>();
}
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>();
//
rotateCenter = transform.Find("马死后的旋转中心");
}
/// <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;
//计算要记录多少帧的输入用秒计算每秒60次FixedUpdate
footInputChargeQueueLength = (int)accelerationTime * 60;
}
/// <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;
//如果有输入,则把马脚朝着目标移动
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;
//撞击地面后,修改跳跃状态
void OnCollisionEnter2D(Collision2D collision)
{
if(collision.gameObject.tag == "Ground")
{
jumpState = JumpState.Ready;
}
}
}