Examples of component design in simple game such as pong

Started by
11 comments, last by thestien 11 years, 11 months ago
Love expects scripts to be placed in a folder. Save the script inside a folder and name the script main.lua, then you can drag the entire folder and drop it on the Love executable to run the contained script(s). It's a bit odd, but it works.

At any rate, one of the reasons I prefer a higher level language like Lua for systems like this is that some things can get hairy under C++. Dynamic typing can smooth out a lot of issues. However, I went ahead and did an example implementation of the system I presented, using C++ and SFML, for demonstration purposes. Note that it is a direct port of the system; I didn't stop to build a better collision system, so the problems that exist in the Lua version (wonky collision, ball getting trapped inside moving paddles, sometimes sticking to walls, etc... like I said, it is not robust at all) still exist. This is meant to be demonstration code, not production code, and if I were making production code I would implement a better broadphase collision system, and implement the collision plane detection differently. Anyway...

Before I present the code, some disclaimers. I don't do higher level programming like this in C++ anymore. I just do low level stuff (scene graphs, etc...) So my C++ skills are very rusty. Also, I implemented a very quick hack of a generic message passing system that operates by encoding a message as a string. This way, I can implement a generic interface for components to handle messages, and leave it up to the components themselves (and any calling parties) to worry about the specific content of the message, without having to build some sort of weird templated class to hold messages or anything. It's probably not the optimal way of doing it, since it requires several string manipulation operations, but it works. Since I don't do this kind of thing in C++, I have no desire to build a generic message passing system in C++.

To begin with, I needed to create some base classes. As in the Lua example, the basic architecture is that any SubSystem (Physics, Collision, etc) implements a corresponding component type. So if you want your object to have a visual representation, you have to call the Graphics Subsystem to create a Graphics Component, and add the component to the object. Subsystems maintain internal vectors of the components that have been created, and their update() methods will simply iterate this internal vector and operate on the components. Doing it this way can have implications as far as cache coherency and data-orientedness are concerned; ie, you can implement your internal component list as a simple flat array of data which is processed sequentially, resulting in fewer cache misses and possibly significant increases in performance.

The implementation I present here, however, does not present the optimal method. Rather than a flat array of data structures, I implement the internal component lists as arrays of pointers to data structures, and the components are dynamically allocated and freed in the createComponent() and removeComponent() methods. Since objects maintain internal pointers to their components for message-passing, it was necessary that the pointers never change; if the subsystem component list is implemented as a dynamic array, re-allocations will break the object's internal component pointers, causing segfaults. Ways around this might include using smart pointers or other more complex techniques. I was not interested in solving this problem here because, again, I don't spend much time in C++. There are many, many different ways of achieving the same kind of architecture.

All Components must inherit from the BaseComponent class, which presents the common interface all components share. This interface is simple, consisting of the kill() and message() methods.

All Subsystems inherit from the BaseSubsystem class, which presents the common interface all subsystems share. This interface is also very simple, consisting only of the removeComponent() method. Note that I do not include createComponent() in the common interface, but rather implement it at the specific subsystem level. This is because the parameters passed to createComponent() vary depending on component type, and this method is not called in any of the base classes so it is not necessary it be known at the interface level.

All objects will be of the type BaseObject. This is not a class that is inherited from; all objects in the world will be instances of this class itself. It implements the basic interface of an object, including kill(), message() addComponent(), removeComponent() and position.

To see what I'm talking about, here is the base framework:

framework_base.h

[spoiler]



#ifndef FRAMEWORK_BASE_H
#define FRAMEWORK_BASE_H
#include <sstream>
#include <vector>
#include <list>

class BaseSubsystem;
class BaseObject;
class BaseComponent
{
public:
BaseComponent(BaseSubsystem *subsystem, BaseObject *owner);
virtual ~BaseComponent();
virtual void message(std::string &msg)=0;
void kill();
BaseObject *getOwner();

private:
BaseSubsystem *subsystem_;
BaseObject *owner_;
};

class BaseObject
{
public:
BaseObject();
~BaseObject();

void kill();
void message(std::string &msg);
void addComponent(BaseComponent *c);
void removeComponent(BaseComponent *c);

float x_,y_;

private:
std::list<BaseComponent *> components_;
};

class BaseSubsystem
{
public:
BaseSubsystem(){};
virtual ~BaseSubsystem(){};
virtual void removeComponent(BaseComponent *c)=0;
};

#endif



[/spoiler]

framework_base.cpp:

[spoiler]



#include "framework_base.h"
#include <iostream>

BaseComponent::BaseComponent(BaseSubsystem *subsystem, BaseObject *owner) : subsystem_(subsystem), owner_(owner)
{
}

BaseComponent::~BaseComponent()
{
}

BaseObject *BaseComponent::getOwner()
{
return owner_;
}

void BaseComponent::kill()
{
if(subsystem_)
{
subsystem_->removeComponent(this);
}
subsystem_=0;
}




BaseObject::BaseObject() : x_(0), y_(0){}
BaseObject::~BaseObject()
{
}

void BaseObject::kill()
{
std::list<BaseComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
(*iter)->kill();
}
components_.clear();
}

void BaseObject::message(std::string &msg)
{
std::list<BaseComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
(*iter)->message(msg);
}
}

void BaseObject::addComponent(BaseComponent *c)
{
components_.push_back(c);
}

void BaseObject::removeComponent(BaseComponent *c)
{
components_.remove(c);
}



[/spoiler]

The BaseComponent and BaseSubsystem classes are pure virtuals, so they must be derived from. However, there does not need to be any deep hierarchies; a single level of derivation is all that is necessary. We just need a common interface for these classes.

With these base systems in place, we can start constructing the subsystems. Here is the graphics subsystem and associated component:

graphicssubsystem.h:

[spoiler]



#ifndef GRAPHICS_SUBSYSTEM_H
#define GRAPHICS_SUBSYSTEM_H
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include "framework_base.h"
#include <vector>

class GraphicsComponent : public BaseComponent
{
public:
GraphicsComponent(BaseSubsystem *subsystem, BaseObject *object, int width, int height) : BaseComponent(subsystem,object),
width_(width), height_(height){}
~GraphicsComponent(){}

void message(std::string &msg){}

float width_,height_;
};

class GraphicsSubsystem : public BaseSubsystem
{
public:
GraphicsSubsystem(sf::RenderWindow *window) : BaseSubsystem(), window_(window){}
~GraphicsSubsystem(){}
void removeComponent(BaseComponent *c);
void createComponent(BaseObject *object, float width, float height);

void draw();

private:
std::vector<GraphicsComponent *> components_;
sf::RenderWindow *window_;
};

#endif



[/spoiler]

graphicssubsystem.cpp:

[spoiler]



#include "graphicssubsystem.h"

void GraphicsSubsystem::removeComponent(BaseComponent *c)
{
std::vector<GraphicsComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
if((*iter)==c)
{
delete (*iter);
components_.erase(iter);
return;
}
}
}
void GraphicsSubsystem::createComponent(BaseObject *object, float width, float height)
{
components_.push_back(new GraphicsComponent(this, object, width, height));
object->addComponent(components_[components_.size()-1]);
}
void GraphicsSubsystem::draw()
{
std::vector<GraphicsComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
BaseObject *owner=(*iter)->getOwner();
sf::RectangleShape rect;
rect.setSize(sf::Vector2f((*iter)->width_, (*iter)->height_));
rect.setPosition(owner->x_-(*iter)->width_/2, owner->y_-(*iter)->height_/2);
window_->draw(rect);
}
}



[/spoiler]

This system works pretty much exactly as in the Lua example, though it uses SFML under the hood. The GraphicsComponent is a simple rectangle of size (width,height), and the owner object is queried for position when drawing. Components are iterated and drawn as simple white rectangles.

Here is the Collision subsystem:

collisionsubsystem.h

[spoiler]



#ifndef COLLISION_SUBSYSTEM_H
#define COLLISION_SUBSYSTEM_H
#include "framework_base.h"
#include <vector>
#include <sstream>

enum EPlanes
{
Top,
Bottom,
Left,
Right
};

class CollisionComponent : public BaseComponent
{
public:
CollisionComponent(BaseSubsystem *subsystem, BaseObject *owner, float width, float height, bool solid) :
BaseComponent(subsystem,owner), width_(width), height_(height), solid_(solid){}
~CollisionComponent(){}

void message(std::string &msg){}

float width_, height_;
bool solid_;
};

class CollisionSubsystem : BaseSubsystem
{
public:
CollisionSubsystem() : BaseSubsystem(){}
~CollisionSubsystem(){}

void removeComponent(BaseComponent *c);
void createComponent(BaseObject *object, float width, float height, bool solid);
void update();

private:
std::vector<CollisionComponent *> components_;

bool testCollision(CollisionComponent *b1, CollisionComponent *b2, EPlanes &plane);
void buildBoxString(std::stringstream &s, CollisionComponent *c);
};

#endif



[/spoiler]

collisionsubsystem.cpp:

[spoiler]



#include "collisionsubsystem.h"
#include <cmath>

void CollisionSubsystem::removeComponent(BaseComponent *c)
{
std::vector<CollisionComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
if((*iter)==c)
{
delete (*iter);
components_.erase(iter);
return;
}
}
}

void CollisionSubsystem::createComponent(BaseObject *object, float width, float height, bool solid)
{
components_.push_back(new CollisionComponent(this, object, width, height, solid));
object->addComponent(components_[components_.size()-1]);
}

void CollisionSubsystem::update()
{
std::vector<CollisionComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
std::vector<CollisionComponent *>::iterator test;
for(test=components_.begin(); test!=components_.end(); ++test)
{
if((*iter) != (*test))
{
EPlanes plane;
if(testCollision((*iter), (*test), plane))
{
std::stringstream msg;
msg << "collide " << (int)plane << " ";
buildBoxString(msg, (*iter));
buildBoxString(msg, (*test));
msg << " " << (*test)->solid_;
std::string s(msg.str());


(*iter)->getOwner()->message(s);
}
}
}
}
}

bool CollisionSubsystem::testCollision(CollisionComponent *b1, CollisionComponent *b2, EPlanes &plane)
{
BaseObject *o1=b1->getOwner();
BaseObject *o2=b2->getOwner();

float dx=o2->x_ - o1->x_;
float dy=o2->y_ - o1->y_;

if(fabs(dx) < (b1->width_/2+b2->width_/2) && fabs(dy) < (b1->height_/2+b2->height_/2))
{
// Intersection
// Calculate the plane that the line intersects
// For now, duplicate existing
float px=dx/(b2->width_/2);
float py=dy/(b2->height_/2);
if(fabs(py)>fabs(px))
{
if(dx<0)
{
plane=Top;
return true;
}
else
{
plane=Bottom;
return true;
}
}
else
{
if(dy<0)
{
plane=Left;
return true;
}
else
{
plane=Right;
return true;
}
}
}
return false;
}

void CollisionSubsystem::buildBoxString(std::stringstream &s, CollisionComponent *c)
{
BaseObject *owner=c->getOwner();
s << owner->x_ << " " << owner->y_ << " " << c->width_ << " " << c->height_ << " ";
}



[/spoiler]

Again, it's a direct port of the Lua version (with attendant problems). Of particular note, however, are the parts in update() where a message is constructed and passed. The message is built as a string using stringstream to serialize the required data and send it along. This is a hack, but it at least eliminates any kind of linkage dependency between the components. There is a meta-dependency, in that both the message passer and receiver need to know the data layout of the message, but this dependency is separate from any compile or link-time dependencies.

The other components as in the original Lua example:

ballphysicssubsystem.h:

[spoiler]



#ifndef BALL_PHYSICS_H
#define BALL_PHYSICS_H
#include "framework_base.h"
#include <SFML/System.hpp>
#include <vector>

class BallPhysicsComponent : public BaseComponent
{
public:
BallPhysicsComponent(BaseSubsystem *subsystem, BaseObject *owner, float vx, float vy) : BaseComponent(subsystem,owner), vx_(vx), vy_(vy){}
~BallPhysicsComponent(){}

void message(std::string &msg);

float vx_, vy_;
};

class BallPhysicsSubsystem : public BaseSubsystem
{
public:
BallPhysicsSubsystem() : BaseSubsystem(){}
~BallPhysicsSubsystem(){}

void removeComponent(BaseComponent *c);
void createComponent(BaseObject *object, float vx, float vy);
void update(sf::Time &elapsed);

private:
std::vector<BallPhysicsComponent *> components_;
};

#endif



[/spoiler]

ballphysicssubsystem.cpp:

[spoiler]



#include "ballphysics.h"
#include "collisionsubsystem.h"
#include <cmath>
#include <sstream>

float sign(float v)
{
if(v<0) return -1.0f;
return 1.0f;
}

void BallPhysicsComponent::message(std::string &msg)
{
std::stringstream m;
m << msg;

// Extract message
std::string eventtype;
m >> eventtype;
if(eventtype==std::string("collide"))
{
// Extract plane
int plane;
m >> plane;

// Extract boxes
float bx1,by1,bw1,bh1;
float bx2,by2,bw2,bh2;

m >> bx1;
m >> by1;
m >> bw1;
m >> bh1;
m >> bx2;
m >> by2;
m >> bw2;
m >> bh2;

// Extract solid
bool solid;
m >> solid;

if(solid)
{
float dx=bx1-bx2;
float dy=by1-by2;
float diffx=(bw1/2+bw2/2) - fabs(dx);
float diffy=(bh1/2+bh2/2) - fabs(dy);
BaseObject *owner=getOwner();

if(diffx<0)
{
owner->x_ = owner->x_ - diffx*sign(dx);
}
if(diffy<0)
{
owner->y_ = owner->y_ - diffy*sign(dy);
}

if(plane==Top || plane==Bottom) vy_=vy_*-1.0f;
if(plane==Left || plane==Right) vx_=vx_*-1.0f;
}
}
}


void BallPhysicsSubsystem::removeComponent(BaseComponent *c)
{
std::vector<BallPhysicsComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
if((*iter)==c)
{
delete (*iter);
components_.erase(iter);
return;
}
}
}

void BallPhysicsSubsystem::createComponent(BaseObject *object, float vx, float vy)
{
components_.push_back(new BallPhysicsComponent(this, object, vx, vy));
object->addComponent(components_[components_.size()-1]);
}

void BallPhysicsSubsystem::update(sf::Time &elapsed)
{
std::vector<BallPhysicsComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
// Move the balls
BaseObject *owner=(*iter)->getOwner();
BallPhysicsComponent *comp=(*iter);

owner->x_=owner->x_+comp->vx_*elapsed.asSeconds();
owner->y_=owner->y_+comp->vy_*elapsed.asSeconds();
}
}



[/spoiler]

playerpaddlephysicssubsystem:

[spoiler]



#ifndef PLAYER_PADDLE_SUBSYSTEM
#define PLAYER_PADDLE_SUBSYSTEM
#include <SFML/System.hpp>

#include "framework_base.h"

class PlayerPaddlePhysicsComponent : public BaseComponent
{
public:
PlayerPaddlePhysicsComponent(BaseSubsystem *subsystem, BaseObject *owner, float low, float high, float speed) :
BaseComponent(subsystem,owner), low_(low), high_(high), speed_(speed){}
~PlayerPaddlePhysicsComponent(){}

void message(std::string &msg){};

float low_, high_, speed_;
};

class PlayerPaddlePhysicsSubsystem : public BaseSubsystem
{
public:
PlayerPaddlePhysicsSubsystem() : BaseSubsystem(){}
~PlayerPaddlePhysicsSubsystem(){}

void removeComponent(BaseComponent *c);
void createComponent(BaseObject *object, float low, float high, float speed);
void update(sf::Time &elapsed);

private:
std::vector<PlayerPaddlePhysicsComponent *> components_;
};

#endif



[/spoiler]

playerpaddlephysicssubsystem.cpp

[spoiler]



#include "playerpaddlesubsystem.h"
#include <SFML/Window.hpp>

void PlayerPaddlePhysicsSubsystem::removeComponent(BaseComponent *c)
{
std::vector<PlayerPaddlePhysicsComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
if((*iter)==c)
{
delete (*iter);
components_.erase(iter);
return;
}
}
}

void PlayerPaddlePhysicsSubsystem::createComponent(BaseObject *object, float low, float high, float speed)
{
components_.push_back(new PlayerPaddlePhysicsComponent(this, object, low, high, speed));
object->addComponent(components_[components_.size()-1]);
}

void PlayerPaddlePhysicsSubsystem::update(sf::Time &elapsed)
{
std::vector<PlayerPaddlePhysicsComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
PlayerPaddlePhysicsComponent *comp=(*iter);
BaseObject *owner=comp->getOwner();
float vy=0;

if(sf::Keyboard::isKeyPressed(sf::Keyboard::Q))
{
vy=-comp->speed_*elapsed.asSeconds();
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Z))
{
vy=comp->speed_*elapsed.asSeconds();
}
owner->y_=owner->y_+vy;
if(owner->y_<comp->low_) owner->y_=comp->low_;
if(owner->y_>comp->high_) owner->y_=comp->high_;
}
}



[/spoiler]

aipaddlephysicssubsystem.h:

[spoiler]



#ifndef AI_PADDLE_SUBSYSTEM
#define AI_PADDLE_SUBSYSTEM
#include "framework_base.h"
#include <SFML/System.hpp>

class AIPaddlePhysicsComponent : public BaseComponent
{
public:
AIPaddlePhysicsComponent(BaseSubsystem *subsystem, BaseObject *owner, float low, float high, float speed) :
BaseComponent(subsystem,owner), low_(low), high_(high), speed_(speed){}
~AIPaddlePhysicsComponent(){}

void message(std::string &msg){};

float low_, high_, speed_;
};

class AIPaddlePhysicsSubsystem : public BaseSubsystem
{
public:
AIPaddlePhysicsSubsystem() : BaseSubsystem(){}
~AIPaddlePhysicsSubsystem(){}

void removeComponent(BaseComponent *c);
void createComponent(BaseObject *object, float low, float high, float speed);
void update(sf::Time &elapsed);

private:
std::vector<AIPaddlePhysicsComponent *> components_;
};

#endif



[/spoiler]

aipaddlephysicssubsystem.cpp:

[spoiler]



#include "aipaddlesubsystem.h"

void AIPaddlePhysicsSubsystem::removeComponent(BaseComponent *c)
{
std::vector<AIPaddlePhysicsComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
if((*iter)==c)
{
delete (*iter);
components_.erase(iter);
return;
}
}
}

void AIPaddlePhysicsSubsystem::createComponent(BaseObject *object, float low, float high, float speed)
{
components_.push_back(new AIPaddlePhysicsComponent(this, object, low, high, speed));
object->addComponent(components_[components_.size()-1]);
}

void AIPaddlePhysicsSubsystem::update(sf::Time &elapsed)
{
std::vector<AIPaddlePhysicsComponent *>::iterator iter;
for(iter=components_.begin(); iter!=components_.end(); ++iter)
{
AIPaddlePhysicsComponent *comp=(*iter);
BaseObject *owner=comp->getOwner();

owner->y_=owner->y_+comp->speed_*elapsed.asSeconds();
if(owner->y_<comp->low_)
{
owner->y_=comp->low_;
comp->speed_=comp->speed_*-1.0f;
}
if(owner->y_>comp->high_)
{
owner->y_=comp->high_;
comp->speed_=comp->speed_*-1.0f;
}
}
}



[/spoiler]

Of course, you need a kernel or game loop. I implemented a quickie that sets up the systems, creates the walls, ball and paddle objects, then executes a loop similar to what Love provides. There is a load(), update() and draw() function. In main, an SFML window is created then execution is handed off to the kernel() which calls load(), then enters a loop until the window is closed.

main.cpp:

[spoiler]


#include <SFML/Window.hpp>
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <iostream>
#include "testsubsystem.h"

#include "graphicssubsystem.h"
#include "collisionsubsystem.h"
#include "ballphysics.h"
#include "playerpaddlesubsystem.h"
#include "aipaddlesubsystem.h"

sf::RenderWindow *window;
GraphicsSubsystem *graphics;
CollisionSubsystem *collision;
BallPhysicsSubsystem *ballphysics;
PlayerPaddlePhysicsSubsystem *playerpaddle;
AIPaddlePhysicsSubsystem *aipaddle;

BaseObject topwall,bottomwall,leftwall,rightwall, ball,ppaddle,apaddle;

void draw()
{
window->clear();

graphics->draw();

window->display();
}

void update(sf::Time &dt)
{
playerpaddle->update(dt);
aipaddle->update(dt);
ballphysics->update(dt);
collision->update();
}

void load()
{
graphics=new GraphicsSubsystem(window);
collision=new CollisionSubsystem();
ballphysics=new BallPhysicsSubsystem();
playerpaddle=new PlayerPaddlePhysicsSubsystem();
aipaddle=new AIPaddlePhysicsSubsystem();

// Build walls
graphics->createComponent(&topwall,800,32);
collision->createComponent(&topwall,800,32,true);
topwall.x_=400;
topwall.y_=16;

graphics->createComponent(&bottomwall,800,32);
collision->createComponent(&bottomwall,800,32,true);
bottomwall.x_=400;
bottomwall.y_=600-16;

graphics->createComponent(&leftwall,32,600);
collision->createComponent(&leftwall,32,600,true);
leftwall.x_=16;
leftwall.y_=300;

graphics->createComponent(&rightwall,32,600);
collision->createComponent(&rightwall,32,600,true);
rightwall.x_=800-16;
rightwall.y_=300;

graphics->createComponent(&ball, 8,8);
collision->createComponent(&ball, 8,8,true);
ballphysics->createComponent(&ball,128,128);
ball.x_=400;
ball.y_=300;

// create player paddle
graphics->createComponent(&ppaddle, 16,64);
collision->createComponent(&ppaddle, 16, 64, true);
playerpaddle->createComponent(&ppaddle, 100, 500, 256);
ppaddle.x_=150;
ppaddle.y_=300;

// Create ai paddle
graphics->createComponent(&apaddle, 16,64);
collision->createComponent(&apaddle, 16, 64, true);
aipaddle->createComponent(&apaddle,100,500,256);
apaddle.x_=800-150;
apaddle.y_=300;

}

void shutdown()
{
topwall.kill();
bottomwall.kill();
leftwall.kill();
rightwall.kill();
ball.kill();
ppaddle.kill();
apaddle.kill();

delete graphics;
delete collision;
delete ballphysics;
delete playerpaddle;
delete aipaddle;
}

void kernel()
{
load();
sf::Clock clock;
sf::Time elapsed;
while(window->isOpen())
{
sf::Time thistime=clock.getElapsedTime();
sf::Event event;
while(window->pollEvent(event))
{
if(event.type == sf::Event::Closed)
{
window->close();
return;
}
}
update(elapsed);
draw();
sf::Time endtime=clock.getElapsedTime();
elapsed=endtime-thistime;
}
shutdown();
window->close();
}

int main(int argc, char **argv)
{
window=new sf::RenderWindow(sf::VideoMode(800, 600), "SFML window");
window->setFramerateLimit(60);
kernel();
delete window;
return 0;
}



[/spoiler]
See the load() function for how the objects are created. As in the Lua demo, it creates 4 walls, a ball and 2 paddles.

And that's it. It works (I tested it), but it is not an exhaustively tested setup, so I wouldn't trust it without further testing. Still, it demonstrates the same concepts as the Lua example. Hopefully it helps.
Advertisement
You are welcome to take a look at my Journal (link in my Sig). I detail my process of converting one of my games to a Component Based Entity System. You can see how I handle adding the components to entities, and the communication between them.

Good Luck!

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

Hi FLeBlanc

i wish i was your boss because i would give you a promotion and a payrise thank you for doing that for me you have gone well above the call of duty :)

now its in c++ i can understand your code fully and also use it as a base for future programs i make. i also think im begining to understand the benefits of learning this way over oo. and now i have the fun of learning and expanding on your example im sure it will open up a whole new rabbit hole for me to fall into but at least now i have a map and a candle to find my way out :) perhaps ill need to learn mapreading also lol

Hi Beernutts that looks very cool i will have a good read of it soon it looks very interesting at the moment i think its quite alot of info in one project to wrap my head around but once i finish teething on this examples then i can have a good chew at yours :)

i cant thank you enough and im sure i will be back to call on your knowledge soon

This topic is closed to new replies.

Advertisement