Examples of component design in simple game such as pong

Started by
11 comments, last by thestien 11 years, 11 months ago
Hi Guys n Girls,

First of all i am not asking for a detailed coded example i dont care about the specific language or graphics although if you do want to type semi c++ then i could probably understand it a little faster :)

I wouldnt say im dumb but i have been having a bit of trouble grasping the component based code and i think it would help me if you could show me how you would implement this in a simple game like pong or something else quite simple.

How i understand components is that say i have classes cMove, cDraw, cCollision ect i then have an cEntity object which takes a pointer to each component i need it to have.

so maybe

class cEntity
{
cPosition* coords;
cMove* mover;
cDraw* drawer;
cCollision* collider;
}


now whether that is right or wrong im not sure but after that i am stuck as to how to implement it do i simply make a container of components and fill it from each type of different cEntity i have so i could end up with.



// these are the walls names

componentArray[0] = topWall.cPosition;
componentArray[1] = bottomWall.cPosition;
componentArray[2] = leftWall.cPosition;
componentArray[3] = rightWall.cPosition;
// and then maybe

for (int i = 0; i < totalcomponents; i++)
{
componentArray->whereIsMyPosition();
}


now please dont judge my code style i know it may be difficult to read but it is purley for an example im just wondering if i am on the right track or if i am not could you please show me with a similar example.

Many Thanks in advance :)
Advertisement
Hi Guys

i would like to re-phrase my question into an actual question.

#1 do you have any example of a simplest version of a component system in the context of an actual game?

i have looked on google and only really seem to find snippets which i dont fully follow.

#2 based on my first post am i in the right ball park in terms of how i percieve component based design?

#3 do you know any good books or web pages aimed at idiots like me who cannot grasp the concept fully or ones that implement them into a working program.

many thanks you help and wisdom is much appreciated :)
Hi,

I can't give you an answer which suits for every situation but how about making a abstract EntityComponent class from which all the components herite? You may store the components in a vector or list such as std::vector<EntityComponent *> Components; So this allows you to add and remove components easily to your entities.

Your entity should have some kind of messaging system for the components, in a way that any component can send messages to the entity itself or other components.

Is there a specific problem you are trying to solve currently?

Cheers!
hi Kauna

thanks for your reply. im really just trying to understand how i would go about using a component system. the problem is i am struggling to find good examples and the ones i do find are a bit complex im sure i half understand it but not quite how to implement it and seeing as pong is a very simple game i figured it would only need a few components and it would be easy to see how it works.

so really its just how does component design work and how do i start to make one.

my above post isnt an example of my design rather a template to use to show me what component design really is.

thanks again for your help
The issue with your question as it stands is that Pong is such a simple game, that using a component system to implement it is really greatly over-engineering the problem.

That being said, there really isn't anything tricky or magical about component systems. They are just an instance of the object composition school of OOP design. A simple way of thinking about object composition is that object composition emphasizes the HAS-A relationships of the object to its parts, in contrast to the "traditional" polymorphic inheritance tree of IS-A relationships.

In a polymorphic system, for example, a FordMustang IS-A Car, a Car IS-A MotorVehicle, a MotorVehicle IS-A WheeledVehicle, etc... Edge cases like a FlyingFordMustang break this kind of brittle system, since you can't derive FlyingFordMusting from FordMustang, because a FordMustang IS-A WheeledVehicle, whereas FlyingFordMustang IS-A WingedVehicle.

By contrast, with an object composed of parts (HAS-A relationships), this sort of brittleness is not a problem. Rather than a FordMustang being a WheeledVehicle, instead a FordMustang object HAS-A BeefyEngine, HAS-A SetOfWheels, HAS-A ShinyFordMustangLogo, etc... and all these components together make a FordMustang. By contrast, a FlyingFordMustang HAS-A BeefyJetEngine, HAS-A SetOfWings, HAS-A ShinyFordMustangLogo, etc... The ShinyFordMustangLogo still identifies it as a FordMustang to any Chevy-hating purists, but it doesn't have to contain the cruft of a regular FordMustang (wheels, beefy engine). Objects are defined by their parts, by their attributes and behaviors, rather than what they supposedly are.

This is a silly, contrived example, of course. To get back to your Pong question:

In object-composition Pong, there is no concrete, discrete class called Paddle. There is no discrete class called Wall. No class called Ball. Instead, what you have are a set of components that define the attributes and behaviors that any entity can have, and a set of container classes that aggregate these attributes and behaviors into discrete entities.

So, for example, you have a CollisionBoxComponent that defines a collision box. You have a GraphicComponent that defines a visible graphic representation. You have a BallPhysics component that applies the physics constraints of a ball (ie, move in its current direction at a specified velocity until a collision occurs, at which point reflect the movement vector relative to the plane of collision) You have a GoalPhysicsComponent that notifies of win/loss conditions. And you have a PaddlePhysics component that reacts to player input (if a certain button is pressed, add velocity in this direction).

A wall, then, would be comprised of a CollisionBoxComponent and a GraphicComponent. The CollisionBoxComponent would be set to the bounding box of the wall piece, and the Graphic would be set to the white line graphic to draw a wall. That is all a Wall needs, since it just sits there.

A ball would be comprised of a CollisionBoxComponent and a GraphicComponent, with a BallPhysics component controlling its motion, the graphic set to the ball graphic. Likewise, a paddle would be simply comprised of a CollisionBoxComponent, GraphicComponent, and PaddlePhysics component.

A Goal would consist of a CollisionBoxComponent and a GoalPhysicsComponent. It doesn't need a GraphicComponent, since it only represents the goal area where a goal is to be detected. It would respond to collisions by passing a win/lose message to interested parties. An attribute of GoalPhysicsComponent would be the goal's owner, or which player owns the goal, so it knows who to notify of a win and who to notify of a loss.

Each game round or turn, the systems would be iterated and updated. The BallPhysics sub-system would iterate all balls and move them. The PaddlePhysics subsystem would iterate all paddles and check for user input, moving the paddles as ordered by the input. The GoalPhysics system, being purely reactive (ie, no movement) would just sit there, waiting. After physics updates, then the CollisionBox sub-system would iterate all contained collision boxes, testing for collisions. For each collision, a message would be generated and sent to the interested parties. In the case of a Wall which has no Physics component, this collision notification is silently ignored. A Wall doesn't care it got hit, a Wall is very Zen. A Wall just sits there. A Ball, however, has a Physics component that is capable of and interested in responding to collision notifications. In this case, it responds to the notification by reflecting its movement vector relative to the point and plane of collision as told to it by the CollisionBox sub-system, and correcting any collision penetration that may have occurred. Then it might play a ping sound to the sound system. A PaddlePhysics component will also silently ignore collision notifications. A GoalPhysics component will react to collisions by notifying the owner of the Goal of a win, and notifying the other player of a loss, notifying as well the score-keeping system of the point scored.

After physics and collisions are updated and resolved, the game loop passes on to the Graphics sub-system, telling it to go ahead and draw all objects.

In a system such as this, there is a bit of communication involved between components, handled via some sort of message system intended to reduce inter-component dependencies. Such dependencies should be kept to a minimum; however, it is nearly impossible to eliminate all dependencies. For example, CollsionBoxComponent, GraphicsComponent and PhysicsComponent(s) all require knowledge of shared state, ie the entity's Position, that must be communicated somehow.

As an example of the concept of shared state, consider that Position/Orientation/Scale (also called transformation, in 3D parlance) are the most commonly shared state data in game-related component systems. Some implementations might handle Transformation as a component of its own, that responds to queries (getPosition, etc...). Other implementations might put Transformation into the base Entity container, accessible by all components without having to go through the message system to query or modify them. Still other implementations might simply duplicate Transformation state across all interested components, so that for example the GraphicsComponent keeps its own local copy of the Ball's position, and so does the BallPhysics component. And so forth. These are decisions you have to make for yourself, and there really isn't any "right" way of handling them.

The backbone of such a system is the means of communication between components and between entities. As mentioned, many implementations use a message-passing system. These systems are common in programming (you can see examples of message-passing everywhere, especially among things like the Windows input queue). A message is like handing a note to someone. You don't have to know anything about that someone to hand them a note, and there is no requirement upon them to even care about your note. In the Pong example, the CollisionBox sub-system would iterate all of its contained box components, checking for collisions. For each collision, as I said, a message would be handed off to each involved party. The contents of this message would need to provide specific information for the interested parties. Where does the collision take place? What are the orientations of the colliding entities? What is the orientation of the "collision plane"? And so forth. All of this information would be bundled up and passed to both colliding entities. In the case of a Wall, this message is silently ignored. In the case of a Ball, this information is used to recalculate the Ball's trajectory based on its previous trajectory and the orientation of the collision plane.

Other implementations might communicate to components in a more direct fashion, by calling into the entity via some sort of requestComponent method, then calling directly into the component returned. My personal preference is to avoid this sort of thing, since it does introduce dependencies (ie, the calling entity needs to know something about the structure of the object being acted upon), but even a message-passing system introduces these dependencies, they're just sort of "hidden". That is, the CollisionBox system is making an assumption (a dependency, of a sort) that one of the interested parties is capable of acting on the collision message it sends. Whether or not either entity does respond to it is irrelevant; there is still a tenuous dependency. Completely eliminating cross-component dependencies is probably impossible, and at any rate probably unnecessary. The assumption that one of the interested parties wants to respond to a collision is probably a safe assumption to make.

Anyway, getting a bit far afield here. You might want to check out the usual suspects when it comes to entity and component systems.

http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/
http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/
Hi FLeBlanc,

All i can say is wow!! or perhaps thank you very much :) i think i will need to re read your reply a few times but that is exactly what i needed.

the reason i said pong is because it is a simple game and i have made a version using inheritance but i have been intruiged by components and now i think im really beginning to grasp it. i could see the power of it before but not how to implement it. lots of phrases in your reply i will need to google but this is more than enough for me to get started on designing a component type game :)


well thanks again this has been an amazing response i appreciate your time and effort :)
Because I was bored and had some time to kill before work, I decided to put together a quick demonstration of component-based Pong. I didn't get to actually finish it (work calls, and I must away) but I did implement walls, a bouncing ball, a player controlled paddle and a really, really stupid AI controlled paddle. Some of the physics and collision are a bit wonky (it was a very rush effort) and non-robust, but it works well enough to demonstrate at least the basic idea of what I was talking about.

The demo is written for the Love library, a simple game library that uses the Lua language. Just download the Love binary, execute the script using the love executable and it should be golden. Here is the script:

[spoiler]

function createObject()
local o={x=0,y=0}
o.components={}
o.message=function(self,msg)
local comp
for _,comp in pairs(self.components) do
if comp then comp:message(msg) end
end
end
o.kill=function(self)
local comp
for _,comp in pairs(self.components) do
comp:kill()
end
self.components={}
end
o.add_component=function(self,c)
table.insert(self.components,c)
c.owner=self
end

return o
end


-- Graphics sub-system and related Component creation

GraphicSubsystem={}
function GraphicSubsystem:init()
self.componentlist={}
end

function GraphicSubsystem:createComponent(o, width, height)
local c={sprite={width=width,height=height}, subsystem=self}

c.kill=function(self)
self.subsystem:destroyComponent(self)
self.subsystem=nil
self.sprite=nil
end

c.message=function(self,msg)
end

self.componentlist[o]=c
o:add_component(c)
end

function GraphicSubsystem:destroyComponent(c)
self.componentlist[c.owner]=nil
end

function GraphicSubsystem:draw()
local o,c
for o,c in pairs(self.componentlist) do
-- Draw sprite
love.graphics.rectangle("fill",o.x-c.sprite.width/2, o.y-c.sprite.height/2, c.sprite.width, c.sprite.height)
end
end


-- Collision Sub-system and related Component creation
CollisionSubsystem={}
function CollisionSubsystem:init()
self.componentlist={}
end

function CollisionSubsystem:createComponent(o, width, height, solid)
local c=
{
boxwidth=width,
boxheight=height,
subsystem=self,
solid=solid
}

c.kill=function(self)
self.subsystem:destroyComponent(self)
self.subsystem=nil
end
c.message=function(self,msg)
end

self.componentlist[o]=c
o:add_component(c)
end

function CollisionSubsystem:destroyComponent(c)
self.componentlist[c.owner]=nil
end

function CollisionSubsystem:testCollision(b1, b2)
local dx,dy=b2.cx - b1.cx, b2.cy-b1.cy

if math.abs(dx) < (b1.width/2+b2.width/2) and math.abs(dy) < (b1.height/2+b2.height/2) then
-- calculate distance of centers as a percentage of total distance along each axis of second box
local px,py=dx/(b2.width/2),dy/(b2.height/2)
if math.abs(py) > math.abs(px) then
-- Greater horizontal penetration than vertical, so return "top" if dx is neg, bottom otherwise
if dx<0 then return true, "top" else return true, "bottom" end
else
if dy<0 then return true, "left" else return true, "right" end
end
end
return false
end

function CollisionSubsystem:update()
-- Test all objects for collision with other objects
-- Note that this is a brain-dead collision system, highly un-optimal, and meant for illustrative purposes only. For the love of God, please do not do your
-- real collision testing like this.
local o,c
for o,c in pairs(self.componentlist) do
local box0={cx=o.x, cy=o.y, width=c.boxwidth, height=c.boxheight}
local o1,c1
for o1,c1 in pairs(self.componentlist) do
if o1~=o then -- Forbid testing collision with self
--local box1={o1.x-c1.boxwidth/2, o1.y-c1.boxheight/2, o1.x+c1.boxwidth/2, o1.y+c1.boxheight/2, o1.x, o1.y}
local box1={cx=o1.x, cy=o1.y, width=c1.boxwidth, height=c1.boxheight}
local collision, plane=self:testCollision(box0, box1)
if collision then
c.owner:message({message="collide", plane=plane, solid=c1.solid})
end

end
end
end
end










-- BallPhysics Sub-system and related Component creation
BallPhysicsSubsystem={}
function BallPhysicsSubsystem:init()
self.componentlist={}
end

function BallPhysicsSubsystem:createComponent(o, speed, starting_dir_x, starting_dir_y)
local len=math.sqrt(starting_dir_x*starting_dir_x + starting_dir_y*starting_dir_y)
local vx=starting_dir_x/len
local vy=starting_dir_y/len

local c=
{
vx=vx,
vy=vy,
speed=speed
}

c.kill=function(self)
self.subsystem:destroyComponent(self)
self.subsystem=nil
end

c.message=function(self,msg)
if(msg.message=="collide") then
--print("Collide received.")
if msg.solid then
-- Only solid objects bounce a ball
if msg.plane=="top" or msg.plane=="bottom" then self.vy=self.vy*-1 end
if msg.plane=="left" or msg.plane=="right" then self.vx=self.vx*-1 end

-- Should we fudge movement here?
--o.x=o.x+self.vx*self.speed*c.lasttime*2
--o.y=o.y+self.vy*self.speed*c.lasttime*2
end
end
end
self.componentlist[o]=c
o:add_component(c)
end

function BallPhysicsSubsystem:destroyComponent(c)
self.componentlist[c.owner]=nil
end

function BallPhysicsSubsystem:update(dt)
-- Move all the balls
local o,c
for o,c in pairs(self.componentlist) do
o.x=o.x+c.vx*c.speed*dt
o.y=o.y+c.vy*c.speed*dt
c.lasttime=dt
end
end




-- PlayerPaddlePhysicsSubsystem and related Component creation
-- Implement a sub-system and a related component to provide a player-controllable paddle.
-- Hard-coded so keys 'q' and 'z' move the paddle up and down.
PlayerPaddlePhysicsSubsystem={}
function PlayerPaddlePhysicsSubsystem:init()
self.componentlist={}
end

function PlayerPaddlePhysicsSubsystem:createComponent(o,y_low_bound, y_high_bound, speed)
local c=
{
speed=speed,
ylow=y_low_bound,
yhigh=y_high_bound,
subsystem=self
}

c.kill=function(self)
self.subsystem:destroyComponent(self)
self.subsystem=nil
end

c.message=function(self,msg)

end

self.componentlist[o]=c
o:add_component(c)
end

function PlayerPaddlePhysicsSubsystem:destroyComponent(c)
self.componentlist[c.owner]=nil
end

function PlayerPaddlePhysicsSubsystem:update(dt)
-- Check for key input
local o,c
for o,c in pairs(self.componentlist) do
local vy=0
if love.keyboard.isDown('q') then vy=-c.speed*dt end
if love.keyboard.isDown('z') then vy=c.speed*dt end
o.y=o.y+vy
if o.y<c.ylow then o.y=c.ylow end
if o.y>c.yhigh then o.y=c.yhigh end
end
end





-- AIPaddlePhysicsSubsystem
-- Implement a (stupid) system to AI a paddle
AIPaddlePhysicsSubsystem={}
function AIPaddlePhysicsSubsystem:init()
self.componentlist={}
end

function AIPaddlePhysicsSubsystem:createComponent(o, y_low_bound, y_high_bound, speed)
local c=
{
speed=speed,
ylow=y_low_bound,
yhigh=y_high_bound,
subsystem=self,
vy=-speed
}

c.kill=function(self)
self.subsystem:destroyComponent(self)
self.subsystem=nil
end

c.message=function(self,msg)

end

self.componentlist[o]=c
o:add_component(c)
end

function AIPaddlePhysicsSubsystem:destroyComponent(c)
self.componentlist[c.owner]=nil
end

function AIPaddlePhysicsSubsystem:update(dt)
local o,c
for o,c in pairs(self.componentlist) do
o.y=o.y+c.vy*dt
if o.y<c.ylow then o.y=c.ylow c.vy=c.vy*-1 end
if o.y>c.yhigh then o.y=c.yhigh c.vy=c.vy*-1 end
end
end

-------------------------------------

--- The Game
walls={}
function love.load()
love.graphics.setMode(800,600,false,true,0)
love.graphics.setBackgroundColor(0,0,0)
love.graphics.setColor(255,255,255)

GraphicSubsystem:init()
CollisionSubsystem:init()
BallPhysicsSubsystem:init()
PlayerPaddlePhysicsSubsystem:init()
AIPaddlePhysicsSubsystem:init()

-- Build game world

topwall=createObject()
GraphicSubsystem:createComponent(topwall,800,32)
CollisionSubsystem:createComponent(topwall, 800, 32, true)
topwall.x=400
topwall.y=16

bottomwall=createObject()
GraphicSubsystem:createComponent(bottomwall,800,32)
CollisionSubsystem:createComponent(bottomwall, 800, 32, true)
bottomwall.x=400
bottomwall.y=600-16

leftwall=createObject()
GraphicSubsystem:createComponent(leftwall,32,600)
CollisionSubsystem:createComponent(leftwall,32,600,true)
leftwall.x=16
leftwall.y=300

rightwall=createObject()
GraphicSubsystem:createComponent(rightwall,32,600)
CollisionSubsystem:createComponent(rightwall,32,600,true)
rightwall.x=800-16
rightwall.y=300

-- Build a ball
ball=createObject()
CollisionSubsystem:createComponent(ball,8,8,true)
GraphicSubsystem:createComponent(ball,8,8)
BallPhysicsSubsystem:createComponent(ball,128,1,1.5)
ball.x=400
ball.y=300


-- Build a player paddle
ppaddle=createObject()
CollisionSubsystem:createComponent(ppaddle,16,64,true)
GraphicSubsystem:createComponent(ppaddle,16,64)
PlayerPaddlePhysicsSubsystem:createComponent(ppaddle,100,500,256)
ppaddle.x=150
ppaddle.y=300

-- Build an AI paddle
apaddle=createObject()
CollisionSubsystem:createComponent(apaddle,16,64,true)
GraphicSubsystem:createComponent(apaddle,16,64)
AIPaddlePhysicsSubsystem:createComponent(apaddle,100,500,256)
apaddle.x=800-150
apaddle.y=300

end

function love.draw()
GraphicSubsystem:draw()
end

function love.update(dt)
-- Update physics
PlayerPaddlePhysicsSubsystem:update(dt)
AIPaddlePhysicsSubsystem:update(dt)
BallPhysicsSubsystem:update(dt)
-- update collisions
CollisionSubsystem:update()
end

[/spoiler]

The way it works is this:

There are 5 currently implemented sub-systems: Graphics, Collision, BallPhysics, PlayerPaddlePhysics and AIPaddlePhysics. Each sub-system has a corresponding component that is used to interface with the system. Objects are implemented as simple containers that hold a list of components, an (x,y) coordinate (held in the container for convenience, since just about every sub-system uses it) and some basic functionality to handle message-handling and to kill the object. Killing an object will kill and remove all of its components.

The lifetime of an object is as such:

1) Create an object, using the helper function createObject(). This creates a blank, empty entity
2) Add components using the createComponent() method of each sub-system. For example, to create a Graphics component of a rectangle sized 32x32, we call GraphicSubsystem:createComponent(object, 32, 32). A createComponent() call will create a new instance of the related component which holds local state for the given object, and stick it in a table held internally by the sub-system. It will also put a reference to that component inside the object itself.
3) Initialize position to place the object in the world

The basic rundown of the loop/application is as such:

1) In love.load, initialize all the sub-systems before doing the main loop. This is to initialize the internal component-list tables for each sub-system.

2) In love.load, create the playing field by setting up 4 walls bounding the screen, a ball, a player paddle and an AI paddle. See the function love.load() for the details.

3) In love.update, call the update() method of each sub-system. A sub-system update method will iterate the sub-system's internal list of components, and perform the basic update on each component in the sub-system. For example, in Collision update, we check for collisions between objects, and send messages (using the object.message() function) to an object if a collision with another object is detected; the message includes the plane of the collided-with object (top, bottom, left or right). In BallPhysics subsystem, the BallPhysics component implements a message handler that listens for these "collide" messages, and reverses the x or y velocity vector component depending on which plane the collision involves. In PlayerPaddlePhysics, the update method queries the keyboard and moves the paddle up or down based on 'q' and 'z' keypresses, bounding it between two Y values specified when the PlayerPaddlePhysicsComponent is created. And in AIPaddlePhysics, the update method causes the paddle to just reciprocate back and forth between the bounds.

4) in love.draw(), the draw() method of GraphicsSubsystem is called, which iterates all components in the internal list and draws the corresponding rectangles.

And voila, you have Pong (albeit Pong minus scoring, minus goals, plus some slightly wonky collision and physics, minus sound). It's very rough, but it demonstrates the idea, I think.
thank you so much that was a very kind thing to do the first post was more than enough but the second one is truely amazing smile.png
i think i understand exactly how this works now and will be studing this tread for a while but now i have the tools and the theory to move away from inheritance and focus on components smile.png

i have looked on google for this kind of example one that is simple enough to understand but shows exactly what is going on i just hope it will help future noobs like me well i know it will.

thank you for helping me smile.png



edit:
i have never used Lua and i have been reading a little to try to understand your code it seems like an awesome language i am going to test your code and then try to convert it to c++ but i think i may try to learn Lua one day :)
Lua, as a higher-level language than C++, offers some little tricks that make things like this easier. For one, the dynamic typing means that I can easily just pass a generic table as the argument to object:message(), and leave it up to the message sender and receiver as far as the actual layout of the table, without having to resort to any of the weird templating tricks or std::map trickery I might have to use to implement a similar structure in C++. Although it's actually not too hard to do. You can use an std::map<std::string, std::string> as a quick hack, and "cast" the second string to whatever you need it to be (number, letter, string, etc...). Another bonus of developing in Lua is the ad hoc nature of prototyping and data structuring. If I am prototyping a system, I can quickly iterate on table layouts to get the data and structure I need without recompiling. Quite often I will prototype even performance-critical systems in Lua, only translating them to C++ and writing a binding layer if it happens that I need the performance.

Lua's powers for data-description also mean that I have done away with all of the special-case data file formats I once-upon-a-time came up with; you know, all the little formats for describing an animated entity, for saving level data, etc... If I have a table full of data, I can just save or load it as a compiled Lua chunk. As an example, in the above Pong demo, you could very quickly save the state of the game at any time by dumping the componentlist tables of the sub-systems to binary chunks; the state could be restored just as easily by loading the chunks back into componentlist.

These days, I write all of my "upper level" logic in Lua, and use C++ only for the performance critical stuff: rendering scene structure, physics, etc...
Cor i know one thing Lua is a bit complicated to understand ive read a few basic love2d tutorials and i still struggling to translate your code :)

also i tried to run your script but i must be doing something wrong as it says no code to run, any ideas?

Many Thanks again :)

This topic is closed to new replies.

Advertisement