Unity 设计生存游戏制作笔记
在 Unity 中,Update 和 FixedUpdate 是两个常用的函数,用于在每一帧中更新游戏对象的状态和行为。它们的区别在于调用时间和执行频率不同。
我们首先创建了这些东西
1.控制鼠标移动的脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseMovingScript : MonoBehaviour
{
public float mouseSensitivity = 100f;
float xRotation = 0f;
float yRotation = 0f;
public float topClamp = -90f;
public float bottomClamp = 90f;
// Start is called before the first frame update
void Start()
{
Cursor.lockstate = CursorLockMOde.Locked;
}
// Update is called once per frame
void Update()
{
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity * Time.deltaTime;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity * Time.deltaTime;
xRotation -= mouseY;
xRotation = Mathf.Clamp(xRotation, topClamp , bottomClamp );
yRotation += mouseX;
transform localRotation = Quaternion Euler(xRotation , yRotation , 0f);
}
}
- XRotation -= mouseY; 这行代码的作用
在 Update() 中,mouseY 代表鼠标在 垂直方向(Y轴) 的移动量。当玩家向上移动鼠标时,mouseY 为 正值,向下移动鼠标时 mouseY 为 负值。
这行代码的作用是反转鼠标Y轴的输入,使得鼠标上移时视角向上,鼠标下移时视角向下。
XRotation 代表 摄像机在X轴方向上的旋转角度(即视角的俯仰角 Pitch)。
mouseY 直接加到 XRotation 上会导致鼠标上移时摄像机向下看,因此用 XRotation -= mouseY; 反转方向,让鼠标上移时视角也上移。
- Mathf.Clamp(XRotation, 90f , 90f);
作用
Mathf.Clamp(value, min, max) 用于限制数值范围,确保 value 不会超过 min 和 max 之间。
这样可以限制 XRotation 在 -90 到 90 之间,防止摄像机翻转,让玩家不能把视角转到头顶或者脚下。
Cursor.lockState = CursorLockMode.Locked;
这行代码用于锁定鼠标指针,让鼠标在游戏窗口内不可见,并且不会移出游戏屏幕。
pickedupWeapon.transform.SetParent(activeWeaponSlot.transform , false);
请问这个false是什么意思
SetParent(Transform parent, bool worldPositionStays)
方法的第二个参数 worldPositionStays
决定了 更改父对象时子对象的世界坐标是否保持不变。
在你的代码中:
pickedupWeapon.transform.SetParent(activeWeaponSlot.transform, false);
这里的 false
表示 子对象(pickedupWeapon)会完全继承新父对象(activeWeaponSlot)的位置、旋转和缩放,也就是 跟随父对象的本地坐标。
worldPositionStays
的两种情况:
true
(默认值):- 保持世界坐标不变,但会更新
localPosition
和localRotation
以补偿位置变化。 - 适用于:希望子对象在场景中 不改变外观 但更换父对象的情况。
- 保持世界坐标不变,但会更新
false
:- 跟随新父对象的本地坐标,
localPosition
、localRotation
和localScale
不变(相对于新父对象)。 - 适用于:希望子对象 完全变成新父对象的一部分,跟随父对象移动。
- 跟随新父对象的本地坐标,
举个例子:
假设 pickedupWeapon
在世界坐标 (10, 0, 5)
,activeWeaponSlot
在 (0, 0, 0)
:
true
(保持世界坐标)pickedupWeapon.transform.SetParent(activeWeaponSlot.transform, true);
pickedupWeapon
仍然在 **(10, 0, 5)**,但它的localPosition
可能会被调整以保持世界坐标不变。
false
(跟随新父对象)pickedupWeapon.transform.SetParent(activeWeaponSlot.transform, false);
pickedupWeapon
的 localPosition 维持不变,但由于activeWeaponSlot
在(0, 0, 0)
,它的 世界坐标可能会改变,以匹配activeWeaponSlot
的本地坐标系。
你的代码的作用:
false
使得 pickedupWeapon
完全继承 activeWeaponSlot
的位置和旋转,让武器正确放置在武器槽里,而不会漂浮在原来的世界坐标位置。
**
internal变量**:意味着你可以从其他脚本访问这个变量,但是你不可从inspector窗口当中修改这个变量
如果动画发生偏移,注意是否是动画的坐标归回初始坐标
如果数值发生了大幅度浮动现象,看看是不是因为在动画里面打了有关数值的关键帧
防止枪穿墙的简单方法:
在maincamera的下面创建一个render用的子camera,与此同时 创建一个新的weapon render层,并且将所有的武器包括预制体加上这个层,之后在maincamera当中将渲染层cull mask选项当中的weaponrenderlayer关闭,之后将rendercamera的渲染cull mask改成只用weaponrenderlayer,之后将background type 设置成uninitialize,将hdr mssa设置成off,之后你的武器就不会穿墙
如果你发现你的image无法显示在hierarchy视图当中,那么你可能需要在方法当中检查image是否与其他的同名方法冲突
Resources.Load
详细解析
Resources.Load
这是 Unity 提供的一个 API,用于从 Resources 目录加载资源。
泛型
path 是资源在 Resources 目录中的相对路径(不带 .prefab 后缀)。
Instantiate(Resources.Load
Resources.Load
Instantiate(…) 创建该预制体的一个实例(克隆)。
.GetComponent
前提条件
你的项目需要有一个名为 “Pistol1911_Weapon” 的 预制体(Prefab)。
这个 Prefab 必须放在 Assets/Resources/ 目录下,否则 Resources.Load() 无法找到它。
这个预制体上必须有 SpriteRenderer 组件,否则 GetComponent
GetComponentInChildren<Weapon>()
是什么?
GetComponentInChildren<Weapon>()
是 Unity 提供的一个方法,用于在当前 GameObject 及其所有子对象中查找指定类型的组件。它返回找到的第一个 Weapon
组件(如果存在),否则返回 null
。
示例 1:基本用法
假设我们有如下的 GameObject 层级结构:
Player (GameObject)
│── ActiveWeaponSlot (GameObject)
│ ├── Weapon (GameObject) <--- 这里有 Weapon 组件
│ ├── Scope (GameObject)
│ ├── Magazine (GameObject)
如果 ActiveWeaponSlot
这个 GameObject 调用:
Weapon activeWeapon = activeWeaponSlot.GetComponentInChildren<Weapon>();
那么:
GetComponentInChildren<Weapon>()
会向下查找,发现Weapon
组件,并返回它。
示例 2:实际代码
在你的 HUDManager
代码中:
Weapon activeWeapon = WeaponManager.Instance.activeWeaponSlot.GetComponentInChildren<Weapon>();
这行代码的作用是:
WeaponManager.Instance.activeWeaponSlot
获取当前激活的武器槽(GameObject
)。.GetComponentInChildren<Weapon>()
在这个GameObject
及其所有子对象中查找Weapon
组件。- 如果找到了
Weapon
组件,就返回它;否则返回null
。
示例 3:使用 GetComponent<>
vs GetComponentInChildren<>
情况 1:当前 GameObject 有 Weapon
组件
Weapon weapon = gameObject.GetComponent<Weapon>();
- 只会检查
gameObject
本身是否有Weapon
组件。 - 不会检查子对象。
情况 2:当前 GameObject 的子对象有 Weapon
组件
Weapon weapon = gameObject.GetComponentInChildren<Weapon>();
- 会检查
gameObject
本身。 - 如果
gameObject
本身没有Weapon
,会继续检查所有子对象。 - 返回找到的第一个
Weapon
组件。
示例 4:GetComponentInChildren<T>(true)
默认情况下,GetComponentInChildren<T>()
只会查找激活的对象。
如果希望包括隐藏的(inactive)子对象,可以使用:
Weapon weapon = gameObject.GetComponentInChildren<Weapon>(true);
true
表示即使对象被禁用(SetActive(false)),也要查找。
总结
GetComponentInChildren<T>()
会向下递归查找子对象,返回找到的第一个T
组件。GetComponent<T>()
只会检查当前对象,不会检查子对象。GetComponentInChildren<T>(true)
可以查找隐藏的子对象。- 在你的代码里:
作用是获取当前激活武器槽(Weapon activeWeapon = WeaponManager.Instance.activeWeaponSlot.GetComponentInChildren<Weapon>();
activeWeaponSlot
)及其子对象上的Weapon
组件。
这个方法特别适合用在 层级结构较深 的 UI 或 3D 物体上,比如武器系统、UI 面板、装备栏等!🚀
#region || ---- Ammo ---- ||
#endregion
用于折叠代码
📌 Unity 动画中的 Root Transform(根变换)是什么?
在 Unity 动画系统中,Root Transform(根变换) 指的是动画中影响整个角色或物体的全局变换(位置、旋转、缩放)的部分,主要用于 Root Motion(根运动)。
当动画中启用了 Root Motion,Unity 会让 Root Transform 直接驱动游戏对象的 Transform
(位置和旋转),而不是依赖 CharacterController
或 Rigidbody
的物理计算。
🎯 Root Transform 具体包含哪些部分?
在 Unity 动画导入设置(Inspector > Animation)中,你会看到以下几个 Root Transform 相关的选项:
选项 | 作用 |
---|---|
Root Transform Position (Y) | 控制动画中 垂直方向(Y 轴) 的位置变化。 |
Root Transform Position (XZ) | 控制动画中 水平移动(X 和 Z 轴) 的位置变化。 |
Root Transform Rotation | 控制动画中 旋转 的变化。 |
🛠 Root Transform 作用
- 控制动画的世界坐标移动
- 例如,一个奔跑动画,如果启用了 Root Motion,角色会真实向前移动,而不是原地播放动画 + 代码控制移动。
- 修正动画方向
- 例如,导入的动画方向不对,可以通过
Root Transform Rotation
调整,使角色始终面向正确方向。
- 例如,导入的动画方向不对,可以通过
- 防止角色漂移或滑动
- 当一个跳跃动画在 Y 轴方向有位移,可以调整
Root Transform Position (Y)
来防止角色跳得太高或浮空。
- 当一个跳跃动画在 Y 轴方向有位移,可以调整
🎮 示例:Root Transform 应用
🟢 示例 1:启用 Root Motion 的行走动画
如果你有一个角色 Player
,并播放 walk
动画:
- 不开启 Root Motion:动画只是原地播放,你需要用代码
transform.Translate()
让角色移动。 - 开启 Root Motion:动画的 Root Transform 直接影响
Player
的Transform
,角色会随着动画移动。
void Start()
{
animator.applyRootMotion = true; // 开启 Root Motion
}
🔵 示例 2:调整 Root Transform 让角色保持正确方向
有时候导入动画后,角色可能会:
朝错误方向移动(例如跑步动画默认是面朝 Z 轴,但你的角色面朝 X 轴)。
你可以在
Animation > Root Transform Rotation
里调整
Based Upon(基于…)
:
- Body Orientation(身体朝向):角色会根据动画自身调整方向,适用于大多数情况。
- Original(原始):保持动画文件的原始旋转方向。
✅ 总结
- Root Transform 是动画中的 根节点变换(位置、旋转、缩放),在 Root Motion 模式下控制角色的移动和旋转。
- Root Transform 主要用于修正动画的移动和方向,防止角色滑动或漂移。
- 如果角色动画需要真实移动,启用 Root Motion,否则可以手动用代码控制
Transform
位置。
这样,你的动画就不会看起来像是在地面上“滑动”了 🚀!
📌 NavMeshAgent 中的 Angular Speed(角速度)是什么?
在 Unity 的 NavMeshAgent
组件中,Angular Speed(角速度) 控制 AI 角色旋转的速度,单位是 度/秒(degrees per second)。
当 NavMeshAgent
需要转向目标方向时,Angular Speed 限制了旋转的最大速度,防止角色瞬间转向,使其旋转更加平滑和自然。
🎯 Angular Speed 的作用
- 控制转向速度
- 角速度越高,角色转向越快,甚至可能瞬间完成转向。
- 角速度越低,角色需要较长时间才能转向目标方向。
- 防止转向过快导致的不自然运动
- 角色的行走、奔跑时的转向会受到
Angular Speed
限制,防止突然急转弯。 - 适用于模拟现实中的惯性,如汽车或人物转向。
- 角色的行走、奔跑时的转向会受到
- 影响 NavMeshAgent 在路径拐弯处的流畅度
- 角速度过低,AI 角色在拐弯时可能会显得“呆滞”或“拖沓”。
- 角速度过高,AI 角色可能会在拐弯时产生不自然的瞬间转向。
🛠 示例:调整 Angular Speed
🟢 示例 1:普通人类角色
navAgent.angularSpeed = 120f; // 让角色每秒最多旋转 120 度
- 效果:角色转向较快但不会太突兀,适合一般行走的 AI 角色。
🔵 示例 2:快速转向(机器人、轻型载具)
navAgent.angularSpeed = 500f; // 角色能瞬间完成转向
- 效果:适用于机器人或赛车,能够几乎瞬间转向。
🔴 示例 3:缓慢转向(坦克、重型载具)
navAgent.angularSpeed = 30f; // 角色转向很慢
- 效果:适用于坦克或笨重角色,转向会很慢,显得更有惯性。
⚠️ Angular Speed 影响转向,但不会影响移动!
Angular Speed
只控制 旋转速度,不影响角色的前进速度(由speed
控制)。```
NavMeshAgent不会强制 AI 角色面向移动方向 ,如果需要角色始终朝向目标方向,可以手动调整: ```csharp if (navAgent.velocity.sqrMagnitude > 0.1f) // 只有在移动时才调整方向 { transform.forward = Vector3.Lerp(transform.forward, navAgent.velocity.normalized, Time.deltaTime * 10f); }
- 这样即使
Angular Speed
低,角色也不会显得“滞后”。
- 这样即使
🎮 总结
✅ Angular Speed
决定 NavMeshAgent 旋转的速度,单位是 度/秒。
✅ 适用于控制 AI 角色拐弯时的流畅度,防止突然瞬转或过度滞后。
✅ 数值建议:
- 普通角色:
120f ~ 180f
- 快速单位(机器人、轻载具):
300f ~ 600f
- 缓慢单位(坦克、重载具):
20f ~ 60f
✅ 如果角色旋转滞后,可以手动调整 transform.forward 让它更流畅。
这样,你的 AI 角色在 NavMesh
上移动时会更符合预期 🚀!
📌 Has Exit Time 在 Unity Animator 中的含义
Has Exit Time
是 Unity Animator 里 状态转换(Transition) 的一个参数,它决定了动画在播放完成后是否会自动切换到下一个状态。
🎯 Has Exit Time 的作用
当一个动画从 A 状态 过渡到 B 状态 时,如果 Has Exit Time 被勾选,表示:
- 动画 A 必须播放到一定时间(Exit Time)才能进行过渡。
- 如果
Exit Time
设为1.0
,意味着动画 A 必须完整播放完,然后才会切换到动画 B。 - 如果
Exit Time
设为0.5
,则动画 A 只需播放到 50% 时就可以进入 B。
如果 Has Exit Time 没有勾选:
- 动画 A 可能会被事件或参数立即打断,立刻进入动画 B(适用于攻击、跳跃等可随时中断的动画)。
🛠 使用示例
🟢 示例 1:角色循环播放奔跑动画
如果
Has Exit Time
被勾选,角色的奔跑动画会播放完整的 1 次,然后才会进入下一个动画。
animator.SetTrigger("Jump");
- 适用情况:适合播放完整的 开场动画、攻击动画、死亡动画,不希望动画被意外打断。
🔵 示例 2:角色攻击可以被打断
如果
Has Exit Time
未勾选,攻击动画可以随时打断进入新的动作,比如连击或闪避。
if (Input.GetKeyDown(KeyCode.Space))
{
animator.SetTrigger("Attack");
}
- 适用情况:适合 攻击、翻滚、跳跃等需要立即响应的动画。
🔄 Exit Time 的数值意义
在 Has Exit Time
选项打开的情况下,你还可以调整 Exit Time
(动画退出时间):
Exit Time 值 | 效果 |
---|---|
1.0 |
动画播放完 100% 后切换(默认)。 |
0.75 |
动画播放到 75% 时切换。 |
0.5 |
动画播放到一半时切换,适用于提前进入下一状态。 |
0.0 |
动画一开始就切换(几乎等于无意义)。 |
⚠️ Has Exit Time 的注意点
- Has Exit Time 适用于循环动画,但可能影响即时响应
- 如果
Has Exit Time
被勾选,可能会导致角色在动画播放完之前无法切换状态,导致动画卡顿或延迟。
- 如果
- 非循环动画通常不勾选 Has Exit Time
- 例如,攻击、跳跃、受伤等动画,通常希望它们可以随时被打断。
- 与 Fixed Duration 结合使用
Fixed Duration
:如果勾选,Exit Time
代表的是固定时间(比如 1 秒);如果不勾选,它代表的是动画百分比(比如 1.0 = 100%)。
🎮 总结
✅ Has Exit Time 控制动画是否必须播放到一定时间后才能转换。
✅ 适用于 需要完整播放的动画(如奔跑、开门、死亡)。
✅ 不适用于 需要随时响应输入的动画(如攻击、跳跃、翻滚)。
✅ 如果动画需要及时打断,不要勾选 Has Exit Time,而是用参数(Trigger、Bool)来控制动画转换!
这样,你的动画切换就会更流畅和符合预期 🎬🚀!
# Answer
逐步分解并详细解释代码:
### 代码解释
```csharp
if(agent.remainingDistance <= agent.stoppingDistance)
```
- *agent.remainingDistance*: 这是NavMeshAgent组件的一个属性,表示从当前位置到目标位置的剩余距离。
- *agent.stoppingDistance*: 这是NavMeshAgent组件的另一个属性,表示当剩余距离小于或等于这个值时,Agent认为已经到达目标位置。
- *if(agent.remainingDistance <= agent.stoppingDistance)*: 这是一个条件判断语句,用于检测Agent是否已经到达当前的目标位置。具体来说,当remainingDistance
小于或等于stoppingDistance
时,条件为真,执行其后的语句块。
### 代码上下文
在OnStateUpdate
方法中,这段代码被用来检测僵尸是否已经到达当前的巡逻点。如果僵尸到达了当前的巡逻点,它就会被分配一个新的随机巡逻点作为目标。
```csharp
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Check if agent arrived at waypoint , move to next waypoint
if(agent.remainingDistance <= agent.stoppingDistance)
{
agent.SetDestination(wayPointsList[Random.Range(0, wayPointsList.Count)].position);
}
}
```
- *OnStateUpdate*: 这是Unity中AnimatorBehaviour类的一个方法,会在动画状态更新时调用。该方法用于在每一帧中检查僵尸是否到达当前的巡逻点。
- *agent.SetDestination*: 如果僵尸到达当前的巡逻点,则通过此方法设置一个新的目标位置。目标位置是从wayPointsList中随机选择的一个巡逻点的坐标。
### 总结
这段代码的主要功能是实现僵尸角色在多个巡逻点之间的随机巡逻。当僵尸到达一个巡逻点后,它会自动选择下一个巡逻点作为目标,并移动向这个新的目标。这样可以模拟真实环境中僵尸的巡逻行为,增加了游戏的趣味性和真实性。
导入图片ui 作为image,可以在stretch里按住alt点击自己想要的效果
这段代码可以可视化一些距离
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, 2f);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, 18f);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, 21f);
}