Finally! The next tutorial. This is the 2nd part to the SDL Collision tutorial. We'll be looking at collision events, the part we left unfinished in the last tutorial. To refresh your memory, a collision event is an event that is stored in queue after a collision has taken place. Take our recent shooter contest for an example. When the player fires a bullet, the bullet flies through the air, and then hits the enemy. The moment the bullet and the enemy collide, an event is triggered and stored in the queue. After all movement has taken place, the queue is iterated through, and events can appropriately respond.
So lets get started. Open up your project from the last tutorial. We're first going to make a few prelimary changes. Make a new file called CEntityCol.cpp. Inside that file, put the following:
#include "CEntity.h"
std::vector<CEntityCol> CEntityCol::EntityColList;
CEntityCol::CEntityCol() {
this->EntityA = NULL;
this->EntityB = NULL;
}
Now, get rid of that code from CEntity.cpp. I am basically moving the code to its own file so that everything is much neater. The second thing. Open up CEntity.h and change:
virtual void OnCollision(CEntity* Entity);
To this:
virtual bool OnCollision(CEntity* Entity);
Also, change the void to bool in CEntity.cpp, CPlayer.h, and CPlayer.cpp. Why the change? Well, you'll see in a minute. ;)
We're already storing the collision events in a queue, now we need to do something with them. Open up CApp_OnLoop.cpp. You'll notice we already have a loop going for entities and we're calling OnLoop. We're basically going to do the same thing again, but after our first loop:
for(int i = 0;i < CEntity::EntityList.size();i++) {
if(!CEntity::EntityList[i]) continue;
CEntity::EntityList[i]->OnLoop();
}
//Collision Events
for(int i = 0;i < CEntityCol::EntityColList.size();i++) {
CEntity* EntityA = CEntityCol::EntityColList[i].EntityA;
CEntity* EntityB = CEntityCol::EntityColList[i].EntityB;
if(EntityA == NULL || EntityB == NULL) continue;
if(EntityA->OnCollision(EntityB)) {
EntityB->OnCollision(EntityA);
}
}
CEntityCol::EntityColList.clear();
Not much going on here. We're first iterating through every event in the queue. We then grab the two entities involved (EntityA, and EntityB), and we make sure they are valid pointers. If so, we first tell EntityA that it has collided with EntityB by calling OnCollision. Now, this is where that void to bool change comes in. EntityA can choose to prevent EntityB from knowing it has collided with him. If you remember, only Entities that are moving check for collision. This would mean EntityA is definitly a moving entity, and EntityB may or may not be moving.
Here's an example to explain why you wouldn't want to waste time calling EntityB's OnCollision. Say you fire a bullet, and it hits a rock entity. Now, lets say the rock has a flag stating it's indestructable. Our rock doesn't care its been hit. So, when the OnCollision is called for the bullet, the bullet can see the entity it has hit is indestructable. And because of that, it can create a little "bullet being destroyed" animation, and then we can ignore telling the rock about it.
This is about the extent of entity collisions. So, now that we have this base, lets do something semi-useful with it.
Open up CEntity.h and add this new function, and variable:
protected:
bool CanJump;
public:
bool Jump();
Then, open up CEntity.cpp, and add the Jump function. Also make CanJump = false in the constructor:
CEntity::CEntity() {
CanJump = false;
// ... Other code ...
}
bool CEntity::Jump() {
if(CanJump == false) return false;
SpeedY = -MaxSpeedY;
return true;
}
Finally, go to the OnMove function. We're going to figure out when our entity is allowed to jump. An entity is allowed to jump whenever it is touching the ground - or, in other words, whenever the entity is no longer moving on the positive Y-axis. So, at the top, we are first always going to reset CanJump to false:
CanJump = false;
Now, go down a bit, and modify the PosValid for the Y-axis to the following:
if(PosValid((int)(X), (int)(Y + NewY))) {
Y += NewY;
}else{
if(MoveY > 0) {
CanJump = true;
}
SpeedY = 0;
}
Now, to run through everything we did. First, we have a flag that states if an entity can jump. When set to true, we are allowed to call the Jump function. This CanJump variable is always false, unless our feet are on the ground. Whenever we actually do want to jump, we simply set our SpeedY in the negative Y-axis direction (up). Remember, gravity is always pushing us down, but at a slow speed. So, if we have a SpeedY of -5, and a AccelY of 0.75, we'll move up for a time, gravity will kick in, and then we will eventually be pushed back down. Thus, giving the effect of a real jump. The trick here is to get the Speed and Acceleration variables just right.
Also, as a side note, the reason we made Jump return true/false is simply for informational purposes. Meaning, when a key is pressed by the player to jump, they won't always be allowed to jump. And, if you wanted, you could do something based upon what Jump() returns.
So a few more tweaks before we try this out. In the constructor of CEntity, change the MaxSpeedX and MaxSpeedY to 10. Then, open up CApp_OnEvent.cpp and add the following to OnKeyDown:
case SDLK_SPACE: {
Player.Jump();
break;
}
And one last thing. Open up CPlayer.cpp, and change the OnCollision function to this:
bool CPlayer::OnCollision(CEntity* Entity) {
Jump();
return true;
}
That's it! Compile, run, and have fun with Yoshi jumping all over the place. You'll notice the simple collision event we added is that when Yoshi 1 collides with Yoshi 2, it causes Yoshi 1 to jump. Kind of lame, but you should get the idea.
I have followed the tutorials and made a few maps and a moving enemy class.
For some reason, though, the program recognizes the collision between the enemy and the player only when they both are in two specific tiles on the map and nowhere else.
Also, for some reason it recognizes that collision even when the player is above the enemy without contact, again only when they both are on those two specific tiles.
I keep looking at the code but I can't find the problem.
Can anyone help?
Thanks in advance. :-)
Email (required, not published):
Website:
Email (required, not published):
Website:
Well, I have a question about this tutorial, in a Windows environment, the program runs perfectly but in linux (mint 14 to be exact) becomes massively slow throughout their execution, I have checked the program and I think the problem is in the function GetSpeedFactor () but I have no idea why, I'm still trying to solve it by myself but should be good if you can orient me.
Thanks!
Email (required, not published):
Website:
Email (required, not published):
Website:
How would I go about fixing this? Can't really think of what piece in the code causes it, and putting a SDL_Delay (1); in the main loop fixes the problem, but that causes a very slight yet still annoying frame-skipping.
Email (required, not published):
Website:
and it works just fine. I'm compiling with MinGW-4.7.2 and having great fun with Yoshi bouncing back and forth black area just outside of map and green blue area.
Email (required, not published):
Website:
Just a post to thank you for your tutorials, they are really great and so easy to understand!
Thanks!
PS: The little "game" at the bottom of your website is nice! =P (Maybe you already know it, but if you spam the space bar the blue thing will keep going higher)
Email (required, not published):
Website:
Email (required, not published):
Website:
Here's my code when an npc dies (in CApp_OnLoop):
for(int i = 0;i < CEntity::EntityList.size();i++) {
if(CEntity::EntityList[i]->Dead == true){
CEntity::EntityList[i]->OnCleanup();
CEntity::EntityList.erase(CEntity::EntityList.begin() + i);
}
}
Email (required, not published):
Website:
If anyone knows I would be very glad to hear, thanks.
Email (required, not published):
Website:
Email (required, not published):
Website:
Email (required, not published):
Website:
You'll need to remove duplicates from collision list (in both ways) to avoid this
Email (required, not published):
Website:
bool CEntity::PosValidEntity(CEntity* Entity, int NewX, int NewY)
{
if(this != Entity && Entity != NULL && Entity->Dead == false &&
Entity->Flags ^ ENTITY_FLAG_MAPONLY &&
Entity->Collides(NewX + Col_X, NewY + Col_Y, Width - Col_Width - 1, Height - Col_Height - 1) == true)
{
for(int i = 0; i < (int)CEntityCol::EntityColList.size(); i++)
{
CEntity* EntityA = CEntityCol::EntityColList[i].EntityA;
CEntity* EntityB = CEntityCol::EntityColList[i].EntityB;
if(EntityA == this && EntityB == Entity) return true; //add this
if(EntityA == Entity && EntityB == this) return true; //and this
}
CEntityCol EntityCol;
EntityCol.EntityA = this;
EntityCol.EntityB = Entity;
CEntityCol::EntityColList.push_back(EntityCol);
return false;
}
return true;
}
Email (required, not published):
Website:
Today I discovered this caused another problems, so maybe that is not how it should be done.
Email (required, not published):
Website:
My problem was that when 2 entities were moving to each other, in the moment of collision they were getting stuck.
That is how it was supposed to be fixed:
bool CEntity::PosValidEntity(CEntity* Entity, int NewX, int NewY)
{
for(int i = 0; i < (int)CEntityCol::EntityColList.size(); i++)
{
CEntity* EntityA = CEntityCol::EntityColList[i].EntityA;
CEntity* EntityB = CEntityCol::EntityColList[i].EntityB;
}
if(this != Entity && Entity != NULL && Entity->Dead == false &&
Entity->Flags ^ ENTITY_FLAG_MAPONLY &&
Entity->Collides(NewX + Col_X, NewY + Col_Y, Width - Col_Width - 0, Height - Col_Height - 0) == true) //there were 1 instead of 0 and it was causing a problem
{
CEntityCol EntityCol;
EntityCol.EntityA = this;
EntityCol.EntityB = Entity;
CEntityCol::EntityColList.push_back(EntityCol);
return false;
}
return true;
}
Email (required, not published):
Website:
Btw, when I collide Yoshi 2 from behind, it jumps too, why does that happen?
Thanks!
Email (required, not published):
Website:
Email (required, not published):
Website:
Email (required, not published):
Website: