一、第一次写游戏脚本:控制英雄移动
"小陈,第二章我们说过组件式架构。现在真正写一个组件——让英雄响应你的输入。"老周打开代码编辑器:
// HeroController.cs —— 挂在英雄 GameObject 上的脚本组件
using UnityEngine;
public class HeroController : MonoBehaviour
{
public float moveSpeed = 3f; // 移动速度(可以在面板调)
private Rigidbody2D rb; // 引用物理组件
void Start()
{
// Start:组件被激活时调用一次(初始化)
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
// Update:每帧调用(游戏循环的"更新"阶段)
// 读取输入
float h = Input.GetAxisRaw("Horizontal"); // ←→ 方向键
float v = Input.GetAxisRaw("Vertical"); // ↑↓ 方向键
// 移动(用 deltaTime 保证帧率无关,第二章讲的!)
Vector2 move = new Vector2(h, v).normalized;
rb.velocity = move * moveSpeed;
}
}
"把这段脚本挂到英雄身上,点 Play——按方向键,英雄就动了。 这就是游戏开发的'第一个成就时刻'。"
二、脚本生命周期:游戏代码的"心跳顺序"
"游戏脚本不是随便跑的,它有严格的生命周期(Lifecycle)。"老周画:
脚本生命周期(Unity/MonoBehaviour):
Awake() 对象被创建时调用(最早,资源初始化)
OnEnable() 组件启用时调用
Start() 第一次 Update 之前调用一次(初始化逻辑)
Update() 每帧调用(60 次/秒,主逻辑)
FixedUpdate() 固定频率调用(物理,50 次/秒)
LateUpdate() 所有 Update 之后调用(相机跟随常用)
OnDisable() 组件停用时调用
OnDestroy() 对象销毁时调用(清理)
顺序示例(一个英雄的一生):
Awake → OnEnable → Start → Update×N → OnDestroy
常见错误:
- 把资源加载放 Update(每次都加载,卡死!)→ 放 Awake/Start
- 忘记在 OnDestroy 清理(事件监听泄漏)→ 游戏越来越卡
"生命周期的意义:知道代码'什么时候被调用'——资源加载放 Awake(只一次)、逻辑更新放 Update(每帧)、物理放 FixedUpdate(固定)。放错位置是新手最常见的 bug 来源。"
三、组件间通信:让不同脚本"说话"
"游戏里有很多组件:血量组件、移动组件、技能组件……它们怎么配合?"老周说,"三种常见方式:"
组件间通信三方式:
① 直接引用(最简单):
用 GetComponent 拿到别的组件
Health health = GetComponent<Health>();
② 事件(松耦合,推荐):
一个组件发出事件,其他组件监听
health.OnDeath += GameManager.OnHeroDied;
③ 消息/广播(引擎级):
SendMessage("TakeDamage", 100);
选择原则:
简单场景 → 直接引用
复杂系统 → 事件(解耦,易扩展)
跨模块 → 消息/单例管理
例子(英雄攻击 → 怪物掉血):
// 攻击方
enemy.GetComponent<Health>().TakeDamage(attackPower);
// 或走事件:
enemy.health.OnDamaged += ShowDamageNumber;
"组件通信是游戏架构的'血管'——MOBA 里英雄攻击、技能命中、掉血、死亡、播报……全是组件之间的事件流动。学会用'事件'解耦,你的代码才不会变成'意大利面'。"
四、数据驱动:让数值"听策划的"
"小陈,你会怎么定义英雄的属性?"老周问。
"写死在代码里?attack = 100……"
"写死代码 = 每次调数值都要改代码重新编译 = 噩梦。"老周摇头,"游戏行业的标准做法是数据驱动(Data-driven):数值放配置文件,代码只负责'读'和'执行'。"
数据驱动设计:
JSON/Excel/资产文件存数值,代码读它
英雄配置(hero_data.json):
{
"name": "云间剑客",
"hp": 850, "attack": 120, "defense": 60,
"move_speed": 3.5,
"skills": ["云斩", "剑影", "破军"]
}
// 代码只负责加载和逻辑
public class HeroData
{
public string name;
public int hp, attack, defense;
public float move_speed;
public List<string> skills;
}
好处:
- 改数值不用改代码(策划自己就能调)
- 新英雄 = 新配置(不用写新代码,只要逻辑复用)
- 方便平衡性测试(数值随便改,随时调)
MOBA 的做法:
每个英雄一份配置(技能数值、成长曲线、皮肤)
新增英雄 → 美术+数值+配置,代码不用大动
"数据驱动是'让游戏内容可控'的核心思想——代码是'引擎',配置是'燃料'。MOBA 一百多个英雄,不可能每个都写一套代码——都是同一套逻辑 + 不同配置。"
五、游戏状态管理:大厅、对局、结算
"游戏不是从头到尾一个场景——登录 → 大厅 → 选英雄 → 对战 → 结算 → 回到大厅。"老周说,"这需要状态管理(Game State)。"
游戏状态(Game State):
大厅状态:浏览英雄、开始匹配
选英雄状态:选择/禁用英雄
加载状态:加载地图资源
对战状态:游戏进行中
结算状态:显示胜负、奖励
实现方式:
- 状态机(类似第四章动画状态机)
- 场景切换(不同场景 = 不同状态)
- 单例 GameManager 管理当前状态
例子(切换状态):
GameManager.Instance.ChangeState(GameState.Battle);
// 触发:加载地图、初始化双方英雄、开始计时
MOBA 对战内还有自己的状态:
对线期 → 中期团战 → 后期决战
以及:小兵波次、野怪刷新、防御塔状态
"游戏状态管理是'游戏的大框架'——一个完整的游戏 = 一串状态的流转。对战类游戏尤其重要:匹配、选人、开局、打团、结算,每个状态都要严谨。"
六、章末:老周的第四层总结
第四层:游戏逻辑与脚本
├── 脚本 = 挂在对象上的"大脑"(组件式编程)
├── 生命周期:Awake→Start→Update→FixedUpdate→Destroy
├── 组件通信:直接引用 / 事件(推荐)/ 消息
├── 数据驱动:数值放配置,代码只读不写死
│ (新英雄 = 新配置,不用新代码)
├── 游戏状态:大厅→选人→对战→结算(状态机管理)
└── 核心:逻辑与数据分离、组件解耦、生命周期清楚
下一章预告:
英雄会动了、会放技能了——但"技能"怎么计算伤害?
血条怎么掉?Buff 怎么叠?暴击怎么算?
—— 战斗系统:技能、伤害、血条。游戏"好玩"的核心。
"小陈,你的英雄终于'听话'了——能走、能跑、能放技能,数值也是策划说了算。"老周说,"但你现在按下攻击键——只是做了个攻击动画,敌人不掉血! 下一章,是最刺激的部分:战斗系统——伤害计算、技能、暴击、Buff。游戏好不好玩,就看这一章了。"