0g 中文文档教程
0G
TypeScript 的失重游戏框架。
TypeScript Focused
0G
在灵活性和信心之间取得平衡,支持对重要数据边界(如组件和核心引擎功能)进行无缝 TypeScript 键入。
ECS inspired
0G
试图采用实体-组件-系统游戏框架的核心思想,并以更大的灵活性和具体的用例来扩展它们。
React Compatible
0G
为像我这样的 React 开发人员提供了一些好处,可以将熟悉的模式集成到游戏开发用例中。 您可以利用 React 为您的游戏 UI 提供动力,或者您可以连接 react-three-fiber
< /a> 或 react-pixi
为您的整个游戏提供动力。
当然,React 会增加额外的开销,并且可能需要大量命令式救助(即 ref
)来呈现实时可视化效果 - 但您可以决定在何处为您的游戏划清界线。
有关详细信息,请查看 @0g/react
包在你阅读了 0G
的基础知识之后。
Show me a Game
class Transform extends Component {
x = 0;
y = 0;
randomJump() {
this.set({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
});
}
}
class ButtonTag extends Component {}
class Button extends StateComponent {
element: HTMLButtonElement | null = null;
lastJump: number;
}
class Score extends Component {
points = 0;
increment() {
this.set({ points: this.points + 1 });
}
}
class ButtonMover extends System {
// new buttons that don't have a Button store connected yet
newButtons = this.query({
all: [ButtonTag, Transform],
none: [Button],
});
// buttons that have been initialized
buttons = this.query({
all: [ButtonTag, Button, Transform],
});
// reference the player to increment component
players = this.query({
all: [Score],
});
setup = this.frame(this.newButtons, (entity) => {
const element = document.createElement('button');
element.innerText = 'Click!';
element.style.position = 'absolute';
element.addEventListener('click', () => {
this.players.forEach((playerEntity) => {
playerEntity.get(Score).increment();
});
entity.get(Transform).randomJump();
entity.get(Button).set({ lastJump: Date.now() });
});
document.bodyElement.appendChild(element);
entity.add(Button, {
element,
});
});
run = this.frame(this.buttons, (entity) => {
const buttonStore = entity.get(Button);
if (Date.now() - buttonStore.lastJump > 3000) {
buttonStore.lastJump = Date.now();
entity.get(Transform).randomJump();
}
// update button positions
const transform = entity.get(Transform);
buttonStore.element.style.left = transform.x;
buttonStore.element.style.top = transform.y;
});
}
class ScoreRenderer extends System {
players = this.query({
all: [Score],
});
scoreElement = document.getElementById('scoreDisplay');
update = this.watch(this.players, [Score], (entity) => {
this.scoreElement.innerText = entity.get(Score).points;
});
}
const game = new Game({
components: { Transform, ButtonTag, Button, Score },
systems: [ButtonMover],
});
game.create('player').add(Score);
game.create('button').add(Transform).add(ButtonTag);
Docs
ECS-based Architecture
Components
class Transform extends Component {
x = 0;
y = 0;
angle = 0;
get position() {
return {
x: this.x,
y: this.y,
};
}
}
要开始为您的游戏行为建模,您可能首先要开始定义组件。
“Components”替换 ECS“Components”命名(消除来自 React Components 的术语歧义)。 组件是您的游戏状态所在的地方。 其余代码的目的是分组、更改或呈现组件。
组件有两种形式:
- Persistent : Serializeable and runtime-editable1, this data forms the loadable "scene."
- Example: Store the configuration of a physics rigidbody for each character
- State: Store any data you want. Usually these stores are derived from persistent stores at initialization time.
- Example: Store the runtime rigidbody created by the physics system at runtime
1 尚不支持运行时编辑……除了在开发工具控制台中。
Entities
const transform = entity.get(stores.Transform);
transform.x = 100;
与经典 ECS 一样,实体是组件分组的标识符。 每个实体都有一个 ID。 在 0G
中,实体对象提供了一些方便的工具来检索、更新和添加组件到自身。
Queries
bodies = this.query({
all: [stores.Body, stores.Transform],
none: [stores.OmitPhysics],
});
作为游戏开发者,在游戏中找到实体并对其进行迭代非常重要。 通常,您希望按特定标准查找实体。 在 0G
中,主要标准是它们关联了哪些组件。
查询由游戏管理,它们会监控实体的变化并选择符合您标准的实体。 例如,您可以有一个查询,它选择所有具有 Transform
和 Body
存储的实体来进行物理计算。
Systems
class DemoMove extends System {
movable = this.query({
all: [stores.Transform],
});
run = this.frame(this.movable, (entity) => {
const transform = entity.getWritable(stores.Transform);
if (this.game.input.keyboard.getKeyPressed('ArrowLeft')) {
transform.x -= 5;
} else if (this.game.input.keyboard.getKeyPressed('ArrowRight')) {
transform.x += 5;
}
});
}
系统是您的游戏逻辑所在的地方。 他们利用查询来访问游戏中满足特定约束的实体。 使用这些查询,他们可以在每一帧中迭代匹配的实体,或者监视特定组件的变化。
Using Systems to Render
系统也是您渲染游戏的方式。 0G
支持灵活的渲染方法。
对于 Vanilla JS,您可能希望使用状态组件(非持久性)来初始化和存储可渲染对象,例如 Pixi Sprite
或 ThreeJS Mesh
。 系统可以像任何其他数据一样更新每一帧的可渲染对象。
如果你想使用 React 渲染游戏,你可以使用 @0g/react
将系统实际编写为 React 组件的包。 该包提供了实现与基于类的系统相同的行为所需的挂钩。
0G
The weightless game framework for TypeScript.
TypeScript Focused
0G
strikes a balance between flexibility and confidence, supporting seamless TypeScript typing for important data boundaries like Components and core engine features.
ECS inspired
0G
tries to take the core ides of Entity-Component-System game frameworks and extend them with greater flexibility and concrete use cases.
React Compatible
0G
has some goodies for React developers like me to integrate familiar patterns into game development use cases. You can utilize React to power your game's UI, or you can hook up react-three-fiber
or react-pixi
to power your whole game.
Of course, React adds additional overhead and can require a lot of imperative bailouts (i.e. ref
s) to render realtime visualizations - but you get to decide where to draw the line for your game.
For more details, take a look at the @0g/react
package after you've read up on the basics of 0G
.
Show me a Game
class Transform extends Component {
x = 0;
y = 0;
randomJump() {
this.set({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
});
}
}
class ButtonTag extends Component {}
class Button extends StateComponent {
element: HTMLButtonElement | null = null;
lastJump: number;
}
class Score extends Component {
points = 0;
increment() {
this.set({ points: this.points + 1 });
}
}
class ButtonMover extends System {
// new buttons that don't have a Button store connected yet
newButtons = this.query({
all: [ButtonTag, Transform],
none: [Button],
});
// buttons that have been initialized
buttons = this.query({
all: [ButtonTag, Button, Transform],
});
// reference the player to increment component
players = this.query({
all: [Score],
});
setup = this.frame(this.newButtons, (entity) => {
const element = document.createElement('button');
element.innerText = 'Click!';
element.style.position = 'absolute';
element.addEventListener('click', () => {
this.players.forEach((playerEntity) => {
playerEntity.get(Score).increment();
});
entity.get(Transform).randomJump();
entity.get(Button).set({ lastJump: Date.now() });
});
document.bodyElement.appendChild(element);
entity.add(Button, {
element,
});
});
run = this.frame(this.buttons, (entity) => {
const buttonStore = entity.get(Button);
if (Date.now() - buttonStore.lastJump > 3000) {
buttonStore.lastJump = Date.now();
entity.get(Transform).randomJump();
}
// update button positions
const transform = entity.get(Transform);
buttonStore.element.style.left = transform.x;
buttonStore.element.style.top = transform.y;
});
}
class ScoreRenderer extends System {
players = this.query({
all: [Score],
});
scoreElement = document.getElementById('scoreDisplay');
update = this.watch(this.players, [Score], (entity) => {
this.scoreElement.innerText = entity.get(Score).points;
});
}
const game = new Game({
components: { Transform, ButtonTag, Button, Score },
systems: [ButtonMover],
});
game.create('player').add(Score);
game.create('button').add(Transform).add(ButtonTag);
Docs
ECS-based Architecture
Components
class Transform extends Component {
x = 0;
y = 0;
angle = 0;
get position() {
return {
x: this.x,
y: this.y,
};
}
}
To start modeling your game behavior, you'll probably first begin defining Components.
"Components" replace ECS "Components" naming (disambiguating the term from React Components). Components are where your game state lives. The purpose of the rest of your code is either to group, change, or render Components.
Components come in two flavors:
- Persistent : Serializeable and runtime-editable1, this data forms the loadable "scene."
- Example: Store the configuration of a physics rigidbody for each character
- State: Store any data you want. Usually these stores are derived from persistent stores at initialization time.
- Example: Store the runtime rigidbody created by the physics system at runtime
1 Runtime editing is not yet supported… except in the devtools console.
Entities
const transform = entity.get(stores.Transform);
transform.x = 100;
As in classic ECS, Entities are identifiers for groupings of Components. Each Entity has an ID. In 0G
, an Entity object provides some convenience tools for retrieving, updating, and adding Components to itself.
Queries
bodies = this.query({
all: [stores.Body, stores.Transform],
none: [stores.OmitPhysics],
});
It's important, as a game developer, to find Entities in the game and iterate over them. Generally you want to find Entities by certain criteria. In 0G
the primary criteria is which Components they have associated.
Queries are managed by the game, and they monitor changes to Entities and select which Entities match your criteria. For example, you could have a Query which selects all Entities that have a Transform
and Body
store to do physics calculations.
Systems
class DemoMove extends System {
movable = this.query({
all: [stores.Transform],
});
run = this.frame(this.movable, (entity) => {
const transform = entity.getWritable(stores.Transform);
if (this.game.input.keyboard.getKeyPressed('ArrowLeft')) {
transform.x -= 5;
} else if (this.game.input.keyboard.getKeyPressed('ArrowRight')) {
transform.x += 5;
}
});
}
Systems are where your game logic lives. They utilize Queries to access Entities in the game which meet certain constraints. Using those Queries, they can iterate over matching entities each frame, or monitor specific Components for changes.
Using Systems to Render
Systems are also how you render your game. 0G
supports flexible approaches to rendering.
For Vanilla JS, you might want to use State Components (non-persistent) to initialize and store a renderable object, like a Pixi Sprite
or a ThreeJS Mesh
. The System can update the renderable each frame like any other data.
If you want to use React to render a game, you can utilize the @0g/react
package to actually write Systems as React Components. The package provides the hooks you'll need to accomplish the same behavior as class-based Systems.