eigenbom

An entity-component system in Python

Oct 1st, 2016
893
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

Adblocker detected! Please consider disabling it...

We've detected AdBlock Plus or some other adblocking software preventing Pastebin.com from fully loading.

We don't have any obnoxious sound, or popup ads, we actively block these annoying types of ads!

Please add Pastebin.com to your ad blocker whitelist or disable your adblocking software.

×