在你的类银河恶魔城中是如何控制人物状态的切换的?

在我的这个项目中,我使用的是有限状态机来实现人物状态的切换的,使用PlayerStateMachine类作为中间类,在该类初始化人物的初始状态,同时提供了一个ChangeState类作为状态切换的方法。同时使用PlayerState作为人物状态的基类,当检测到人物状态需要改变时,调用相应的ChangeState方法来修改状态

在你的项目中,你的人物的速度是线性增加的还是直接为设置的速度?如果实现人物速度的逐渐增加?

物速度是直接使用设置的固定值(如moveSpeeddashSpeed等)。例如PlayerMoveState中通过player.SetVelocity(xInput * player.moveSpeed, rb.velocity.y)直接应用设定的速度。我们可以使用自带的函数方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在Player类中添加加速相关变量
public float acceleration = 2f; // 加速度
private float currentSpeed;

// 在移动状态的Update中使用渐进式加速
public override void Update()
{
base.Update();
// 目标速度 = 输入方向 * 最大速度
float targetSpeed = xInput * player.moveSpeed;
// 逐渐逼近目标速度
currentSpeed = Mathf.MoveTowards(currentSpeed, targetSpeed, acceleration * Time.deltaTime);
player.SetVelocity(currentSpeed, rb.velocity.y);
}

在你的项目中,能否实现人物的2段跳(默认是没有的),如果实现人物的2段跳?

  1. 添加二段跳状态变量:在Player类中增加二段跳计数和是否允许二段跳的标记

    1
    2
    public int maxJumpCount = 2; // 最大跳跃次数(含第一段)
    private int currentJumpCount;
  2. 在跳跃状态中处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // PlayerJumpState.Enter() 中初始化跳跃计数
    public override void Enter()
    {
    base.Enter();
    currentJumpCount++;
    rb.velocity = new Vector2(rb.velocity.x, player.jumpForce);
    }

    // 在地面状态中重置跳跃计数(PlayerGroundState.Enter())
    public override void Enter()
    {
    base.Enter();
    player.currentJumpCount = 0; // 落地后重置
    }

在你的项目中,你是使用什么来实现人物移动的,是通过Transform还是示意图Rigibody来实现?

项目中通过Rigidbody2D实现人物移动。例如:PlayerMoveState中调用player.SetVelocity(xInput * player.moveSpeed, rb.velocity.y)`Entity类中的SetVelocity方法直接操作Rigidbody2D.velocity

1
2
3
4
5
public void SetVelocity(float _Xvelocity, float _Yvelocity)
{
rb.velocity = new Vector2(_Xvelocity, _Yvelocity);
FlipController(_Xvelocity);
}

这种方式相比直接修改Transform更符合物理规律,能正确与碰撞体交互。

update,Latedupdata,Fixupdate的区别?

Update

  • 执行时机:每帧调用一次,调用频率与帧率相关(帧率越高,调用越频繁),时间间隔不固定(受硬件性能、场景复杂度影响)。

  • 核心特点:用于处理逻辑更新,但不适合直接操作物理系统(因时间间隔不稳定,可能导致物理效果异常)。

  • 应用场景:

    • 输入检测(如按键、鼠标事件),例如:

      1
      2
      3
      4
      5
      void Update() {
      if (Input.GetKeyDown(KeyCode.Space)) {
      // 处理跳跃输入
      }
      }
    • 状态机切换、UI 逻辑更新(如技能冷却显示、背包物品切换)。

    • 非物理相关的移动(如相机跟随的基础逻辑)。

2. LateUpdate

  • 执行时机:在所有 Update 执行完毕后调用,每帧一次,时间间隔同样不固定。

  • 核心特点:确保在所有逻辑更新完成后再执行,避免因 Update 执行顺序导致的同步问题。

  • 应用场景:

    • 相机跟随(避免角色移动后相机未及时更新导致的抖动):

      1
      2
      3
      4
      void LateUpdate() {
      // 角色移动(在 Update 中处理)后,相机再跟进
      camera.transform.position = new Vector3(player.position.x, player.position.y, -10);
      }
    • 依赖其他物体状态的逻辑(如角色动画完成后再触发特效)。

3. FixedUpdate

  • 执行时机:以固定时间间隔调用(默认 0.02 秒 / 次,可在 Edit > Project Settings > Time 中修改 Fixed Timestep),与帧率无关。

  • 核心特点:专门用于物理系统更新,确保物理模拟的稳定性和一致性。

  • 应用场景:

    • 操作 Rigidbody(如角色移动、碰撞检测):

      1
      2
      3
      4
      void FixedUpdate() {
      // 物理移动(使用 Rigidbody.velocity 或 AddForce)
      rb.velocity = new Vector2(moveSpeed * inputX, rb.velocity.y)
      }
    • 物理相关的检测(如射线检测、碰撞体状态判断)。

    • 需要固定时间间隔的逻辑(如周期性伤害计算)。

​ 物理系统需要稳定、可预测的时间间隔来保证模拟的准确性和一致性,而FixedUpdate的固定时间步长特性完美适配这一需求,避免了因帧率波动导致的物理行为异常(如速度突变、碰撞失效等)。因此,所有涉及Rigidbody、碰撞检测、力的施加等物理操作,都应优先在FixedUpdate中处理。

总结对比

方法 调用频率 核心用途 注意事项
Update 每帧一次(间隔不固定) 逻辑更新、输入检测 避免直接操作物理组件
LateUpdate 所有 Update 后调用 依赖其他更新的逻辑(如相机) 不适合复杂计算,避免影响帧率
FixedUpdate 固定时间间隔(如 0.02s) 物理系统、固定间隔逻辑 时间间隔固定,适合需要稳定频率的操作

const,readonly是什么以及区别?

  • const
    • 编译时常量,必须在声明时赋值,且值不能修改
    • 只能用于值类型或字符串
    • 示例:public const float Gravity = 9.8f;
  • readonly
    • 运行时常量,可在声明时或构造函数中赋值,赋值后不可修改
    • 可用于引用类型
    • 示例:public readonly float MaxHealth;(在构造函数中赋值)
  • 核心区别:const 是编译期确定的值,readonly 是运行期确定的值;const 在所有实例中共享同一值,readonly 可在不同实例中赋不同值。

Unity能支持序列化字典吗?如果不能如何实现字典的序列化?

  • Unity 默认不支持Dictionary<TKey, TValue>的序列化(Inspector 中不显示,无法保存)。

  • 实现字典序列化的方式:使用SerializableDictionary

    (自定义可序列化字典):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    [System.Serializable]
    public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver
    {
    [SerializeField] private List<TKey> keys = new List<TKey>();
    [SerializeField] private List<TValue> values = new List<TValue>();

    public void OnBeforeSerialize()
    {
    keys.Clear();
    values.Clear();
    foreach (var pair in this)
    {
    keys.Add(pair.Key);
    values.Add(pair.Value);
    }
    }

    public void OnAfterDeserialize()
    {
    Clear();
    for (int i = 0; i < keys.Count; i++)
    {
    Add(keys[i], values[i]);
    }
    }
    }
    1. 用两个平行列表模拟字典(List<TKey> + List<TValue>

你在项目中使用了scriptObject设置游戏配置内容,为什么?

  • 数据与逻辑分离:将配置(如角色属性、技能参数)存储在 ScriptableObject 资产中,无需修改代码即可调整数值。
  • 资源复用:多个对象可共享同一 ScriptableObject 实例,减少内存占用。
  • 便捷调试:在 Inspector 中直观修改参数,实时预览效果。
  • 可持久化:配置数据作为资产独立保存,不依赖场景,便于版本控制。

你了解原生的C#的异步编程吗?Task,await?

  • Task:表示一个异步操作,可用于封装耗时任务(如文件 IO、网络请求)。

  • await:用于暂停当前方法执行,等待Task完成后再继续,不会阻塞主线程。

  • 示例(Unity 中使用)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    using System.Threading.Tasks;
    using UnityEngine;

    public class AsyncExample : MonoBehaviour
    {
    private async void Start()
    {
    Debug.Log("开始加载数据...");
    // 异步加载数据,不阻塞主线程
    var data = await LoadDataAsync("config.json");
    Debug.Log("数据加载完成:" + data);
    }

    private Task<string> LoadDataAsync(string path)
    {
    return Task.Run(() =>
    {
    // 模拟耗时操作(如读取文件)
    System.Threading.Thread.Sleep(2000);
    return "加载的配置内容";
    });
    }
    }
  • 注意:Unity 主线程相关操作(如修改 UI、操作 Transform)需在主线程执行,可通过UnitySynchronizationContext切换上下文。