So yesterday I started implementing the physics in this game I'm working on and ended up using different collisions detection and resolution for them, which I thought it was worth sharing and explaining my reasoning for each of them.
The first collision I implemented was the player's; otherwise, this happens.
In order to implement collisions, you first need to decide how you want to detect them and how you want to resolve them. There are a couple of ways we could do it:
In the overlapping way, this is after we calculate the position of the player on the frame and check if the player is on top of the tiles. The main problem with this approach is how to resolve it, as this kind of collision doesn't really tell you much about it, only whether they are on top of each other. In this case, we could move the player to the last known position where it wasn't colliding, like the last frame position.
//Basic rect vs rect overlapping //Expands rect2 and makes rect1 a point, if the point lies in the expanded rect they overlap bool rect_vs_rect(const Rect& rect1,const Rect& rect2){ sf::FloatRect expanded = {rect2.center-rect2.size/2.f-rect1.size/2.f,rect2.size+rect1.size}; return expanded.contains(rect1.center); } //Collision detection and resolution algorithm sf::Vector2f player_size{world.player.radius*2.f,world.player.radius*2.f}; for(int i = sy;i<ey;i++){ for(int j = sx;j<ex;j++){ if(world.planet.get_block({j,i}).id==0) continue;//Skip empty blocks sf::Vector2f block_pos{j*game_vars::block_size,i*game_vars::block_size}; block_pos+=game_vars::block_size2d/2.f; auto result = physics::rect_vs_rect({player_final_pos,player_size},{block_pos,game_vars::block_size2d}); if(result){ world.player.vel={0.f,0.f};//Cancel the player movement when causes a collision } } }
It seems to work fine for detecting when the player touches the ground, but the problem arises when factoring some other axis. Moving sideways on the ground becomes impossible when gravity or the angle of movement pushes the player slightly into the tile below, as all movement is canceled when a collision is detected. Something that gives a bit more information about the collision would greatly help to fix these issues.
Instead of calculating if the player overlaps any tiles, we could use some math to calculate the exact moment of collision (and some other variables like the point of collision and the normal vector) to make the detection and resolution more accurate. The algorithm used is explained in this YouTube video Arbitrary Rectangle Collision Detection & Resolution - Complete!
//Detect tiles that collide and store the time of collision for(int i = sy;i<ey;i++){ for(int j = sx;j<ex;j++){ if(world.planet.get_block({j,i}).id==0) continue; sf::Vector2f block_pos{j*game_vars::block_size,i*game_vars::block_size}; auto result = physics::sweep_rect_vs_rect({world.player.pos,world.player.radius*0.85f},world.player.vel*delta_time,{block_pos+game_vars::block_size2d/2.f,game_vars::block_size2d}); if(result.collision){ collision_times.push_back({world.planet.map_2d_to_1d({j,i}),result.t}); } } } //Sort by time of collision std::sort(collision_times.begin(),collision_times.end(),[](const std::pair<size_t,float>& a, const std::pair<size_t,float>& b){ return a.second<b.second; } //Collision resolution for(const auto& coll:collision_times){ auto index_2d = world.planet.map_1d_to_2d(coll.first); sf::Vector2f block_pos{index_2d.x*game_vars::block_size,index_2d.y*game_vars::block_size}; auto result = physics::sweep_rect_vs_rect({world.player.pos,world.player.radius*0.85f},world.player.vel*delta_time,{block_pos+game_vars::block_size2d/2.f,game_vars::block_size2d}); if(result.collision){ //Calculate the direction and magnitud of the pushback so the player is not in collision auto vel_abs = sf::Vector2f{fabsf(world.player.vel.x),fabsf(world.player.vel.y)}; auto pushback = sf::Vector2f{result.normal.x*vel_abs.x,result.normal.y*vel_abs.y}*(1.f-(result.t*0.1f)); world.player.vel+=pushback; } }
This process is where more convoluted that the simple overlap but is way more robust, here is how it looks working.
No more getting stuck at the cost of some CPU time. And yes for some of you with a keen eye I use a rectangle for the collision box of the player even though it's a circle, still looks pretty good!
The other collision I wanted to talk about is the one that the items have, in this case the items are the small red squares(WIP).
These items have two behaviors: chase the player if they are in range, or drop until they hit a block and stop. The first behavior is pretty simple: check if the distance between the player and the items is less than the pull range, or, in other words, if the position of the object, which is a point, is inside the circle formed by the player position and the pull range, we are back to the overlapping collisions.
//Use distance squared so there is we can skip the square root calculations if (math::sqr_dist(player.pos,item.pos)<30000.f){ item.chasing_playing=true; }
Falling until they hit a block can be simplified into checking if there is a block below and, if so, stopping moving. Exploiting the fact that the blocks are affected by gravity and don't have that much horizontal speed, the task can be done pretty efficiently.
//Snippet item update item.vel+={0.f,game_vars::gravity*delta_time}; item.vel.y=std::min(item.vel.y,1200.f);//Limit fall speed sf::Vector2f old_pos = item.pos; item.pos+=item.vel*delta_time; auto block = world.map_pos_to_block(old_pos); auto block_below = block+sf::Vector2i{0,1}; if(world.planet.get_block(block_below).id!=0){ float block_top_y = block_below.y*game_vars::block_size-game_vars::item_size.y/2.f; if(old_pos.y<block_top_y&&item.pos.y>block_top_y){ item.pos.y=line-0.01f;//Put item just above the block item.vel={0.f,0.f}; //Stop the item } }
There is no need to do complex calculations; all we do here is check if the item has moved through a horizontal line. There are still some situations where the item will get stuck in walls given that there is no check for side tiles, but that doesn't happen that often and there is no game-breaking stuff, so it is staying. I could use the same method I use for the player collision in this case, but even though this is not really a problem, it is a waste of CPU time to calculate a complex method of resolution when a simple if check does the trick.
And that's it for today. Different problems implemented called for different solutions.
Did you like this post? Tell us
Leave a comment
Log in with your itch.io account to leave a comment.