Moving Rectangle in SFML, how to make it smoother? - c++

This is my code:
#include <SFML/Graphics.hpp>
#include <iostream>
#include <Windows.h>
int main()
{
sf::RenderWindow window(sf::VideoMode(800, 600), "SFML Game");
window.setFramerateLimit(200);
//Variable that keeps the game loop running
bool play = true;
//Event object holding all events
sf::Event event;
//States for button/events
bool Left = false;
bool Right = false;
bool space = false;
//Variables
int rectXPosition = 375; //Rectangles X position
int rectYPosition = 400; //Rectangles Y position
//Images
sf::Texture image1;
if (image1.loadFromFile("Images/GreekSoldier.png") == -1)
{
std::cout << "FAILED!!!" << "\n";
return 1;
}
//Render shapes
sf::RectangleShape rect;
rect.setSize(sf::Vector2f(77, 150)); //Width and height
rect.setPosition(375, 400); //Position
rect.setFillColor(sf::Color::White); //Color
rect.setTexture(&image1);
//Game loop
while (play == true)
{
//EVENTS
while (window.pollEvent(event))
{
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Left)
{
//Set the state to true
Left = true;
}
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Right)
{
Right = true;
}
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Space)
{
space = true;
}
//Event type is window closed
if (event.type == sf::Event::Closed)
{
//Set play to false in order to stop the game loop
play = false;
}
}
//LOGIC
if (Left == true)
{
int x = 0;
for (x = 1; x < 2; x++)
{
rectXPosition-=5; //X position variable of the rectangle
rect.setPosition(rectXPosition, rectYPosition);
window.clear();
window.draw(rect);
window.display();
Sleep(5);
}
Left = false;
}
if (Right == true)
{
int x = 0;
for (x = 1; x < 2; x++)
{
rectXPosition+= 5; //X position variable of the rectangle
rect.setPosition(rectXPosition, rectYPosition);
window.clear();
window.draw(rect);
window.display();
Sleep(5);
}
Right = false;
}
if (space == true)
{
int x = 0;
for (x = 1; x < 15; x++)
{
rectYPosition-= 3; //X position variable of the rectangle
rect.setPosition(rectXPosition, rectYPosition);
window.clear();
window.draw(rect);
window.display();
Sleep(10);
}
for (x = 1; x < 15; x++)
{
rectYPosition+= 3; //X position variable of the rectangle
rect.setPosition(rectXPosition, rectYPosition);
window.clear();
window.draw(rect);
window.display();
Sleep(10);
}
space = false;
}
//RENDERING
window.clear();
//Draw the rectangle shape
window.draw(rect);
window.display();
}
//Clean up and close the window
window.close();
return 0;
}
How would I make the movement more smoother? How do I make it so the rectangle(the soldier) can jump and move at the same time? And how do I stop it so, when holding down left it doesn't take 3 seconds for it to actually move?
I've added a youtube video if you guys can't be bothered to compile it and just want to see what I'm talking about.
https://www.youtube.com/watch?v=g_JLwGh1Wgo

The KeyPressed events are generated when someone presses a key and then repeat every so often (but not every frame, which makes it not smooth). They are intended to be used for text input, for example.
Pressing another key will also interrupt the repetition of the events for the previous key, so your other problem has the same cause.
You should handle the KeyPressed events only for some instant actions, like pressing a menu button. In this particular example you should get rid of this altogether and use only sf::Keyboard by replacing the line:
if (Left == true)
with:
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
and so on...
I would explain why sf::Keyboard is the right thing here, but the documentation does it very well.
Another big problem is that you have multiple loops that don't let anything else happen in the game. You should get yourself familiar with the concepts of event loops, velocity and gravity (and make corresponding variables).
When the player presses Space you shouldn't hardcode the character to move N pixels up and N pixels down while stopping every other possible action in the game, but instead change the character's vertical velocity and use it to change the character's position in every frame (and also have that velocity be reduced by gravity).
The most important point to take from this is you must have only 1 main event loop that calls window.display.

Related

Window keeps getting black and white when left mouse button get pressed - C++, SFML

This is my code, I wanted to make a clone of Paint, but when I press the mouse left button to draw, the window gets black first and then it checks the button pression again and then draws the black circles. Another problem that I have is that it's too slow to draw the circles and when I move the mouse while pressing the button, I get some blank spaces beetween the black circles.
#include <iostream>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/Audio.hpp>
#include <SFML/Network.hpp>
#include <SFML/System.hpp>
using std::cin, std::cout, std::string;
using namespace sf;
int main() {
RenderWindow w(VideoMode(1280, 900), "w", Style::Close);
Event e;
w.clear(Color::White);
w.display();
CircleShape pixel(5.f);
pixel.setFillColor(Color::Black);
int i = 0;
while (w.isOpen()) {
while (w.pollEvent(e)) {
if (e.type == Event::Closed) {
w.close();
}
while (e.type == Event::MouseButtonPressed) {
pixel.setPosition(Mouse::getPosition(w).x - 5, Mouse::getPosition(w).y - 5);
w.draw(pixel);
w.display();
}
}
}
return 0;
}
So, I tried two approaches, yours - slightly modified and my with SF::VertexArray.
First of all, you have to clear your window (scroll down and read the red box) to not have a flashy screen, but how to keep what you drew?
The solution to that is off-screen drawing - SF::RenderTexture.
Second, the gaps in lines - there are not enough frames to keep track of your cursor. What it means is that your mouse between frames moved more than your "pixel" width or height.
How to fix it? Have something like a cursor movement buffer or just predict where your mouse was between frames by using a line equation (gets "blocky" on low fps).
Your approach:
int main() {
RenderWindow w(VideoMode(1280, 900), "w", Style::Close);
Event e;
CircleShape pixel(5.f);
pixel.setFillColor(Color::Black);
RenderTexture renderTexture;
renderTexture.create(1280, 900);
sf::Sprite sprite(renderTexture.getTexture());
bool mousePressed = false;
Vector2f lastMousePos(Mouse::getPosition(w).x - 5, Mouse::getPosition(w).y - 5);
Vector2f newMousePos;
float a;
float b;
Vector2f deltaPos;
while (w.isOpen()) {
newMousePos = Vector2f(Mouse::getPosition(w).x - 5, Mouse::getPosition(w).y - 5);
while (w.pollEvent(e)) {
if (e.type == Event::Closed) {
w.close();
}
if (e.type == Event::MouseButtonPressed) {
mousePressed = true;
}
if (e.type == Event::MouseButtonReleased) {
mousePressed = false;
}
if (e.type == Event::KeyPressed && e.key.code == Keyboard::Space) {
renderTexture.clear(Color::White); //clearing window
}
}
w.clear(Color::Blue);
if (mousePressed) {
deltaPos = lastMousePos - newMousePos;
a = deltaPos.y / deltaPos.x; // y = a*x + b (Line equation)
b = lastMousePos.y - (deltaPos.y / deltaPos.x * lastMousePos.x);
if (deltaPos.x != 0) {
if (deltaPos.x < 0) {
for (float x = lastMousePos.x; x < newMousePos.x;) {
pixel.setPosition(x, x * a + b);
x += 0.1f;
renderTexture.draw(pixel);
}
}
else
for (float x = lastMousePos.x; x > newMousePos.x;) {
pixel.setPosition(x, x * a + b);
x -= 0.1f;
renderTexture.draw(pixel);
}
}
else {
if (deltaPos.y != 0) {
if (deltaPos.y < 0) {
for (float y = lastMousePos.y; y < newMousePos.y;) {
pixel.setPosition(lastMousePos.x, y);
y += 0.1f;
renderTexture.draw(pixel);
}
}
else
for (float y = lastMousePos.y; y > newMousePos.y;) {
pixel.setPosition(lastMousePos.x, y);
y -= 0.1f;
renderTexture.draw(pixel);
}
}
else {
pixel.setPosition(lastMousePos);
renderTexture.draw(pixel);
}
renderTexture.display();
}
}
sf::Sprite sprite(renderTexture.getTexture());
w.draw(sprite);
w.display();
lastMousePos = newMousePos;
}
return 0;
}
The other version, however, has a big downside - lines have no thickness. You can fix it using sf::quads instead of sf::lines, but it needs more coding and math.
sf::Lines solution:
int main() {
RenderWindow w(VideoMode(1280, 900), "w", Style::Close);
Event e;
RenderTexture renderTexture;
renderTexture.create(1280, 900);
sf::Sprite sprite(renderTexture.getTexture());
bool mousePressed = false;
Vector2f lastMousePos;
Vector2f newMousePos(Mouse::getPosition(w).x, Mouse::getPosition(w).y);
VertexArray myLine(Lines, 2);
myLine[0].color = Color::Black;
myLine[1].color = Color::Black;
while (w.isOpen()) {
lastMousePos = newMousePos;
newMousePos = Vector2f(Mouse::getPosition(w).x, Mouse::getPosition(w).y);
while (w.pollEvent(e)) {
if (e.type == Event::Closed) {
w.close();
}
if (e.type == Event::MouseButtonPressed) {
mousePressed = true;
}
if (e.type == Event::MouseButtonReleased) {
mousePressed = false;
}
if (e.type == Event::KeyPressed && e.key.code == Keyboard::Space) {
renderTexture.clear(Color::White);
}
}
w.clear(Color::White);
myLine[0].position = lastMousePos;
myLine[1].position = newMousePos;
if (mousePressed) {
renderTexture.draw(myLine);
renderTexture.display();
}
sf::Sprite sprite(renderTexture.getTexture());
w.draw(sprite);
w.display();
}
return 0;
}
But in both versions there is a bug that looks like this:
And I can't figure out why, even though in the 2nd code I don't do any math and it still occurs. So maybe someone will figure it out (made this post as a Community wiki, edit if you know what might be wrong), but for now treat my code as a guide, not as a solution.

Game does not launch from Menu

I am new to Game Development. I managed to create a simple game (like Space Invaders) as well as a simple start Menu using C++ and SFML. However, upon pressing "Enter" on the main menu, the game is not being launched. How do I link it properly? I appreciate your help. This is not homework.
main.cpp codes
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include "GameObjectManager.h"
#include "Menu.h"
using namespace std;
int main()
{
sf::Texture galaxyBackgroundTexture;
sf::Sprite galaxyBackground;
if (!galaxyBackgroundTexture.loadFromFile("Textures/galaxybackground.png")) {
cout << "Failed to load Image" << endl;
}
galaxyBackground.setTexture(galaxyBackgroundTexture);
sf::RenderWindow window(sf::VideoMode(1200, 800), "Space Invader Test");
Menu menu(window.getSize().x, window.getSize().y);
window.setFramerateLimit(144);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Key::Return)
{
menu.GetPressedItem();
cout << "Play button has been pressed." << endl;
GameObjectManager* gameObjectManagerManager = new GameObjectManager(&window);
gameObjectManager->update();
gameObjectManager->render(window);
}
else if (event.type == sf::Event::Closed)
{
window.close();
}
else if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape)
{
window.close();
}
}
window.clear();
window.draw(galaxyBackground);
menu.draw(window);
window.display();
}
return 0;
}
Menu.h
#pragma once
#include "SFML/Graphics.hpp"
#define MAX_NUMBER_OF_ITEMS 2
class Menu
{
public:
Menu(float width, float height);
~Menu();
void draw(sf::RenderWindow& window);
int GetPressedItem() { return selectedItemIndex; }
private:
int selectedItemIndex;
sf::Font font;
sf::Text menu[MAX_NUMBER_OF_ITEMS];
};
Menu.cpp
#include "Menu.h"
Menu::Menu(float width, float height)
{
if (!font.loadFromFile("arial.ttf"))
{
cout << "can't load font" << endl;
}
// initialise Menu items
menu[0].setFont(font);
menu[0].setColor(sf::Color::Red);
menu[0].setString("Play");
menu[0].setPosition(sf::Vector2f(width / 2, height / (MAX_NUMBER_OF_ITEMS + 1) * 1));
// EXIT
menu[1].setFont(font);
menu[1].setColor(sf::Color::White);
menu[1].setString("Exit");
menu[1].setPosition(sf::Vector2f(width / 2, height / (MAX_NUMBER_OF_ITEMS + 1) * 2));
}
selectedItemIndex = 0;
Menu::~Menu()
{
}
void Menu::draw(sf::RenderWindow &window)
{
for (int i = 0; i < MAX_NUMBER_OF_ITEMS; i++)
{
window.draw(menu[i]);
}
}
The console window would print:
"Play button has been pressed"
But it does not proceed to the game. Nothing else happens.
"window redefinition" error occurs because both your RenderWindow objects have the same identifiers (i.e. window). You might want to change the name of the second window or better yet use the same window.
The second error, sf::Text::setColor() is deprecated means that it is no longer "useable" or "is not suggested to be used". SFML has two new better functions for this:
sf::Text::setFillColor() : to set the fill of your text.
sf::Text::setOutlineColor() : to give your text an outline (you also need to do change the thickness using setOutlineThickness()).
Moreover, I'd suggest you to use a State Machine for different scenes instead of two separate windows. It really isn't that difficult and will help you learn a few more things. You're somewhat already achieving this with your gameObjectManager. You just need to abstract it and implement it for your menu class as well. And since you have only two scenes you can simply use an integer or boolean to switch between these two.
EDIT: an idea of what you need to do to your main.cpp file
int main()
{
sf::RenderWindow window(sf::VideoMode(1200, 800), "Space Invader Test");
GameObjectManager* gameObjectManagerManager = nullptr;
bool inGame = false; //true = game has started, false = menu screen
while (window.isOpen())//this is the main loop
{
sf::Event event;
while (window.pollEvent(event)) //this is the event loop
{
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Key::Return)
{
if(!inGame)
{
if(menu.GetPressedItem() == PlayButton) //assuming your function returns which button in the menu has been pressed
{
cout << "Play button has been pressed." << endl;
inGame = true;
gameObjectManagerManager = new GameObjectManager(&window);
}
else
{
window.close(); //since the other button is exit
}
}
}
if (event.type == sf::Event::Closed) window.close();
if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) window.close();
}
//this is the place where you call your updates
if(inGame) gameObjectManagerManager->update();
window.clear();
window.draw(galaxyBackground);
if(!inGame)menu.draw(window);
if(inGame) gameObjectManagerManager->render();
window.display();
}
return 0;
}

is there's a way to make SFML Event MouseButtonPressed work after one click?

so I am trying to make a small game using sfml but I am stuck at MouseButton event I try to choose from the menu I has to spam clicks for it to work I thought the problem was my mouse so I tried another one and it didn't solve the problem
void draw(sf::RenderWindow &window)
{
mousePos = window.mapPixelToCoords(sf::Mouse::getPosition(window));
window.draw(Background_rect);
for (int i = 0; i < 3; i++)
{
window.draw(rect[i]);
if (rect[i].getGlobalBounds().contains(mousePos))
{
while (window.pollEvent(event))
{
if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left)
{
mainMenuPress = mainMenuSelected[i];
}
}
}
}
}
so i think the code is good but I don't understand why it doesn't work after 1 click
i couldn't solve it as event mouseButtonPressed i don't know what's the problem with event type but it didn't work for me i had to change the condition as mouse isButtonPressed and it worked
void draw(sf::RenderWindow &window)
{
mousePos = window.mapPixelToCoords(sf::Mouse::getPosition(window));
window.draw(Background_rect);
for (int i = 0; i < 3; i++)
{
if (rect[i].getGlobalBounds().contains(mousePos))
{
if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) //Changed
{
mainMenuPress = mainMenuSelected[i];
}
}
window.draw(rect[i]);
}
}
thanks for your help
As you have shown more code, I thing this for loop might ba the problem -
you should move pollEvent to the place where you update any stuff, and then make any ifs and loops inside it
// in update
while (window.pollEvent(event))
{
for (int i = 0; i < 3; i++)
{
if (event.type == sf::Event::MouseButtonPressed && event.mouseButton.button == sf::Mouse::Left)
{
if (rect[i].getGlobalBounds().contains(mousePos))
{
mainMenuPress = mainMenuSelected[i];
}
}
}
}
// and then draw

C++ Sfml, How can I create a collision box for my Sprite

I have a question to sfml.
I am relative new to C++ and sfml.
I am trying to create a Space Invaders type of game.
I currently have some problems with collision,
between the enemy's bullets and the rocket,
I'm talking about line 145. This line:
if (collide(rocket, enemy_bullets[i]))
{
window.close();
}
Can you create something like a collision box?,
because I don't want to collide with the whole rocket sprite,
I only want to collide with parts of it, e.g not the transparent parts.
#include <SFML/Graphics.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <chrono>
void print(std::string string)
{
std::cout << string << std::endl;
}
sf::CircleShape create_bullet(sf::Vector2f possition, sf::Int16 offset)
{
sf::CircleShape circel;
circel.setRadius(10);
circel.setPosition(possition.x + offset, possition.y);
return circel;
}
bool collide(sf::Sprite a, sf::CircleShape b)
{
return a.getGlobalBounds().intersects(b.getGlobalBounds());
}
int main()
{
int speed;
speed = 25;
sf::RenderWindow window(sf::VideoMode(1200, 800), "Space Invaders", sf::Style::Titlebar | sf::Style::Close);
sf::Texture rocket_texture;
if (!rocket_texture.loadFromFile("data/rocket.png"))
{
print("Problem with loding file data/rocket.png");
exit(-1);
}
sf::Texture enemy_texture;
if (!enemy_texture.loadFromFile("data/enemy.png"))
{
print("Problem with loding file data/enemy.png");
exit(-1);
}
sf::Sprite rocket;
sf::Sprite enemy;
std::chrono::milliseconds couldown = std::chrono::milliseconds(0);
std::chrono::milliseconds time;
std::chrono::milliseconds enemy_couldown = std::chrono::milliseconds(0);
bool enemy_fire = false;
float bulletspeed = 0.02;
// sf::CircleShape test = create_bullet();
int changex;
rocket.setTexture(rocket_texture);
rocket.setPosition(500, 650);
rocket.scale(0.5, 0.5);
std::vector<sf::Sprite> enemy_list;
std::vector<sf::CircleShape> player_bullets;
std::vector<sf::CircleShape> enemy_bullets;
enemy.setTexture(enemy_texture);
enemy.scale(0.2, 0.2);
for (int i =0; i<8; i++)
{
enemy.setPosition(i * 150, 400);
enemy_list.push_back(enemy);
}
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
changex = 0;
switch (event.type)
{
// window closed
case sf::Event::Closed:
window.close();
break;
// key pressed
case sf::Event::KeyPressed:
if (event.key.code == sf::Keyboard::A)
{
if (rocket.getPosition().x >= 0 )
{
changex = changex - speed;
}
}
else if (event.key.code == sf::Keyboard::D)
{
if (rocket.getPosition().x <= 1100)
{
changex = changex + speed;
}
}
else if (event.key.code == sf::Keyboard::Space)
{
time = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
if (couldown < time - std::chrono::milliseconds(100)){
couldown = time;
player_bullets.push_back(create_bullet(rocket.getPosition(), 47));
}
}
break;
default:
break;
}
rocket.move(changex, 0);
}
window.clear();
window.draw(rocket);
//swindow.draw(test);
for (int i=0; i<player_bullets.size();i++)
{
player_bullets[i].move(0,-bulletspeed);
window.draw(player_bullets[i]);
if (player_bullets[i].getPosition().y < 0)
{
player_bullets.erase(player_bullets.begin()+i);
}
}
for (int i = 0; i < enemy_bullets.size(); i++)
{
enemy_bullets[i].move(0, bulletspeed);
window.draw(enemy_bullets[i]);
if (enemy_bullets[i].getPosition().y > 800)
{
enemy_bullets.erase(enemy_bullets.begin() + i);
}
if (collide(rocket, enemy_bullets[i]))
{
window.close();
}
}
time = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch());
if (enemy_couldown < time - std::chrono::milliseconds(2000))
{
enemy_couldown = time;
enemy_fire = true;
}
// Draw all Enemys
for (int i = 0; i < enemy_list.size(); i++)
{
for (int j = 0; j < player_bullets.size(); j++)
{
if (collide(enemy_list[i], player_bullets[j]))
{
enemy_list.erase(enemy_list.begin() + i);
}
}
if (enemy_fire)
{
enemy_couldown = time;
// ADD: Move enemys
enemy_bullets.push_back(create_bullet(enemy_list[i].getPosition(), 13));
}
window.draw(enemy_list[i]);
}
enemy_fire = false;
window.display();
}
return 0;
}
If you have any idea how to do that,
I would like to hear it.
Thanks, in advance
You can make a class that derives from sf::Sprite that has a sf::FloatRect for a hitbox, you will need to make a function to set the hitbox.
class Sprite : public sf::Sprite {
sf::FloatRect hitbox;
}
You can move the hitbox to the sprites location with:
getTransform().transformRect(hitbox);
I have used this in the past for hitboxes with SFML.
Edit, Here is an full example program:
#include <SFML/Graphics.hpp>
/// custom sprite class with hitbox
class HitboxSprite : public sf::Sprite {
public:
/// sets the hitbox
void setHitbox(const sf::FloatRect& hitbox) {
m_hitbox = hitbox;
}
/// gets the hitbox (use this instead of getGlobalBounds())
sf::FloatRect getGlobalHitbox() const {
return getTransform().transformRect(m_hitbox);
}
private:
sf::FloatRect m_hitbox;
};
int main() {
sf::RenderWindow window(sf::VideoMode(256, 128), "Example");
// create two sprites, player and enemy
HitboxSprite player;
player.setPosition({ 64.f, 64.f });
HitboxSprite enemy;
enemy.setPosition({ 128.f, 64.f });
enemy.setColor(sf::Color::Red);
// create sprite texture and apply to sprites
sf::Texture square_texture;
square_texture.loadFromFile("32x32square.png");
player.setTexture(square_texture);
enemy.setTexture(square_texture);
// set custom hitboxes
// (this one starts (8, 8) pixels from the top left and has a size of (16, 16)
// (this means the hitbox will be 1/2 of the square in the middle)
player.setHitbox({ 8.f, 8.f, 16.f, 16.f });
enemy.setHitbox({ 8.f, 8.f, 16.f, 16.f });
sf::Clock clock;
while (window.isOpen()) {
// process events
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed)
window.close();
}
const float dt = clock.restart().asSeconds();
constexpr float player_speed = 128.f;
// move player with arrow keys
player.move({
player_speed * dt * (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) - sf::Keyboard::isKeyPressed(sf::Keyboard::Left)),
player_speed * dt * (sf::Keyboard::isKeyPressed(sf::Keyboard::Down) - sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
});
// check for collision
const bool colliding = player.getGlobalHitbox().intersects(enemy.getGlobalHitbox());
// set background color based on collision
window.clear(colliding ? sf::Color::Green : sf::Color::Blue);
// draw sprites
window.draw(enemy);
window.draw(player);
// display
window.display();
}
return 0;
}
If you need any part explained let me know.
Here is the translucent png I made with the center part being the hitbox:

How to switch back and forth between two different sections on a sprite sheet while user still holds a key using SFML

So my only goal here is to move the sprite left and right, and while the sprite is moving, I want to switch between two different parts of my sprite sheet every half a second.
I've tried everything I could think of so I left it working and without only the implementation of the needed feature. (EDIT: I've changed it in an attempt to try to follow advice given by another user) I've looked online, but nothing I could find answered my question as far as I could tell. I'm sure there's something I'm overlooking and that it is quite simple, but that's why I need your input.
The third parts of the sprite sheet I want to use is at x = 800, animations facing left are on top at y = 0, and the animations facing right are on bottom at y = 600
Here is the sprite sheet I am using
The first ones are for standing still, and the second two on either row are the ones I want to switch between while "walking"
At any rate, here is my code:
#include "pch.h"
#include <iostream>
#include "SFML/Graphics.hpp"
#include <random>
#include <unordered_map>
enum State
{
Walking,
Standing
};
enum Direction
{
Left,
Right
};
int main(int argc, char ** argv)
{
/* Gregory */
sf::Texture GregorySpriteSheet_T;
GregorySpriteSheet_T.loadFromFile("Images/GregorySpriteSheet.png");
sf::IntRect GregorySpriteRect(0, 600, 400, 600);
sf::Sprite Gregory(GregorySpriteSheet_T, GregorySpriteRect);
sf::RenderWindow renderWindow(sf::VideoMode(1600,800), "SFML 2 Demo");
sf::Event event;
sf::Time timePerFrame = sf::seconds(1.0f / 60.0f);
sf::Clock deltaClock;
sf::Time timeSinceLastUpdate = sf::Time::Zero;
State isWalking{ Standing };
sf::Clock walkClock;
Direction direction = Right;
/* RENDER WINDOW LOOP */
while (renderWindow.isOpen())
{
sf::Time deltaTime = deltaClock.restart();
timeSinceLastUpdate += deltaTime;
while (timeSinceLastUpdate >= timePerFrame)
{
timeSinceLastUpdate -= timePerFrame;
while (renderWindow.pollEvent(event))
{
if (event.type == sf::Event::EventType::Closed || sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
{
renderWindow.close();
}
if (event.type == sf::Event::EventType::KeyPressed)
{
if (event.key.code == sf::Keyboard::Right)
{
isWalking = Walking;
direction = Right;
}
}
if (event.type == sf::Event::EventType::KeyReleased)
{
if (event.key.code == sf::Keyboard::Right)
{
isWalking = Standing;
}
}
if (event.type == sf::Event::EventType::KeyPressed)
{
if (event.key.code == sf::Keyboard::Left)
{
isWalking = Walking;
direction = Left;
}
}
if (event.type == sf::Event::EventType::KeyReleased)
{
if (event.key.code == sf::Keyboard::Left)
{
isWalking = Standing;
}
}
}
if (isWalking == Walking)
{
if (direction == Right)
GregorySpriteRect.top = 600;
if (direction == Left)
GregorySpriteRect.top = 0;
if (GregorySpriteRect.left == 0)
GregorySpriteRect.left = 400;
if ((int(walkClock.getElapsedTime().asSeconds() / 1.5f) % 2) == 1)
{
if (GregorySpriteRect.left == 400)
GregorySpriteRect.left == 800;
if (GregorySpriteRect.left == 800)
GregorySpriteRect.left == 400;
walkClock.restart();
}
}
{
using kb = sf::Keyboard;
if (kb::isKeyPressed(kb::Right))
{
Gregory.move(400 * timePerFrame.asSeconds(), 0.0f);
direction = Right;
isWalking = Walking;
}
if (kb::isKeyPressed(kb::Left))
{
Gregory.move(-400 * timePerFrame.asSeconds(), 0.0f);
direction = Left;
isWalking = Walking;
}
if (kb::isKeyPressed(kb::Right) && kb::isKeyPressed(kb::Left))
{
isWalking = Standing;
}
}
if (isWalking == Standing)
{
GregorySpriteRect.left = 0;
if (direction == Right)
GregorySpriteRect.top = 600;
if (direction == Left)
GregorySpriteRect.top = 0;
}
}
Gregory.setTextureRect(GregorySpriteRect);
renderWindow.clear();
renderWindow.draw(Gregory);
renderWindow.display();
}
/* END RENDER WINDOW LOOP */
}
Here is one suggestion, but there are plenty of alternatives:
First, rewrite your event handling slightly such that you can differentiate between the first time a key is held and subsequent times. A simple way is to have an enum State { Standing, Walking } and set it to the appropriate value in your key handler (eg "not walking and key is held? set state to walking")
Then, when the player starts walking, (re)start a "walk" clock.
Finally, when you are actually rendering your player, check this clock and divide the expired time since walking by your walking period (this determines how long one of the walking frames should be held). If there were an even number of such walking periods, use the left walking sprite, otherwise the right walking sprite.
This technique easily generalizes to larger sets of walking frames, of course.
So for anybody that winds up here in the future, I was missing a vital line:
Gregory.setTextureRect(GregorySpriteRect);
Either in my "if" statement for the clock under the "if (isWalking == Walking)", or under it (like I've done here). Pasted the full code below. Works for both directions now. Not sure how optimal this is but if you're an amateur like me this should help.
Also mind that I've reverted back some of the changes suggested from Botje's answer. Although I must credit him for helping me very much with the recommendation of using enum's for Walking and Standing, as well as direction. I also used a ">" operator rather than the "==" operator as suggested by my professor due to rounding errors of float point variables.
Cheers!
#include "pch.h"
#include <iostream>
#include "SFML/Graphics.hpp"
#include <random>
#include <unordered_map>
enum State
{
Walking,
Standing
};
enum Direction
{
Left,
Right
};
int main(int argc, char ** argv)
{
/* Gregory */
sf::Texture GregorySpriteSheet_T;
GregorySpriteSheet_T.loadFromFile("Images/GregorySpriteSheet.png");
sf::IntRect GregorySpriteRect(0, 600, 400, 600);
sf::Sprite Gregory(GregorySpriteSheet_T, GregorySpriteRect);
sf::RenderWindow renderWindow(sf::VideoMode(1600,800), "SFML 2 Demo");
sf::Event event;
sf::Time timePerFrame = sf::seconds(1.0f / 60.0f);
sf::Clock deltaClock;
sf::Time timeSinceLastUpdate = sf::Time::Zero;
State isWalking{ Standing };
sf::Clock walkClock;
Direction direction = Right;
/* RENDER WINDOW LOOP */
while (renderWindow.isOpen())
{
sf::Time deltaTime = deltaClock.restart();
timeSinceLastUpdate += deltaTime;
while (timeSinceLastUpdate >= timePerFrame)
{
timeSinceLastUpdate -= timePerFrame;
while (renderWindow.pollEvent(event))
{
if (event.type == sf::Event::EventType::Closed || sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
{
renderWindow.close();
}
if (event.type == sf::Event::EventType::KeyPressed)
{
if (event.key.code == sf::Keyboard::Right)
{
isWalking = Walking;
direction = Right;
GregorySpriteRect.top = 600;
}
}
if (event.type == sf::Event::EventType::KeyReleased)
{
if (event.key.code == sf::Keyboard::Right)
{
isWalking = Standing;
}
}
if (event.type == sf::Event::EventType::KeyPressed)
{
if (event.key.code == sf::Keyboard::Left)
{
isWalking = Walking;
direction = Left;
GregorySpriteRect.top = 0;
}
}
if (event.type == sf::Event::EventType::KeyReleased)
{
if (event.key.code == sf::Keyboard::Left)
{
isWalking = Standing;
}
}
}
if (isWalking == Walking)
{
if (direction == Right)
GregorySpriteRect.top = 600;
else if (direction == Left)
GregorySpriteRect.top = 0;
else
GregorySpriteRect.top = 600;
if (GregorySpriteRect.left == 0)
GregorySpriteRect.left = 400;
if (walkClock.getElapsedTime().asSeconds() > 0.5f)
{
if (GregorySpriteRect.left == 400)
GregorySpriteRect.left = 800;
else if (GregorySpriteRect.left == 800)
GregorySpriteRect.left = 400;
else
GregorySpriteRect.left += 0;
walkClock.restart();
}
Gregory.setTextureRect(GregorySpriteRect);
}
{
using kb = sf::Keyboard;
if (kb::isKeyPressed(kb::Right))
{
Gregory.move(400 * timePerFrame.asSeconds(), 0.0f);
direction = Right;
}
if (kb::isKeyPressed(kb::Left))
{
Gregory.move(-400 * timePerFrame.asSeconds(), 0.0f);
direction = Left;
}
if (kb::isKeyPressed(kb::Right) && kb::isKeyPressed(kb::Left))
{
isWalking = Standing;
direction = Right;
}
}
if (isWalking == Standing)
{
GregorySpriteRect.left = 0;
if (direction == Right)
GregorySpriteRect.top = 600;
if (direction == Left)
GregorySpriteRect.top = 0;
}
}
Gregory.setTextureRect(GregorySpriteRect);
renderWindow.clear();
renderWindow.draw(Gregory);
renderWindow.display();
}
/* END RENDER WINDOW LOOP */
}