As the title suggests, I have an OOP error in C++, the LNK2001 unresolved externals error. Here is my code, where did I go wrong?
I'm using sfml to use the graphics in VS2015.
// OOPExample.hpp
#pragma once
#include <SFML\Graphics.hpp>
#include <SFML\System.hpp>
#ifndef OOPEX_H
#define OOPEX_H
class OOPExample{
public:
static sf::CircleShape shape;
static float rot;
static void draw();
static void rotate();
};
#endif // OOPEX_H
// OOPExample.cpp
#include "OOPExample.hpp"
#include <SFML\Graphics.hpp>
void OOPExample::rotate() {
OOPExample::rot += 0.1f;
return;
};
void OOPExample::draw() {
OOPExample::shape.setFillColor(sf::Color::Red);
OOPExample::shape.setRotation(rot);
return;
};
// Source.cpp
#include <SFML/Graphics.hpp>
#include "OOPExample.hpp"
int main()
{
sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
OOPExample oopexample();
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(oopexample().shape);
window.display();
}
return 0;
}
From what I've seen it looks like I need a definition of the methods declared in OOPExample.hpp, but I have those exact definitions in OOPExample.cpp. Am I instantiating the class incorrectly in Source.cpp?
To your question about the link errors. The comments highlight most of them... These are not OOP errors but rather link time errors when you build your project. I don't know if you have any experience with compiled system-level languages; but it would be a good idea to learn the basics of the compile-link cycle and what is expected by the linker when putting the final program together. A simple example of how to define a static member variable follows.
// class1.h
Class1
{
public:
private:
static float rotation;
};
// class1.cpp
#include "class1.h"
int Class1::rotation = 5.0f;
Note that int Class1::rotation = 5.0f; happens once at program initialisation.
I don't know if you are following some tutorial where they are creating this class like this, but you have a worrying amount of static members. This is an OOP problem. If you want to make many objects/instances of OOPExample you need to understand what static means. In the context of a class, when you apply the static keyword to a variable it means that all OOPExample objects will share that one variable. This makes static member variables good for default values and things like the number of a given class. You could have a static int OOPExample::count; to count the number of OOPExample objects that you have made. I will put that in an example later.
There may be many reasons for link errors, particularly missing definitions. PcAF highlighted an important one in the comments to your question. But you may also be missing the SFML libraries. I vaguely remember SFML tutorials including detailed instructions on how to link their libraries in your environment. In VS that will be somewhere in your project properties. You will obviously get similar errors if you declared something in the header that is not in the implementation (usually the cpp). This was the case with your static variable, but applies to functions as well.
Now there is a lot wrong with the three files you provided. I edited them to highlight some of the problems but it is far from perfect. I would not approach it in this way because sf::CircleShape is already an object-orientated entity. It has all the things you are trying to implement already implemented. Never over-abstract a problem (I also realised at some point that we were rotating a filled circle haha). You should really follow the advice to get a good textbook and start from the ground-up. SFML is a huge library that will distract you from understanding the fundamentals of C++. OOP is but one aspect of C++ and you need to embrace all C++ fundamentals to use OOP effectively. If you do this, you will have the most powerful abstraction mechanisms available (in my opinion).
My edits follow, but really, it is just a demonstration of how deep the rabbit hole goes (it gets much worse). How to instantiate OOPExample is shown in main.
// OOPExample.h
#ifndef OOPEX_H
#define OOPEX_H
// Only include what you need to. Users of this header should be exposed to as
// little SFML as possible.
#include <SFML/Graphics/CircleShape.hpp>
class OOPExample{
public:
// Parameterless constructor.
OOPExample(); // Note, this sets the rotation to the default rotation.
// One that takes a initial rotation.
OOPExample(float initial_rotation);
// Rotate 0.1f (by default) or by user specified amount.
void rotate(float rotation = 0.1f);
// window.draw() takes a Drawable as its first argument. Fortunately,
// CircleShape is a shape which in turn is a Drawable. Notice that we
// return by constant reference. Callers cannot edit our shape but they
// get a reference to the sf::CircleShape shape instance so they can read
// it.
// const, & (i.e. reference), pointers requires a deep understanding of
// object ownership, copying by value, by reference, and now of
// particular interest in C++11, moving.
const sf::CircleShape &getShape() const;
// You forgot to declare and define this.
void setRotation(float rotation);
// Set the default rotation for all objects created with the
// parameterless constructor.
static void setDefaultRotation(float rotation);
// The destructor.
virtual ~OOPExample();
private:
sf::CircleShape shape;
// sf::CircleShape already has a rotation with getters and setters.
// Don't over abstract!
// Our setRotation, rotate functions seem a bit unneccesary.
// float rotation;
// Defaults.
static sf::CircleShape default_circle;
static float default_rotation;
// Count example.
static int count;
};
#endif // OOPEX_H
// OOPExample.cpp
// Personally, and I know with most other C++ developers, I prefer my header's
// extension to be .h. .hpp usually identifies headers with
// implementations/definitions of classes in the header file itself (necessary
// in some circumstances).
#include "OOPExample.h"
//
// How to initialise static member variables. This happens once at the
// beginning of the program.
//
// All our circles have a default radius of 5.
sf::CircleShape OOPExample::default_circle = sf::CircleShape(5);
// And rotation of 0.
float OOPExample::default_rotation = 0;
int OOPExample::count = 0;
// The definition of the parameterless constructor.
OOPExample::OOPExample()
// A class initialiser list. How we build a new object.
: shape(default_circle) // We copy the default circle.
{
// Do other stuff to construct the object if you need to. For example:
shape.setFillColor(sf::Color::Red);
setRotation(default_rotation);
count++; // We just made another OOPEXample instance.
}
// The definition of a constructor that takes an initial rotation. I just
// realised we are rotating a circle!
OOPExample::OOPExample(float initial_rotation)
: shape(default_circle) // We copy the default circle.
{
// Do other stuff to construct the object if you need to. For example:
shape.setFillColor(sf::Color::Red);
// Notice: we used the user provided argument this time.
setRotation(initial_rotation);
count++; // We just made another OOPEXample instance.
}
void OOPExample::rotate(float rotation)
{
shape.rotate(rotation);
// return; // No need to specify a return for a void.
}
const sf::CircleShape &OOPExample::getShape() const
{
return shape;
}
void OOPExample::setRotation(float rotation)
{
shape.setRotation(rotation);
}
void OOPExample::setDefaultRotation(float rotation)
{
// OOPExample scoping is unnecessary.
OOPExample::default_rotation = rotation;
}
OOPExample::~OOPExample()
{
// Do things required for cleanup, i.e. deinit.
// One OOPExample just reached the end of its lifetime. Either it
// was deleted or reached the end of the
// scope (i.e. {}) it was created in.
count--;
}
// main.cpp
#include "OOPExample.h"
#include <SFML/Graphics.hpp>
int main()
{
sf::RenderWindow window(sf::VideoMode(200, 200), "SFML works!");
// Call the default, parameterless, constructor to instantiate an object
// of OOPExample.
OOPExample oopexample;
// Create another with a initial rotation of your choosing.
OOPExample another_obj(0.5f);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
// The instance object of OOPexample is referred to by oopexample.
// window.draw(oopexample().shape);
// This member is now private.
//window.draw(oopexample.shape);
window.draw(oopexample.getShape());
window.display();
}
return 0;
}
Related
It seems like a very weird situation. I just want to draw a sf::Text object that is handle outside the main loop (in another class).
I show you only the essential. This code works (it draws other things that are handle directly in the main) and so it compiles.
Main :
int main()
{
//we handle the creation of the window //
//...
//Example where the sf::Text is handle in the main (it works)
sf::Font font;
font.loadFromFile("arial.ttf");
sf::Text mainTxt("It works !!",font);
mainTxt.setPosition(sf::Vector2f(64,"It works !!"));
mainTxt.setCharacterSize(25);
mainTxt.setFillColor(sf::Color::Blue);
TextManager textManager(); //My class that don't work...
sf::Event event;
while (window.isOpen())
{
// We handle event (we don't care here)
// ...
window.draw(mainTxt); // it works
window.draw(textManager.getMyText()); // do nothing
window.display();
window.clear();
}
return 0;
}
TextManager header :
#ifndef TEXTMANAGER_H
#define TEXTMANAGER_H
#include <SFML/Graphics.hpp>
class TextManager
{
public:
TextManager();
virtual ~TextManager();
sf::Text getMyText();
private:
sf::Text myText;
};
#endif // TEXTMANAGER_H
TextManager cpp
#include "TextManager.h"
TextManager::TextManager()
{
sf::Font font;
font.loadFromFile("arial.ttf");
sf::Text myText("Not drawn on the screen :-(",font);
myText.setPosition(sf::Vector2f(164,0));
myText.setCharacterSize(25);
myText.setFillColor(sf::Color::Blue);
}
// in this example (that do not work) I do not change fspText. But, I want to update it at each call of the get function. So, it is not a constant class member.
sf::Text TextManager::getMyText() {
return myText;
}
TextManager::~TextManager()
{
//dtor
}
What I really do not understand, is the fact that with a custom class of mine, I can access a class member object with this type of getter. I also did some research, and I think It should return a copy of the sf::Text object.
I try lot of things, like return reference or const reference etc... I do not understand.
I hope my problem is well displayed.
Thank you for your help ;-)
And have a nice day !
This
TextManager textManager(); //My class that don't work...
is function declaration, not construction of object.
Should be:
TextManager textManager; //My class that don't work...
By
sf::Text myText("Not drawn on the screen :-(",font);
you define a local variable called myText the same as your data member. So, myText returned by getMyText is just not affected.
Read the docs before coding:
It is important to note that the sf::Text instance doesn't copy the
font that it uses, it only keeps a reference to it. Thus, a sf::Font
must not be destructed while it is used by a sf::Text (i.e. never
write a function that uses a local sf::Font instance for creating a
text).
taken from SFML reference.
class TextManager
{
public:
TextManager();
virtual ~TextManager();
sf::Text& getMyText(); // <- pass by reference
private:
sf::Font myFont;
sf::Text myText;
};
and
TextManager::TextManager()
{
myFont.loadFromFile("arial.ttf");
myText.setString("Not drawn on the screen :-(");
myText.setFont(myFont);
myText.setPosition(sf::Vector2f(164,0));
myText.setCharacterSize(25);
myText.setFillColor(sf::Color::Blue);
}
might work.
I'm creating a car simulation using SFML.
As a matter of organization and logic, I created a single class "car", which also inherits sf::RectangleShape, and within this class there are other SFML objects, among them a Texture and a method to setup it.
I want to have multiple cars, so I created a vector of class "car".
In this example, I left only 2 cars with the images:
"car-red.png" and "car-black.png".
Here is an extract from the logic I'm using (I did a test program to make it easier to understand):
#include <iostream>
#include <vector>
#include <SFML/Graphics.hpp>
#define debug(x) std::cout << #x << "=" << x << std::endl;
using namespace std;
class car : public sf::RectangleShape {
public:
string s;
sf::Texture tex;
sf::Sprite img;
void imgInit(string imgFile) {
tex.loadFromFile(imgFile);
img.setTexture(tex);
s = imgFile;
}
};
int main()
{
vector<car> vecRet;
car objRet;
objRet.imgInit("car-red.png");
objRet.setSize(sf::Vector2f(150, 70));
objRet.setFillColor(sf::Color::Yellow);
vecRet.push_back(objRet);
objRet.imgInit("car-black.png");
objRet.setPosition(sf::Vector2f(300, 300));
objRet.img.setPosition(objRet.getPosition());
vecRet.push_back(objRet);
debug(vecRet[0].s);
debug(vecRet[1].s);
debug(vecRet[0].img.getTexture());
debug(vecRet[1].img.getTexture());
sf::RenderWindow window(sf::VideoMode(500,500), "Window", sf::Style::Close);
window.setFramerateLimit(120);
while (window.isOpen())
{
for (sf::Event event; window.pollEvent(event);) {
if (event.type == sf::Event::Closed)
window.close();
}
window.clear();
window.draw(vecRet[0]);
window.draw(vecRet[1]);
window.draw(vecRet[0].img);
window.draw(vecRet[1].img);
window.display();
}
return EXIT_SUCCESS;
}
There are two problems I can not solve:
1) Even doing push_back of the two cars, only the last image prevails.
Apparently, push_back refers to a single RAM address for the image.
Then the result looks like this:
That is, the second image (car-black.png) is probably overlapping the address of the first image.
The curious thing is that this only happens with the Texture class. In the example, the program debug a string within the class and in this case there is no overlap:
vecRet[0].s=car-red.png
vecRet[1].s=car-black.png
However the Texture objects within the class vector are pointing to the same memory address:
vecRet[0].img.getTexture()=000000C57A5FF250
vecRet[1].img.getTexture()=000000C57A5FF250
How to solve this?
2) The second problem is that, for each vecRet.push_back(objRet), the following errors appear in the console:
An internal OpenGL call failed in Texture.cpp(98).
Expression:
glFlush()
Error description:
GL_INVALID_OPERATION
The specified operation is not allowed in the current state.
What is this?
Your first problem happens because you only instantiate a single car, but you load both images onto it. The second problem might be caused by the fact that push_back() copies the elements into the vector, resulting in the creation of four cars in total, rather than two.
Try this code:
vector<car> vecRet(2);
vecRet[0].imgInit("car-red.png");
vecRet[0].setSize(sf::Vector2f(150, 70));
vecRet[0].setFillColor(sf::Color::Yellow);
vecRet[1].imgInit("car-black.png");
vecRet[1].setPosition(sf::Vector2f(300, 300));
vecRet[1].img.setPosition(vecRet[1].getPosition());
I just started programming with SFML, I'm trying to make something shoot a bullet. Currently I have made the bullet move one way when I press space. But I cannot shoot multiple bullets at the same time, it just uses the same image. I can't figure out what I should do, if I should give each bullet it's own name?
Also should I delete it when it's out from the window? If I should how can I do it?
Here's the relevant code:
void Game::handleWeaponInput(sf::Keyboard::Key key, bool isPressed)
{
if (key == sf::Keyboard::Space)
{
mIsFired = isPressed;
if (!mBulletTexture.loadFromFile("Media/Textures/Bullet.png"))
{
}
mBullet.setTexture(mBulletTexture);
mBullet.setPosition(100.f, 100.f);
mBullet.setRotation(90.f);
}
}
And:
void Game::update(sf::Time elapsedTime)
{
sf::Vector2f bulletMovement(0.f, 0.f);
if (mIsFired)
bulletMovement.x += 300.f;
mBullet.move(bulletMovement * elapsedTime.asSeconds());
}
One thing you can do is create a std::vector of bullets so you can track each one individually.
class Game
{
sf::Texture mBulletTexture;
// declare a std::vector of bullets
std::vector<sf::Sprite> mBullets;
public:
void handleWeaponInput(sf::Keyboard::Key key, bool isPressed);
void update(sf::Time elapsedTime);
};
void Game::handleWeaponInput(sf::Keyboard::Key key, bool isPressed)
{
if (key == sf::Keyboard::Space)
{
mIsFired = isPressed;
if (!mBulletTexture.loadFromFile("Media/Textures/Bullet.png"))
{
}
mBullets.emplace_back(); // add another sf::Sprite to vector
mBullets.back().setTexture(mBulletTexture);
mBullets.back().setPosition(100.f, 100.f);
mBullets.back().setRotation(90.f);
}
}
void Game::update(sf::Time elapsedTime)
{
sf::Vector2f bulletMovement(0.f, 0.f);
if (mIsFired)
bulletMovement.x += 300.f;
// move each bullet in the vector
for(auto& bullet: mBullets)
bullet.move(bulletMovement * elapsedTime.asSeconds());
}
Obviously this exact code won't work properly as you will also have to manage how each bullet moves (what direction etc....). But hopefully it will give you an idea how you could scale from one bullet to many bullets.
It may be worth defining a class Bullet that contains the sf::Sprite along with information about the bullet's speed and direction?
Then you could make your vector with those:
class Bullet
{
sf::Sprite sprite;
sf::Texture texture;
sf::Vector2f direction;
public:
// ...
};
class Game
{
// declare a std::vector of bullets
std::vector<Bullet> mBullets;
// ...
};
first of all, I want to tackle your update method: You should update each existing bullet each frame independently of whether you fired one or not:
void Game::update(sf::Time elapsedTime) {
sf::Vector2f bulletMovement(300.f, 0.f);
for (auto& bullet : mBullets) // range-based-for and auto require C++11
bullet.move(bulletMovement * elapsedTime.asSeconds());
}
As others have already suggested, for a simple use case, a vector of bullets works nicely. If you need more complex data structures to handle your game entities, try researching on things like "scene graph" or "quad tree".
std::vector<sf::Sprite> mBullets;
to add a bullet, simply use
// possibly construct sprite seperatly and add some transforms beforehand
mBullets.push_back(sf::Sprite(/* however you store your texture */));
And yes, it is a good idea to destroy bullets once they are outside of your screen, since you would be wasting draw calls and other operations on entities which you will never see again. Using the tools SFML already provides, one could do it like this:
void Game::destroyBulletsOutsideView() {
sf::FloatRect viewBounds(0, 0, mWindow.getSize().x, mWindow.getSize().y);
for (auto iter = mBullets.begin(); iter != mBullets.end(); ++iter) {
if (!viewBounds.intersects(iter->getGlobalBounds())
mBullets.erase(iter);
}
}
Side note: You will likely want to add something like a reload or cooldown time to your Bullets, so you can't shoot tons of bullets per second.
I have a texture and sprite in a base class that is being extended by another class, however when drawn, the sprite displays as a white box. I know this is something to do with the sprite losing it's link to the texture object, but I'm kind of new to C++, so I'm not really sure how it happened.
Here is the code (I've removed some of the irrelevant parts to cut down the size):
Pickup.h:
#ifndef PICKUPS_PICKUP_H
#define PICKUPS_PICKUP_H
#include <SFML\Graphics.hpp>
#include "..\Player.h"
namespace Pickups
{
class Pickup
{
private:
sf::Vector2f position;
sf::Texture texture;
sf::Sprite sprite;
public:
Pickup();
bool drop(float dt);
void draw(sf::RenderWindow* window);
void setTexture(sf::String name);
void setPos(sf::Vector2f position);
sf::Vector2f getPos();
void isColliding(Player* player);
virtual void onCollect(Player* player) = 0;
};
}
#endif
pickup.cpp:
#include "Pickup.h"
namespace Pickups
{
Pickup::Pickup()
{
}
void Pickup::draw(sf::RenderWindow* window)
{
window->draw(sprite);
}
void Pickup::setTexture(sf::String name)
{
if (!texture.loadFromFile("images/pickups/" + name + ".png"))
std::cout << "Error loading image: images/pickups/" + name.toAnsiString() + ".png" << std::endl;
else
sprite.setTexture(texture);
}
}
Health.h:
#ifndef PICKUPS_HEALTH_H
#define PICKUPS_HEALTH_H
#include "Pickup.h"
namespace Pickups
{
class Health : public Pickup
{
private:
int worth;
public:
Health(sf::Vector2f position, int worth);
void onCollect(Player* player);
};
}
#endif
health.cpp:
#include "Health.h"
namespace Pickups
{
Health::Health(sf::Vector2f position, int worth)
{
setTexture("health");
setPos(position);
this->worth = worth;
}
void Health::onCollect(Player* player)
{
player->addLives(worth);
}
}
(I don't know if this is part of the problem, but I might as well post it too)
I store the pickups in a vector like so:
std::vector<Pickups::Health> pickups;
A std::vector copies or moves the inserted elements, so as long as you have the default copy constructor or as long as you do not change this dirty a texture per element-style, (the elements just need to have one common texture object to actually point to, so you waste much much memory) the pointer that the sf::Sprite object holds to the texture gets invalid. To see why we need to think whats happens on insertion:
You setup a nice Pickupish object and add it to the vector which calls the copy-constructor. Lets say your nice object that you wanted to add is object A and the now added/copied object is B. Both have a sprite S and a texture T. Both textures are valid, but the problem is this: A's S points to A's T, so after copying it to B B's S points also to A's T! As I assume A is just temporary so it gets destructed, together with its texture, and there you have it, a nice white box.
You can solve this in some other dirty ways like making your own copy-constructor in Pickup like this:
Pickup::Pickup(const Pickup& other)
: position(other.position), texture(other.texture), sprite(other.sprite)
{ sprite.setTexture(texture); }
or by storing std::unique_ptr<Pickups::Health>'s and not just Pickups::Health's.
However a much better way you should use is some kind of Resourcemanager, which just stores all relevant textures, ideally one, a big tileset, because loading once but big is faster than loading multiple but small textures. You can write your own very simple manager or use some other e.g. the one from the great Thor library. To set a specific tile as texture for a Sprite just call sf::Sprite::setTextureRect.
I want to mention some additional improvements to your design. Let Pickup derive from sf::Drawable and define its pure virtual draw function, which you can make private. Thus your from Pickup deriving object doesn't need to know from any sf::RenderTarget and you can just do target.draw(myPickupObject).
There is no need to store the position, just let Pickup derive from sf::Transformable, too. You don't have to implement any functions, the only thing you need to do is applying the matrix to the sf::RenderStates object thats passed to draw.
Overall your draw function might look like this:
void Pickup::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
//'applying' transformation-matrix
states.transform *= getTransform();
target.draw(sprite, states);
}
So your Pickup has now only sf::Sprite as member and overall your header just needs to include SFML/Graphics/Sprite.hpp.
For avoid this type of problem I always declare my Texture as a pointer and deleting it in the destructor of the class. Like this your Texture will always exist whenever your object is not destroyed.
And it's always good to verify the loading of the image :
if (!texture.loadFromFile("images/pickups/health.png")){
//some error code
}
But it's not the problem here.
So, I'm trying to reproduce one thing I learned in Java in my C++ program and I just can't make this work!
Here's a example of what I want to do:
class Game{
public:
int screenWidth, screenHeight;
Screen* titleScreen;
void createScreen(){
titleScreen = new Screen(this);
}
}
class Screen{
public:
Game* game;
Rect quad1;
Screen(Game* game){
this->game = game;
quad1.x = game->screenWidth/2;
}
}
I'm not even sure if this code is right because I created it right now just to show what I want to do.
So, basically, what I want to do is create a reference for "Game" inside "Screen" so I can use its atributes and methods(in this case, the screen width), even though "Screen" is being instantiated inside "Game". I did something like this in Java and it worked perfectly, but for C++ I get so many erros that I don't even know how to interpret them... I tried using pointer for the parameters and instead of using "this" I tried using "&this" but none of them worked and I get pratically the same errors...
So, what am I doing wrong? How can i make this work? Is this even possible in C++?
Use a forward declaration and define the function after the definition of the things it needs.
class Screen; // Forward declaration
class Game{
public:
int screenWidth, screenHeight;
Screen * titleScreen;
void createScreen();
}
class Screen{
public:
Game* game;
Rect quad1;
Screen(Game *game){
this->game = game;
quad1.x = game->screenWidth/2;
}
}
// Out-of-line definition.
// This should be declared "inline" if it's in a header file.
void Game::createScreen(){
titleScreen = new Screen(this);
}
a)Make forward declaration of Game class.
b)Make Screen(Game *game) constructor to accept a pointer not an object since you are passing this pointer.
Refer following sample
http://ideone.com/J8He9f