I am implementing some very basic platforming physics in C++ for a simple tile-based platformer. I am following the algorithm here (https://gamedev.stackexchange.com/questions/18302/2d-platformer-collisions) as exactly as I can yet I am still getting a weird glitch where the player bounces on a tile. I'm not exactly sure what is going on. The code is in C++ using SDL
void Player::update(vector< vector<Tile> > map) {
vector<Tile> collidingTiles;
x += xVel;
y += yVel;
boundingBox = Rect(x,y,16,16);
for(int iy=0;iy<MAPH;iy++) {
for(int ix=0;ix<MAPW;ix++) {
if (map[ix][iy].solid == true) {
if (boundingBox.collide(map[ix][iy].boundingBox)) {
collidingTiles.push_back(map[ix][iy]); // store all colliding tiles, will be used later
Rect intersectingRect = map[ix][iy].boundingBox; // copy the intersecting rect
float xOffset = x-intersectingRect.x; // calculate x-axis offset
float yOffset = y-intersectingRect.y; //calculate y-axis offset
if (abs(xOffset) < abs(yOffset)) {
x += xOffset;
}
else if (abs(xOffset) > abs(yOffset)) {
y += yOffset;
}
boundingBox = Rect(x,y,16,16); // reset bounding box
yVel = 0;
}
}
}
}
if (collidingTiles.size() == 0) {
yVel += gravity;
}
};
if (abs(xOffset) < abs(yOffset)) {
x += xOffset;
}
else if (abs(xOffset) > abs(yOffset)) {
y += yOffset;
}
yVel = 0;
In this code, if abs(xOffset) == abs(yOffset), nothing happens, meaning that the player can enter a solid tile diagonally. Removing the second test should remove the issue and modify y if the algorithm has to make a choice:
Also, I'm wondering if you really want to reset the vertical velocity if a horizontal collision occurs. This also implies that gravity should still apply if the collision was horizontal (otherwise the walls and ceiling would be sticky).
int gravityApplies = true;//fix 2a
...
if (abs(xOffset) < abs(yOffset)) {
x += xOffset;
xVel = 0; //fix 2
}
else { //fix 1
y += yOffset;
yVel = 0; //fix 2
if( yOfsset < 0 ){ // fix 2a
gravityApplies = false;
}
}
...
if (gravityApplies) { //fix 2a
yVel += gravity;
}
If you want elastic collisions, use yVel = -yVel instead of yVel = 0 (and similar for the other direction). You can even do yVel = yVel * rest where rest ranges from -1 to 0 (-0.8 is a good choice) to get semi-elastic collisions.
Related
I was making a super-mario-like game in C++/SFML and while trying to write some code for Mario collisions, I had some problems: The character, when collides with a vertical stack of blocks while moving along the horizontal axis, get stuck on one side of the blocks, like if it is walking on an invisible block. I tried to modify the collision function like making mario to collide with blocks horizontally only if his position related to blocks is contained into the block coordinates.
I include some code for the movement(keyPressed is a function that returns the key pressed):
void Player::movement(Time& gameTime) {
player.move(v * gameTime.asSeconds());
view.setCenter(player.getPosition().x + 16, 590);
if (keyPressed(up) && !jumping) {
jumping = true;
v.y = jumpSpeed;
}
if (v.y > 200) {
jumping = true;
}
else {
crouch = false;
}
if (keyPressed(left)) {
if (v.x > -moveSpeed) {
v.x -= 100;
}
else {
v.x = -moveSpeed;
}
}
else if (keyPressed(right)) {
noKeyPressed = false;
if (v.x < moveSpeed) {
v.x += 100;
}
else {
v.x = moveSpeed;
}
}
else {
if (v.x < -100) {
v.x += 100;
}
else if (v.x > 100) {
v.x -= 100;
}
else {
v.x = 0;
}
}
gravity();
if (big) { //Big is a variable that tells me if mario is big or small
heightValue = 33; //heightValue is a variable that stores the sprite height in pixels
}
else {
heightValue = 16;
}
}
void Player::gravity() {
if (!big) {
if (player.getPosition().y + 32 < 1100) {
if (v.y < maxSpeed) {
v.y += 100;
}
}
if (alive) { //This is useful to check if big mario has fallen
if (player.getPosition().y + 32 >= 1200) {
player.setPosition(player.getPosition().x, 1200 - 32);
jumping = false;
alive = false;
}
}
}
else {
if (player.getPosition().y + 64 < 1100) {
if (v.y < maxSpeed) {
v.y += 100;
}
}
if (alive) { //This is useful to check if small mario has fallen
if (player.getPosition().y + 64 >= 1200) {
player.setPosition(player.getPosition().x, 1200 - 64);
jumping = false;
alive = false;
}
}
}
}
And the collision function, where the block class has 4 small blocks around the block sprite that simplify collisions:
void Player::collisions(Block* block) {
if (this->player.getGlobalBounds().intersects(block->up.getGlobalBounds())) {
if (!big) {
if (this->player.getPosition().y + heightValue <= block->block.getPosition().y) {
this->player.setPosition(this->player.getPosition().x, block->up.getPosition().y - 32);
v.y = 0;
jumping = false;
score = 100;
}
}
else {
if (this->player.getPosition().y + heightValue <= block->block.getPosition().y) {
this->player.setPosition(this->player.getPosition().x, block->up.getPosition().y - 64);
v.y = 0;
jumping = false;
score = 100;
}
}
}
if (this->player.getGlobalBounds().intersects(block->down.getGlobalBounds())) {
this->player.setPosition(this->player.getPosition().x, block->down.getPosition().y + 1);
v.y = 0;
}
if (this->player.getGlobalBounds().intersects(block->left.getGlobalBounds()) && v.x > 0) {
this->player.setPosition(block->left.getPosition().x - 32, this->player.getPosition().y);
}
else if (this->player.getGlobalBounds().intersects(block->right.getGlobalBounds()) && v.x < 0) {
this->player.setPosition(block->right.getPosition().x + 1, this->player.getPosition().y);
}
}
I hope I explained accurately the problem.
simply do your collision detection one axis at a time, this allows nice smooth movement along tiles
pseudocode example:
//X axis
oldPos = position
if(leftKey):
position.x -= speed
if(rightKey):
position.x += speed
if(collision):
position = oldPos
//Y axis
oldPos = position
if(upKey):
position.y -= speed
if(downKey):
position.y += speed
if(collision):
position = oldPos
This is my first attempt with SFML and game development, and I'm having issues with the collision and gravity.
I'm making a 2D platformer game using a tilemap system.
Collision seems to be working(slightly) but is very choppy and I just can't seem to get gravity to work properly. I've tried a few different tutorials but I cant get anything working and I'm at a bit of a loss right now.
If someone could point out what I'm missing here it'd be greatly appreciated!!
Heres the code I'm using for these elements:
In PlayerSprite class(attempt at gravity)
PlayerSprite::PlayerSprite(const sf::Vector2f& size) : AnimatedSprite(size)
{
playerPos = (sf::Vector2f(300.0f, 400.0f));
dead = false;
jumpHeight = 5.f;
scale = 50.f;
accelGravity = 0.5f;
maxGravity = 5.f;
velocity.x = 2.0f;
velocity.y = 2.0f;
playerTexture.loadFromFile("gfx/spritemansheet.png");
setSize(sf::Vector2f(48, 48));
setPosition(playerPos);
setTexture(&playerTexture);
}
PlayerSprite::~PlayerSprite()
{
}
void PlayerSprite::update(float dt)
{
onGround = false;
if (input->isKeyDown(sf::Keyboard::A)) {
input->setKeyUp(sf::Keyboard::A);
playerPos.x -= (dt * step) * 5;
setPosition(playerPos);
//currentAnimation = &walkBack;
}
if (input->isKeyDown(sf::Keyboard::D)) {
input->setKeyUp(sf::Keyboard::D);
playerPos.x += (dt * step) * 5;
setPosition(playerPos);
//currentAnimation = &walk;
}
if (input->isKeyDown(sf::Keyboard::W) ) {
input->setKeyUp(sf::Keyboard::Space);
playerPos.x += (dt * step) * 5;
setPosition(playerPos);
//currentAnimation = &jump;
velocity.y = -5.f * -1;
}
if (onGround == false) {
velocity.y += accelGravity;
if (velocity.y > maxGravity) {
velocity.y = maxGravity;
}
}
if (sf::Mouse::isButtonPressed(sf::Mouse::Left))
{
input->setMouseLeftUp(sf::Mouse::Left);
currentAnimation = &attack;
}
}
void PlayerSprite::setInput(Input* newInp)
{
input = newInp;
}
void PlayerSprite::collisionResponse(Sprite* sp)
{
if (velocity.x > 0) {
velocity.x = 0.f;
}
if (velocity.x < 0) {
velocity.x = 0.f;
}
if (velocity.y > 0) {
velocity.y = 0.f;
onGround = true;
}
if (velocity.y < 0) {
velocity.y = 0.f;
}
setPosition(getPosition().x, sp->getPosition().y - getSize().y);
}
Collision Detection in Game.cpp
void Game::update(float dt)
{
player.update(dt);
manager.update(dt);
std::vector<Tile>* world = worldMap.getScene();
for (int i = 0; i < (int)world->size(); i++) {
if ((*world)[i].isAlive()) {
if (checkGroundBounding(&player, &(*world)[i])) {
player.collisionResponse(&(*world)[i]);
}
}
}
void Game::render() {
beginDraw();
window->draw(bg);
window->draw(player);
manager.render(window);
worldMap.render(window);
endDraw();
}
bool Game::checkGroundBounding(PlayerSprite* b1, Tile* b2)
{
//get radius of sprites
float r1 = b1->getSize().x / 2;
float r2 = b2->getSize().x / 2;
float xposb1 = b1->getPosition().x + r1;
float xposb2 = b2->getPosition().x + r2;
float yposb1 = b1->getPosition().y + r1;
float yposb2 = b2->getPosition().y + r2;
if (pow(xposb2 - xposb1, 2) + pow(yposb2 - yposb1, 2) < pow(r1 + r2, 2)) {
return true;
}
return false;
}
you're collision is super wonky, simply set a oldPos variable to your player position, then do your movement code but only for the X axis, if collision is detected, set the position to oldPos, then set oldPos to the current position again, and repeat for the Y axis:
Pseudocode example:
//X axis
oldPos = position
if(leftKey):
position.x -= speed
if(rightKey):
position.x += speed
if(collision):
position = oldPos
//Y axis
oldPos = position
if(upKey):
position.y -= speed
if(downKey):
position.y += speed
if(collision):
position = oldPos
also you can replace your tile iteration with:
for(Tile t : *world){
t->doStuff();
}
I also don't recommend declaring your tile vector in your update function unless you hate performance
Right now when i start my game am making in C++ i walk left right up or down.. But the character just slides doesn't look like he's walking.. And i have all the pictures already loaded into my game and they are working.. But i don't know how i would solve it.. Thing i can't figure out is how to make the picture change when u hold the button..
This is in allegro by the way..
Here is my code for the drawing player:
void Player::Draw(BITMAP *Buffer){
draw_sprite(Buffer, Oskar[picNumber], x, y);
}
Oskar[] is the name of the Array with all the pictures..
Here is what changes the picture for the character when u press the buttons:
void Player::Controls(){
if(key[KEY_RIGHT]){
velocityX = speed;
picNumber = 6;
}
else if(key [KEY_LEFT]){
velocityX = -speed;
picNumber = 9;
}
else{
velocityX = 0;
}
if(key [KEY_UP]){
velocityY = -speed;
picNumber = 3;
}
else if(key [KEY_DOWN]){
velocityY = speed;
picNumber = 0;
}
else{
velocityY = 0;
}
x += velocityX;
y += velocityY;
}
Its all about the variable i created picNumber.. All pictures i have is in an Array and the picNumber is represents what picture to be drawn..
Would be nice to get some help on this.. I've been thinking all day about this..
EDIT
#include "Player.h"
#include "Global.h"
#include <allegro.h>
Player::Player(){
}
Player::~Player(){
}
void Player::Init(){
x = 10;
y = 10;
velocityX = 0;
velocityY = 0;
speed = 1;
picNumber = x % MAXPICS;
OskarFront[0] = load_bitmap("Character\\OskarFront.bmp", NULL);
OskarFront[1] = load_bitmap("Character\\OskarStanding.bmp", NULL);
OskarFront[2] = load_bitmap("Character\\OskarFront2.bmp", NULL);
OskarBack[0] = load_bitmap("Character\\OskarBack.bmp", NULL);
OskarBack[1] = load_bitmap("Character\\OskarStandingBack.bmp", NULL);
OskarBack[2] = load_bitmap("Character\\OskarBack2.bmp", NULL);
OskarRight[0] = load_bitmap("Character\\Oskar1.bmp", NULL);
OskarRight[1] = load_bitmap("Character\\Oskar.bmp", NULL);
OskarRight[2] = load_bitmap("Character\\Oskar2.bmp", NULL);
OskarLeft[0] = load_bitmap("Character\\OskarLeft.bmp", NULL);
OskarLeft[1] = load_bitmap("Character\\OskarLeftStand.bmp", NULL);
OskarLeft[2] = load_bitmap("Character\\OskarLeft2.bmp", NULL);
}
void Player::Update(){
Player::Controls();
}
void Player::Draw(BITMAP *Buffer){
if(walkingRight == true){
draw_sprite(Buffer, OskarRight[picNumber], x, y);
}
else if(walkingLeft == true){
draw_sprite(Buffer, OskarLeft[picNumber], x, y);
}
else if(walkingFront == true){
draw_sprite(Buffer, OskarFront[picNumber], x, y);
}
else if(walkingBack == true){
draw_sprite(Buffer, OskarBack[picNumber], x, y);
}
else{
draw_sprite(Buffer, OskarFront[1], x, y);
}
}
void Player::Controls(){
if(key[KEY_RIGHT]){
velocityX = speed;
walkingRight = true;
}
else if(key [KEY_LEFT]){
velocityX = -speed;
walkingLeft = true;
}
else{
walkingRight = false;
walkingLeft = false;
velocityX = 0;
}
if(key [KEY_UP]){
velocityY = -speed;
walkingFront = true;
}
else if(key [KEY_DOWN]){
velocityY = speed;
walkingBack = true;
}
else{
velocityY = 0;
walkingFront = false;
walkingBack = false;
}
x += velocityX;
y += velocityY;
}
here is now the new full code i typed after getting help here.. Its now not working when am walking up its showing the front picture and am walking down the up picture is showing.. But left and right works.. Also its not changing picture like animation..
You only have one index for each direction of movement!
Assuming you have drawn about 3 (left leg forward, overlap, right leg forward) you would need to increment the index with time or movement as the march progresses.
Interesting how far Allegro has progressed- a decade ago it was mainly DOS graphics (I used it with DJGPP) and had a note from the author living in a London flat. How austere I thought, only now do I see how far off of London prices we provincials are.
In answer to your comment, try something like:
#define MAXPICS 3
BITMAP *OskarWalksLeft[MAXPICS];
BITMAP *OskarWalksRight[MAXPICS];
picNumber = x % MAXPICS;
if (facingLeft) {
draw_sprite(Buffer, OskarWalksLeft[picNumber], x, y);
} else //facingRight
draw_sprite(Buffer, OskarWalksRight[picNumber], x, y);
or if you don't want a new frame per pixel but every 3 or so try replacing
picNumber = x % MAXPICS;
with
picNumber = (x / 3) % MAXPICS;
I've tried tidying up your code a little and come up with this, I don't have allegro so couldn't test it:
#include "Player.h"
#include "Global.h"
#include <allegro.h>
Player::Player(){}
Player::~Player(){}
enum playerDir {left, right, front, back, stationary} direction;
BITMAP *Oskar[stationary][3];
void Player::Init(){
x = 10;
y = 10;
velocityX = 0;
velocityY = 0;
speed = 1;
picNumber = x % MAXPICS;
char filename[256];
const char *dirStrs[] = {"left","right","front","back"};
for (playerDir i = left; i < stationary; (int)i = (int)i + 1)
for (j = 0; j < 3; ++j) {
strcpy(filename, "Character\\Oskar");
strcat(filename, dirStrs[i]);
strcat(filename, itoa(j));
strcat(filename, ".bmp");
Oskar[i][j] = load_bitmap(filename, NULL);
}
}
void Player::Update(){Player::Controls();}
void Player::Draw(BITMAP *Buffer){
switch (direction) {
case left :
draw_sprite(Buffer, OskarLeft[picNumber], x, y);
break;
case right :
draw_sprite(Buffer, OskarRight[picNumber], x, y);
break;
case front :
draw_sprite(Buffer, OskarFront[picNumber], x, y);
break;
case back :
draw_sprite(Buffer, OskarBack[picNumber], x, y);
break;
default:
draw_sprite(Buffer, OskarFront[1], x, y);
}
}
void Player::Controls(){
direction = stationary;
if(key[KEY_RIGHT]){
velocityX = speed;
direction = right;
} else if(key [KEY_LEFT]){
velocityX = -speed;
direction = left;
} else velocityX = 0;
if(key [KEY_UP]){
velocityY = -speed;
direction = front;
} else if(key [KEY_DOWN]){
velocityY = speed;
direction = back;
} else velocityY = 0;
x += velocityX;
y += velocityY;
}
You don't seem to be doing any animation. It seems you are just setting a specific frame based on the direction key the user is pressing. You need a function that does something like this:
void Player::updateFrame(float time_delta)
{
time_to_next_frame -= time_delta;
while (time_to_next_frame <= 0.0f)
{
nextFrame();
time_to_next_frame += time_per_frame;
}
}
Call it every frame, passing it the amount of time that has elapsed since the last frame.
In response to your comments on the other 2 answers, you seem to be unsure of how to not go over a specific value.
To do this (for instance you want something in the range [0, 2] + c, where c is some constant determining the start of your index. So for instance, you could do the following:
void Player::nextFrame( int startIndex /* Changes based on your direction */) {
static unsigned short iIndex = 0;
// Increment and map value to the range [0, 2]
++iIndex;
iIndex %= 3;
// Note: this assumes that picNumber is a member function, else you
// will have to pass it by reference to this function.
picNumber = iIndex + startIndex;
}
This should do what you want (i.e. depending on the direction you change the start index to suit, and then it increments, mapping the value to the range [0, 2].
Hope this helps!
I am working on a college compsci project and I would like some help with a field of view algorithm. I works mostly, but in some situations the algorithm sees through walls and hilights walls the player should not be able to see.
void cMap::los(int x0, int y0, int radius)
{ //Does line of sight from any particular tile
for(int x = 0; x < m_Height; x++) {
for(int y = 0; y < m_Width; y++) {
getTile(x,y)->setVisible(false);
}
}
double xdif = 0;
double ydif = 0;
bool visible = false;
float dist = 0;
for (int x = MAX(x0 - radius,0); x < MIN(x0 + radius, m_Height); x++) { //Loops through x values within view radius
for (int y = MAX(y0 - radius,0); y < MIN(y0 + radius, m_Width); y++) { //Loops through y values within view radius
xdif = pow( (double) x - x0, 2);
ydif = pow( (double) y - y0, 2);
dist = (float) sqrt(xdif + ydif); //Gets the distance between the two points
if (dist <= radius) { //If the tile is within view distance,
visible = line(x0, y0, x, y); //check if it can be seen.
if (visible) { //If it can be seen,
getTile(x,y)->setVisible(true); //Mark that tile as viewable
}
}
}
}
}
bool cMap::line(int x0,int y0,int x1,int y1)
{
bool steep = abs(y1-y0) > abs(x1-x0);
if (steep) {
swap(x0, y0);
swap(x1, y1);
}
if (x0 > x1) {
swap(x0,x1);
swap(y0,y1);
}
int deltax = x1-x0;
int deltay = abs(y1-y0);
int error = deltax/2;
int ystep;
int y = y0;
if (y0 < y1)
ystep = 1;
else
ystep = -1;
for (int x = x0; x < x1; x++) {
if ( steep && getTile(y,x)->isBlocked()) {
getTile(y,x)->setVisible(true);
getTile(y,x)->setDiscovered(true);
return false;
} else if (!steep && getTile(x,y)->isBlocked()) {
getTile(x,y)->setVisible(true);
getTile(x,y)->setDiscovered(true);
return false;
}
error -= deltay;
if (error < 0) {
y = y + ystep;
error = error + deltax;
}
}
return true;
}
If anyone could help me make the first blocked tiles visible but stops the rest, I would appreciate it.
thanks,
Manderin87
You seem to be attempting to create a raycasting algorithm. I assume you have knowledge of how Bresenham's lines work, so I'll cut to the chase.
Instead of checking the visibility of each cell in the potential field of view, you only need to launch Bresenham lines from the FOV centre towards each cell at the very perimetre of the potential FOV area (the square you loop through). At each step of the Bresenham line, you check the cell status. The pseudocode for each ray would go like this:
while (current_cell != destination) {
current_cell.visible = true;
if (current_cell.opaque) break;
else current_cell = current_cell.next();
}
Please remember that raycasting produces tons of artifacts and you might also need postprocessing after you have calculated your field of view.
Some useful resources:
ray casting on Roguebasin
ray casting FOV implementation in libtcod (in C, you can dig through the repository for a C++ wrapper to it)
a FOV study
I am new to c++ and I have been practicing collision in a small game program that does nothing and I just can't get the collision right
So I use images loaded into variables
background = oslLoadImageFile("background.png", OSL_IN_RAM, OSL_PF_5551);
sprite = oslLoadImageFile("sprite.png", OSL_IN_RAM, OSL_PF_5551);
bush = oslLoadImageFile("bush.png", OSL_IN_RAM, OSL_PF_5551);
While there are variables stored like
sprite->x = 3;
if ( (sprite->x + spritewidth > bush->x) && (sprite->x < bush->x + bushwidth) && (sprite->y + spriteheight > bush->y) && (sprite->y < bush->y + bushheight) )
{
bushcol = 1;
}
else
{
bushcol = 0;
}
So when i press a button
if (osl_keys->held.down)
{
if (bushcol == 1)
{
sprite->y = bush->y + 38;
}
else
{
sprite->y += 3;
}
}
if (osl_keys->held.up)
{
if (bushcol == 1)
{
sprite->y = bush->y - 23;
}
else
{
sprite->y -= 3;
}
}
if (osl_keys->held.right)
{
if (bushcol == 1)
{
sprite->x = bush->x - 28;
}
else
{
sprite->x += 3;
}
}
if (osl_keys->held.left)
{
if (bushcol == 1)
{
sprite->x = bush->x + 28;
}
else
{
sprite->x -= 3;
}
}
i was thinking of things like
sprite->y = bushheight - 24;
but it doesnt work
Any suggestions?
I'd suggest making a function solely for the purpose of bounding box colision detection.
It could look like
IsColiding(oslImage item1, oslImage item2)
{
/* Perform check */
}
in which you perform the check if there is a collision between image 1 and image 2.
As for the algorithm you're trying to use check out this wikipedia for example AABB bounding box
Especially this part:
In the case of an AABB, this tests
becomes a simple set of overlap tests
in terms of the unit axes. For an AABB
defined by M,N against one defined by
O,P they do not intersect if (Mx>Px)
or (Ox>Nx) or (My>Py) or (Oy>Ny) or
(Mz>Pz) or (Oz>Nz).
I think you have the basic idea. Just check your work. Here is a simple version which compiles:
#import <stdlib.h>
typedef struct {
// I'm going to say x, y, is in the center
int x;
int y;
int width;
int height;
} Rect;
Rect newRect(int x, int y, int w, int h) {
Rect r = {x, y, w, h};
return r;
}
int rectsCollide(Rect *r1, Rect *r2) {
if (r1->x + r1->width/2 < r2->x - r2->width/2) return 0;
if (r1->x - r1->width/2 > r2->x + r2->width/2) return 0;
if (r1->y + r1->height/2 < r2->y - r2->height/2) return 0;
if (r1->y - r1->height/2 > r2->y + r2->height/2) return 0;
return 1;
}
int main() {
Rect r1 = newRect(100,200,40,40);
Rect r2 = newRect(110,210,40,40);
Rect r3 = newRect(150,250,40,40);
if (rectsCollide(&r1, &r2))
printf("r1 collides with r2\n");
else
printf("r1 doesnt collide with r2\n");
if (rectsCollide(&r1, &r3))
printf("r1 collides with r3\n");
else
printf("r1 doesnt collide with r3\n");
}
First of all i suppose you mean
sprite->y = bush->**y** - 3;
second i dont know what platform you are using, but often times the y-coordinates are inverted. i.e., y=0 corresponds to top of the screen. In that case your comparisons might not work.
third collision checking can quickly become complicated when you add rotation and non-rectangular objects to it. You should consider using CGAL or some other computational geometry algorithms library, which can handle polygon intersection.