If game programmers ever cracked open Design Patterns at all, never got past Singleton. Gang of Four’s is inapplicable to games in its original version.Robert Nystrom
Design patterns in applications
Design patterns in games
Unreal Blueprints
Unity Visual Scripting
What we need
Example: Pacman
Implementation
1 | |
2 | public async Task EnterDoorAction(Door door) { |
3 | this.Context.Player.BlockInput(); |
4 | await new DoorAnimation(door).Open(); |
5 | await new WalkAnimation(this.Context.Player).Walk(this.Context.Player.direction); |
6 | this.Context.Player.Hide(); // hide the sprite once it approaches the house |
7 | await new DoorAnimation(door).Close(); |
8 | await Delay(500); // wait for 500 ms |
9 | } |
10 | |
11 | ....... |
12 | |
13 | public async Task OnPlayerDoorApproached(Door door) { |
14 | await new EnterDoorAction(door); |
15 | await new SceneLoader(door.TargetScene); |
16 | } |
1 | this.owner.addComponent(new ChainComponent() |
2 | .call(() => player.blockInput()) |
3 | .waitFor(new DoorAnimComponent(DoorActions.OPEN)) |
4 | .waitFor(new WalkAnim(player, direction)) |
5 | .call(() => player.hide()) |
6 | .waitFor(new DoorAnimComponent(DoorActions.CLOSE)) |
7 | .waitTime(500)); |
1 | IEnumerator Spawn () { |
2 | // Create a random wait time before the prop is instantiated. |
3 | float waitTime = Random.Range(minTimeBetweenSpawns, maxTimeBetweenSpawns); |
4 | // Wait for the designated period. |
5 | yield return new WaitForSeconds(waitTime); |
6 | |
7 | // Instantiate the prop at the desired position. |
8 | Rigidbody2D propInstance = Instantiate(backgroundProp, spawnPos, Quaternion.identity); |
9 | // Restart the coroutine to spawn another prop. |
10 | StartCoroutine(Spawn()); |
11 | } |
1 | // wait for 2 seconds and load another scene |
2 | this.sendMessage(Messages.PAUSE); |
3 | this.scene.callWithDelay(2000, () => { |
4 | Factory.loadScene(Scenes.MAIN_MENU); |
5 | }); |
6 | this.finish(); |
1 | void Item::SpawnDeferred(const std::function<void(ItemEntity&)>;& OnSpawned, const QuatT& transform) { |
2 | ExecuteDeferred([this, OnSpawned, transform]() { |
3 | auto* spawnedItem = entitySystem->SpawnUnsafeItem(transform); |
4 | if (spawnedItem) { |
5 | OnSpawned(*spawnedItem); |
6 | } |
7 | }); |
8 | } |
9 | //========================================================================= |
10 | Deferrable::~Deferrable() { |
11 | GetDeferredSystem().CancelAllDeferred(*this); |
12 | } |
13 | |
14 | //========================================================================= |
15 | template <class Fn> |
16 | bool Deferrable::ExecuteDeferred(Fn&& fn) const { |
17 | const auto ret = GetDeferredSystem().ExecuteDeferred(std::forward<Fn>(fn), *this); |
18 | return ret; |
19 | } |
1 | if(asteroid.position.distance(rocket.position) <= MIN_PROXIMITY) { // detect proximity |
2 | rocket.runAnimation(ANIM_EXPLOSION); // react instantly and handle everything |
3 | asteroid.runAnimation(ANIM_EXPLOSION); |
4 | playSound(SOUND_EXPLOSION); |
5 | asteroid.destroy(); |
6 | rocket.destroy(); |
7 | } |
1 | // collision-system.ts |
2 | let collisions = this.collisionSystem.checkProximity(allGameObjects); |
3 | collisions.forEach(colliding => this.sendEvent(COLLISION_TRIGGERED, colliding)); |
4 | // rocket-handler.ts |
5 | onCollisionTriggered(colliding) { |
6 | this.destroy(); |
7 | this.sendEvent(ROCKET_DESTROYED); |
8 | } |
9 | // sound-component.ts |
10 | onGameObjectDestroyed() { |
11 | this.playSound(SOUND_EXPLOSION); |
12 | } |
1 | void() PlayerDie = { |
2 | DropBackpack(); |
3 | self.weaponmodel=""; |
4 | self.view_ofs = '0 0 -8'; |
5 | self.deadflag = DEAD_DYING; |
6 | self.solid = SOLID_NOT; |
7 | self.flags = self.flags - (self.flags & FL_ONGROUND); |
8 | self.movetype = MOVETYPE_TOSS; |
9 | |
10 | if (self.velocity_z < 10) |
11 | self.velocity_z = self.velocity_z + random()*300; |
12 | |
13 | DeathSound(); |
14 | |
15 | if (self.weapon == IT_AXE) { |
16 | player_die_ax1 (); |
17 | return; |
18 | } |
19 | |
20 | i = 1 + floor(random()*6); |
21 | if (i == 1) |
22 | player_diea1(); |
23 | else if (i == 2) |
24 | player_dieb1(); |
25 | else player_diec1(); |
26 | }; |
Individual units
Battle formation
Randomly
Sequentially
The object A reads the previous state of the object B, and the object B reads the previous state from the object C
Clean-up
1 | void AnimationCache::_clear_cache() { |
2 | while (connected_nodes.size()) { |
3 | connected_nodes.front()->get() |
4 | ->disconnect("tree_exiting", callable_mp(this, &AnimationCache::_node_exit_tree)); |
5 | connected_nodes.erase(connected_nodes.front()); |
6 | } |
7 | path_cache.clear(); |
8 | cache_valid = false; |
9 | cache_dirty = true; |
10 | } |
11 | |
12 | void AnimationCache::_update_cache() { |
13 | cache_valid = false; |
14 | |
15 | for (int i = 0; i < animation->get_track_count(); i++) { |
16 | // ... 100 lines of code |
17 | } |
18 | |
19 | cache_dirty = false; |
20 | cache_valid = true; |
21 | } |
22 |
1 | class Brainbot extends Unit { |
2 | |
3 | private damage: number; |
4 | private currentWeapon: WeaponType; |
5 | |
6 | constructor() { |
7 | super(UnitType.BRAIN_BOT); |
8 | } |
9 | |
10 | init(damage: number, currentWeapons: WeaponType) { |
11 | this.damage = damage; |
12 | this.currentWeapon = currentWeapons; |
13 | } |
14 | } |
Context (Blackboard)
1 | public void OnTriggerEvent(Event evt, GameContext ctx) { |
2 | |
3 | if(evt.Key == "LIFE_LOST") { |
4 | ctx.Inventory.clear(); |
5 | ctx.Boosts.clear(); |
6 | ctx.Player.Lives--; // access the context |
7 | if(ctx.Player.Lives <= 0) { |
8 | this.FireEvent("GAME_OVER"); |
9 | } |
10 | } |
11 | } |
1 | class NullAnimComponent extends Component { |
2 | |
3 | constructor() { |
4 | super('AnimComponent') |
5 | } |
6 | |
7 | onUpdate() { |
8 | // immediately end |
9 | this.finish(); |
10 | } |
11 | } |
1 | const getPlayer(scene: Scene) => scene.findObjectByName('player'); |
2 | |
3 | const getAllUnits(scene: Scene) => scene.findObjectsByTag('unit_basic'); |
4 | |
5 | const getAllUnitsWithinRadius(scene: Scene, pos: Vector, radius: number) => { |
6 | return getAllUnits(scene).filter(unit => unit.pos.distance(pos) <= radius); |
7 | } |
8 | |
9 | const getAllExits(scene: Scene) => { |
10 | const doors = scene.findObjectsByTag('door'); |
11 | return doors.filter(door => !door.locked); |
12 | } |
1 | // stateless, the creature will jump each frame |
2 | updateCreature() { |
3 | if(eventSystem.isPressed(KeyCode.UP)) { |
4 | this.creature.jump(); |
5 | } |
6 | } |
7 | |
8 | // introduction of a state |
9 | updateCreature() { |
10 | if(eventSystem.isPressed(KeyCode.UP) && this.creature.state !== STATE_JUMPING) { |
11 | this.creature.changeState(STATE_JUMPING); |
12 | eventSystem.handleKey(KeyCode.UP); |
13 | this.creature.jump(); |
14 | } |
15 | } |
1 | class Builder { |
2 | private _position: Vector; |
3 | private _scale: Vector; |
4 | |
5 | position(pos: Vector) { |
6 | this.position = pos; |
7 | return this; |
8 | } |
9 | |
10 | scale(scale: Vector) { |
11 | this.scale = scale; |
12 | return this; |
13 | } |
14 | |
15 | build() { |
16 | return new GameObject(this._position, this._scale); |
17 | } |
18 | } |
19 | |
20 | new Builder().position(new Vector(12, 54)).scale(new Vector(2, 1)).build(); |
1 | new Builder(scene) |
2 | .localPos(this.engine.app.screen.width / 2, this.engine.app.screen.height / 2) |
3 | .anchor(0.5) |
4 | .withParent(scene.stage) |
5 | .withComponent( |
6 | new FuncComponent('rotation') |
7 | .doOnUpdate((cmp, delta, absolute) => cmp.owner.rotation += 0.001 * delta)) |
8 | .asText('Hello World', new PIXI.TextStyle({ fill: '#FF0000', fontSize: 80})) |
9 | .build(); |
Prefabs in Unity
1 | const superBallTransmuter = (entity: GameObject) => { |
2 | entity.removeComponent<BallBehavior>(); |
3 | entity.addComponent(new SuperBallBehavior()); |
4 | entity.state.speed = SUPER_BALL_SPEED; |
5 | entity.state.size = SUPER_BALL_SIZE; |
6 | return entity; |
7 | } |
1 | class UnitFactory { |
2 | |
3 | private pikemanBuilder: Builder; // preconfigured to build pikemans |
4 | private musketeerBuilder: Builder; // preconfigured to build musketeers |
5 | private archerBuilder: Builder; // preconfigured to build archers |
6 | |
7 | public spawnPikeman(position: Vector, faction: FactionType): GameObject { |
8 | return this.pikeman.position(position).faction(faction).build(); |
9 | } |
10 | |
11 | public spawnMusketeer(position: Vector, faction: FactionType): GameObject { |
12 | return this.musketeerBuilder.position(position).faction(faction).build(); |
13 | } |
14 | |
15 | public spawnArcher(position: Vector, faction: FactionType): GameObject { |
16 | return this.archerBuilder.position(position).faction(faction).build(); |
17 | } |
18 | } |
So, I would like to build an airport for you and you don't allow me to tear down some old lady's house.Everyone who played OpenTTD