tinyecs 0.3.5__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tinyecs-0.3.5/src/tinyecs.egg-info → tinyecs-0.4.0}/PKG-INFO +1 -1
- {tinyecs-0.3.5 → tinyecs-0.4.0}/pyproject.toml +1 -1
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/__init__.py +76 -45
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/compsys.py +12 -12
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/background.py +4 -4
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/bouncing_sprites.py +2 -2
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/example.py +6 -6
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/homing-missiles.py +14 -14
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/marquee.py +2 -2
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/tutorial.py +42 -36
- {tinyecs-0.3.5 → tinyecs-0.4.0/src/tinyecs.egg-info}/PKG-INFO +1 -1
- {tinyecs-0.3.5 → tinyecs-0.4.0}/tests/test_tinyecs.py +30 -17
- {tinyecs-0.3.5 → tinyecs-0.4.0}/LICENSE +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/README.rst +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/setup.cfg +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/components.py +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demo.py +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs/demos/__init__.py +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/SOURCES.txt +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/dependency_links.txt +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/entry_points.txt +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/requires.txt +0 -0
- {tinyecs-0.3.5 → tinyecs-0.4.0}/src/tinyecs.egg-info/top_level.txt +0 -0
|
@@ -24,7 +24,7 @@ type DomainID = Hashable
|
|
|
24
24
|
type Property = Hashable
|
|
25
25
|
type Component = object
|
|
26
26
|
|
|
27
|
-
type
|
|
27
|
+
type EntityComponentsBundle = tuple[EntityID, list[Component]]
|
|
28
28
|
type _OptionalProperties = Iterable[Property] | None
|
|
29
29
|
type _RunSystemResult = dict[EntityID, Any]
|
|
30
30
|
|
|
@@ -32,7 +32,7 @@ type _RunSystemResult = dict[EntityID, Any]
|
|
|
32
32
|
# The typehinting system can't work with functions using *args, **kwargs.
|
|
33
33
|
# This solution comes from https://docs.python.org/3/library/typing.html#annotating-callable-objects
|
|
34
34
|
class SystemFunction(Protocol):
|
|
35
|
-
def __call__(self,
|
|
35
|
+
def __call__(self, eid: EntityID, *cids: ComponentID, **kwargs: dict[str, object]) -> Any: ...
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
eidx = {} # entity index
|
|
@@ -282,16 +282,16 @@ def remove_component(eid: EntityID, *cids: ComponentID) -> None:
|
|
|
282
282
|
obj.shutdown_()
|
|
283
283
|
|
|
284
284
|
|
|
285
|
-
def add_system(
|
|
285
|
+
def add_system(fn: SystemFunction, *cids: ComponentID) -> None:
|
|
286
286
|
r"""Add a system for the specifiied cids.
|
|
287
287
|
|
|
288
|
-
:param
|
|
288
|
+
:param fn: The system function
|
|
289
289
|
:param cids: The component ids that are required for this system
|
|
290
290
|
:return: None
|
|
291
291
|
|
|
292
292
|
The prototype for the function is::
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
fn(delta_time, eid, *comps)
|
|
295
295
|
|
|
296
296
|
where delta_time is e.g. the miliseconds from a pygame tick. eid is the id
|
|
297
297
|
of the entity that matches, and \*comps are all requested components for
|
|
@@ -304,13 +304,13 @@ def add_system(fkt: SystemFunction, *cids: ComponentID) -> None:
|
|
|
304
304
|
"""
|
|
305
305
|
|
|
306
306
|
create_archetype(*cids)
|
|
307
|
-
sidx[
|
|
307
|
+
sidx[fn] = cids
|
|
308
308
|
|
|
309
309
|
|
|
310
|
-
def remove_system(
|
|
310
|
+
def remove_system(fn: SystemFunction) -> None:
|
|
311
311
|
"""Remove the given function from the registry.
|
|
312
312
|
|
|
313
|
-
:param
|
|
313
|
+
:param fn: The system function
|
|
314
314
|
:return: None
|
|
315
315
|
|
|
316
316
|
Remove the match for this function from the registry
|
|
@@ -319,11 +319,11 @@ def remove_system(fkt: SystemFunction) -> None:
|
|
|
319
319
|
"""
|
|
320
320
|
|
|
321
321
|
for domain in didx:
|
|
322
|
-
remove_system_from_domain(domain,
|
|
322
|
+
remove_system_from_domain(domain, fn)
|
|
323
323
|
|
|
324
324
|
# Ignore unregistered systems, since we're removing anyways
|
|
325
325
|
try:
|
|
326
|
-
del sidx[
|
|
326
|
+
del sidx[fn]
|
|
327
327
|
except KeyError:
|
|
328
328
|
pass
|
|
329
329
|
|
|
@@ -480,7 +480,7 @@ def comp_of_eid(eid: EntityID, cid: ComponentID) -> Component:
|
|
|
480
480
|
return comps_of_eid(eid, cid)[0]
|
|
481
481
|
|
|
482
482
|
|
|
483
|
-
def eid_of_comp(comp: Component) -> set
|
|
483
|
+
def eid_of_comp(comp: Component) -> set[EntityID]:
|
|
484
484
|
"""Find the entity id for object comp.
|
|
485
485
|
|
|
486
486
|
:param comp: The component to find the entity of
|
|
@@ -519,63 +519,94 @@ def cid_of_comp(eid: EntityID, comp: Component) -> ComponentID:
|
|
|
519
519
|
raise UnknownComponentError(f'Component {comp} not found in entity {eid}')
|
|
520
520
|
|
|
521
521
|
|
|
522
|
-
def
|
|
523
|
-
|
|
522
|
+
def _create_call_list(cids: tuple[ComponentID],
|
|
523
|
+
has_properties: _OptionalProperties) -> list[tuple[EntityID, Component]]:
|
|
524
|
+
at = tuple(cids)
|
|
525
|
+
if at not in archetype:
|
|
526
|
+
create_archetype(*cids)
|
|
527
|
+
|
|
528
|
+
adict = archetype[at]
|
|
529
|
+
# need to get call_list upfront, since kill_system could modify the dict
|
|
530
|
+
# Also: not running in the if clause is vastly faster than checking an
|
|
531
|
+
# empty set.
|
|
532
|
+
if has_properties:
|
|
533
|
+
property_filter = set(has_properties)
|
|
534
|
+
call_list = [(eid, *parms) for eid, parms in adict.items()
|
|
535
|
+
if property_filter <= plist[eid]]
|
|
536
|
+
else:
|
|
537
|
+
call_list = [(eid, *parms) for eid, parms in adict.items()]
|
|
538
|
+
return call_list
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def run_system(fn: SystemFunction,
|
|
524
542
|
*cids: ComponentID,
|
|
525
543
|
has_properties: _OptionalProperties = None,
|
|
526
544
|
**kwargs: dict[str, Any]) -> _RunSystemResult:
|
|
527
545
|
"""Run the system for the matching cids.
|
|
528
546
|
|
|
529
|
-
:param
|
|
530
|
-
:param fkt: the actual system function
|
|
547
|
+
:param fn: the actual system function
|
|
531
548
|
:param cids: the components to run on
|
|
532
549
|
:param has_properties: set of required properties
|
|
533
550
|
:return: A dictionary with entity IDs as key and the function result as value
|
|
534
551
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
552
|
+
For every entity that has all the listed components, ``fn`` is run. The
|
|
553
|
+
function prototype of ``fn`` is::
|
|
554
|
+
|
|
555
|
+
def callback(eid: EntityID, *comps: Component) -> Any
|
|
556
|
+
|
|
557
|
+
The list can further be narrowed down my filtering for given properties.
|
|
539
558
|
|
|
540
559
|
This function is a direct call. Alternatively, you can use add_system
|
|
541
560
|
combined with run_all_systems or run_domain below.
|
|
542
561
|
"""
|
|
543
562
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
create_archetype(*cids)
|
|
563
|
+
call_list = _create_call_list(cids, has_properties)
|
|
564
|
+
return {eid: fn(eid, *parms, **kwargs) for eid, *parms in call_list}
|
|
547
565
|
|
|
548
|
-
adict = archetype[at]
|
|
549
|
-
# need to get call_list upfront, since kill_system could modify the dict
|
|
550
|
-
# Also: not running in the if clause is vastly faster than checking an
|
|
551
|
-
# empty set.
|
|
552
|
-
if has_properties:
|
|
553
|
-
property_filter = set(has_properties)
|
|
554
|
-
call_list = [(eid, *parms) for eid, parms in adict.items()
|
|
555
|
-
if property_filter <= plist[eid]]
|
|
556
|
-
else:
|
|
557
|
-
call_list = [(eid, *parms) for eid, parms in adict.items()]
|
|
558
|
-
return {eid: fkt(dt, eid, *parms, **kwargs) for eid, *parms in call_list}
|
|
559
566
|
|
|
567
|
+
def run_bulk_system(fn: SystemFunction,
|
|
568
|
+
*cids: ComponentID,
|
|
569
|
+
has_properties: _OptionalProperties = None,
|
|
570
|
+
**kwargs: dict[str, Any]) -> Any:
|
|
571
|
+
"""Run the bulk system for all entities with matching cids.
|
|
560
572
|
|
|
561
|
-
|
|
573
|
+
:param fn: the actual system function
|
|
574
|
+
:param cids: the components to run on
|
|
575
|
+
:param has_properties: set of required properties
|
|
576
|
+
:return: A dictionary with entity IDs as key and the function result as value
|
|
577
|
+
|
|
578
|
+
The fn is called with a list of entity ID and components tuples of all
|
|
579
|
+
entities that contain the listed components. The list can further be
|
|
580
|
+
narrowed down my filtering for given properties.
|
|
581
|
+
|
|
582
|
+
``fn`` is called given a list of ``(EntityID, (components,...))``. In
|
|
583
|
+
contrast to ``run_system``, the user is responsible to loop over the list.
|
|
584
|
+
|
|
585
|
+
The prototype of ``fn`` is::
|
|
586
|
+
|
|
587
|
+
def callback(call_list: list[tuple[EntityID, list[Component]]]) -> Any
|
|
588
|
+
"""
|
|
589
|
+
|
|
590
|
+
call_list = _create_call_list(cids, has_properties)
|
|
591
|
+
return fn(call_list, **kwargs) if call_list else None
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def run_all_systems(**kwargs) -> dict[SystemFunction, _RunSystemResult]:
|
|
562
595
|
"""Run all registered systems.
|
|
563
596
|
|
|
564
|
-
:param dt: Delta time
|
|
565
597
|
:return: A dict of function as key, and the result of run_system as value
|
|
566
598
|
|
|
567
599
|
This calls above run_system for all registered systems with their
|
|
568
600
|
appropriate components.
|
|
569
601
|
"""
|
|
570
602
|
|
|
571
|
-
return {
|
|
572
|
-
for
|
|
603
|
+
return {fn: run_system(fn, *comps, **kwargs)
|
|
604
|
+
for fn, comps in sidx.items()}
|
|
573
605
|
|
|
574
606
|
|
|
575
|
-
def run_domain(
|
|
607
|
+
def run_domain(domain: DomainID, **kwargs) -> dict[SystemFunction, _RunSystemResult]:
|
|
576
608
|
"""Run all systems within domain.
|
|
577
609
|
|
|
578
|
-
:param dt: Delta time
|
|
579
610
|
:return: A dict of function as key, and the result of run_system as value
|
|
580
611
|
|
|
581
612
|
This is the same as run_all_systems, but limited to a specific domain.
|
|
@@ -584,14 +615,14 @@ def run_domain(dt: float, domain: DomainID) -> dict[SystemFunction, _RunSystemRe
|
|
|
584
615
|
if domain not in didx:
|
|
585
616
|
return {}
|
|
586
617
|
|
|
587
|
-
return {
|
|
588
|
-
for
|
|
618
|
+
return {fn: run_system(fn, *sidx[fn], **kwargs)
|
|
619
|
+
for fn in didx[domain]}
|
|
589
620
|
|
|
590
621
|
|
|
591
622
|
def create_archetype(*cids: ComponentID) -> None:
|
|
592
623
|
"""Create an archetype from the provided cids.
|
|
593
624
|
|
|
594
|
-
:param cids The list of cids that define the archetype.
|
|
625
|
+
:param cids: The list of cids that define the archetype.
|
|
595
626
|
:return: None
|
|
596
627
|
|
|
597
628
|
An archetype is a fixed combination of components. Each time a component
|
|
@@ -669,11 +700,11 @@ def remove_from_archetype(eid: EntityID, cid: ComponentID | None = None) -> None
|
|
|
669
700
|
del adict[eid]
|
|
670
701
|
|
|
671
702
|
|
|
672
|
-
def comps_of_archetype(*cids: ComponentID, has_properties: _OptionalProperties = None) -> list[
|
|
703
|
+
def comps_of_archetype(*cids: ComponentID, has_properties: _OptionalProperties = None) -> list[EntityComponentsBundle]:
|
|
673
704
|
"""Return the given archetype.
|
|
674
705
|
|
|
675
|
-
:param cids The cids that define the archetype.
|
|
676
|
-
:param has_properties Optional set of required properties
|
|
706
|
+
:param cids: The cids that define the archetype.
|
|
707
|
+
:param has_properties: Optional set of required properties
|
|
677
708
|
:return: A list of tuples of (eid, components)
|
|
678
709
|
:raises UnknownArchetypeError: If the given archetype doesn't exist.
|
|
679
710
|
|
|
@@ -249,12 +249,12 @@ class RSAImage:
|
|
|
249
249
|
self.update()
|
|
250
250
|
|
|
251
251
|
|
|
252
|
-
def dead_system(
|
|
252
|
+
def dead_system(eid, dead, **kwargs):
|
|
253
253
|
"""Reap entities marked with a `dead` component."""
|
|
254
254
|
ecs.remove_entity(eid)
|
|
255
255
|
|
|
256
256
|
|
|
257
|
-
def deadzone_system(
|
|
257
|
+
def deadzone_system(eid, world, position, *, container, **kwargs):
|
|
258
258
|
"""Kill entities moving outside defined boundaries
|
|
259
259
|
|
|
260
260
|
To avoid sprites flying off to infinity, a dead zone can be defined, that
|
|
@@ -284,7 +284,7 @@ def deadzone_system(dt, eid, world, position, *, container):
|
|
|
284
284
|
ecs.remove_entity(eid)
|
|
285
285
|
|
|
286
286
|
|
|
287
|
-
def extension_system(
|
|
287
|
+
def extension_system(eid, extension, **kwargs):
|
|
288
288
|
"""Add additional components.
|
|
289
289
|
|
|
290
290
|
Parameters:
|
|
@@ -318,29 +318,29 @@ def extension_system(dt, eid, extension):
|
|
|
318
318
|
ecs.remove_component(eid, 'extension')
|
|
319
319
|
|
|
320
320
|
|
|
321
|
-
def force_system(
|
|
321
|
+
def force_system(eid, force, momentum, *, dt, **kwargs):
|
|
322
322
|
"""Apply a force to a momentum"""
|
|
323
323
|
momentum += force * dt
|
|
324
324
|
|
|
325
325
|
|
|
326
|
-
def lifetime_system(
|
|
326
|
+
def lifetime_system(eid, lifetime, **kwargs):
|
|
327
327
|
"""Kill an entity after a specified time"""
|
|
328
328
|
if lifetime.cold():
|
|
329
329
|
ecs.remove_entity(eid)
|
|
330
330
|
|
|
331
331
|
|
|
332
|
-
def momentum_system(
|
|
332
|
+
def momentum_system(eid, momentum, position, *, dt):
|
|
333
333
|
"""Apply momentum to a position (a.k.a. "move")."""
|
|
334
334
|
position += momentum * dt
|
|
335
335
|
|
|
336
336
|
|
|
337
|
-
def mouse_system(
|
|
337
|
+
def mouse_system(eid, mouse, position):
|
|
338
338
|
"""Place the position of an entity to the mouse cursor."""
|
|
339
339
|
mp = pygame.mouse.get_pos()
|
|
340
340
|
position.xy = Vector2(mp)
|
|
341
341
|
|
|
342
342
|
|
|
343
|
-
def scale_system(
|
|
343
|
+
def scale_system(eid, scale, momentum, *, dt):
|
|
344
344
|
"""Apply friction to a momentum.
|
|
345
345
|
|
|
346
346
|
In contrast to a force, which adds a directional vector to the momentum,
|
|
@@ -353,7 +353,7 @@ def scale_system(dt, eid, scale, momentum):
|
|
|
353
353
|
friction_system = scale_system
|
|
354
354
|
|
|
355
355
|
|
|
356
|
-
def sprite_system(
|
|
356
|
+
def sprite_system(eid, sprite, position, **kwargs):
|
|
357
357
|
"""Set the rect.center of sprite to position."""
|
|
358
358
|
|
|
359
359
|
if sprite.rect:
|
|
@@ -362,7 +362,7 @@ def sprite_system(dt, eid, sprite, position):
|
|
|
362
362
|
sprite.rect = sprite.image.get_rect(center=position)
|
|
363
363
|
|
|
364
364
|
|
|
365
|
-
def wsad_system(
|
|
365
|
+
def wsad_system(eid, wsad, position, *, dt):
|
|
366
366
|
"""An example system to control a playear with the `wsad` keys.
|
|
367
367
|
|
|
368
368
|
Given the following entity:
|
|
@@ -374,7 +374,7 @@ def wsad_system(dt, eid, wsad, position):
|
|
|
374
374
|
|
|
375
375
|
running this system will modify the position component of the entity.
|
|
376
376
|
|
|
377
|
-
ecs.run_system(
|
|
377
|
+
ecs.run_system('wsad', 'position', 'player', dt=dt)
|
|
378
378
|
|
|
379
379
|
Parameters
|
|
380
380
|
----------
|
|
@@ -396,4 +396,4 @@ def wsad_system(dt, eid, wsad, position):
|
|
|
396
396
|
if keys[pygame.K_d]: v.x += 1
|
|
397
397
|
|
|
398
398
|
# Normalize so diagonals are not faster then axis motion
|
|
399
|
-
if v: position += v.normalize() * wsad
|
|
399
|
+
if v: position += v.normalize() * wsad
|
|
@@ -6,11 +6,11 @@ from types import SimpleNamespace
|
|
|
6
6
|
from pygame import Vector2
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def momentum_system(
|
|
9
|
+
def momentum_system(eid, momentum, position, *, dt):
|
|
10
10
|
position += momentum * dt
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def border_system(
|
|
13
|
+
def border_system(eid, border, position, momentum, **kwargs):
|
|
14
14
|
if position.x < 0:
|
|
15
15
|
position.x = -position.x
|
|
16
16
|
momentum.x = -momentum.x
|
|
@@ -25,7 +25,7 @@ def border_system(dt, eid, border, position, momentum):
|
|
|
25
25
|
momentum.y = -momentum.y
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def background_system(
|
|
28
|
+
def background_system(eid, background, position, surface, *, dt):
|
|
29
29
|
def draw_one(angle, offset):
|
|
30
30
|
v0 = Vector2(position)
|
|
31
31
|
v1 = Vector2(2000, 0).rotate(angle)
|
|
@@ -81,7 +81,7 @@ def main():
|
|
|
81
81
|
|
|
82
82
|
screen.fill('black')
|
|
83
83
|
|
|
84
|
-
ecs.run_all_systems(dt)
|
|
84
|
+
ecs.run_all_systems(dt=dt)
|
|
85
85
|
|
|
86
86
|
pygame.display.flip()
|
|
87
87
|
clock.tick(FPS)
|
|
@@ -14,7 +14,7 @@ class Sprite(pygame.sprite.Sprite):
|
|
|
14
14
|
self.rect = self.image.get_rect()
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def bounding_box_system(
|
|
17
|
+
def bounding_box_system(eid, world, position, momentum, **kwargs):
|
|
18
18
|
if position.x < 0:
|
|
19
19
|
position.x = -position.x
|
|
20
20
|
momentum.x = -momentum.x
|
|
@@ -95,7 +95,7 @@ def main():
|
|
|
95
95
|
screen.fill('black')
|
|
96
96
|
|
|
97
97
|
group.update(dt)
|
|
98
|
-
ecs.run_all_systems(dt)
|
|
98
|
+
ecs.run_all_systems(dt=dt)
|
|
99
99
|
|
|
100
100
|
screen.blit(click, click_rect)
|
|
101
101
|
group.draw(screen)
|
|
@@ -25,7 +25,7 @@ class Velocity:
|
|
|
25
25
|
dy: float
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def motion_system(
|
|
28
|
+
def motion_system(eid, position, velocity, *, dt):
|
|
29
29
|
# Note that assigning a new Vector to position would not make it back
|
|
30
30
|
# into e.position. But since we don't change the position instance
|
|
31
31
|
# itself, we're safe.
|
|
@@ -33,14 +33,14 @@ def motion_system(dt, eid, position, velocity):
|
|
|
33
33
|
position.y += velocity.dy * dt
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def lifetime_system(
|
|
36
|
+
def lifetime_system(eid, lifetime, *, dt):
|
|
37
37
|
lifetime.time_left -= 1 * dt
|
|
38
38
|
if lifetime.time_left <= 0:
|
|
39
39
|
print(f'removing entity {eid}')
|
|
40
40
|
ecs.remove_entity(eid)
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
def render_system(
|
|
43
|
+
def render_system(eid, printable):
|
|
44
44
|
# the printable component contains no data at all and can be ignored,
|
|
45
45
|
# but it will still be passed as a parameter.
|
|
46
46
|
name, pos, velocity, lifetime = ecs.comps_of_eid(eid,
|
|
@@ -72,7 +72,7 @@ def main():
|
|
|
72
72
|
|
|
73
73
|
ecs.run_system(1, render_system, Comps.PRINTABLE)
|
|
74
74
|
for i in range(15):
|
|
75
|
-
ecs.run_system(1, motion_system, Comps.POSITION, Comps.VELOCITY)
|
|
75
|
+
ecs.run_system(1, motion_system, Comps.POSITION, Comps.VELOCITY, dt=1)
|
|
76
76
|
ecs.run_system(1, lifetime_system, Comps.LIFETIME)
|
|
77
77
|
ecs.run_system(1, render_system, Comps.PRINTABLE)
|
|
78
78
|
|
|
@@ -105,8 +105,8 @@ def main():
|
|
|
105
105
|
|
|
106
106
|
for i in range(15):
|
|
107
107
|
# ecs.run_all_systems(1)
|
|
108
|
-
ecs.run_domain(
|
|
109
|
-
ecs.run_domain(
|
|
108
|
+
ecs.run_domain('updates', dt=1)
|
|
109
|
+
ecs.run_domain('render')
|
|
110
110
|
|
|
111
111
|
print(f'All entities: {ecs.eidx}')
|
|
112
112
|
print(f'All components: {ecs.cidx}')
|
|
@@ -10,12 +10,12 @@ from pgcooldown import Cooldown
|
|
|
10
10
|
from pygame import Vector2
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def dead_sprite_system(
|
|
13
|
+
def dead_sprite_system(eid, dead, sprite):
|
|
14
14
|
sprite.kill()
|
|
15
15
|
ecs.remove_entity(eid)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def lifetime_sprite_system(
|
|
18
|
+
def lifetime_sprite_system(eid, lifetime, sprite):
|
|
19
19
|
if lifetime.hot():
|
|
20
20
|
return
|
|
21
21
|
sprite.kill()
|
|
@@ -54,7 +54,7 @@ def create_shard(position, image, *groups):
|
|
|
54
54
|
ecs.add_component(e, 'lifetime', Cooldown(random() * 0.25))
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def homing_missile_system(
|
|
57
|
+
def homing_missile_system(eid, homing_missile, position, momentum, *, dt):
|
|
58
58
|
target_pos = ecs.comp_of_eid(homing_missile.target, 'position')
|
|
59
59
|
|
|
60
60
|
target = target_pos
|
|
@@ -88,14 +88,14 @@ def homing_missile_system(dt, eid, homing_missile, position, momentum):
|
|
|
88
88
|
momentum.rotate_ip(copysign(rotation, delta_phi))
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def fire_system(
|
|
91
|
+
def fire_system(eid, fire, position):
|
|
92
92
|
for i in range(fire.count):
|
|
93
93
|
fire.fkt(pos=position)
|
|
94
94
|
|
|
95
95
|
ecs.remove_component(eid, 'fire')
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
def bounding_box_system(
|
|
98
|
+
def bounding_box_system(eid, world, position, momentum):
|
|
99
99
|
if position.x < 0:
|
|
100
100
|
position.x = -position.x
|
|
101
101
|
momentum.x = -momentum.x
|
|
@@ -206,15 +206,15 @@ def main():
|
|
|
206
206
|
|
|
207
207
|
screen.fill('black')
|
|
208
208
|
|
|
209
|
-
ecs.run_system(
|
|
210
|
-
ecs.run_system(
|
|
211
|
-
ecs.run_system(
|
|
212
|
-
ecs.run_system(
|
|
213
|
-
ecs.run_system(
|
|
214
|
-
ecs.run_system(
|
|
215
|
-
ecs.run_system(
|
|
216
|
-
ecs.run_system(
|
|
217
|
-
ecs.run_system(
|
|
209
|
+
ecs.run_system(ecsc.mouse_system, 'mouse', 'position')
|
|
210
|
+
ecs.run_system(lifetime_sprite_system, 'lifetime', 'sprite')
|
|
211
|
+
ecs.run_system(dead_sprite_system, 'dead', 'sprite')
|
|
212
|
+
ecs.run_system(ecsc.dead_system, 'dead')
|
|
213
|
+
ecs.run_system(ecsc.momentum_system, 'momentum', 'position', dt=dt)
|
|
214
|
+
ecs.run_system(ecsc.sprite_system, 'sprite', 'position')
|
|
215
|
+
ecs.run_system(bounding_box_system, 'world', 'position', 'momentum')
|
|
216
|
+
ecs.run_system(homing_missile_system, 'homing_missile', 'position', 'momentum', dt=dt)
|
|
217
|
+
ecs.run_system(fire_system, 'fire', 'position')
|
|
218
218
|
|
|
219
219
|
screen.blit(click, click_rect)
|
|
220
220
|
group.draw(screen)
|
|
@@ -10,7 +10,7 @@ from pgcooldown import Cooldown
|
|
|
10
10
|
from pygame import Vector2
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def marquee_system(
|
|
13
|
+
def marquee_system(eid, marquee, border, surface, *, dt):
|
|
14
14
|
def bounce(point, momentum):
|
|
15
15
|
point += momentum * dt
|
|
16
16
|
if point.x < 0:
|
|
@@ -80,7 +80,7 @@ def main():
|
|
|
80
80
|
|
|
81
81
|
screen.fill(BLACK)
|
|
82
82
|
|
|
83
|
-
ecs.run_system(
|
|
83
|
+
ecs.run_system(marquee_system, 'marquee', 'border', 'surface', dt=dt)
|
|
84
84
|
|
|
85
85
|
pygame.display.flip()
|
|
86
86
|
clock.tick(FPS)
|
|
@@ -173,17 +173,18 @@ will be used. To run e.g. the `momentum_system`, put the following into the
|
|
|
173
173
|
game loop:
|
|
174
174
|
|
|
175
175
|
``py
|
|
176
|
-
ecs.run_system(
|
|
176
|
+
ecs.run_system(momentum_system, 'momentum', 'position', dt=deltatime)
|
|
177
177
|
```
|
|
178
178
|
|
|
179
179
|
When the game loop passes this call, `run_system` will find all entities that
|
|
180
180
|
have both components `momentum` and `position` and pass these together with
|
|
181
|
-
|
|
181
|
+
the entity ID into the `momentum_system` function. Remember, that kwargs can
|
|
182
|
+
be used to pass additional arguments from the main scope.
|
|
182
183
|
|
|
183
184
|
Writing the `momentum_system` is easy:
|
|
184
185
|
|
|
185
186
|
```py
|
|
186
|
-
def momentum_system(
|
|
187
|
+
def momentum_system(eid, momentum, position, *, dt):
|
|
187
188
|
position += momentum * dt
|
|
188
189
|
```
|
|
189
190
|
|
|
@@ -199,11 +200,16 @@ passed in after the components. The following will be explained in the actual
|
|
|
199
200
|
script later.
|
|
200
201
|
|
|
201
202
|
```py
|
|
202
|
-
ecs.run_system(
|
|
203
|
+
ecs.run_system(deadzone_system, 'position', deadzone=WORLD)
|
|
203
204
|
```
|
|
204
205
|
|
|
205
206
|
So the system is basically the `update(dt)` function in an OO driven game.
|
|
206
207
|
|
|
208
|
+
If you prefer to loop over the list of entities yourself, instead of your
|
|
209
|
+
system being called for each entity individually, theren is `run_bulk_system`,
|
|
210
|
+
which gets the same arguments as ``run_system``, but will pass the full list
|
|
211
|
+
to the system.
|
|
212
|
+
|
|
207
213
|
At this point you might get the feeling, that you will have a very large list
|
|
208
214
|
of systems in a big block in your game loop, and that's exactly right. You
|
|
209
215
|
either hate that, which is fine, so the option is either to go back to an OO
|
|
@@ -216,7 +222,7 @@ There are some alternatives still.
|
|
|
216
222
|
program and call a single function in your game loop:
|
|
217
223
|
|
|
218
224
|
```py
|
|
219
|
-
ecs.run_all_systems(dt)
|
|
225
|
+
ecs.run_all_systems(dt=dt)
|
|
220
226
|
```
|
|
221
227
|
|
|
222
228
|
2. To give you a more fine grained control over what systems run together, the
|
|
@@ -236,7 +242,7 @@ method of your component. You still need that block of `run_system` or above
|
|
|
236
242
|
shortcuts in your game loop though.
|
|
237
243
|
|
|
238
244
|
```py
|
|
239
|
-
def call_update_system(
|
|
245
|
+
def call_update_system(eid, component, dt=dt):
|
|
240
246
|
component.update(dt)
|
|
241
247
|
```
|
|
242
248
|
|
|
@@ -359,15 +365,15 @@ completeness:
|
|
|
359
365
|
# addition.
|
|
360
366
|
WORLD = SCREEN.scale_by(1.25)
|
|
361
367
|
|
|
362
|
-
def momentum_system(
|
|
368
|
+
def momentum_system(eid, momentum, position, *, dt):
|
|
363
369
|
"""Add a delta time scaled momentum to the position."""
|
|
364
370
|
position += momentum * dt
|
|
365
371
|
|
|
366
|
-
def sprite_position_system(
|
|
372
|
+
def sprite_position_system(eid, sprite, position):
|
|
367
373
|
"""Apply the position to the rect of the sprite for the sprite group"""
|
|
368
374
|
sprite.rect.center = position
|
|
369
375
|
|
|
370
|
-
def deadzone_system(
|
|
376
|
+
def deadzone_system(eid, position, *, world):
|
|
371
377
|
"""Kill sprites that move off screen"""
|
|
372
378
|
if world.collidepoint(position):
|
|
373
379
|
return
|
|
@@ -391,7 +397,7 @@ to the entity instead of overwriting the component variable.
|
|
|
391
397
|
|
|
392
398
|
.. code-block::
|
|
393
399
|
|
|
394
|
-
def take_damage_system(
|
|
400
|
+
def take_damage_system(eid, health, damage):
|
|
395
401
|
ecs.add_component(eid, 'health', health - damage)
|
|
396
402
|
|
|
397
403
|
For semantic reasons, tinyecs also provides `update_component`, but that's
|
|
@@ -425,9 +431,9 @@ The lines marked with `>` are additions to the game loop template all above.
|
|
|
425
431
|
#### Running the systems
|
|
426
432
|
|
|
427
433
|
```py
|
|
428
|
-
> ecs.run_system(
|
|
429
|
-
> ecs.run_system(
|
|
430
|
-
> ecs.run_system(
|
|
434
|
+
> ecs.run_system(momentum_system, 'momentum', 'position', dt=dt)
|
|
435
|
+
> ecs.run_system(deadzone_system, 'position', world=WORLD)
|
|
436
|
+
> ecs.run_system(sprite_position_system, 'sprite', 'position')
|
|
431
437
|
|
|
432
438
|
screen.fill('black')
|
|
433
439
|
|
|
@@ -494,17 +500,17 @@ def create_box_entity(position, sprite_group):
|
|
|
494
500
|
ecs.add_component(e, 'sprite', DemoSprite(sprite_group))
|
|
495
501
|
|
|
496
502
|
|
|
497
|
-
def momentum_system(
|
|
503
|
+
def momentum_system(eid, momentum, position, *, dt):
|
|
498
504
|
"""Add a delta time scaled momentum to the position."""
|
|
499
505
|
position += momentum * dt
|
|
500
506
|
|
|
501
507
|
|
|
502
|
-
def sprite_position_system(
|
|
508
|
+
def sprite_position_system(eid, sprite, position):
|
|
503
509
|
"""Apply the position to the rect of the sprite for the sprite group"""
|
|
504
510
|
sprite.rect.center = position
|
|
505
511
|
|
|
506
512
|
|
|
507
|
-
def deadzone_system(
|
|
513
|
+
def deadzone_system(eid, position, *, world):
|
|
508
514
|
"""Kill sprites that move off screen"""
|
|
509
515
|
if world.collidepoint(position):
|
|
510
516
|
return
|
|
@@ -533,9 +539,9 @@ while running:
|
|
|
533
539
|
pos = pygame.mouse.get_pos()
|
|
534
540
|
create_box_entity(pos, group)
|
|
535
541
|
|
|
536
|
-
ecs.run_system(
|
|
537
|
-
ecs.run_system(
|
|
538
|
-
ecs.run_system(
|
|
542
|
+
ecs.run_system(momentum_system, 'momentum', 'position', dt=dt)
|
|
543
|
+
ecs.run_system(deadzone_system, 'position', world=WORLD)
|
|
544
|
+
ecs.run_system(sprite_position_system, 'sprite', 'position')
|
|
539
545
|
|
|
540
546
|
screen.fill('black')
|
|
541
547
|
|
|
@@ -585,12 +591,12 @@ import tinyecs.compsys as ecsc
|
|
|
585
591
|
|
|
586
592
|
UNSTABLE! DON'T RELY ON THIS YET!
|
|
587
593
|
|
|
588
|
-
`def dead_system(
|
|
594
|
+
`def dead_system(eid, dead)`:
|
|
589
595
|
Sometimes it is useful to not remove a sprite immediately from the system.
|
|
590
596
|
Instead, you can add a component tagged e.g. 'dead', and later reap all
|
|
591
597
|
entities marked with that tag.
|
|
592
598
|
|
|
593
|
-
`def deadzone_system(
|
|
599
|
+
`def deadzone_system(eid, world, position, *, container)`:
|
|
594
600
|
|
|
595
601
|
Basically the function created in this tutorial, with one addition.
|
|
596
602
|
|
|
@@ -603,25 +609,25 @@ import tinyecs.compsys as ecsc
|
|
|
603
609
|
|
|
604
610
|
ecs.add_component(e, 'world', True)
|
|
605
611
|
|
|
606
|
-
`def extension_system(
|
|
612
|
+
`def extension_system(eid, extension)`:
|
|
607
613
|
Will be removed. tinyecs installs pgcooldown as a requirement and that
|
|
608
614
|
comes with the `CronD` class which does the same and more.
|
|
609
615
|
|
|
610
|
-
`def force_system(
|
|
616
|
+
`def force_system(eid, force, momentum, *, dt)`:
|
|
611
617
|
Applies (adds) a constant force to a momentum.
|
|
612
618
|
|
|
613
|
-
`def lifetime_system(
|
|
619
|
+
`def lifetime_system(eid, lifetime)`:
|
|
614
620
|
Kills the entity once lifetime has run out. Expects `lifetime` to be an
|
|
615
621
|
instance of `pgcooldown.Cooldown`
|
|
616
622
|
|
|
617
|
-
`def momentum_system(
|
|
623
|
+
`def momentum_system(eid, momentum, position, *, dt)`:
|
|
618
624
|
The same as we wrote in the tutorial above.
|
|
619
625
|
|
|
620
|
-
`def mouse_system(
|
|
626
|
+
`def mouse_system(eid, mouse, position)`:
|
|
621
627
|
Update a position component with the position of the mouse cursor.
|
|
622
628
|
|
|
623
|
-
`def scale_system(
|
|
624
|
-
`def friction_system(
|
|
629
|
+
`def scale_system(eid, scale, momentum, *, dt)`:
|
|
630
|
+
`def friction_system(eid, scale, momentum, *, dt)`:
|
|
625
631
|
Apply friction to a momentum.
|
|
626
632
|
|
|
627
633
|
In contrast to a force_system, which adds a directional vector to the
|
|
@@ -630,10 +636,10 @@ import tinyecs.compsys as ecsc
|
|
|
630
636
|
|
|
631
637
|
`friction_system` is an alias to `scale_system`.
|
|
632
638
|
|
|
633
|
-
`def sprite_system(
|
|
639
|
+
`def sprite_system(eid, sprite, position)`:
|
|
634
640
|
The same as we wrote above, apply the position to `sprite.rect.center`.
|
|
635
641
|
|
|
636
|
-
`def wsad_system(
|
|
642
|
+
`def wsad_system(eid, wsad, position)`:
|
|
637
643
|
An example system to control a playear with the `wsad` keys.
|
|
638
644
|
This was just a proof of concept, but I'm currently using it, so perhaps
|
|
639
645
|
it will stay.
|
|
@@ -690,17 +696,17 @@ def create_box_entity(position, sprite_group):
|
|
|
690
696
|
ecs.add_component(e, 'sprite', DemoSprite(sprite_group))
|
|
691
697
|
|
|
692
698
|
|
|
693
|
-
def momentum_system(
|
|
699
|
+
def momentum_system(eid, momentum, position, *, dt):
|
|
694
700
|
"""Add a delta time scaled momentum to the position."""
|
|
695
701
|
position += momentum * dt
|
|
696
702
|
|
|
697
703
|
|
|
698
|
-
def sprite_position_system(
|
|
704
|
+
def sprite_position_system(eid, sprite, position):
|
|
699
705
|
"""Apply the position to the rect of the sprite for the sprite group"""
|
|
700
706
|
sprite.rect.center = position
|
|
701
707
|
|
|
702
708
|
|
|
703
|
-
def deadzone_system(
|
|
709
|
+
def deadzone_system(eid, position, *, world):
|
|
704
710
|
"""Kill sprites that move off screen"""
|
|
705
711
|
if world.collidepoint(position):
|
|
706
712
|
return
|
|
@@ -730,9 +736,9 @@ def main():
|
|
|
730
736
|
pos = pygame.mouse.get_pos()
|
|
731
737
|
create_box_entity(pos, group)
|
|
732
738
|
|
|
733
|
-
ecs.run_system(
|
|
734
|
-
ecs.run_system(
|
|
735
|
-
ecs.run_system(
|
|
739
|
+
ecs.run_system(momentum_system, 'momentum', 'position', dt=dt)
|
|
740
|
+
ecs.run_system(deadzone_system, 'position', world=WORLD)
|
|
741
|
+
ecs.run_system(sprite_position_system, 'sprite', 'position')
|
|
736
742
|
|
|
737
743
|
screen.fill('black')
|
|
738
744
|
|
|
@@ -28,20 +28,20 @@ class Ping:
|
|
|
28
28
|
ack: bool = False
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def wounding_system(
|
|
31
|
+
def wounding_system(eid, health, **kwargs):
|
|
32
32
|
health.health -= 100
|
|
33
33
|
return health.health
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def stats_system(
|
|
37
|
-
return f'{
|
|
36
|
+
def stats_system(eid, name, health, **kwargs):
|
|
37
|
+
return f'{eid=}, {name.name=}, {health.health=}'
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def ping_system(
|
|
40
|
+
def ping_system(eid, ping):
|
|
41
41
|
ping.ack = True
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def move_system(
|
|
44
|
+
def move_system(eid, pos, velocity, *, dt, **kwargs):
|
|
45
45
|
pos.x += velocity.dx * dt
|
|
46
46
|
pos.y += velocity.dy * dt
|
|
47
47
|
return (pos.x, pos.y)
|
|
@@ -161,27 +161,40 @@ def test_comps_of_eid():
|
|
|
161
161
|
|
|
162
162
|
def test_run_system():
|
|
163
163
|
e1, e2 = setup()
|
|
164
|
-
ecs.run_system(
|
|
164
|
+
ecs.run_system(wounding_system, 'health')
|
|
165
165
|
assert ecs.eidx[e2]['health'].health == 900
|
|
166
166
|
assert ecs.cidx['health'][e2].health == 900
|
|
167
167
|
|
|
168
168
|
worked = False
|
|
169
169
|
|
|
170
|
-
def sys_no_args(
|
|
170
|
+
def sys_no_args(eid):
|
|
171
171
|
nonlocal worked
|
|
172
172
|
worked = True
|
|
173
173
|
|
|
174
|
-
ecs.run_system(
|
|
174
|
+
ecs.run_system(sys_no_args)
|
|
175
175
|
assert worked
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
def test_run_bulk_system():
|
|
179
|
+
ecs.reset()
|
|
180
|
+
for i in range(10):
|
|
181
|
+
eid = ecs.create_entity()
|
|
182
|
+
ecs.add_component(eid, 'number', i)
|
|
183
|
+
|
|
184
|
+
def bulk_runner(call_list):
|
|
185
|
+
assert len(call_list) == 10
|
|
186
|
+
return sum([i for eid, i in call_list])
|
|
187
|
+
|
|
188
|
+
assert ecs.run_bulk_system(bulk_runner, 'number') == 45
|
|
189
|
+
|
|
190
|
+
|
|
178
191
|
def test_run_all_systems():
|
|
179
192
|
e1, e2 = setup()
|
|
180
193
|
ecs.add_system(move_system, 'pos', 'velocity')
|
|
181
194
|
ecs.add_system(wounding_system, 'health')
|
|
182
195
|
ecs.add_component(e2, 'pos', SimpleNamespace(x=100, y=200))
|
|
183
196
|
ecs.add_component(e2, 'velocity', SimpleNamespace(dx=5, dy=7))
|
|
184
|
-
res = ecs.run_all_systems(1)
|
|
197
|
+
res = ecs.run_all_systems(dt=1)
|
|
185
198
|
|
|
186
199
|
assert ecs.eidx[e2]['pos'] == SimpleNamespace(x=105, y=207)
|
|
187
200
|
assert res[move_system][e2] == (105, 207)
|
|
@@ -261,7 +274,7 @@ def test_reset():
|
|
|
261
274
|
|
|
262
275
|
|
|
263
276
|
def test_kill_from_system():
|
|
264
|
-
def kill_system(
|
|
277
|
+
def kill_system(eid, kill):
|
|
265
278
|
ecs.remove_entity(eid)
|
|
266
279
|
|
|
267
280
|
ecs.reset()
|
|
@@ -273,7 +286,7 @@ def test_kill_from_system():
|
|
|
273
286
|
ecs.add_component(e, 'kill', True)
|
|
274
287
|
|
|
275
288
|
assert len(ecs.eidx) == 10
|
|
276
|
-
ecs.run_system(
|
|
289
|
+
ecs.run_system(kill_system, 'kill')
|
|
277
290
|
assert len(ecs.eidx) == 5
|
|
278
291
|
|
|
279
292
|
|
|
@@ -305,11 +318,11 @@ def test_run_domain():
|
|
|
305
318
|
e = ecs.create_entity()
|
|
306
319
|
ecs.add_component(e, 'ping', Ping(False))
|
|
307
320
|
|
|
308
|
-
res = ecs.run_domain(
|
|
321
|
+
res = ecs.run_domain('infra')
|
|
309
322
|
assert ping_system in res
|
|
310
323
|
assert e in res[ping_system]
|
|
311
324
|
|
|
312
|
-
res = ecs.run_domain(
|
|
325
|
+
res = ecs.run_domain('non-existent')
|
|
313
326
|
assert res == {}
|
|
314
327
|
|
|
315
328
|
|
|
@@ -404,12 +417,12 @@ def test_add_to_archetype():
|
|
|
404
417
|
# Also tests remove_from_archetype
|
|
405
418
|
setup()
|
|
406
419
|
|
|
407
|
-
def test_system(
|
|
420
|
+
def test_system(eid, test):
|
|
408
421
|
pass
|
|
409
422
|
|
|
410
423
|
e = ecs.create_entity()
|
|
411
424
|
ecs.add_component(e, 'test', True)
|
|
412
|
-
ecs.run_system(
|
|
425
|
+
ecs.run_system(test_system, 'test')
|
|
413
426
|
assert e in ecs.archetype[('test',)]
|
|
414
427
|
|
|
415
428
|
ecs.remove_component(e, 'test')
|
|
@@ -457,10 +470,10 @@ def test_property_get_entities():
|
|
|
457
470
|
def test_property_run_system():
|
|
458
471
|
setup_property_tests()
|
|
459
472
|
|
|
460
|
-
def flag_system(
|
|
473
|
+
def flag_system(eid, comp1, comp2):
|
|
461
474
|
ecs.add_component(eid, 'flag', True)
|
|
462
475
|
|
|
463
|
-
ecs.run_system(
|
|
476
|
+
ecs.run_system(flag_system, 'comp-1', 'comp-2', has_properties={'is-even'})
|
|
464
477
|
assert len(ecs.eids_by_cids('flag')) == 5
|
|
465
478
|
|
|
466
479
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|