Pokud se herní vývojáři kdy zaobírali návrhovými vzory, málokdy vytvořili něco komplexnějšího než singleton. Kniha Design Patterns se ve své původní podobě na hry vůbec nehodí.Robert Nystrom
Návrhové vzory v aplikacích
Návrhové vzory ve hrách

Unreal Blueprinty

Unity Visual Scripting

Co potřebujeme


Příklad: Pacman

Implementace

| 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 | }; |


Individuální jednotky

Bitevní formace
Náhodně
Sekvenčně
Objekt A čte předchozí stav objektu B a objekt B čte předchozí stav objektu 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 v 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 | } |






Tak já vám tady chci postavit letiště a vy mě nenecháte zbourat barák starý Mrázkový.Každý, kdo hrál OpenTTD