写程序写到一定的阶段後,会开始发现,其实做出想要的功能并不困难。
真正难的,其实是如何写出有弹性的程序码以应对各种需求跟变化。
卡比接下来要做的,是在一般游戏引擎都会实作的设计模式,Component。
在游戏设计中的 Component 跟 Web 的 Component 不同,
这边的 Component 是用来提供 GameObject 行为的。
不过卡比会进一步将逻辑跟资料的部分在拆出来,
让 Component 用於封装资料, System 用於处理逻辑。
卡比注意到,目前每个游戏角色都有一个类似的 render
函式,
而这部分的程序码几乎一样,我们来试试看能不能将他共用。
首先,先在 src/types.ts
定义新的介面。
-- src/types.ts
export interface Renderer {
renderer: {
type: "graphics";
src: number[][];
};
}
并修改 GameObject
,
export interface GameObject {
handleInput?(pressed: Key[]): void;
update?(delta: number): void;
- render(app: Application): void;
}
接着,卡比以 src/characters/LaserCannon.ts
作为范例,进行修改
注意到,卡比在这边用了 Type Intersections
的方式来延展 GameObject
。
-- src/characters/LaserCannon.ts
export default function LaserCannon(): GameObject & Renderer {
return {
renderer: {
type: "graphics",
src: [
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
],
},
position: { x: 0, y: 0 },
handleInput(pressed) {
if (pressed.includes(Key.Left)) {
this.position.x -= 1;
return;
}
if (pressed.includes(Key.Right)) {
this.position.x += 1;
return;
}
},
};
}
建立新的档案 src/systems/render.ts
,
专门用来处理 render
相关的逻辑。
function Graphics({ renderer }: Renderer) {
const src = renderer.src;
const graphics = new _Graphics();
for (let y = 0; y < src.length; y++) {
for (let x = 0; x < src[y].length; x++) {
if (src[y][x] === 0) continue;
graphics.beginFill(0xffffff);
graphics.drawRect(x, y, 1, 1);
graphics.endFill();
}
}
return graphics;
}
export function render(stage: Container, instance: GameObject & Renderer) {
let renderer: DisplayObject | undefined = undefined;
if (instance.renderer.type === "graphics") {
renderer = Graphics(instance);
}
if (renderer) {
stage.addChild(renderer);
renderer.position.set(instance.position.x, instance.position.y);
}
}
接着,更改 src/main.ts
来接上我们的 Render System 。
app.ticker.add(() => {
app.stage.removeChildren();
instance.handleInput?.(getKeyPressed());
instance.update?.(app.ticker.deltaMS);
render(app.stage, instance);
});
确认画面运作没问题,我们的重构就完成了。
接下来,将 LaserCannon
的其他程序码也一并 Component 化。
-- src/types.ts
export type Vector = {
x: number;
y: number;
};
export interface Transform {
position: Vector;
}
export interface Control {
handleInput(pressed: Key[]): void;
}
export interface GameObject {
update?(delta: number): void;
}
-- src/characters/LaserCannon.ts
import { clamp } from "../functions/utils";
import { Control, GameObject, Key, Renderer, Transform } from "../types";
export default function LaserCannon(): GameObject &
Transform &
Control &
Renderer {
return {
renderer: {
type: "graphics",
src: [
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
],
},
position: { x: 10, y: 10 },
handleInput(pressed) {
if (pressed.includes(Key.Left)) {
this.position.x -= 1;
return;
}
if (pressed.includes(Key.Right)) {
this.position.x += 1;
return;
}
},
};
}
因为我们将 render
相关的逻辑移到 Render System
,
但还未实作 Transform
的相关逻辑。
在 System
中,我们需要过滤被传入的物件是否拥有 Transform
元件,
如果拥有 Transform
才需要执行 position
相关的逻辑操作。
-- src/types.ts
export function canTransform<T extends GameObject>(
instance: T
): instance is T & Transform {
return "position" in instance;
}
-- src/systems/render.ts
export function render(stage: Container, instance: GameObject & Renderer) {
let renderer: DisplayObject | undefined = undefined;
if (instance.renderer.type === "graphics") {
renderer = Graphics(instance);
}
if (renderer) {
stage.addChild(renderer);
}
+ if (renderer && canTransform(instance)) {
+ renderer.position.set(instance.position.x, instance.position.y);
+ }
}
前情提要 经过上一回的测验,我发现了自己不是天选之人。 「哪尼,为什麽我不是天选之人,我不是有魔力吗...
今天开始要讲分散式系统的一些概念罗。 影片在此: Day04_关於分散式系统的一些概念 (一) ...
好的,今天我们要来看的就是我们的精华啦-聊天室。 原本我们在设计邀约流程的时候是。 (原本设想的流程...
天亮了 昨晚是平安夜 关於迷雾森林故事 焦虑抑制剂 4号:我跟全场站不同边耶,我站7耶,我跟7号玩家...
Q1. 什麽是 php 反序列化? 为了让程序中的物件可以在保存到 persistent datab...