Enough setup- the question I have is around organizing two types of entities: drawable entities and collidable entities. I have a renderer interface: IRenderer and then an implementation called DXRenderer which is for DirectX 10.1. This isn't for runtime late binding purposes (the projects in which they reside are static libs, not dlls) but for separation of concerns purposes. My Game class (from which I have a Pong subclass) accepts an IRenderer* and registers with it IDrawable*s. Per frame, IRenderer iterates through all of its IDrawables and - if they are not hidden - the renderer calls IDrawable::Draw(IRenderer* pRenderer), passing itself it.
Now, this achieves a fairly pleasant separation of concerns, but I'm now a bit worried about the amount of work I've just created for myself. So far, I have only two IDrawable implementations: StringDrawable and RectangleDrawable. IRenderer thus has two methods corresponding to these: IRenderer::DrawFIlledRectangle(...) and IRenderer::DrawString(...). The two issues here are: having to add another method almost per IDrawable interface, and having to open up each method sufficiently to support a plethora of rendering options (so far, you can't even choose what color you want to render the rectangle or string - it's just a black brush from d2d).
The second issue is pretty similar, in that it's about me painting myself into a bit of a designerly corner. The Game class has a CollisionDetector, into which games can register ICollidable objects. Per frame, the CollisionDetector iterates through all of the moving objects and checks if they have collided with something (ie: it iterates through everything else per object, this feels a wee bit heavy-handed, but is in no way slowing things down so far- still takes <1ms per frame). ICollidables only support bounding Rectangles at the moment, so one issue is how I would be able to support multiple types of bounding shape (well, for Pong I only really need rectangles and circles, but still...) The next issue is about how I go about responding to a collision. CollisionDetector retrieves the bounding rectangle of two ICollidables and finds their intersecting Rectangle, if one exists. I then pass the primary object the intersecting rectangle and a pointer to the ICollidable that it hit.
If we take the example of the player's paddle moving vertically in the playing area, I have created two Wall objects: _floor and _ceiling. These are ICollidables. Currently, Paddle::Collided(...) is called and the top of intersecting rectangle is the same as the top of the Paddle, then I "know" we're in the _ceiling and need to move down the same distance as the intersecting rectangle's height. Conversely, if Paddle::Collided(...) is called and the bottom of the intersection is equal to the bottom of the Paddle, the I "know" we're in the _floor and need to move the Paddle up the same distance as the intersecting rectangle's height. Clearly, this is not going to apply to many more scenarios. Eventually, I'm going to have to pass more context into the Collided(...) method, or request it from ICollidables.
Also, I'm not too happy about doing Euler integration on the velocity to get the position of the paddle, but something like RK4 is going to be massive overkill until I can justify it. I'm doing this on evenings and at weekends in between the myriad responsibilities I have, so each change I make can't really take more than an hour without seeing some tangible results. However, is this kind of 'corrective' collision detection still the norm? Ie: after the Game's Update method is called, objects have already intersected so enact some kind of restorative hack to make things look right. I far prefer the idea of predictively detecting collisions, but I guess that's also overkill for now. I do (eventually) wish to make more than just Pong with this framework, though, which is why I'm going to so many pains as to make things extensible and `neat`.
Thanks, and apologies for the rambling wall of text.
-Edit: Here's some code to illuminate what I'm talking about-
// Update loop in my Game base class
void Game::Loop(double time)
{
Update(time);
_collisionDetector.TestForCollisions();
_pRenderer->RenderFrame(time);
}
// Renderer iterates through IDrawable*s and asks them to draw themselves...
void IRenderer::RenderFrame(double time)
{
std::for_each(this->_vpDrawables.cbegin(), this->_vpDrawables.cend(), boost::bind(&DrawDrawable, _1, this, time));
}
void DrawDrawable(const IDrawable* pDrawable, IRenderer* pRenderer, double time)
{
pDrawable->Draw(pRenderer, time);
}
// I need an implementation of IDrawable for everything that can be drawn. At the moment, all I can do is render black filled rectangles...
void RectangleDrawable::Draw(IRenderer* pRenderer, double time) const
{
if(!Hidden)
pRenderer->FilledRectangle(_position.Left, _position.Top, _position.Right - _position.Left, _position.Bottom - _position.Top);
}
//...and black strings:
void StringDrawable::Draw(IRenderer* pRenderer, double time) const
{
if(!Hidden)
pRenderer->String(_position.X, _position.Y, _text);
}
//To use this in a game, I need to register something as drawable:
Pong::Pong(float width, float height, IRenderer* pRenderer) :
Game(pRenderer),
_screenExtents(width, height),
_playArea(width*.05f, height*.05f, width*.95f, height*.95f),
_floor(Math::Rectangle<float>(.0f, height*.95f, width, height)),
_ceiling(Math::Rectangle<float>(.0f, .0f, width, height*.05f)),
_playerLeft(width*.05f, height*.05f),
_playerRight(width*.95f, height*.05f)
{
AddDrawable(&_playerLeft);
AddDrawable(&_playerRight);
AddCollidable(&_playerLeft);
AddCollidable(&_playerRight);
AddCollidable(&_floor);
AddCollidable(&_ceiling);
}
//This does mean that Player is IDrawable, but it just delegates to its components, which will (in turn) delegate to either RectangleDrawable or StringDrawable, of which they are composed:
void Player::Draw(IRenderer* pRenderer, double time) const
{
_paddle.Draw(pRenderer, time);
_score.Draw(pRenderer, time);
}
Collision detection is much the same story:
// I'm sure this is going to be hugely inefficient at some point:
void CollisionDetector::TestForCollisions()
{
for(CollidableCollection::iterator i(_vpCollidables.begin()); i!=_vpCollidables.end(); ++i)
{
if((*i)->IsMoving())
{
for(CollidableCollection::iterator j(_vpCollidables.begin()); j!=_vpCollidables.end(); ++j)
{
if((*i) != (*j)) // don't test for collisions between ourselves
{
Rectangle<float> intersection;
if((*i)->GetBounds().GetIntersect((*j)->GetBounds(), intersection))
{
(*i)->Collided((*j), intersection);
}
}
}
}
}
}
// As we saw before, Player implements ICollidable, but again it delegates to components:
void Player::Collided(ICollidable* pCollidable, const Math::Rectangle<float>& intersection)
{
_paddle.Collided(pCollidable, intersection);
}
// Paddle handles the collision, but this also feels like I'm doing only what will work for this specific case - I've tried to design a general solution but there is still too much knowledge that is apropos of nothing:
void Paddle::Collided(ICollidable* pCollidable, const Math::Rectangle<float>& intersection)
{
const Rectangle<float>& position(_position.GetBounds());
if(intersection.Top == position.Top)
{
// we have intersected with the ceiling, move down...
_position.Move(.0f, intersection.Bottom - intersection.Top);
}
else if (intersection.Bottom == position.Bottom)
{
// we have intersected with the ceiling, move up...
_position.Move(.0f, intersection.Top - intersection.Bottom);
}
}