Entity Abstraction Layer

Hi guys,

I’m trying to develop an abstraction layer on the top of Panda 3D Python code, similar to Crystal Space (CEL), Nebula 3 or Unreal Engine 3 (i think, but i’m not sure) ones.

The idea behind this is very simple: instead of heavy use of inheritance, is a best choice to organize logic entities with an “aggregation” approach.

In this approach there are 3 types of object:

  • Entity: is an high level logic entity (like a player, a forest tree, a weapon or a car). It has many properties and an associated behaviour code (the game logic).

  • Property: is a set of functionalities the owner can use. A property is an access point to Panda 3D features. An example of “property” can be a “sound source”, an “input handler”, a “3D mesh” and so on. Each property talks with other properties (of the same entity or of another one) with messages/events.

  • Message/Event: the standard event message system of Panda 3D.

For example, in a race game, we could describe the game logic as:

entity: Player
properties: mesh3d, physics object, camera handler, input handler, etc.

entity: Track
properties: mesh3d, physics world, etc.

entity: Semaphore
properties: mesh3d, trigger, etc.

entity: Opponent
properties: mesh3d, physics object, IA actor, etc.

This solution is very smart and modular, with an high level of reuse.

I’ve already implemented base classes (Entity and Property) and some specialized properties:

  • mesh3D
  • camera_handler
  • input_handler
  • physics_world
  • physics_object

These are the sources of Entity and Property base classes:

class Entity(object):
    def __init__(self, *args, **kwargs):
        self.__properties = {}
        
    def attach_property(self, property):
        property.owner = self
        self.__properties[property.name] = property
        
    def detach_property(self, property):
        key = property.name
        self.__properties[key].destroy()
        del self.__properties[key]
        
    def get_property(self, name):
        return self.__properties[name]
        
    def has_property(self, name):
        return name in self.__properties.keys()
from direct.showbase import DirectObject
from direct.showbase.MessengerGlobal import messenger

class Property(object, DirectObject.DirectObject):  
    def __init__(self, *args, **kwargs):
        DirectObject.DirectObject.__init__(self)
        self.owner = None
        self.name = self.__module__.rpartition('.')[2]
        
    def destroy(self):
        self.owner = None
        self.ignoreAll()
    
    @classmethod
    def event_name_from(cls, prop, suffix):
        return '%s.%s' % (prop.name, suffix)
    
    @classmethod
    def send_message(cls, name, args=[]):
        messenger.send(name, args)

…So, what do you think about this solution? :slight_smile:

This is a (simplified) example of specialized property and its usage in a real case:

camera_handler:

from .. import prop
from pandac.PandaModules import Camera, Vec3, Point3

class CameraHandler(prop.Property):  
    def __init__(self, *args, **kwargs):
        prop.Property.__init__(self)
        self.__camera = base.cam
        self.__target = None
        
        self.accept('mesh3d.node_path.position.changed', self.__on_linked_position_changed)
        
    def __get_camera(self):
        return self.__camera
        
    def __set_camera(self, value):
        if value != self.__camera:
            self.__camera = value
            evt_name = self.event_name_from(self, 'camera.changed')
            self.send_message(evt_name)
            
    def __del_camera(self):
        self.__camera.removeNode()
        evt_name = self.event_name_from(self, 'camera.removed')
        self.send_message(evt_name)

    camera = property(__get_camera, __set_camera, __del_camera)
    
    def is_linked(self):
        return self.__target is not None
        
    def link(self, node_path):
        self.__camera.lookAt(node_path.getPos())
        self.__target = node_path
        
    def make_third_person(self, target, distance=Vec3(0, 12, 3)):
        self.__camera.getParent().reparentTo(target)
        self.__camera.setPos(distance)
        self.__camera.lookAt(Point3(0, 0, 0))
        self.__target = target
        
    def make_first_person(self, target, height=0):
        self.__camera.getParent().reparentTo(target)
        self.__camera.setPos(Vec3(0, 0, height))
        self.__camera.lookAt(Point3(0, 1000, 0))
        self.__target = target
        
    def make_free(self):
        self.__camera.getParent().reparentTo(render)
        self.__target = None
    
    def __on_linked_position_changed(self, owner, value):
        if self.__target is None or owner != self.owner:
            return
        
        if self.__target != self.__camera.getParent().getParent():
            self.__camera.lookAt(value)

Usage:

class Player(entity.Entity):
    def __init__(self, *args, **kwargs):
        entity.Entity.__init__(self, *args, **kwargs)
        
        # Mesh 3D.
        root = kwargs['root']
        m3d = mesh3d.Mesh3D(*args, **kwargs)
        self.attach_property(m3d)
        m3d.node_path = loader.loadModel(kwargs['model_name'])
        m3d.node_path.reparentTo(root)

        # Camera handler.
        cam = camera_handler.CameraHandler(*args, **kwargs)
        self.attach_property(cam)
        cam.make_third_person(m3d.node_path)

       ...

These concepts are actually very close to the core philosophy of Python. Although at first glance Python appears to follow the strict object-oriented inheritance model espoused by languages like C++ and Smalltalk, it is in fact a much looser model, along the lines of your Entities/Properties dichotomy. In fact, Python “methods” are more like your properties than they are like C++ methods, because they are looked up by name on a particular instance, and they don’t necessarily need to be in the inheritance hierarchy.

In fact, a Python instance object is really just a dictionary. When you say “a.foo = 1”, you are really saying “a.dict[‘foo’] = 1”, modifying a dictionary value. Python lets you add or remove elements to an instance object willy-nilly, the same way you can add keys to a dictionary.

Inheritance is less important in Python than semantics. If a class implements a particular set of methods, by whatever means it acquired those methods, it all amounts to the same thing. This remarkable property has been dubbed “duck typing”.

David

This actually really reminds me of the old Quake3 style games. In the map editor you could place an entity but it could be many things IE player, light source, rotator, etc and each entity had a set of parameters that you could tweak as you saw fit or add more parameters if you needed. Is this th effect you are going for?

Uhm…yes. Do you know LEGO? :wink:

The idea behind it is well described in this page:

http://flohofwoe.blogspot.com/2007/11/nebula3s-application-layer-provides.html

just asking out of curiosity… but … those entities and properties listed here… they sorta sound very much like “our” nodes in the scene graph… just that their name is different.
nodes , like entities can have properties.
and for me, python already is quite high-level-abstraction from the math and all the nasty stuff i dont even want to think about.
so do we really need a abstraction layer which looks like the same thing we already have, except its now colored in the industries standart color?

Yes this is a good design decision. I also favor aggregation vs deep inheritance model. It’s just simpler to read and work with.

But, that’s what I’m saying: Python already supports aggregation, right out of the box. You don’t need to do anything to add it in.

David

My newbie python colors come out.

Well this is a much welcomed change from C++. I am liking python more and more :slight_smile:

Thank you David.