4. 你的第一个脚本
解锁整个框架的那一个模式,外加你最先会用到的五个服务的可复制粘贴代码片段。
4.1 快速开始
新建一个 C# 脚本,把下面的代码粘进去,挂到任意场景中的任意 GameObject 上,然后按下 Play:
using CommonGameSystem.Core;
using UnityEngine;
public class HelloCgs : MonoBehaviour
{
private void Start()
{
var scheduler = ServiceLocator.Resolve<IScheduler>();
var tween = ServiceLocator.Resolve<ITweenService>();
scheduler.After(1f, () => Debug.Log("One second after Play."));
tween.To(1f, 2f, 0.5f,
scale => transform.localScale = Vector3.one * scale,
EaseType.OutBack);
}
}
对象带着弹性过冲弹到两倍大小,一秒后一条消息出现。注意缺了什么:没有管理器预制体,没有初始化场景,没有设置组件。引导程序在你的 Start 运行之前就注册好了全部 23 个服务。
4.2 服务解析是如何工作的
每个 CGS 服务的获取方式都相同:向服务定位器索取你需要的接口。
var save = ServiceLocator.Resolve<ISaveService>();
三条规则让这既快又安全:
- 尽早解析,缓存结果。 服务在第一个场景加载之前就已注册,所以在
Awake和Start中解析都是安全的。把引用存进字段并复用——绝不要在Update里调用Resolve。 - 缺失的服务会抛异常。 如果某个接口没有注册任何实现,
Resolve<T>()会抛出一个含义清晰的异常。想做软检查,用ServiceLocator.TryResolve(out T service)或ServiceLocator.IsRegistered<T>()。 - 一切都可以替换。
ServiceLocator.Replace<T>(instance)可以在任何时刻换上你自己的实现,或一个什么都不做的空版本(第 1 章 1.4 节)。
典型的服务使用方长这样:
public class GameHud : MonoBehaviour
{
private ISaveService _save; // cached once, used many times
private void Awake()
{
_save = ServiceLocator.Resolve<ISaveService>();
}
}
4.3 你最先会用到的五个服务
下面每段代码都运行在一个 MonoBehaviour 内部,并沿用 4.1 节的 using 语句。
存档与读档
任何可序列化的类都可以成为存档文件。写入是原子性的——存档进行到一半时崩溃,也绝不会损坏之前的文件。完整参考:Save/Load。
[System.Serializable]
public class PlayerData { public int Level; public float Health; }
var save = ServiceLocator.Resolve<ISaveService>();
save.Save("slot0", new PlayerData { Level = 3, Health = 75f });
var result = save.Load<PlayerData>("slot0");
if (result.Status == SaveStatus.Ok)
Debug.Log("Loaded level " + result.Value.Level);
事件
任何类都可以成为事件。发布者和订阅者彼此从不引用——它们只共享事件类型。完整参考:Event Bus。
public sealed class CoinCollected { public int Amount; }
var events = ServiceLocator.Resolve<IEventBus>();
System.IDisposable ticket = events.Subscribe<CoinCollected>(
e => Debug.Log("Coins gained: " + e.Amount));
events.Publish(new CoinCollected { Amount = 5 });
ticket.Dispose(); // stop listening (do this in OnDestroy)
定时器
定时器按被调度的先后顺序触发。一次性定时器会自行清理;循环定时器会一直运行,直到你将其释放。完整参考:Scheduler。
var scheduler = ServiceLocator.Resolve<IScheduler>();
scheduler.After(2f, () => Debug.Log("Two seconds later, exactly once."));
System.IDisposable heartbeat =
scheduler.Every(0.5f, () => Debug.Log("Every half second."));
heartbeat.Dispose(); // stop the repeating timer when done
补间
补间把一个值从一个数字动画到另一个数字,并把每一步交给你处理。补间默认运行在游戏玩法时钟上,因此游戏暂停时它们也随之暂停。完整参考:Tween。
var tween = ServiceLocator.Resolve<ITweenService>();
CanvasGroup group = GetComponent<CanvasGroup>();
tween.To(1f, 0f, 0.75f,
alpha => group.alpha = alpha,
EaseType.OutQuad,
onComplete: () => Debug.Log("Fade finished."));
输入
输入通过你的输入动作资产中的命名动作映射来读取。把 "Gameplay" 和 "Jump" 换成你自己资产里的名字。为 InputAction 类型添加 using UnityEngine.InputSystem;。完整参考:Input。
private IInputService _input;
private InputAction _jump;
private void Start()
{
_input = ServiceLocator.Resolve<IInputService>();
_jump = _input.GetAction("Gameplay", "Jump");
}
private void Update()
{
if (_jump != null && _input.WasPressedThisFrame(_jump))
Debug.Log("Jump!");
}
4.4 接下来去哪
你现在已经掌握了解锁整个框架的那一个模式:解析接口、缓存、调用。接下来:
- 服务参考。 每个服务都有自己的页面——从文档索引开始,或在你的项目里查看
Assets/CommonGameSystem.Core/Documentation/Modules/(例如SaveLoad.md或Tween.md)。每页都覆盖完整 API、常见模式和真正要紧的注意事项。 - 示例。 RPG Starter Sample(第 3 章)展示了上面几个服务在一个小型游戏切片中协作的样子。
- 本手册的其余部分。 第 5 章讲解五个核心概念;第 6 章把框架连接到你自己的资产。
- 更新日志。 Welcome 窗口的快捷链接(第 3 章)可以打开你所安装版本的文档和更新日志。
下一章:5. 核心概念