SHARE
TWEET

An entity-component system in Python

eigenbom Oct 1st, 2016 364 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. ###############################################
  2. # An example of using Python decorators to
  3. # build an Entity-Component system.
  4. # Here be dragons.
  5. # @eigenbom 10/2016
  6. ###############################################
  7.  
  8. import copy
  9. from enum import Enum
  10. from functools import wraps
  11.  
  12. ###########################
  13. # Base entity
  14. ###########################
  15.  
  16. class Entity(object):
  17.     """Generic entity class"""
  18.  
  19.     def __init__(self, name=None, *args, **kwargs):
  20.         self.components = list()
  21.         self.lazy_init = dict()
  22.  
  23.         # Common
  24.         self.name = name
  25.  
  26.         # Passed args override defaults
  27.         self.__dict__.update(*args)
  28.         self.__dict__.update(**kwargs)
  29.  
  30.     def __str__(self):
  31.         components = [c.__name__ for c in self.components]
  32.         res = '"%s"'%self.name
  33.         res += " ["
  34.         for i, c in enumerate(self.components):
  35.             res += c.__name__ + (" " if i != len(self.components)-1 else "")
  36.         res += "]\n"
  37.         for k, v in sorted(self.__dict__.items()):
  38.             if k in ["name", "components", "lazy_init"]: continue
  39.             res += "  %s: %s\n"%(k,v)
  40.         return res
  41.  
  42.  
  43. def merge_entities(one, other):
  44.     """Merge two entities"""
  45.     e = copy.deepcopy(one)
  46.     e.components.extend(other.components)
  47.     for k,v in other.__dict__.items():
  48.         if k in ["components"]: continue
  49.         if k not in e.__dict__:
  50.             e.__dict__[k] = v
  51.         elif isinstance(e.__dict__[k], list):
  52.             e.__dict__[k].extend(v)
  53.         elif isinstance(e.__dict__[k], dict):
  54.             e.__dict__[k].update(v)
  55.         else:
  56.             # TODO: intelligently merge conflicting vars? (give vars a priority?)
  57.             pass
  58.     return e
  59.  
  60. ###########################
  61. # Decorators
  62. ###########################
  63.  
  64. def entity(func):
  65.     """(Decorator) Creates an entity from a factory function"""
  66.     @wraps(func)
  67.     def func_wrapper(*args, **kwargs):
  68.         e = Entity(*args, **kwargs)
  69.         if e.name is None:
  70.             # Name defaults to factory function name
  71.             e.name = func.__name__
  72.         e.lazy_init.update(func(**kwargs))
  73.         return e
  74.     return func_wrapper
  75.  
  76. def inherits(factory, **kwargsfactory):
  77.     """(Decorator) Uses an existing factory function to create an entity."""
  78.     def wrapper(func):
  79.         @wraps(func)
  80.         def func_wrapper(*args, **kwargs):
  81.             e = factory(*args, **kwargs, **kwargsfactory)
  82.             res = func(**kwargs, **kwargsfactory)
  83.             if isinstance(res, Entity):
  84.                 e = merge_entities(e, res)
  85.                 e.__dict__.update(e.lazy_init)
  86.             else:
  87.                 e.lazy_init.update(res)
  88.                 e.__dict__.update(e.lazy_init)
  89.             return e
  90.         return func_wrapper
  91.     return wrapper
  92.  
  93. def component(f, **kwargs):
  94.     """(Decorator) Adds a component (function) to an entity"""
  95.     def decorator(func):
  96.         @wraps(func)
  97.         def func_wrapper(*args, **kwargs2):
  98.             e = func(*args, **kwargs2)
  99.             e.components.append(f)
  100.             e = f(e, **kwargs)
  101.             e.__dict__.update(e.lazy_init)
  102.             return e
  103.         return func_wrapper
  104.     return decorator
  105.  
  106. ###########################
  107. # Components
  108. ###########################
  109.  
  110. def info(e, tooltip=None, description=None):
  111.     """(Component) Holds basic information about an entity."""
  112.     # Merge multiple tooltips
  113.     e.tooltip = e.__dict__.get("tooltip", "") + " " + tooltip
  114.     e.description = description
  115.     return e
  116.  
  117. def decay(e, rate=1):
  118.     """(Component) Decays an object over time."""
  119.     e.health = 1
  120.     e.decaying = True
  121.     e.decay_rate = rate
  122.     # (Then we'd add a function that actually does the decay)
  123.     # e.g., e.add_handler(...)
  124.     return e
  125.  
  126. Mob = Enum('Mob', 'humanoid flying')
  127. def mob(e, type=None, types=None):
  128.     """(Component) Is a Mob."""
  129.     e.mob = True
  130.     mob_types = e.__dict__.get("mob_types", [])
  131.     if types:
  132.         mob_types.extend(types)
  133.     if type:
  134.         mob_types.append(types)
  135.     e.mob_types = mob_types
  136.     return e
  137.  
  138. ###########################
  139. # Factories / Prefabs
  140. ###########################
  141.  
  142. Material = Enum('Material', 'uranium silver jelly etc')
  143.  
  144. @component(info, tooltip="It glows.")
  145. @component(decay, rate=2)
  146. @entity
  147. def uranium(**kwargs):
  148.     return dict(
  149.         material=Material.uranium,
  150.         health=10
  151.     )
  152.  
  153. @component(mob, type=Mob.humanoid)
  154. @entity
  155. def human(**kwargs):
  156.     return dict (
  157.         health = 10
  158.     )
  159.  
  160. @component(info, tooltip="Up and Atom!")
  161. @inherits(uranium)
  162. @inherits(human, mob_types=[Mob.humanoid, Mob.flying])
  163. def radioactive_man(**kwargs):
  164.     return dict (
  165.         health = 50
  166.     )
  167.  
  168. def example():
  169.     world = [
  170.         uranium(name="Nuclear Pile"),
  171.         human(name="Ben"),
  172.         radioactive_man(name="Captain Atom")
  173.     ]
  174.     for e in world:
  175.         print(e)
  176.  
  177. if __name__ == "__main__":
  178.     example()
RAW Paste Data
Top