No movement

Hi, I’m new here, sorry in advance. I’m starting with SDL and game development as a whole and decided to open random Youtube tutorial and create my first game (Sokoban) in C++ using SDL. I have a big issue though. Player has no movement. I’ve looked, compared code and I can’t see where the problem lies. Can anyone help?

player.h:

#pragma once

#include “game.h”

struct vec2 {
int x;
int y;
};

class Player {

public:
Player(class Game* g);
void move(int x, int y);
void draw(SDL_Renderer* renderer);

private:
vec2 pos;
SDL_Texture* texture;
SDL_Rect posRect, spriteRect;
class Game* game;
};

player.cpp:

#include “player.h”

Player::Player(Game* g) {
game = g;
texture = game->loadtexture(“textures/player.png”);
pos.x = 0;
pos.y = 0;
posRect = { pos.x, pos.y, TILE_SIZE, TILE_SIZE };
spriteRect = { 0, 0, TILE_SIZE, TILE_SIZE };
}

void Player::move(int x, int y) {

int newPlayerX = pos.x + x;
int newPlayerY = pos.y + y;

if (game->hitwall(newPlayerX, newPlayerY)) {
	return;
}


pos.x = newPlayerX;
pos.y = newPlayerY;

posRect.x = pos.x * TILE_SIZE;
posRect.y = pos.y * TILE_SIZE;

}
void Player::draw(SDL_Renderer* renderer) {
SDL_RenderCopy(renderer, texture, &spriteRect, &posRect);
}

game.h:

#pragma once

#include “utils.h”
#include “level_manager.h”
#include “player.h”

class Game {

public:
bool init();
void gameloop();
void shutdown();

SDL_Texture* loadtexture(string path);
bool hitwall(int x, int y);

private:
void handleevents();
void update();
void draw();

bool isrunning = true;

SDL_Window*  window = nullptr;
SDL_Renderer* renderer = nullptr;

SDL_Texture* wall_tex = nullptr;
SDL_Texture* ground_tex = nullptr;

class level_manager* levelmanager;
class Player* player;

};

game.cpp:

#include “game.h”

bool Game::init() {
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
cout << "SDL failed to initialize: " << SDL_GetError() << endl;
return false;
}

if (IMG_Init(IMG_INIT_PNG) == 0) {
    cout << "SDL_Image failed to initialize: " << IMG_GetError() << endl;
    return false;
}

window = SDL_CreateWindow("Sokoban", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SCREEN_WIDTH, SCREEN_HEIGHT, 0);
if(!window) {
    cout << "Window failed to initialize: " << SDL_GetError() << endl;
    return false;
}

renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if(!renderer) {
    cout << "Renderer failed to initialize: " << SDL_GetError() << endl;
    return false;
}
levelmanager = new level_manager();
levelmanager->loadlevel("levels/sokoban_lvl_test.txt");

wall_tex = loadtexture("textures/wall.png");
ground_tex = loadtexture("textures/ground.png");

player = new Player(this);

for (int i = 0; i < TILE_ROWS; i++) {
    for (int j = 0; j < TILE_COLS; j++) {
        if (levelmanager->levelmap[j][i] == 'p') {
            player->move(j, i);
        }
    }
}

return true;

}

void Game::gameloop() {
while(isrunning) {
handleevents();
update();
draw();
}
}

void Game::handleevents() {
SDL_Event event;

while (SDL_PollEvent(&event)) {
    if (event.type == SDL_QUIT) {
        isrunning = false;
    }
}

if (event.type == SDL_KEYDOWN) {
    switch (event.key.keysym.sym)
    {
    case SDLK_RIGHT:
        player->move(1, 0);
        break;
    case SDLK_LEFT:
        player->move(-1, 0);
        break;
    case SDLK_DOWN:
        player->move(0, 1);
        break;
    case SDLK_UP:
        player->move(0, -1);
        break;
    default:
        break;
    }
}

const Uint8* keystates = SDL_GetKeyboardState(NULL);

if (keystates[SDL_SCANCODE_ESCAPE]) {
    isrunning = false;
}

}

void Game::update() {

}

void Game::draw() {
SDL_SetRenderDrawColor(renderer, 40, 40, 40, 255);
SDL_RenderClear(renderer);

for(int i = 0; i < TILE_ROWS; i++) {
    for(int j = 0; j < TILE_COLS; j++) {

        SDL_Rect rect = { j * TILE_SIZE, i * TILE_SIZE, TILE_SIZE, TILE_SIZE };

        if(levelmanager->levelmap[j][i] == 'x') {
            SDL_RenderCopy(renderer, wall_tex, NULL, &rect);
        } else {
            SDL_RenderCopy(renderer, ground_tex, NULL, &rect);
        }

        //SDL_RenderFillRect(renderer, &rect);
    }

}

player->draw(renderer);

SDL_RenderPresent(renderer);

}

void Game::shutdown() {
SDL_DestroyTexture(wall_tex);
SDL_DestroyTexture(ground_tex);

SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);

IMG_Quit();
SDL_Quit();

}

SDL_Texture* Game::loadtexture(string path) {
SDL_Surface* temp_s = IMG_Load(path.c_str());
if (temp_s == NULL) {
cout << "Failed to load surface: " << IMG_GetError() << endl;
}

SDL_Texture* newtex = SDL_CreateTextureFromSurface(renderer, temp_s);
if (newtex == NULL) {
    cout << "Failed to conver the texture: " << SDL_GetError() << endl;
}

SDL_FreeSurface(temp_s);

return newtex;

}

bool Game::hitwall(int x, int y) {
return levelmanager->levelmap[x][y] == ‘x’;
}

Of course there is more code, like utils or levelmanager but this is those above are the only files where player is ever mentioned. Any help would be great

The problem is in the function above. The call of SDL_KEYDOWN must be inside the event loop.

The event loop works like this:
If we had the events KEY_DOWN e follow by MOUSE_BUTTON
First run
1-SDL_PoolEvents will put the KEY_DOWN in the event struct
2-then you can use the if (event.type == SDL_KEYDOWN) to process the event
Second run
1-SDL_PoolEvents will put the MOUSE_BUTTON in the event struct (the before was overwritten.
2-now you can use inside the while (event loop) the current avaliable event.
And soo on until the event poll return 0 (none events)

But, as a tip, I recommend you do something like this:
-change the read of inputs in some variable
Example:

struct PlayerInput {
  bool right = false, left = false, up = false, down = false;
  bool atack = false;
};

And so, you change the above obj members to true when press the key, and false when unpress.

Why this?
Because it facilitate your work and evitates reprocessing the inputs when the events can be repeating. For example: if the SDK_RIGHT will be repeating 3x, so the player in the above code will move with 3x in just one loop!
But with a PlayerInput, the repeating occours just once and you process the movement outside the event loop, basically in the update function.

Like said by the captain planet of code:
And remeber kids: input should be only inputs, update should be only updates, and draws should be only draws.
Go planet!!!

1 Like

Omg thank you so much!! I had been looking for the problem for a whole day, I will try your tip too

1 Like