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