Oh, crap, I haven't looked at the framework extensions in a while, I'll get back to you on that. I need to review how it's setup.
Regarding collision detection:
This kind of deals with software architecture and design. Think of it this way, you are wanting to check collision between different objects and deal damage against player or enemies and prevent objects from moving through each other when they are not passable. This would probably be handled in a physics component. This component wouldn't handle damage of course, but would resolve collisions. As far as calling those types of functions, you'd probably want to call those from Game.cpp after determining and resolving a collision.
Something I've done in the past is each object that is collidable had a DoCollision or HandleCollision function. Once I determined and resolved the collision, I'd pass object B to object A's collision function and object A to object B's collision function. If A needs to take damage, A would call B's GetDamageAmount() and same for B.
Code: Select all
// In Game::UpdateModel
// ... update positions
// Check, resolve and handle collisions
for( int i = 0; i < numObjects; ++i )
{
auto& obj_a = objects[i];
auto& obj_b = objects[i+1];
if( phys.IsColliding( obj_a, obj_b )
{
obj_a.HandleCollision( obj_b );
obj_b.HandleCollision( obj_a );
}
}
void Wall::HandleCollision( const Object& Other )
{
// Nothing to do here, wall doesn't take damage
}
void User::HandleCollision( const Object& Other )
{
const auto other_type = Other.GetType();
if( other_type == Object::Type::Enemy ||
other_type == Object::Type::Projectile )
{
if( other_type == Object::Type::Projectile &&
Other.GetOwner() != Object::Owner::User )
{
health -= Other.GetDamageAmount();
}
}
}
void Enemy::HandleCollision( const Object& Other )
{
const auto other_type = Other.GetType();
if( other_type == Object::Type::User ||
other_type == Object::Type::Projectile )
{
if( other_type == Object::Type::Projectile &&
Other.GetOwner() != Object::Owner::Enemy )
{
health -= Other.GetDamageAmount();
}
}
}
This code example deals with inheritance, if you haven't gotten that far, then you will have to come up with another solution.
Regarding the RectF class:
Personally, using left,top,right,bottom is a lot easier to reason about and in some cases require less math operations like trying to determine collisions and/or bounds checking. On the other hand, translating a rect that only stores it's position and size is easier because you only have to update it's position X and Y values while storing each side requires updating translating four variables. So for instance, let's say you define a collision rect as position an size first.
Easiest way is defining the rect in terms of an offset from the character as opposed to the character's position and size.
Given that the character is 32x64 pixels and it's base is at it's feet, you might store the rect as:
rect.pos.x = -16.f;
rect.pos.y = -64.f;
rect.size.width = 32.f;
rect.size.height = 64.f;
You'd then translate the rect to the players position before doing collision checking:
Code: Select all
player.pos.x = 320;
player.pos.y = 200;
RectF RectF::Translate( const Vec2f& Delta )const
{
return pos + Delta; // -16.f + 320 = 304.f, -64.f + 200 = 136.f
}
This means that the returned collision rect's left,top corner would be ( 304.f, 136.f ). The right,bottom corner would be calculated by adding width and height to those coordinates ( 336.f, 200.f ). This assumes you want the position of the player to be centered along the X axis and at the bottom of the Y axis of the object. You'll have to change it if you want the rect to be calculated differently.
If you do the same using left,top,right,bottom:
Code: Select all
rect.left = -16.f;
rect.top = -64;
rect.right = 16.f
rect.bottom = 0;
RectF RectF::Translate( const Vec2& Delta );
{
RectF result;
result.left = left + Delta.x; // -16.f + 320 = 304.f;
result.top = top + Delta.y; // -64.f + 200 = 136.f;
result.right = right + Delta.x; // 16.f + 320 = 336.f;
result.bottom = bottom + Delta.y // 0.f + 200 = 200.f;
return result;
}
Now compare the overlapping test:
Code: Select all
bool RectF::IsOverlapping( const RectF& Other )const
{
// Stored position and size
return
pos.x + size.width > Other.pos.x && pos.x < Other.pos.x + Other.size.width &&
pos.y + size.height > Other.pos.y && pos.y < Other.pos.y + Other.size.height;
// Stored boundaries
return
right > Other.left && left < Other.right &&
bottom > Other.top && top < Other.bottom;
}
As you can see, each has it's benefits and drawbacks to readability and maybe efficiency. You can make the pos/size version of the overlapping function easier to read by having functions that return the sides after calculation GetLeft() > Other.GetRight() && GetRight() < Other.GetLeft() and so on, but the extra calculations are still there, just hidden.
Also, should I use RectF for my characters, or is again a Vec2 and width & height preferable?
No hesitation, use a Rect class, it gives you an abstraction that can be reasoned about when dealing with the object's boundaries for collision and drawing. Plus, if you are using my framework extensions for chili's framework, then you would need to create rects from the size and position for each object before drawing them anyway. Of course, you can always just write your own stuff
;