from dataclasses import dataclass
from pathlib import Path
+from typing import override
import pygame
from pygame import Vector3
+from pacman.ecs.assets import Asset, Assets
from pacman.ecs.hierarchy import (
ChildOf,
Hierarchy,
@dataclass
-class Sprite2D(Component):
+class Sprite2D(Component, Asset):
surface: pygame.Surface
+ @override
+ @classmethod
+ def load(cls, ty: type, path: str) -> "Sprite2D":
+ del ty
+ return Sprite2D(pygame.image.load(path))
+
def poll_events(events: WindowEvents) -> None:
events.events = pygame.event.get()
coord.x = 30.0 + math.cos(tick) * 30.0
-if __name__ == "__main__":
- world = (
- World()
- .with_plugins(minimal_plugins, graphics_plugins)
- .with_systems(
- UpdateSchedule,
- move_sprites,
- move_root,
- )
- )
- assets = list(Path("./asset").rglob("*.png"))
+def setup_world(world: World, assets: Assets[Sprite2D]) -> None:
+ asset_paths = list(Path("./asset").rglob("*.png"))
root = world.spawn(
tuple[Coord2D, CoordAbs2D, WiggleRoot],
(
[
(
Coord2D(
- 30.0 * (n := (i + j) % len(assets)),
+ 30.0 * (n := (i + j) % len(asset_paths)),
500.0 + 10.0,
1.0 * n + j,
),
CoordAbs2D(),
- Sprite2D(pygame.image.load(asset)),
+ assets.load(asset_path.as_posix()),
ShouldRender(),
WiggleSprite(),
ChildOf(root),
)
for j in range(3)
- for i, asset in enumerate(assets)
+ for i, asset_path in enumerate(asset_paths)
],
)
+ pass
+
+if __name__ == "__main__":
+ world = (
+ World()
+ .with_plugins(minimal_plugins, graphics_plugins)
+ .with_systems(StartupSchedule, setup_world)
+ .with_systems(
+ UpdateSchedule,
+ move_sprites,
+ move_root,
+ )
+ )
world.run_main()
--- /dev/null
+from abc import ABC, abstractmethod
+from collections.abc import Callable
+from os import PathLike
+from os.path import abspath
+from typing import Self, cast, get_args, override
+
+from pacman.ecs.world import Resource, World
+from pacman.utils.type_resolve import resolve_type_aliases
+
+
+class Asset(ABC):
+ @classmethod
+ @abstractmethod
+ def load(cls, ty: type, path: str) -> Self: ...
+
+
+class Assets[T: Asset](Resource):
+ def __init__(self, ty: type) -> None:
+ self._lookup: dict[str, T] = {}
+ self._asset_ty: type[T] = get_args(resolve_type_aliases(ty))[0]
+
+ def load(self, s: str | PathLike[str]) -> T:
+ s = abspath(s)
+ if s not in self._lookup:
+ self._lookup[s] = self._asset_ty.load(self._asset_ty, s)
+ return self._lookup[s]
+
+ @override
+ @classmethod
+ def extract_from_world(
+ cls, ty: type, world: World
+ ) -> Callable[[], Self | None]:
+ inner = cast(
+ Callable[[], Self | None], Resource.extract_from_world(ty, world)
+ )
+ return lambda: cast(
+ Self, (res if (res := inner()) is not None else Assets(ty))
+ )