Source code for mini_arcade_core.scenes.game_scene

"""
Game scene base class with replay capture controls enabled.
"""

from __future__ import annotations

from dataclasses import dataclass, field, replace
from typing import Any, Callable, ClassVar, Generic, Iterable, Mapping, TypeVar

from mini_arcade_core.backend.keys import Key
from mini_arcade_core.engine.render.packet import RenderPacket
from mini_arcade_core.runtime.context import RuntimeContext
from mini_arcade_core.runtime.input_frame import InputFrame
from mini_arcade_core.scenes.sim_scene import SimScene
from mini_arcade_core.scenes.systems.base_system import BaseSystem
from mini_arcade_core.scenes.systems.builtins import (
    ActionSnapshot,
    ConfiguredActionIntentSystem,
    IntentCommandSystem,
    IntentPauseSystem,
)
from mini_arcade_core.scenes.systems.builtins.capture_hotkeys import (
    CaptureHotkey,
    SceneCaptureConfig,
)

# pylint: disable=invalid-name
TContext = TypeVar("TContext")
TWorld = TypeVar("TWorld")
# pylint: enable=invalid-name


[docs] @dataclass(frozen=True) class GameSceneSystemsConfig(Generic[TContext]): """ Declarative configuration for common gameplay scene systems. """ controls_scene_key: str | None = None intent_factory: Callable[[ActionSnapshot, TContext], object] | None = None input_system_name: str = "action_intent" input_channel: str | None = "player_1" input_write_to_ctx_intent: bool = True input_fallback_bindings: Mapping[str, Any] | None = None input_system_factory: ( Callable[[RuntimeContext], BaseSystem[TContext] | None] | None ) = None pause_command_factory: Callable[[TContext], object] | None = None pause_intent_attr: str = "pause" pause_system_name: str = "pause_intent" intent_command_bindings: Mapping[str, Callable[[TContext], object]] = ( field(default_factory=dict) ) intent_commands_system_name: str = "intent_commands" extra_system_factories: tuple[ Callable[[RuntimeContext], BaseSystem[TContext] | None], ... ] = () render_system_factory: ( Callable[[RuntimeContext], BaseSystem[TContext] | None] | None ) = None
[docs] class GameScene(SimScene[TContext, TWorld], Generic[TContext, TWorld]): """ Scene base class intended for gameplay scenes. Enables replay hotkeys by default in addition to screenshot/video. """ capture_config: ClassVar[SceneCaptureConfig] = replace( SimScene.capture_config, replay_record_toggle=CaptureHotkey(enabled=True, key=Key.F10), replay_play_toggle=CaptureHotkey(enabled=True, key=Key.F11), ) auto_systems_enabled: ClassVar[bool] = True systems_config: ClassVar[GameSceneSystemsConfig[TContext] | None] = None def __init__(self, ctx): super().__init__(ctx) self._auto_systems_installed = False
[docs] def build_auto_systems(self) -> Iterable[BaseSystem[TContext]]: """ Return built-in/common systems auto-installed for this game scene. Subclasses can override to provide input/render/hotkey systems that should be attached automatically with no manual pipeline wiring. """ cfg = self.systems_config if cfg is None: return () systems: list[BaseSystem[TContext]] = [] if cfg.input_system_factory is not None: input_system = cfg.input_system_factory(self.context) if input_system is not None: systems.append(input_system) elif ( cfg.controls_scene_key is not None and cfg.intent_factory is not None ): systems.append( ConfiguredActionIntentSystem( controls=getattr(self.context.settings, "controls", None), scene_key=cfg.controls_scene_key, intent_factory=cfg.intent_factory, fallback_bindings=cfg.input_fallback_bindings, name=cfg.input_system_name, channel=cfg.input_channel, write_to_ctx_intent=cfg.input_write_to_ctx_intent, ) ) if cfg.pause_command_factory is not None: systems.append( IntentPauseSystem( pause_command_factory=cfg.pause_command_factory, name=cfg.pause_system_name, intent_attr=cfg.pause_intent_attr, ) ) if cfg.intent_command_bindings: systems.append( IntentCommandSystem( bindings=dict(cfg.intent_command_bindings), name=cfg.intent_commands_system_name, ) ) for system_factory in cfg.extra_system_factories: extra_system = system_factory(self.context) if extra_system is not None: systems.append(extra_system) if cfg.render_system_factory is not None: render_system = cfg.render_system_factory(self.context) if render_system is not None: systems.append(render_system) return tuple(systems)
def _ensure_auto_systems(self) -> None: if self._auto_systems_installed or not self.auto_systems_enabled: return self.systems.extend(self.build_auto_systems()) self._auto_systems_installed = True
[docs] def tick(self, input_frame: InputFrame, dt: float) -> RenderPacket: self._ensure_auto_systems() return super().tick(input_frame, dt)