4

I'm trying to make a character which moves around the screen in C++ using the SFML library. Every time a make a project i end up with the same LNK 2001 error.

Please explain to me what I've done wrong and how do i fix it. I'm still a beginner so if you have any recommendations about fixing errors or writing code I'd love it.

Main.cpp:

#include "Game.cpp"
#include "Game.h"

int main ()
{
    Game Game;

    Game.GameRun();
}

Game.h:

#pragma once

#include <SFML\Window.hpp>
#include "Character.h"

class Game
{
public:
    void GameRun ();
    void UpdateGame(Character player);
    void Controls(int sourceX, int sourceY, Character player);
};

Character.h:

#pragma once

#include <SFML\Graphics.hpp>
#include <SFML\System.hpp>
#include <SFML\Window.hpp>
#include "Game.h"

class Character
{
public:
    enum Direction {Down, Left, Right, Up};

    int characterX;
    int characterY;

    sf::Texture txt_character;
    sf::Sprite spr_character;
};

Game.cpp:

#include "Game.h"
#include "Character.h"

#include <iostream>

inline void Game::GameRun()
{
    sf::RenderWindow window(sf::VideoMode(800, 600), "SFML test");

    Character player;

    player.characterX = 1;
    player.characterY = Character::Down;

    player.txt_character.loadFromFile("character sprite sheet.png");
    player.spr_character.setTexture(player.txt_character);

    while(window.isOpen())
    {
        Game::UpdateGame(player);
    }
}

inline void Game::UpdateGame(Character player)
{
    sf::Event event;
    sf::RenderWindow window(sf::VideoMode(800, 600), "SFML test");

    while (window.pollEvent(event))
    {
        if (event.type == sf::Event::Closed)
        window.close();
    }
    Game::Controls(player.characterX, player.characterY, player);

    player.spr_character.setTextureRect(sf::IntRect(player.characterX * 32, player.characterY * 32, 32, 32));

    window.draw(player.spr_character);
    window.display();
    window.clear();
}

inline void Game::Controls(int sourceX, int sourceY, Character player)
{
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
    {
        sourceY = Character::Up;
        sourceX++;
        if (sourceX * 32 >= player.txt_character.getSize().x)
            sourceX = 0;
        player.spr_character.move(0, -2);
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
    {
        sourceY = Character::Down;
        sourceX++;
        if (sourceX * 32 >= player.txt_character.getSize().x)
            sourceX = 0;
        player.spr_character.move(0, 2);
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::A))
    {
        sourceY = Character::Left;
        sourceX++;
        if (sourceX * 32 >= player.txt_character.getSize().x)
            sourceX = 0;
        player.spr_character.move(-2, 0);
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::D))
    {
        sourceY = Character::Right;
        sourceX++;
        if (sourceX * 32 >= player.txt_character.getSize().x)
            sourceX = 0;
        player.spr_character.move(2, 0);
    }
}

Errors:

1>main.obj : error LNK2001: unresolved external symbol "public: static int Character::characterY" (?characterY@Character@@2HA)

1>main.obj : error LNK2001: unresolved external symbol "public: static int Character::characterX" (?characterX@Character@@2HA)

1>C:\Users\SONY\Documents\Visual Studio 2010\Projects\Top Down Game\Debug\Game.exe : fatal error LNK1120: 2 unresolved externals

Thanks :D

EDIT: FYI i had the code without classes and it was working perfectly, it was just a big block of code in the main.cpp

EDIT: link to folder for program: https://www.dropbox.com/s/3yyjti8zwu019s7/Top%20Down%20Game%20With%20Classes.rar

mkdewidar
  • 51
  • 6

1 Answers1

3

EDIT 2014/03/08: I tried your sources. Remove the #include "Game.cpp" inside the Main.cpp file and the inline keyword inside the Game.cpp file.

The logic behind wasn't working so I fixed it a minimum so it will work as soon as you have the compiling (linking) error fixed. You should try my code to see if it helps out of the box.

A little explanation first

Passing the player object by value (as when you have void someMethod(Character player)) sends a copy of the object to the method. So when you make changes to the player object, those changes are only made to the copy and not the actual player. I changed your code so it sends a pointer instead (void someMethod(Character * player). The method receive a pointer to the actual player object and changes made to that object reflects on the actual player object.

There are a couple solution other than using a pointer (which some people might say is overkill or too dangerous).

It could be instead :

  • A reference void someMethod(Character& player)
  • or a return value Character someMethod(Character player)
  • or a mix of the two Character someMethod(const Character& player)
  • or a member variable (defined in the header file)

Another thing is that you keep a sf::Texture by value inside your Character class. sf::Texture are really "heavy" objects, so they should not be passed by value and should be kept as pointer. The best thing you could do (and that I've not done in your code below) is to completely remove the sf::Texture from the Character class and only keep the sf::Sprite which already holds the sf::Texture (in a optimised way, so you don't bother with it).

Here's the code

Main.cpp

#include "Game.h"

int main ()
{
    Game Game;

    Game.GameRun();
}

Game.h

#ifndef GAME_H_
#define GAME_H_

//#include <SFML/Window.hpp> /* you don't need this */
#include <SFML/Graphics/RenderWindow.hpp>


// forward declaration, this prevents recursive/circular dependencies.
// only works with pointer or referenced type.
class Character;

class Game
{
public:
    void GameRun ();

    // I change the param to pointer to Character for logic reasons.
    // You were doing changes on a Character COPY of the player,
    // which was deleted when leaving the scope of the functions.
    void UpdateGame(Character * player);
    void Controls(Character * player);
private:
    // I added this here, so you can create the window in the 
    // GameRun and UpdateGame methods where needed.
    sf::RenderWindow window;
};

#endif

Game.cpp

#include "Game.h"
#include "Character.h"

#include <SFML/Window/Event.hpp> /* you only need this */

#include <iostream>

void Game::GameRun()
{
    // I moved the window creation here because you were 
    // creating a new window each update which made the windows
    // flickers each frame, making it impossible to close properly.
    window.create(sf::VideoMode(800, 600), "SFML test");

    Character * player = new Character();

    player->characterX = 1;
    player->characterY = Character::Down;

    // if the file isn't available, this will prevent further problems.
    if (player->txt_character.loadFromFile("character.png"))
    {
        player->spr_character.setTexture(player->txt_character);
    }

    while (window.isOpen())
    {
        Game::UpdateGame(player);
    }
}

void Game::UpdateGame(Character * player)
{
    sf::Event event;


    while (window.pollEvent(event))
    {
        if (event.type == sf::Event::Closed){

            window.close();

        }
    }
    Controls(player);

//  player.spr_character.setTextureRect(
//          sf::IntRect(player.characterX * 32, player.characterY * 32, 32,
//                  32));

    window.clear();
    window.draw(player->spr_character);
    window.display();

}

// You were already sending the player so I removed the other
// params and used the player instead.
void Game::Controls(Character * player)
{
    int sourceX = player->characterX;

    if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
    {
        player->characterY = Character::Up;
        sourceX++;
        if (sourceX * 32 >= player->txt_character.getSize().x)
            sourceX = 0;
        player->spr_character.move(0, -2);
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
    {
        player->characterY = Character::Down;
        sourceX++;
        if (sourceX * 32 >= player->txt_character.getSize().x)
            sourceX = 0;
        player->spr_character.move(0, 2);
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::A))
    {
        player->characterY = Character::Left;
        sourceX++;
        if (sourceX * 32 >= player->txt_character.getSize().x)
            sourceX = 0;
        player->spr_character.move(-2, 0);
    }
    if (sf::Keyboard::isKeyPressed(sf::Keyboard::D))
    {
        player->characterY = Character::Right;
        sourceX++;
        if (sourceX * 32 >= player->txt_character.getSize().x)
            sourceX = 0;
        player->spr_character.move(2, 0);
    }
    player->characterX = sourceX;
}

Character.h

#ifndef CHARACTER_H_
#define CHARACTER_H_

//#include <SFML\Graphics.hpp>
//#include <SFML\System.hpp>
//#include <SFML\Window.hpp>
//#include "Game.h"
#include <SFML/Graphics/Sprite.hpp> /* Don't include more than you need */
#include <SFML/Graphics/Texture.hpp>

class Character
{
public:
    enum Direction {Down, Left, Right, Up};

    int characterX;
    int characterY;

    sf::Texture txt_character; // Don't keep the texture value, it's really heavy.
    sf::Sprite spr_character;
};

#endif

General C++ information

Community
  • 1
  • 1
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
  • 1
    Hey, i got the same error, actually i got an extra one: error LNK2005: "public: void __thiscall Game::GameRun(void)" (?GameRun@Game@@QAEXXZ) already defined in Game.obj could u also explain more of what u did in a simpler fashion? im a beginner after all :D – mkdewidar Mar 07 '14 at 18:53
  • For the error, do you have "included in the project" the source files? I think that MS Visual Studio may complains when you don't include the files. (To add them, right-click on them in the Solution Explorer, if they're not there at all, click the `Show All Files` button in the Solution Explorer). For the additional explanations, I'll try to update my answer. – Emile Bergeron Mar 07 '14 at 19:01
  • 1
    i did click on the show all and realized that the sprite sheet wasn't "included in project". error remains though...for some reason changing functions to inline in game.cpp causes the "already defined" error to disappear. – mkdewidar Mar 07 '14 at 19:14
  • Do you have tested my code integrally or have you made the changes manually? Have you notice I have removed the `#include "Game.cpp"` from the Main.cpp file? It should never be necessary to include a cpp file. – Emile Bergeron Mar 07 '14 at 19:35
  • After testing it, the `#include "Game.cpp` really seems to be the problem. Try removing it and removing the inline keyword also. – Emile Bergeron Mar 07 '14 at 20:03
  • 1
    i copy pasted ur code to replace mine. and if i remove inline i get that "already defined error" – mkdewidar Mar 07 '14 at 20:14
  • 1
    I just removed the #include for the Game.cpp and one of the unresolved externals disappeared!!! one more left : error LNK2019: unresolved external symbol "public: void __thiscall Game::GameRun(void)" (?GameRun@Game@@QAEXXZ) referenced in function _main – mkdewidar Mar 07 '14 at 20:17
  • Try `Build Menu -> Clean PROJECT` then `Build Menu -> Rebuild` – Emile Bergeron Mar 07 '14 at 20:22
  • It's usually at the top besides `FILE EDIT VIEW` etc. But if it's not available on your version, you could right-click on your project OR solution name in the Solution Explorer and you'll have the same options as the `BUILD Menu`. – Emile Bergeron Mar 07 '14 at 20:32
  • is it the clean option? when i right click my solution? – mkdewidar Mar 07 '14 at 20:34
  • Yes, the clean option first, then build or rebuild. – Emile Bergeron Mar 07 '14 at 20:36
  • 1
    could you pack/zip the MS Visual Studio project and upload it somewhere (like dropbox) and share it so I could really see what's going on. Because right now, it does not seem to be the code the problem, I mean from my side it's working fine... – Emile Bergeron Mar 07 '14 at 20:42
  • I've added the link to the dropbox folder I've made. check above. – mkdewidar Mar 08 '14 at 20:36
  • 1
    Tested your code and I was having the same error as you (`error LNK2019: unresolved external symbol "public: void __thiscall Game::GameRun(void)"`). I changed this `inline void Game::GameRun()` into this `void Game::GameRun()` inside `Game.cpp` and it worked. I only removed the `inline` keyword as I said earlier. – Emile Bergeron Mar 08 '14 at 22:37
  • So what should I do? Cause when I remove the inline I get another error, which IDE/compiler are u using, I'm using visual 2010 and I'm starting to think that it's the problem. – mkdewidar Mar 08 '14 at 23:02
  • I tried it with VS 2012 which is what was available to me at the moment. What's the other errors exactly? – Emile Bergeron Mar 08 '14 at 23:09
  • LNK2019: unresolved external symbol "public: void __thiscall Game::GameRun(void)" (?GameRun@Game@@QAEXXZ) referenced in function _main. I think I should just change the IDE, or maybe it's something in the settings. – mkdewidar Mar 09 '14 at 11:03