I have been trying to make a simple game in SDL. I Found that keeping every thing in on file was getting messy so i moved some functions into their own file and set up a global header file to store all the global things i would use. but from there it has been all errors!
Files:
-Main.cpp
-Makefile
-Assets/
-Player.bmp
-Build/
-build.deb
-Modules/
-Global.h
-HandleKeys.cpp
-HandleKeys.h
Main.cpp:
#include <stdio.h>
#include "Modules/Global.h"
#include "Modules/HandleKeys.h"
//Starts up SDL and creates window
bool init();
//Loads media
bool loadMedia();
extern int SCREEN_WIDTH = 640;
extern int SCREEN_HEIGHT = 480;
//Frees media and shuts down SDL
void close();
//Loads individual image
SDL_Surface* loadSurface( std::string path );
//The window we'll be rendering to
SDL_Window* gWindow = NULL;
//The surface contained by the window
SDL_Surface* gScreenSurface = NULL;
//The images that correspond to a keypress
SDL_Surface* Entity[ KEY_PRESS_SURFACE_TOTAL ];
//Current displayed image
SDL_Surface* gCurrentSurface = NULL;
int PlayerX;
int PlayerY;
bool init()
{
//Initialization flag
bool success = true;
//Initialize SDL
if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
{
printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError() );
success = false;
}
else
{
//Create window
gWindow = SDL_CreateWindow( title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
if( gWindow == NULL )
{
printf( "Window could not be created! SDL Error: %s\n", SDL_GetError() );
success = false;
}
else
{
//Get window surface
gScreenSurface = SDL_GetWindowSurface( gWindow );
}
}
return success;
}
string get(list<string> _list, int _i){
list<string>::iterator it = _list.begin();
for(int i=0; i<_i; i ){
it;
}
return *it;
}
bool loadMedia()
{
//Loading success flag
bool success = true;
for( int i = 0; i <= KEY_PRESS_SURFACE_TOTAL; i )
{
if (i != KEY_PRESS_SURFACE_TOTAL){
Entity[ PLAYER_SURFACE ] = loadSurface( get(EntityPaths, i) );
}
}
return success;
}
void close()
{
//Deallocate surfaces
for( int i = 0; i < KEY_PRESS_SURFACE_TOTAL; i )
{
SDL_FreeSurface( Entity[ i ] );
Entity[ i ] = NULL;
}
//Destroy window
SDL_DestroyWindow( gWindow );
gWindow = NULL;
//Quit SDL subsystems
SDL_Quit();
}
SDL_Surface* loadSurface( std::string path )
{
//Load image at specified path
SDL_Surface* loadedSurface = SDL_LoadBMP( path.c_str() );
if( loadedSurface == NULL )
{
printf( "Unable to load image %s! SDL Error: %s\n", path.c_str(), SDL_GetError() );
}
return loadedSurface;
}
int main( int argc, char* args[] )
{
//Start up SDL and create window
if( !init() )
{
printf( "Failed to initialize!\n" );
}
else
{
//Load media
if( !loadMedia() )
{
printf( "Failed to load media!\n" );
}
else
{
//Main loop flag
bool quit = false;
//Event handler
SDL_Event e;
//Set default current surface
gCurrentSurface = Entity[ PLAYER_SURFACE ];
//While application is running
while( !quit )
{
//Handle events on queue
while( SDL_PollEvent( &e ) != 0 )
{
//User requests quit
if( e.type == SDL_QUIT )
{
quit = true;
}
HandelKey(e);
}
SDL_Rect rect;
rect.x = PlayerX;
rect.y = PlayerY;
rect.w = 0;
rect.h = 0;
//Apply the current image
SDL_BlitSurface( gCurrentSurface, NULL, gScreenSurface, &rect );
//Update the surface
SDL_UpdateWindowSurface( gWindow );
}
}
}
//Free resources and close SDL
close();
return 0;
}
Makefile:
#OBJS specifies which files to compile as part of the project
OBJS = Main.cpp
#MODS specifies which Modules to Compile.
#NOTE: only add .cpp files, no .h files!
MODS = Modules/HandleKeys.cpp
#CC specifies which compiler we're using
CC = g
#COMPILER_FLAGS specifies the additional compilation options we're using
# -w suppresses all warnings
COMPILER_FLAGS = -w
#LINKER_FLAGS specifies the libraries we're linking against
LINKER_FLAGS = -lSDL2
#OBJ_NAME specifies the name of our exectuable
OBJ_NAME = Build/build.deb
#This is the target that compiles our executable
all : $(OBJS)
$(CC) $(OBJS) $(MODS) $(COMPILER_FLAGS) $(LINKER_FLAGS) -o $(OBJ_NAME)
Global.h
#ifndef __GLOBAL_H__
#define __GLOBAL_H__
//Included by all files!
#include <SDL2/SDL.h>
#include <string>
#include <list>
using namespace std;
extern int SCREEN_WIDTH = 640;
extern int SCREEN_HEIGHT = 480;
extern char* title = "Top Down";
enum Entity
{
PLAYER_SURFACE,
KEY_PRESS_SURFACE_TOTAL
};
extern list<string> EntityPaths
{
"Assets/Player.bmp",
};
//Starts up SDL and creates window
extern bool init();
//Loads media
extern bool loadMedia();
//Frees media and shuts down SDL
extern void close();
//Loads individual image
extern SDL_Surface* loadSurface( std::string path );
//The window we'll be rendering to
extern SDL_Window* gWindow;
//The surface contained by the window
extern SDL_Surface* gScreenSurface;
//The images that correspond to a keypress
extern SDL_Surface* Entity[ KEY_PRESS_SURFACE_TOTAL ];
//Current displayed image
extern SDL_Surface* gCurrentSurface;
extern int PlayerX;
extern int PlayerY;
extern const int PlayerSpeed;
#endif
HandleKeys.cpp:
#include "Global.h"
#include "HandleKeys.h"
void HandleKey(SDL_Event e){
if( e.type == SDL_KEYDOWN )
{
//Select surfaces based on key press
switch( e.key.keysym.sym )
{
case SDLK_UP:
PlayerY -= PlayerSpeed;
break;
case SDLK_DOWN:
PlayerY = PlayerSpeed;
break;
case SDLK_LEFT:
PlayerX -= PlayerSpeed;
break;
case SDLK_RIGHT:
PlayerX = PlayerSpeed;
break;
}
}
}
HandleKeys.h:
#ifndef __HANDLEKEYS_H__
#define __HANDLEKEYS_H__
extern void HandelKey(SDL_Event e);
#endif
Make file output:
g Main.cpp Modules/HandleKeys.cpp -w -lSDL2 -o Build/build.deb
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: /tmp/ccvPJ1dy.o:(.data 0x0): multiple definition of `SCREEN_WIDTH'; /tmp/ccdTvusg.o:(.data 0x0): first defined here
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: /tmp/ccvPJ1dy.o:(.data 0x4): multiple definition of `SCREEN_HEIGHT'; /tmp/ccdTvusg.o:(.data 0x4): first defined here
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: /tmp/ccvPJ1dy.o:(.data.rel.local 0x0): multiple definition of `title'; /tmp/ccdTvusg.o:(.data.rel.local 0x0): first defined here
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: /tmp/ccvPJ1dy.o:(.bss 0x0): multiple definition of `EntityPaths[abi:cxx11]'; /tmp/ccdTvusg.o:(.bss 0x0): first defined here
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: /tmp/ccvPJ1dy.o: warning: relocation against `PlayerSpeed' in read-only section `.text'
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: /tmp/ccdTvusg.o: in function `main':
Main.cpp:(.text 0x3e0): undefined reference to `HandelKey(SDL_Event)'
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: /tmp/ccvPJ1dy.o: in function `HandleKey(SDL_Event)':
HandleKeys.cpp:(.text 0x49): undefined reference to `PlayerSpeed'
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: HandleKeys.cpp:(.text 0x5f): undefined reference to `PlayerSpeed'
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: HandleKeys.cpp:(.text 0x75): undefined reference to `PlayerSpeed'
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: HandleKeys.cpp:(.text 0x8b): undefined reference to `PlayerSpeed'
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: warning: creating DT_TEXTREL in a PIE
collect2: error: ld returned 1 exit status
make: *** [Makefile:23: all] Error 1
i have messed around with using the "extern" parameter but nothing relay works, i have also messed around with how i define things in both global.h and main.cpp.
I am on pop OS (a Ubuntu based Linux distro).
Edit: I added the wrong main.cpp (I Had another open in my vscode workspace!)
Edit2: I now have a new error:
/usr/lib/gcc/x86_64-unknown-linux-gnu/12.1.0/../../../../x86_64-unknown-linux-gnu/bin/ld: /tmp/ccwvE6bo.o: in function `main':
Main.cpp:(.text 0x3e0): undefined reference to `HandelKey(SDL_Event)'
collect2: error: ld returned 1 exit status
make: *** [Makefile:23: all] Error 1
CodePudding user response:
The lines
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
in Main.cpp
and the lines
extern int SCREEN_WIDTH = 640;
extern int SCREEN_HEIGHT = 480;
in Global.h
are definitions. A declaration with an initializer is always a definition.
By defining the same variable in more than one translation unit (i.e. .cpp
file), you are violating the one-definition rule.
I suggest that you change
extern int SCREEN_WIDTH = 640;
extern int SCREEN_HEIGHT = 480;
in Global.h
to the following:
extern const int SCREEN_WIDTH;
extern const int SCREEN_HEIGHT;
That way, it is only a declaration and no longer a definition.
You have a similar problem with title
defined in Global.h
:
extern char* title = "Top Down";
This line is a definition.
Header files should generally only contain declarations of variables, not definitions, because otherwise, if the header files is included by more than one translation unit, it will violate the one-definition rule.
Therefore, I suggest that you change this line to a declaration:
extern const char* title;
In one of the translation units, for example Main.cpp
, you should then provide the definition:
const char* title = "Top Down";
The variable EntityPaths
has a similar problem:
extern list<string> EntityPaths
{
"Assets/Player.bmp",
};
This is a definition. You should not define it in the header file. Instead, you should only provide a declaration:
extern list<string> EntityPaths;
You should define it in exactly one translation unit, for example in Main.cpp
.
In Global.h
, you have declared PlayerSpeed
:
extern const int PlayerSpeed;
However, you did not define it anywhere. I suggest that you add
const int PlayerSpeed;
to Main.cpp
.
The line
extern void HandelKey(SDL_Event e);
in HandleKeys.h
has a typo. It should be HandleKey
, not HandelKey
.