用TypeScript实现Unity风格的协程系统
前言
在Unity C#开发中,协程(Coroutine)是处理异步逻辑的神兵利器,但在TypeScript生态中,原生并不提供类似的协程机制。笔者在开发复杂前端动画逻辑时,面对层层嵌套的setTimeout
和Promise.then()
,决定借鉴C#协程设计思想,基于生成器函数和Promise封装了TS协程类。
借助Puerts的能力,可以在Unity或者UE的TS脚本中实现像C#一样的协程效果:通过yield return
优雅地实现延时执行、等待任务(Promise、子协程)等异步操作,让定时任务、异步队列等场景的代码组织变得行云流水。
言归正传
1. 协程状态管理
通过枚举类型定义状态流转机制,核心状态机驱动协程生命周期,配合私有变量_state
跟踪执行状态,通过isRunning和IsCompleted属性暴露运行状态:
enum ECoroutineState {
Ready, // 就绪状态
Running, // 执行中
Paused, // 暂停(当前未实现)
Completed, // 完成
Failed // 执行异常
}
private _state: ECoroutineState = ECoroutineState.Ready;
/**
* 协程是否完成
*/
get IsCompleted(): boolean {
return this._state === ECoroutineState.Completed;
}
/**
* 协程是否正在运行
*/
get isRunning(): boolean {
return this._state === ECoroutineState.Running;
}
2. 协程启动与停止
通过Start()方法启动协程并返回Promise,Stop()可强制终止执行:
Start(): Promise<T> {
if (this._state !== ECoroutineState.Ready) return this._promise;
this._state = ECoroutineState.Running;
this.step(); // 开始迭代
return this._promise;
}
Stop(): void {
this._state = ECoroutineState.Completed;
}
3. 实现延时等待
WaitForSeconds将秒级等待封装为可yield的指令:
// 使用示例:yield WaitForSeconds(1.5)
export function WaitForSeconds(_Seconds: number): Coroutine<void> {
return new Coroutine(function* () {
yield _Seconds * 1000;
});
}
在处理器中通过setTimeout实现延时:
// 内部处理逻辑
private handleDelay(_MS: number): void {
setTimeout(() => this.step(), _MS);
}
4. 整合Promise支持
WaitForPromise包装现有Promise,实现协程内等待异步操作:
// 使用示例:yield WaitForPromise(fetch(url))
export function WaitForPromise<T>(_promise: Promise<T>): Coroutine<T> {
return new Coroutine(function* () {
return yield _promise;
});
}
处理器通过then()连接Promise结果:
// Promise完成回调处理
private handlePromise(_promise: Promise<any>): void {
_promise.then(
(result) => this.step(result),
(error) => this.step(error, true)
);
}
5. 协程嵌套执行
通过WaitForCoroutine支持协程嵌套执行:
// 使用示例:yield WaitForCoroutine(subCoroutine)
export function WaitForCoroutine<T>(_coroutine: Coroutine<T>): Coroutine<T> {
return new Coroutine(function* () {
return yield _coroutine;
});
}
子协程完成后通过Promise回调唤醒父协程:
// 子协程完成回调处理
private handleNestedCoroutine(_child: Coroutine<any>): void {
_child.Start().then(
(result) => this.step(result),
(error) => this.step(error, true)
);
}
放出代码
Coroutine.ts
/**
* 协程状态枚举
*/
enum ECoroutineState {
Ready,
Running,
Paused,
Completed,
Failed
}
/**
* 协程类 - 模拟C#风格的协程
*/
export class Coroutine<T = any> {
private _generator: Generator<unknown, T, any>;
private _iterator: IteratorResult<unknown, T>;
private _state: ECoroutineState = ECoroutineState.Ready;
private _resolve!: (_value: T) => void;
private _reject!: (reason?: any) => void;
private _promise: Promise<T>;
private _childCoroutine: Coroutine<any> | null = null;
/**
* 创建协程
* @param _generatorFn 生成器函数
*/
constructor(_generatorFn: () => Generator<unknown, T, any>) {
this._generator = _generatorFn();
this._promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
/**
* 启动协程
*/
Start(): Promise<T> {
if (this._state !== ECoroutineState.Ready) {
return this._promise;
}
this._state = ECoroutineState.Running;
this.step();
return this._promise;
}
/**
* 停止协程
*/
Stop(): void {
this._state = ECoroutineState.Completed;
}
/**
* 协程是否完成
*/
get IsCompleted(): boolean {
return this._state === ECoroutineState.Completed;
}
/**
* 协程是否正在运行
*/
get isRunning(): boolean {
return this._state === ECoroutineState.Running;
}
/**
* 执行下一步
*/
private step(_value?: any, _isError: boolean = false): void {
if (this._state !== ECoroutineState.Running) {
return;
}
try {
if (_isError) {
this._iterator = this._generator.throw(_value);
} else {
this._iterator = this._generator.next(_value);
}
if (this._iterator.done) {
this._state = ECoroutineState.Completed;
this._resolve(this._iterator.value);
return;
}
this.handleYieldValue(this._iterator.value);
} catch (error) {
this._state = ECoroutineState.Failed;
this._reject(error);
}
}
/**
* 处理yield返回的值
* @param _value yield返回的值
*/
private handleYieldValue(_value: unknown): void {
if (_value instanceof Coroutine) {
// 如果是嵌套协程
this.handleNestedCoroutine(_value);
} else if (_value instanceof Promise) {
// 如果是Promise
this.handlePromise(_value);
} else if (typeof _value === 'number') {
// 如果是数字,视为延时(毫秒)
this.handleDelay(_value);
} else {
// 其他值直接继续
setTimeout(() => this.step(_value), 0);
}
}
/**
* 处理嵌套协程
* @param _child 子协程
*/
private handleNestedCoroutine(_child: Coroutine<any>): void {
this._childCoroutine = _child;
_child.Start().then(
(result) => {
this._childCoroutine = null;
this.step(result);
},
(error) => {
this._childCoroutine = null;
this.step(error, true);
}
);
}
/**
* 处理Promise
* @param _promise Promise对象
*/
private handlePromise(_promise: Promise<any>): void {
_promise.then(
(result) => this.step(result),
(error) => this.step(error, true)
);
}
/**
* 处理延时
* @param _MS 毫秒数
*/
private handleDelay(_MS: number): void {
setTimeout(() => this.step(), _MS);
}
}
/**
* 辅助函数:创建延时协程
* @param _MS 毫秒数
* @returns 协程
*/
export function WaitForSeconds(_Seconds: number): Coroutine<void> {
let ms=_Seconds*1000;
return new Coroutine(function* () {
//let ms=_Seconds*1000;
yield ms;
});
}
/**
* 辅助函数:等待Promise完成
* @param _promise Promise对象
* @returns 协程
*/
export function WaitForPromise<T>(_promise: Promise<T>): Coroutine<T> {
return new Coroutine(function* () {
return yield _promise;
});
}
/**
* 辅助函数:等待另一个协程完成
* @param _coroutine 协程对象
* @returns 协程
*/
export function WaitForCoroutine<T>(_coroutine: Coroutine<T>): Coroutine<T> {
return new Coroutine(function* () {
return yield _coroutine;
});
}
调用例子
import {Coroutine,WaitForSeconds,WaitForPromise,WaitForCoroutine} from "./Coroutine";
import fetch from 'node-fetch'; //新增此行
import * as JSON5 from 'json5';
async function getRequest(url: string): Promise<void> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log("getRequest",JSON5.stringify(data));
} catch (error) {
console.error('fetch error:', error);
}
}
console.log('===Coroutine.Start()===>');
new Coroutine(function* () {
//等待指定时间
yield WaitForSeconds(1);
console.log('WaitForSeconds(1)');
//等待http请求任务
getRequest('http://t.weather.sojson.com/api/weather/city/101260804');
//等待嵌套协程
const result = yield WaitForCoroutine(
new Coroutine(function* () {
yield WaitForSeconds(1);
return '嵌套结果';
})
);
console.log('WaitForCoroutine嵌套协程结果:', result);
return '所有操作完成';
}).Start();
总结
本文实现的协程系统具备以下优势:
多类型支持:可等待延时(秒)、可等待Promise、可等待子协程等不同对象
状态完备:完整生命周期的状态管理,支持异常捕获
链式调用:通过Promise链式调用实现异步流程控制
嵌套执行:支持无限层级协程嵌套,满足复杂业务需求
特别适合需要处理复杂时序逻辑的场景,如:游戏角色的连续动作、UI动画序列、分阶段加载流程等。相比原生Promise方案,代码可维护性显著提升,是Unity开发者转向TS开发时的平滑过渡方案。