SFML sprite.move() not working - c++

I'm making a game in SFML and I'm trying to add shooting, but for some reason sprite.move() doesn't seem to work. Here's the relevant code:
Weapon.cpp
void Weapon::update(float delta, sf::RenderWindow& window, Player player) {
for (auto s : shots) {
s.move(delta);
s.draw(window);
}
switch (type) {
case RANGED:
if (sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
shots.push_back(Shot(shot, player.position, Helper::getMousePos(window)));
Helper::log(std::to_string(shots.size()));
}
break;
case MELEE:
break;
}
}
Shot.cpp
Shot::Shot(sf::Sprite sprite_, sf::Vector2f origin, sf::Vector2f target)
{
sprite = sprite_;
sprite.setPosition(origin);
direction = Helper::normalizeVector(target - origin);
speed = 200;
}
void Shot::move(float delta) {
sprite.move(direction * speed * delta);
}
void Shot::draw(sf::RenderWindow& window) {
window.draw(sprite);
}
What happens is the shots spawn at the player's location, but they don't move. Rather, they kinda vibrate as if they're trying to move but something is stopping them. Let me know if you need more code.

The problem is most likely in the loop
for (auto s : shots) {
s.move(delta);
s.draw(window);
}
Here you create the loop variable s by value, meaning it's a copy of the elements in the container. Modifying a copy will of course not modify the original.
Instead loop using references:
// Notice the ampersand here
// |
// v
for (auto& s : shots) {
s.move(delta);
s.draw(window);
}

Related

C++ Struct Members not Updating in Funtion

Firstly, while not new to programming, I am very new to C++, so please bear with me.
I am using the Raylib library to attempt making a particle system for a game.
This consists of a struct with a few private members and public functions:
struct Particle {
Particle() {
mPosVector = {(float)GetMouseX(), (float)GetMouseY()};
mVelVector = {(float)GetRandomValue(15, 70)/100, (float)GetRandomValue(15, 70)/100};
mSize = GetRandomValue(5, 15);
}
void update(double deltaTime) {
mPosVector.x += mVelVector.x;
mPosVector.y += mVelVector.y;
}
void draw() {
DrawRectangleV(mPosVector, {(float)mSize, (float)mSize}, WHITE);
}
private:
Vector2 mPosVector;
Vector2 mVelVector;
int mSize;
};
The Vector2 type is defined by Raylib:
struct Vector2 {
float x;
float y;
};
In my main function I have an std::vector storing Particles. A particle gets added when the left mouse button is pressed. I loop through the Particles vector twice, once for updating position based on velocity and once for drawing. I was originally doing these both in one loop, but was still getting the problem that I will get onto, so tried it this way.
This is the current code:
std::vector<Particle> particles = {Particle()};
while (!WindowShouldClose()) {
deltaTime = GetFrameTime();
if (IsMouseButtonDown(0)) {
particles.push_back(Particle());
}
for (Particle part : particles) {
part.update(deltaTime);
}
BeginDrawing();
ClearBackground(BLACK);
DrawFPS(10, 10);
DrawText((numToString<double>(deltaTime*1000).substr(0, 5) + "ms").c_str(), 10, 40, 20, WHITE);
for (Particle part : particles) {
part.draw();
}
EndDrawing();
So, my problem: While particles are being instantiated as expected while pressing the left mouse button and being drawn, for some reason their positions are not being updated by their velocity. I have tried printing debug information to the console, such as the velocity, and it is as expected, but for some unknown reason to me (probably just me being stupid) their positions aren't being updated.
Any help would be greatly appreciated.
for (Particle part : particles) {
part.update(deltaTime);
}
this is making a copy of each entry , you need
for (Particle &part : particles) {
part.update(deltaTime);
}
to get a reference to the object in the vector to update it in place
To understand, think that the ranged for is just short hand for this
for(int i = 0; i < particles.size(); i++)
{
// this line copies the value
particle p = particles[i];
}
whereas the one with & in it does
for(int i = 0; i < particles.size9); i++)
{
// this line gets a reference to the ith entry
particle &p = particles[i];
}
Its nothing special to do with the ranged for loop.

How do I get a destructor on an object in a vector not to throw a failed assertion? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I'm programming a Breakout game in C++. I'm having a HUGE problem that's preventing me from giving the game multi-ball functionality. I think it has something to do with the destructor. Have a look:
for loop for the balls (Driver.cpp):
for (Ball& b : balls) { // Loops over all balls
(...)
// Collision for when you miss
if (b.getYPos() > HEIGHT) { // If the ball falls below the defined height of the screen
balls.erase(balls.begin() + b.getID()); // Wipe the ball out of memory to make room (Troublesome line)
Ball::addToID(-1); // Shift the ball ID to assign to the next ball back one
(...)
}
And I get this error:
Debug Error!
Program: Breakout.exe
abort() has been called
(Press Retry to debug the application)
Do you know why this mysterious crash is happening? Or more importantly, a fix for it?
Here's a replicable piece of code to help:
Driver.cpp:
#include <vector>
#include <allegro5\allegro.h>
#include "Ball.h"
using namespace std;
vector<Ball> balls(0); // Pay attention to this line
const POS WIDTH = 1600, HEIGHT = 900;
int main {
while (running) {
if (al_key_down(&key, ALLEGRO_KEY_SPACE)) { // Spawn the ball
balls.push_back(Ball(WIDTH / 2, 500, 10, 10)); // Spawn the ball
balls[Ball::getIDtoAssign()].setYSpeed(5);
}
for (Ball& b : balls) { // Pay attention to this loop
b.draw(); // This line is what's crashing.
b.move();
(...)
// Collision for when you miss
balls.erase(
remove_if(balls.begin(), balls.end(),
[=](Ball& b) {
// Collision for when you miss
return b.getYPos() > HEIGHT; // If the ball falls below the defined height of the screen, wipe the ball out of memory to make room
}
),
balls.end()
);
}
}
}
}
return 0;
}
Ball.h:
#pragma once
#include <allegro5\allegro_primitives.h>
using namespace std;
class Ball {
public:
Ball();
Ball(float x, float y, float w, float h);
~Ball();
void draw();
void move();
float getYPos();
void setYSpeed(float set);
private:
float xPos; // Horizontal position
float yPos; // Vertical position (upside down)
float width; // Sprite width
float height; // Sprite height
float xSpeed; // Horizontal speed
float ySpeed; // Vertical speed (inverted)
}
Ball.cpp:
#include "Ball.h"
short Ball::ballIDtoAssign = 0;
Ball::Ball() {
this->xPos = 0;
this->yPos = 0;
this->width = 0;
this->height = 0;
this->xSpeed = 0;
this->ySpeed = 0;
}
Ball::Ball(float x, float y, float w, float h) {
this->xPos = x;
this->yPos = y;
this->width = w;
this->height = h;
this->xSpeed = 0;
this->ySpeed = 0;
}
Ball::~Ball() {
// Destructor
}
void Ball::draw() {
al_draw_filled_rectangle(xPos, yPos, xPos + width, yPos + height, al_map_rgb(0xFF, 0xFF, 0xFF));
}
void Ball::move() {
xPos += xSpeed;
yPos += ySpeed;
}
float Ball::getYPos() {
return yPos;
}
void Ball::setYSpeed(float set) {
ySpeed = set;
}
You cannot modify a container while you are iterating through it with a range-for loop. You don't have access to the iterator that the loop uses internally, and erase() will invalidate that iterator.
You can use the container's iterators manually, paying attention to the new iterator that erase() returns, eg:
for(auto iter = balls.begin(); iter != balls.end(); ) { // Loops over all balls
Ball& b = *iter:
...
// Collision for when you miss
if (b.getYPos() > HEIGHT) { // If the ball falls below the defined height of the screen
...
iter = balls.erase(iter); // Wipe the ball out of memory to make room
}
else {
++iter;
}
}
Alternatively, use the erase-remove idiom via std::remove_if() instead:
balls.erase(
std::remove_if(balls.begin(), balls.end(),
[=](Ball &b){
// Collision for when you miss
return b.getYPos() > HEIGHT; // If the ball falls below the defined height of the screen, wipe the ball out of memory to make room
}
),
balls.end()
);
UPDATE: now that you have posted more of your code, it is clear to see that you are trying to use ID numbers as indexes into the vector, but you are not implementing those IDs correctly, and they are completely unnecessary and should be eliminated.
The Ball::ballID member is never being assigned any value, so in this statement:
balls.erase(balls.begin() + b.getID()); // The troublesome line
Trying to erase() the result of balls.begin() + b.getID() causes undefined behavior since the iterator has an indeterminate value, thus you can end up trying to erase the wrong Ball object, or even an invalid Ball object (which is likely the root cause of your runtime crash).
Also, in this section of code:
balls.push_back(Ball(WIDTH / 2, 500, 10, 10)); // Spawn the ball
balls[Ball::getIDtoAssign()].setYSpeed(5);
Ball::addToID(1);
Since you want to access the Ball object you just pushed, that code can be simplified to this:
balls.back().setYSpeed(5);
And I already gave you code further above to show you how to remove balls from the vector without using IDs.
So, there is need for an ID system at all.
With that said, try something more like this:
Driver.cpp:
#include <vector>
...
#include "Ball.h"
using namespace std;
vector<Ball> balls;
const POS WIDTH = 1600, HEIGHT = 900;
int main {
...
while (running) {
...
if (input.type == ALLEGRO_EVENT_TIMER) { // Runs at 60FPS
...
if (al_key_down(&key, ALLEGRO_KEY_SPACE)) { // Spawn the ball
balls.push_back(Ball(WIDTH / 2, 500, 10, 10)); // Spawn the ball
balls.back().setYSpeed(5);
}
for (auto iter = balls.begin(); iter != balls.end(); ) {
Ball &b = *iter;
...
if (b.getYPos() > HEIGHT) { // Collision for when you miss
iter = balls.erase(iter);
}
else {
++iter;
}
}
/* alternatively:
for (Ball& b : balls) {
b.draw();
b.move();
}
balls.erase(
std::remove_if(balls.begin(), balls.end(),
[=](Ball &b){
// Collision for when you miss
return b.getYPos() > HEIGHT; // If the ball falls below the defined height of the screen, wipe the ball out of memory to make room
}
),
balls.end()
);
*/
}
}
return 0;
}
Ball.h:
#pragma once
...
class Ball {
public:
...
// NO ID METHODS!
private:
...
// NO ID MEMBERS!
}
Ball.cpp:
#include "Ball.h"
...
// NO ID MEMBER/METHODS!
OK, so I managed to figure out why the program crashes. It was because I had the erase-remove inside the for loop which can cause all sorts of problems.

Write text input on the screen in SFML

So I'm creating a graphing calculator. I have an input string s. From the string, I can graph it using SFML. I start from the a MIN x-coordinate to a MAX x-coordinate, get the corresponding y from a EvaluateString() method, and all the coordinates to a VertexArray v. I wrote my method and the graphing method already and it all worked well.
However, I have a small issue. I want to input my string on the screen, such as "sin(cos(tan(x)))" like this. I'm struggling to find a way to do it. I kinda figured out it has to do with the event TextEntered, but still I can't find anything completely.
Please suggest me a way.
class Calculator{
public:
void main();
private:
WindowSize DefaultWindow;
sf::RenderWindow window;
Cartesian vertexX[2],vertexY[2];
sf::Vertex axis[4];
const double MAX = 10;
const double MIN = -10;
const double INCREMENT = 0.001;
};
int main(){
DefaultWindow.Max = Cartesian(10,10);
DefaultWindow.Min = Cartesian(-10,-10);
DefaultWindow.plane.width=1500;
DefaultWindow.plane.height=1500;
// Set up x and y-axis
vertexX[0] = Cartesian(-100,0);
vertexX[1] = Cartesian(100, 0);
vertexY[0] = Cartesian(0,-100);
vertexY[1] = Cartesian(0,100);
axis[0] = sf::Vertex(convertCartesiantoWindow(vertexX[0],DefaultWindow));
axis[1] = sf::Vertex(convertCartesiantoWindow(vertexX[1],DefaultWindow));
axis[2] = sf::Vertex(convertCartesiantoWindow(vertexY[0],DefaultWindow));
axis[3] = sf::Vertex(convertCartesiantoWindow(vertexY[1],DefaultWindow));
// Set up the window
window.create(sf::VideoMode(1500, 1500), "Graphing calculator");
// Input string
string s = "sin(cos(tan(x)))";
// Stack c contains all the Cartesian coordinate vertices
// Cartesian is a struct which contains x and y coordinates
Stack<Cartesian> c;
sf::VertexArray v;
// For a certain function in string s, I evaluate it
// and return the y_coordinate from the function EvaluateString (s, i)
// Push each (x,y) evaluated in the Stack c
for (double i = MIN; i <= MAX; i+= INCREMENT)
c.Push(Cartesian(i,EvaluateString(s,i)));
// v is VertexArray which contains all the vertices (x,y)
v = plot(DefaultWindow, c);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
window.close();
break;
}
}
}
// Draw the graph
window.clear(sf::Color::Black);
window.draw(axis,4,sf::Lines);
window.draw(v);
window.display();
}
As #super suggest, use a library would be a nice solution, and surely better than mine, but just in case this satisfies your needs, I implemented a super basic TextField class.
It may be plenty of errors, but it can gives you an idea on how to achieve that functionality.
A TextField is nothing more than a rectangle which contains a text. Since it will have a sf::Text, it must have a sf::Font. Additionally, I limit the number of characters that it will contain. In order for us to write inside the TextField, we have to know if it's selected, i.e. if it has the focus. So, a first approach could be:
class TextField : public sf::Transformable, public sf::Drawable{
private:
unsigned int m_size;
sf::Font m_font;
std::string m_text;
sf::RectangleShape m_rect;
bool m_hasfocus;
};
We need a constructor for this class:
class TextField : public sf::Transformable, public sf::Drawable{
public:
TextField(unsigned int maxChars) :
m_size(maxChars),
m_rect(sf::Vector2f(15 * m_size, 20)), // 15 pixels per char, 20 pixels height, you can tweak
m_hasfocus(false)
{
m_font.loadFromFile("C:/Windows/Fonts/Arial.ttf"); // I'm working on Windows, you can put your own font instead
m_rect.setOutlineThickness(2);
m_rect.setFillColor(sf::Color::White);
m_rect.setOutlineColor(sf::Color(127,127,127));
m_rect.setPosition(this->getPosition());
}
private:
unsigned int m_size;
sf::Font m_font;
std::string m_text;
sf::RectangleShape m_rect;
bool m_hasfocus;
};
We also need some basic methods, we want to get the text inside:
const std::string sf::TextField::getText() const{
return m_text;
}
and move it, placing it somewhere inside our window:
void sf::TextField::setPosition(float x, float y){
sf::Transformable::setPosition(x, y);
m_rect.setPosition(x, y);
}
this is a tricky one. We are overwritting setPosition method of sf::Transformable because we need to update our own m_rect.
Also, we need to know if a point is inside of the box:
bool sf::TextField::contains(sf::Vector2f point) const{
return m_rect.getGlobalBounds().contains(point);
}
pretty simple, we use cointains method of sf::RectangleShape, already in sfml.
Set (or unset) focus on the TextField:
void sf::TextField::setFocus(bool focus){
m_hasfocus = focus;
if (focus){
m_rect.setOutlineColor(sf::Color::Blue);
}
else{
m_rect.setOutlineColor(sf::Color(127, 127, 127)); // Gray color
}
}
easy one. For aesthetics, we also change the outline color of the box when focused.
And last, but not least, our TextField has to behave some way when input (aka an sf::Event) is received:
void sf::TextField::handleInput(sf::Event e){
if (!m_hasfocus || e.type != sf::Event::TextEntered)
return;
if (e.text.unicode == 8){ // Delete key
m_text = m_text.substr(0, m_text.size() - 1);
}
else if (m_text.size() < m_size){
m_text += e.text.unicode;
}
}
That delete key check is little dirty, I know. Maybe you can find better solution.
That's all! Now main looks like:
int main()
{
RenderWindow window({ 500, 500 }, "SFML", Style::Close);
sf::TextField tf(20);
tf.setPosition(30, 30);
while (window.isOpen())
{
for (Event event; window.pollEvent(event);)
if (event.type == Event::Closed)
window.close();
else if (event.type == Event::MouseButtonReleased){
auto pos = sf::Mouse::getPosition(window);
tf.setFocus(false);
if (tf.contains(sf::Vector2f(pos))){
tf.setFocus(true);
}
}
else{
tf.handleInput(event);
}
window.clear();
window.draw(tf);
window.display();
}
return 0;
}
Proof of concept:
std::string str;
sf::String text;
// In event loop...
if (event.Type == sf::Event::TextEntered)
{
// Handle ASCII characters only
if (event.Text.Unicode < 128)
{
str += static_cast<char>(event.Text.Unicode);
text.SetText(str);
}
}
// In main loop...
window.Draw(text);
This should create an sf::Event::TextEntered for input, and sf::String for output

How do I restore the sprite into original image after swapped it to another one in coco2dx?

In my gaming context, the sprite is a smiling baby, when the baby is touched, the current sprite image changed to a crying baby, then after 3 seconds of crying sound effect ended, the sprite will restore to the smiling baby image.
My problem is
How to swap back with the previous image?
I have changed the smiling baby to a crying one, but i have no idea how could I swap it with the original smiling one?
How to ensure one click at one time?
Each time the baby sprite is touched, the audio will start to play which is not ideal, because I hope the event function is only called after the previous event process is finished.
here is my code, and thank you a lot!
bool HelloWorld::init()
{
// 1. super init first
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
//add background scene
auto backgroundSprite = Sprite :: create("scene001.jpg");
backgroundSprite->setAnchorPoint(Vec2(0,0));
backgroundSprite->setScaleX((visibleSize.width / backgroundSprite->getContentSize().width) * 1);
backgroundSprite->setScaleY((visibleSize.height / backgroundSprite->getContentSize().height) * 1);
addChild(backgroundSprite);
//add smileBaby sprite
auto smileBabySprite = Sprite :: create("figure001.png");
smileBabySprite -> setPosition(Vec2(500,400));
addChild(smileBabySprite);
//add crying audio
auto audio = CocosDenshion::SimpleAudioEngine::getInstance();
auto babyListener = EventListenerTouchOneByOne::create();
babyListener -> onTouchBegan = [smileBabySprite, audio](Touch* touch, Event* event)
{
//change smileBaby sprite to cryingBaby sprite
smileBabySprite->setTexture(CCTextureCache::sharedTextureCache()->addImage("figure002.png"));
audio -> playEffect("babycry.mp3",false,1.0f,1.0f,1.0f);
return true;
};
babyListener -> onTouchEnded=[smileBabySprite](Touch* touch, Event* event )
{
};
_eventDispatcher -> addEventListenerWithSceneGraphPriority(babyListener, this);
return true;
}
What you want is to keep state about if the baby is crying or not. The best thing to do is keep this logic in a custom Node subclass.
Here is some (almost pseudo-)code to get you started:
Baby.h:
#pragma once
#include "cocos2d.h"
class Baby : public cocos2d::Node
{
private:
cocos2d::Sprite *_sprite; // Weak reference
bool _crying;
float _cryingTime;
public:
CREATE_FUNC(Baby);
protected:
virtual bool init() override;
virtual void update(float delta) override;
public:
void touched();
bool isInside(cocos2d::Touch *touch) const
protected:
void setSprite();
};
Baby.cpp:
#include "Baby.h"
USING_NS_CC;
bool Baby::init()
{
if (!Node::init())
return false;
_crying = false;
setSprite();
scheduleUpdate();
return true;
}
void Baby::update(float delta)
{
Node::update(delta);
if (_crying) {
_cryingTime -= delta;
if (_cryingTime <= 0.0f) {
_crying = false;
setSprite();
}
}
}
void Baby::touched()
{
if (_crying)
return; // Already crying
_crying = true;
_cryingTime = 3.0f; // Length of audio, I guess?
setSprite();
// Start crying sound here
}
bool Baby::isInside(Touch *touch) const
{
Vec2 locationInNode = _sprite->convertToNodeSpace(touch->getLocation());
Size size = _sprite->getContentSize();
Rect rect = Rect(0.0f, 0.0f, size.width, size.height);
return rect.containsPoint(locationInNode);
}
void Baby::setSprite()
{
if (_sprite)
_sprite->removeFromParent();
_sprite = Sprite::initWithFile(_crying ? "baby_crying.png" : "baby.png");
Vec2 size = getContentSize();
_sprite->setPosition(size.width * 0.5f, size.height * 0.5f);
addChild(_sprite);
}
You add the Baby node in the parent instead of the sprite, using:
_baby = Baby::create();
_baby->setPosition(Wherever);
addChild(_baby);
Where _baby is an instance variable and use the isInside() method to test if a touch event is within the bounds of the sprite and call its touched() method:
Touch *touch = ...;
if (_baby->isInside(touch)) {
_baby->touched();
}
and the Baby object will ignore the touch depending on state.

How to make my own classes with C++ and SFML

I have currently started working with SFML after learning the basics of C++. I have learnt about Arrays, References and everything that comes before it but have struggled to grasp the concept of using classes.
In SFML I have created a simple sprite movement program but, I would like to move this information into a class (lets say it will be called "Player"). I have messed around a lot but I can not get it to work.
I have tried creating a function in a class that would check for player input, but I can not access my sprite that I created in main. I would like to move everything related to the player into a Player class but need some advice.
What is the correct way to do this? (Please don't say go back and learn about classes, this is where I want to learn about them!)
main.cpp
#include <SFML/Graphics.hpp>
#include <string>
#include <iostream>
int main()
{
//character position
enum Direction{ Down, Left, Right, Up };
sf::Vector2i source(1, Down);
//window
sf::RenderWindow window(sf::VideoMode(1200, 700), "Testing");
window.setKeyRepeatEnabled(false);
//player character
sf::Texture pTexture;
sf::Sprite pSprite;
if(!pTexture.loadFromFile("image/playerSprite.png"))
std::cout << "Texture Error" << std::endl;
pSprite.setTexture(pTexture);
pSprite.setScale(1.5f, 1.5f);
//game loop
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) //move up
{
source.y = Up;
pSprite.move(0, -0.2);
//animation
source.x++;
if(source.x * 32 >= pTexture.getSize().x)
{
source.x = 0;
}
}
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) //move down
{
source.y = Down;
pSprite.move(0, 0.2);
//animation
source.x++;
if(source.x * 32 >= pTexture.getSize().x)
{
source.x = 0;
}
}
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) //move right
{
source.y = Right;
pSprite.move(0.2, 0);
//animation
source.x++;
if(source.x * 32 >= pTexture.getSize().x)
{
source.x = 0;
}
}
else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) //move left
{
source.y = Left;
pSprite.move(-0.2, 0);
//animation
source.x++;
if(source.x * 32 >= pTexture.getSize().x)
{
source.x = 0;
}
}
pSprite.setTextureRect(sf::IntRect(source.x * 32, source.y * 32, 32, 32));
window.draw(pSprite);
window.display();
}
return 0;
}
Disclaimer: You shouldn't expect that kind of answer, you really should read more on OOP to get the point, this has nothing to do with SFML, this is just basic refactoring.
How to think with OOP
First thing first, before coding a feature, you should design the OOP structure that really suits the situation. See each class as part of a whole, that is your program. A class in fact is just an aggregation of data with useful methods that only affects the data inside the class (or the data provided via method parameters) in a meaningful way.
See the basics of C++ (more the OOP part for you) to understand how to get it to work in C++. The concepts are similar in other programming languages.
Working with your provided code
What you asked for was a Player class and it's a great idea to get the player code out of the program main logic. You need to ask yourself: "What my player code needs to work?"
The player class
Basically, your player is only a sprite and a position. So you encapsulate those data into your Player class as private members. That keeps other code from messing with the player data. To use the player data, you need to provide methods in the class that each affects only the Player.
Texture and Sprite
I have kept the Texture outside of the player on purpose. Textures are heavy objects, that's why the Sprite object only keeps a pointer to it. Sprites are lightweight and can be changed and copied easily. The managing of texture objects and other assets is another subject, though here's my own resource manager code.
Optional
I did not took the time to change your code much, but you could change the way you handle the movement to only make one "move" method that takes a Player::Direction has a parameter.
To help you a little more and to give you some more guidelines on the subject, I used "forward declaration" and moved your Direction enum inside the class. It's maybe not the best way to achieve what you want, but I've only change your own code to avoid getting you lost.
The Code
Anyway, here's my go at this.
Player.h
#ifndef PLAYER_H_
#define PLAYER_H_
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/Sprite.hpp>
// Forward Declaration
namespace sf {
class Texture;
}
// provide your namespace to avoid collision/ambiguities
namespace test {
/*
*
*/
class Player: public sf::Drawable {
public:
enum Direction {
Down, Left, Right, Up
};
Player(const sf::Texture& playerTexture);
virtual ~Player();
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
void moveUp();
void moveDown();
void moveLeft();
void moveRight();
private:
sf::Sprite mSprite;
sf::Vector2i mSource;
};
} /* end namespace test */
#endif /* PLAYER_H_ */
Player.cpp
#include "Player.h"
// you need this because of forward declaration
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Rect.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
namespace test {
Player::Player(const sf::Texture& imagePath) :
mSprite(imagePath),
mSource(1, Player::Down) {
// do not need that line anymore, thanks to initialiser list
//pSprite.setTexture(pTexture);
mSprite.setScale(1.5f, 1.5f);
}
Player::~Player() {
// TODO Auto-generated destructor stub
}
void Player::draw(sf::RenderTarget& target, sf::RenderStates states) const {
target.draw(mSprite, states);
}
void Player::moveUp() {
mSource.y = Up;
mSprite.move(0, -0.2);
//animation
mSource.x++;
if (mSource.x * 32 >= (int) mSprite.getTexture()->getSize().x) {
mSource.x = 0;
}
mSprite.setTextureRect(sf::IntRect(mSource.x * 32, mSource.y * 32, 32, 32));
}
void Player::moveDown() {
mSource.y = Down;
mSprite.move(0, 0.2);
//animation
mSource.x++;
if (mSource.x * 32 >= (int) mSprite.getTexture()->getSize().x) {
mSource.x = 0;
}
}
void Player::moveLeft() {
mSource.y = Left;
mSprite.move(-0.2, 0);
//animation
mSource.x++;
if (mSource.x * 32 >= (int) mSprite.getTexture()->getSize().x) {
mSource.x = 0;
}
}
void Player::moveRight() {
mSource.y = Right;
mSprite.move(0.2, 0);
//animation
mSource.x++;
if (mSource.x * 32 >= (int) mSprite.getTexture()->getSize().x) {
mSource.x = 0;
}
}
} /* end namespace test */
main.cpp
#include <SFML/Graphics.hpp>
//#include <string> // not used for now
#include <iostream>
// don't forget to include your own header
#include "Player.h"
int main() {
// just to save typing the "std::"
using std::cout;
using std::endl;
using std::cerr;
//window
sf::RenderWindow window(sf::VideoMode(1200, 700), "Testing");
window.setKeyRepeatEnabled(false);
//player texture
sf::Texture pTexture;
if (!pTexture.loadFromFile("image/playerSprite.png")) {
cerr << "Texture Error" << endl;
}
test::Player thePlayer(pTexture);
//game loop
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
}
window.clear();
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) //move up
{
thePlayer.moveUp();
} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) //move down
{
thePlayer.moveDown();
} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) //move right
{
thePlayer.moveRight();
} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) //move left
{
thePlayer.moveLeft();
}
window.draw(thePlayer);
window.display();
}
return 0;
}
Other good practices
Accessors, or Getters/Setters, are member functions that gives one the access to a class private member.
In your code, you could do something like that:
class Player {
public:
Player(const sf::Texture& playerTexture);
virtual ~Player();
// to give access to a const reference of the sprite
// One could call it like: sf::Sprite mySprite = myPlayerObject.getSprite();
// notice also that the method itself is const, which assure you that
// myPlayerObject won't change by calling getSprite()
const sf::Sprite& getSprite() const{
return mSprite;
}
// setSprite is not a const method, so it will change the data
// inside myPlayerObject
void setSprite(const sf::Sprite& newSprite){
mSprite = newSprite;
}
private:
sf::Sprite mSprite;
sf::Vector2i mSource;
};