哈乐沃德面试
在你的类银河恶魔城中是如何控制人物状态的切换的?
在我的这个项目中,我使用的是有限状态机来实现人物状态的切换的,使用PlayerStateMachine类作为中间类,在该类初始化人物的初始状态,同时提供了一个ChangeState类作为状态切换的方法。同时使用PlayerState作为人物状态的基类,当检测到人物状态需要改变时,调用相应的ChangeState方法来修改状态
在你的项目中,你的人物的速度是线性增加的还是直接为设置的速度?如果实现人物速度的逐渐增加?
物速度是直接使用设置的固定值(如moveSpeed、dashSpeed等)。例如PlayerMoveState中通过player.SetVelocity(xInput * player.moveSpeed, rb.velocity.y)直接应用设定的速度。我们可以使用自带的函数方法
1 | // 在Player类中添加加速相关变量 |
在你的项目中,能否实现人物的2段跳(默认是没有的),如果实现人物的2段跳?
添加二段跳状态变量:在Player类中增加二段跳计数和是否允许二段跳的标记
1
2public int maxJumpCount = 2; // 最大跳跃次数(含第一段)
private int currentJumpCount;在跳跃状态中处理
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 | public void SetVelocity(float _Xvelocity, float _Yvelocity) |
这种方式相比直接修改Transform更符合物理规律,能正确与碰撞体交互。
update,Latedupdata,Fixupdate的区别?
Update
执行时机:每帧调用一次,调用频率与帧率相关(帧率越高,调用越频繁),时间间隔不固定(受硬件性能、场景复杂度影响)。
核心特点:用于处理逻辑更新,但不适合直接操作物理系统(因时间间隔不稳定,可能导致物理效果异常)。
应用场景:
输入检测(如按键、鼠标事件),例如:
1
2
3
4
5void Update() {
if (Input.GetKeyDown(KeyCode.Space)) {
// 处理跳跃输入
}
}状态机切换、UI 逻辑更新(如技能冷却显示、背包物品切换)。
非物理相关的移动(如相机跟随的基础逻辑)。
2. LateUpdate
执行时机:在所有 Update 执行完毕后调用,每帧一次,时间间隔同样不固定。
核心特点:确保在所有逻辑更新完成后再执行,避免因 Update 执行顺序导致的同步问题。
应用场景:
相机跟随(避免角色移动后相机未及时更新导致的抖动):
1
2
3
4void 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
4void 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[]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver
{
[] private List<TKey> keys = new List<TKey>();
[] 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]);
}
}
}- 用两个平行列表模拟字典(
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
23using 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切换上下文。
