Entity systems for managing game objects

Miscellaneous Forums/General Discussion/Entity systems for managing game objects

I came across this article: http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/ and thought it looked like an interesting way to handle game entities and what not. I don't really understand it all 100%, and just knocked out the following code which seems to be along the same kind of lines. It's basically a component based entity system where each entity can have a different set of components that you kind of plug in your game objects according to your needs (article explains it all).

There doesn't seem to be many decent code examples floating around so I had a go:

Blitzmax code:
SuperStrict

'A world type to contain the game objects
Type tWorld
	
	'our world needs a few lists to contain our objects
	
	'firstly a list to contain our cameras
	Field cameras:TList = CreateList()
	
	'and a list to contain all the entities
	Field entities:TList = CreateList()
	
	'Any types that extend tWorld must have an init method to set the world up
	Method init() Abstract
	
	Method addCamera(c:tcamera)
		cameras.AddLast(c)
	End Method
	Method addEntity(e:tEntity)
		entities.AddLast(e)
	End Method
	
	'update all entities and cameras in the lists
	Method update()
		For Local c:tCamera = EachIn cameras
			c.update()
		Next
		For Local e:tEntity = EachIn entities
			e.update()
		Next
	End Method
	
	'The render method will draw the world and all the objects it contains
	Method render()
		Cls
		
		'iterate through all the cameras in the world
		For Local c:tCamera = EachIn cameras
			
			'iterate through all the entities in the world
			For Local e:tEntity = EachIn entities
				'set the scale of the entity depending on its z and the camera z
				'draw the entity on the screen relative to where it is in the world and which camera is looking at it
				'the calculations also keep things relative to the current zoom level of the camera
				'also, obviously it only needs to be drawn if it has a shape component
				If e.shape
					SetScale e.position.z * c.position.z, e.position.z * c.position.z
					SetRotation e.movement.direction
					SetColor e.shape.red, e.shape.green, e.shape.blue
					DrawRect (e.position.x * c.position.z) + c.screenx + (- c.position.x * c.position.z),  ..
							 (e.position.y * c.position.z) + c.screeny + (- c.position.y * c.position.z),  ..
							  e.shape.w, e.shape.h
				End If
			Next
			
		Next
		
		Flip
	End Method
	
	'The main game loop that runs the world
	Method run()
		While Not KeyDown(KEY_ESCAPE)
			update()
			render()
		Wend
	End Method
	
End Type

'Components. these are the components that can be used to make our entities
Type tController
	
	'controller type is use to define how the entities are controlled in some way, eg player input
	
	'a link to the movement component
	Field Movement:tMovement
        
	Method setmovement(v:tMovement)
		movement = v
	End Method
	Method getmovement:tMovement()
		Return Movement
	End Method

	'every controller must have an update method to handle however it's controlled, receive input, do ai etc.
	Method Update() Abstract

End Type
Type tPosition
    
	'The position component stores where in the world the entity is located

	Field x:Float = 0
	Field y:Float = 0
	Field z:Float = 1
	       
	'A link a movement component that will update the location of the entity
	Field mover:tMovement
        
	Method Create:tPosition()
		Local p:tPosition = New tPosition
		Return p
	End Method
        
	Method setmover(v:tMovement)
		mover = v
	End Method
	Method getmover:tMovement()
		Return mover
	End Method
        
	Method update()
		'use the movement component to update the position 
		'I guess I could have put this in the movement type instead...
		x:+mover.speed * Sin(mover.direction)
		y:-mover.speed * Cos(mover.direction)
	End Method
	
	Method setx(v:Float)
		x = v
	End Method
	Method sety(v:Float)
		y = v
	End Method
	Method setz(v:Float)
		z = v
	End Method
	
	Method getx:Float()
		Return x
	End Method
	Method gety:Float()
		Return y
	End Method
	Method getz:Float()
		Return z
	End Method
End Type
Type tMovement
    
	'The movement component that stores speed direction etc.

	Field direction:Float
	Field speed:Float
	Field accelleration:Float

        
	Method Create:tMovement()
		Local m:tMovement = New tMovement
		Return m
	End Method
                
	Method setaccelleration(v:Float)
		accelleration = v
	End Method
	Method setspeed(v:Float)
		speed = v
	End Method
	Method setdirection(v:Float)
		direction = v
	End Method
        
	Method getaccelleration:Float()
		Return accelleration
	End Method
	Method getspeed:Float()
		Return speed
	End Method
	Method getection:Float()
		Return direction
	End Method

End Type
Type tShape
	
	'shape component that defines what the entity looks like and how it should be rendered

	Field w:Float					'Width
	Field h:Float					'Height
	
	Field red:Int
	Field green:Int
	Field blue:Int
	
	Method Create:tShape()
		Return New tShape
	End Method
	
	Method setw(v:Float)
		w = v
	End Method
	Method seth(v:Float)
		h = v
	End Method
	Method getw:Float()
		Return w
	End Method
	Method geth:Float()
		Return h
	End Method
	
	Method setcolour(r:Int, g:Int, b:Int)
		red = r
		green = g
		blue = b
	End Method
	Method getcolour(r:Int Var, g:Int Var, b:Int Var)
		r = red
		g = green
		b = blue
	End Method
End Type

'Base Objects
Type tEntity

	'The base entity object

	'links to the components that will make up the entity
	Field position:tPosition
	Field movement:tMovement
	Field controller:tController
	Field shape:tShape
	
	'The init method is used to "plug in" which ever components we need for the entities we make
	Method init() Abstract
	'the update method to run each update method of the components (see example below)
	Method update() Abstract
	
	Method setposition(v:tPosition)
		position = v
	End Method
	Method setmovement(v:tmovement)
		movement = v
	End Method
	Method setcontroller(v:tcontroller)
		controller = v
	End Method
	Method setshape(v:tshape)
		shape = v
	End Method

	Method getposition:tPosition()
		Return position
	End Method
	Method getmovement:tmovement()
		Return movement
	End Method
	Method getcontroller:tcontroller()
		Return controller
	End Method
	Method getshape:tshape()
		Return shape
	End Method
	Method kill()
		position = Null
		movement = Null
		controller = Null
		shape = Null
	End Method

End Type
Type tCamera
	
	'camera type to make rendering more managable

	'can have a focus so the camera will follow a particular entity
	Field focus:tEntity
	'the camera requires the position component to give it a location in the world
	Field position:tPosition
	
	'we need to store the center of the screen so that objects can be drawn in the proper place.
	'for example, when the camera is located at coordinates 0,0 and an entity is at 0,0, it will
	'be drawn in the center of the screen.
	
	Field screenx:Float
	Field screeny:Float

	Method Create:tCamera()
		Local c:tCamera = New tCamera
		c.init()
		Return c
	End Method
	
	Method init()
		'assign a position component
		position = New tPosition.Create()
		screenx = GraphicsWidth() / 2
		screeny = GraphicsHeight() / 2
	End Method
	
	Method update()
		'stay focused on the chosen entity
		If focus
			position.x = focus.position.x
			position.y = focus.position.y
		End If
	End Method
	
	Method setfocus(v:Player)
		focus = v
	End Method
	Method getfocus:tentity()
		Return focus
	End Method

End Type

'Example----------------------
'we make a controller to control the player based on tController
Type KeyInput Extends tController
        
	Field moveup:Int = KEY_W
	Field movedown:Int = KEY_S
	Field turnleft:Int = KEY_A
	Field turnright:Int = KEY_D
        
	Method Create:KeyInput()
		Local c:KeyInput = New KeyInput
		Return c
	End Method
        
	Method Update()
		'simple movement controlls
		If KeyDown(moveup)
			movement.speed:+movement.accelleration
		End If
		If KeyDown(movedown)
			movement.speed:-movement.accelleration
		End If
		If KeyDown(turnleft)
			movement.direction:-3
		End If
		If KeyDown(turnright)
			movement.direction:+3
		End If
	End Method
End Type

'make a player type based on entity that will use all 4 components
Type Player Extends tEntity

	Method Create:Player()
		Local e:Player = New Player
		e.init()
		Return e
	End Method
        
	Method init()
		'Plugin the components and set a few attributes for our player
		'need to make some components aware of the other components, eg, the controller needs
		'to know what its moving, and position what its being moved by
		movement = New tMovement.Create()
		movement.setaccelleration(0.1)
		position = New tPosition.Create()
		position.setmover(movement)
		controller = New KeyInput.Create()
		controller.setmovement(movement)
		shape = New tShape.Create()
		shape.setw(20)
		shape.seth(20)
		shape.setcolour(255, 255, 255)
	End Method
        
	Method update()
		'Controller updates the movement which in turn updates the position
		controller.update()
		position.update()
	End Method
        
End Type
'Now a basic Tile type also based on entity (most game objects should be i guess)
'this only needs the position and shape components, as they won't be moving anywhere
Type tile Extends tEntity
	
	Method Create:tile()
		Local t:tile = New tile
		t.init()
		Return t
	End Method

	Method init()
		'plugin the position and shape components
		position:tPosition = New tPosition.Create()
		shape:tShape = New tShape.Create()
		shape.setw(40)
		shape.seth(40)
		shape.setcolour(255, 0, 0)
	End Method
	
	Method update()
		'nothing to update in this basic example!
	End Method
	
End Type
'Now to create our world! Just set up some tiles and a player.
Type myWorld Extends tWorld
	Field p:Player
	Field c:tCamera
	
	Method Create:myworld()
		Local m:myWorld = New myWorld
		m.init()
		Return m
	End Method
	
	Method init()
		c = New tCamera.Create()
		p = New Player.Create()
		
		'create the environment and add to the lists
		For Local c:Int = 1 To 25
			Local t:tile = New tile.Create()
			t.position.setx(Rnd(- 500, 500))
			t.position.sety(Rnd(- 500, 500))
			addentity(t)
		Next
		
		'focus the camera on the player and add them to the world lists
		c.SetFocus(p)
		addCamera(c)
		addEntity(p)
	End Method

End Type

Graphics 640, 480

'pretty much it, just create an instance of the world and run it!
Local w:myWorld = New myWorld.Create()
w.run()


a,s,d,w are the controls if you run it.

It's pretty basic but would be interested to hear some different views on how you approach handling your game objects aside from the usual object hierarchy.

Pete,

Thanks for sharing. I'll read this later.

IPete2.

"Unhandled Exception::Attempt to acess field or method of Null object"

This line is lit, under the render method "SetRotation e.movement.direction"

Basically how I'm doing things already. ;) Good stuff though!

You should never iterate through all the entities in a scene. Never.

For rendering some kind of scene graph system to discard large chunks of the world should be used.

Entity behavior should be based around events, rather than updating every single entity each frame. For example, instead of checking each bullet each frame to see if it hit something, just use a callback function that gets called when the bullet hits.

Other than that, I think the conventional entity hierarchy approach works great; C++ programmers just don't know how to make good hierarchies without creating spaghetti code. CMovable? Everything should be movable. They also like to mix their entity hierarchy up with their scene graph, which makes no sense to me.

Interesting article. But if I read correct you missed a point. You're still creating a BLOB with a lot of NULL pointers. Checking those every time shouldn't happen, or at least that's what I got from his article.

I'm currently still using a baseclass which I extend, but for the scale of my projects I think that's fine. With my current project when I have to do collision detection I'm only iteration through a list of the nearby objects, as the whole world is divided up into chunks, like Leadwerks said. I tend to expand it so only the onscreen objects get rendered.

Mortis: not sure, I just copied and pasted that code to double check and it works fine, I'm using blitzmax 1.30 if that makes a difference.

Good points Leadwerks. I haven't really looked at scene graphs, will do some more research :) Do you have a simple example about what you mean by the callbacks for updating stuff.. If there's 100 bullets and one tree do you mean the tree just checks its local area for bullet collisions? I guess scene graphs come into this as well...

I agree you can do away with hardcoded hierarchies - they introduce separations and obstacles between objects that don't need to be there - think more of a freeflowing `object soup` where each object can have its own stuff - scripts, actions, events, call backs, whatever. Each object can do *everything* in the system (see my worklog about holographic object storage).