Hands-On Game Development with WebAssembly
上QQ阅读APP看书,第一时间看更新

The player's spaceship game object

Up to this point, we have been keeping all of the values that track our player's ship in global variables. From an organizational perspective, this is less than ideal. The first game object we will create will be the player's ship object. We will start with a basic class and add more object-oriented features to our code later.

Here is the code for our new header file, game.hpp:

#ifndef __GAME_H__
#define __GAME_H__
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <emscripten.h>
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
#include <string>
#include <vector>

#define SPRITE_FILE "sprites/Franchise.png"
#define MAX_VELOCITY 2.0
#define PI 3.14159
#define TWO_PI 6.28318

extern Uint32 last_time;
extern Uint32 last_frame_time;
extern Uint32 current_time;
extern SDL_Window *window;
extern SDL_Renderer *renderer;
extern SDL_Rect dest;
extern SDL_Texture *sprite_texture;
extern SDL_Event event;
extern bool left_key_down;
extern bool right_key_down;
extern bool up_key_down;
extern bool down_key_down;
extern bool space_key_down;
extern float delta_time;
extern int diff_time;

class PlayerShip {
public:
float m_X;
float m_Y;
float m_Rotation;
float m_DX;
float m_DY;
float m_VX;
float m_VY;

PlayerShip();
void RotateLeft();
void RotateRight();
void Accelerate();
void Decelerate();
void CapVelocity();
void Move();
void Render();
};

extern PlayerShip player;
#endif

All of our CPP files will include this game.hpp header file. The first few lines of this file are to make sure we do not include this file more than once. We are then defining all of the global variables we had defined in our older C files:

extern Uint32 last_time;
extern Uint32 last_frame_time;
extern Uint32 current_time;
extern SDL_Window *window;
extern SDL_Renderer *renderer;
extern SDL_Rect dest;
extern SDL_Texture *sprite_texture;
extern SDL_Event event;
extern bool left_key_down;
extern bool right_key_down;
extern bool up_key_down;
extern bool down_key_down;
extern float delta_time;

In the header file, we are not allocating space on to the heap. The use of the extern keyword before our global variable definitions tells the compiler that we declared the global variable in one of the .cpp files. Right now, we still have a lot of global variables. We will be reducing the number of these globals as we make modifications to our code in this chapter.

If this were production code, it would make sense to move all of these values into classes, but, for now, we are only creating a PlayerShip object. We also have our class definition for PlayerShip. Developers usually create class definitions inside header files.

After we define all of our global variables, we will need our class definition.

Here is the definition of our PlayerShip class:

class PlayerShip {
public:
float m_X;
float m_Y;
float m_Rotation;
float m_DX;
float m_DY;
float m_VX;
float m_VY;

PlayerShip();
void RotateLeft();
void RotateRight();
void Accelerate();
void Decelerate();
void CapVelocity();
void Move();
void Render();
};

extern PlayerShip player;

In this book, we are going to declare all of our attributes public. That means our code can access them from anywhere, not just from inside this function. If you are working on a project with more than one developer, this is not usually considered to be a good practice. Preventing other classes from being able to directly modify some of our attributes such as m_DX and m_DY is a good idea if you do not want another developer to directly alter specific attributes that only functions in a class are meant to modify. For demonstration purposes, however, having everything in our class defined as public will simplify our design.

After we define our attributes, we have a series of functions that will be associated with this class once defined. The first function, PlayerShip()has the same name as our class, which makes it the constructor, that is, the function that is called by default when our app creates an object of the  PlayerShip type. If we wished, we could define a destructor function, which would run when the object was destroyed, by calling it ~PlayerShip(). We do not currently need a destructor for that object so we will not define it here, which means we will rely on C++ to create a default destructor for this class.

All of the other functions we have defined in this class correspond to functions we created in previous C versions of our game. Moving all of these functions to a class allows us to organize our code better. Notice that after our class definition, we created another global variable that is a PlayerShip called player. The compiler shares this player object in all of the .cpp files that include our game.hpp file.