Upload
francis-serina
View
48
Download
1
Embed Size (px)
DESCRIPTION
Introductory lecture to Game Engines
Citation preview
Game Programming I: Introduction
UP ITTC Game Development Track Francis Joseph Viola-Fernando Seriña
Goals
Objectives
Identify and implement the fundamental structure of all video games in C++
Identify and implement the different modules in a video game
Prerequisite: Computer Programming 3 (C++)
As a Software
Video Games are fundamentally, software. Development of Video Games follow Software Engineering Practices and Methods.
Unlike Business applications, a Video Game should continue with or without User Input.
A major difference between Video Games and other software is that development teams include more than just programmers. They include artists, level designers, writers, etc. – similar to movie production.
Video Games
Video Games always require a computer to receive player input, perform logic and return feedback to the player.
Thus, card games and board games are not considered Video Games (even if they have Video Game versions).
Modules & Managers Elements of a Video Game
Sample Modules
Game Logic
Input Audio
Video
Resources Data
Timer
Sample Modules
A Video Game is comprised of different systems working together
Each system is further divided into subsystems. For example: Game Logic may consist of Physics and AI Graphics may include 2D and 3D Resources may handle images, meshes, audio sources, etc.
Each module may have a Manager that would handle all the subsystems and their interactions between other systems
Most of the modules correspond to specific hardware types
Specialized Modules
There are devices which have specialized hardware for specific modules. For example: Physics Modules (Physics Processing Units)
Post Rendering Effects (Digital Signal Processor)
Network Module
Logging (Debugging)
Game Manager
The Game Manager is the core of a Video Game. Ideally, it is the entry and exit point of the program. The game manager initializes the other modules in the game.
All objects in the scene that are either visible, has logic or both is managed by the Game Manager. These objects are commonly called game objects (which will be discussed later).
The Game Manager handles the Game Loop (to be discussed in the next chapter)
Time Manager
The Time Manager (or Timer) keeps track of the amount of time between iterations of the game loop
Usually measured in milliseconds
Other information that the Timer may keep track of: Time elapsed since the Video Game started
Time elapsed between video output (to determine or control frames per second)
Input Manager
The Input Manager (or simply Input) records the states of all available input devices to be used
Input devices may differ per platform as listed: For Desktop/Laptops; keyboard, joystick, mouse, webcam,
microphone, etc. For Consoles/Handhelds; game pad buttons, joystick, IR, device
orientation or motion, multi-touchscreen, camera, microphone, etc.
Other information the Input may record: Duration of an input state (ex: charging of a weapon) Events or Change of state of an input (ex: on press, on release) Order of consecutive events (ex: combo, double click)
Output Manager
The Output Manager (or simply Output) handles feedback to the player
Output is usually split into Audio and Video subsystems
The Audio subsystem control the playing of music and sound effects
The Video subsystem manages the visual feedback to the player, which may be in 2D, 3D or both
The Game Loop Fundamental Structure for all Video Games
The Game Loop
Input
Update Output
The Game Loop
As mentioned before, Video Games should continue processing whether the user has entered any input or not
Thus, we cannot use blocking input functions such as printf() or cin to receive input
Then, we update the logic of the game and return an output before we repeat the cycle
Each cycle in the game loop is called a frame, similar to the frames of a movie. After all, a video game is an interactive movie
The Game Loop
bool isPlaying = true; if (!Init()) return -1; // Enter the game loop while (isPlaying) { Input(); Update(); Output(); } CleanUp();
Init() and CleanUp()
Init() is used for initializing the video game, initializing the modules and loading resources
CleanUp() is used for releasing resources and shutting down the other modules
Note: The constructor and destructor may be used instead of Init() and
CleanUp()
These 2 functions are not part of the game loop
Input()
Input() calls the Input Manager to recheck all the states from the input devices
Update()
Update() contains the logic of the Video Game
Player’s objects react to Input
Collision Tests, Physics and AI are also executed in the Update
May not exactly be in the order as shown
Update Timer
Collision Tests and Reactions
React to Input
Update AI
Update Physics
Output()
Output() gives the player feedback
Output devices differ per platform but they generally involve both Video and Audio
Rather than displaying directly to the screen, we put the output in a buffer
Clear Buffer
Objects Write to Buffer
Output Buffer
Exiting the Game Loop
The Game Loop, and eventually the game itself, exits when the condition (isPlaying) becomes false
Simple DirectMedia Layer C cross-platform multimedia library
SDL
Cross-platform multimedia library to handle the following: Events (aka Input: keyboard, mouse, joystick)
Time
Output (audio, video)
Threads
SDL has a lot of extensions to make use of networking, different file formats and others
Official Website: http://www.libsdl.org/
Installation
Download the latest developmental libraries for your OS at http://www.libsdl.org/download-1.2.php
The README included in the archives should be enough to install it
To use the headers and libraries, each project needs to edit their properties to include the headers and link the libraries properly
NOTE: Get the development libraries if available. They have devel in their
package names
This lecture uses SDL 1.2.14
Setting up a project (Xcode 3)
After installing the SDL framework, Documentation and Project Templates as mentioned in the README
In the New Project wizard, select Application under User Templates and select SDL Application
Setting up a project (Xcode 3)
Name the project SDL01
In the Groups & Files panel, rename main.c to main.cpp to allow C++
Also in Groups & Files, select Targets then double click on SDL01
Setting up a project (Xcode 3)
Under the Build tab, change the Base SDK to Current Mac OS
Build and run to test if it works
The program is an empty window which exits on any key press
Extensions
SDL only loads BMP (for images) and WAV (for audio)
However, we need compressed file formats to reduce application size
SDL_image allows loading of JPG , PNG and more
SDL_mixer allows loading of MP3 and more
Other SDL extensions that we may need in the future: SDL_net and SDL_ttf
Working with SDL
Objectives
Implement the Game Loop in C++ using SDL
Start from src/01/main.cpp
Build and run main.cpp. You will get an empty 640x480 window that exits when clicking the close button
src/01/main.cpp explained
#include "SDL.h"
This directive loads all the functionality of SDL. Note: the exact path of the header may be different depending on your system
src/01/main.cpp explained
// Global Variables bool isPlaying = true; SDL_Surface *screen = 0;
isPlaying is a flag to determine if the game is still running. This controls the game loop.
screen is the video surface which will be displayed on the screen. All other images are drawn unto screen.
src/01/main.cpp explained
// Function prototypes bool Init(); void Input(); void Update(); void Output(); void CleanUp(); The said functions are used for modularization purposes.
src/01/main.cpp explained
int main(int argc, char *argv[]) { if (!Init()) return -1; // Enter the game loop while (isPlaying) { Input(); Update(); Output(); } CleanUp(); return 0; }
src/01/main.cpp explained – Init()
/* Initialize the SDL library */ if ( SDL_Init(SDL_INIT_EVERYTHING) < 0 ) { fprintf(stderr, "Couldn't initialize SDL: %s\n", SDL_GetError()); return false; } SDL_Init() initializes the modules specified in the parameter. For now, we load everything. SDL_Init() should return 0 if no error has been found. Otherwise, it returns a value < 0. It is
highly advisable to check if SDL has indeed initialized and handle the case if it doesn’t. /* Set 640x480 video mode */ screen = SDL_SetVideoMode(640,480, 32, SDL_SWSURFACE); if (screen == NULL) { fprintf(stderr, "Couldn't set 640x480x32 video mode: %s\n", SDL_GetError()); SDL_Quit(); return false; } SDL_SetVideoMode() sets up the video surface and returns a pointer to the surface. The
parameters are width, height, bits per pixel and video flags.
src/01/main.cpp explained – Input()
SDL_Event e; while (SDL_PollEvent(&e)) { switch (e.type) { case SDL_QUIT: // Pressing the close button // will quit the program isPlaying = false; break; } } This is the event handling portion of the game. Currently, we will
only handle the quit event (pressing the close button of the window).
src/01/main.cpp explained – Output()
SDL_Flip(screen); In order to display output, we draw images unto the surface
screen and display screen unto the screen. Basically, screen acts as a buffer to be drawn on.
SDL_Flip() draws the buffer unto the screen. This should always be the last line of the Output() and eventually, the last part of the game loop
src/01/main.cpp explained – CleanUp()
SDL_Quit(); When done with SDL, we call on SDL_Quit() to free up the
resources created by SDL_Init() and SDL_SetVideoMode(). Thus, we don’t need to manually free up screen, but other surfaces must be freed manually.
Sprites SDL_Surface
Sprites
Sprites are image resources that are displayed on screen
In SDL, a sprite is called a Surface. Anything that can be drawn or drawn unto is a surface.
For this lecture, we will assume that all sprites are located under images folder in the working directory
Double check the working directory of your project to know where to put the images
Sprites
Copy the images folder from src/02/ to your working directory
Continuing from src/01/main.cpp…
Add the following global variable SDL_Surface *ship = 0;
Load image unto the sprite (Init()) ship = SDL_LoadBMP("images/ship.bmp"); if (!ship) { fprintf(stderr, "Error: %s", SDL_GetError()); }
Write sprite unto the screen (Output()) SDL_BlitSurface(ship, NULL, screen, NULL);
Free up sprite (CleanUp()) SDL_FreeSurface(ship);
Build and Run
Build and run the program
You should see a ship with pink background unto a black screen
SDL_LoadBMP()
SDL_LoadBMP() takes 1 parameter of type (char *) which denotes the path of the image to be loaded.
SDL_LoadBMP() allows us to load Windows Bitmap images into our program. Similar to SDL_SetVideoMode(), if loading fails, it returns a null pointer.
It is highly advisable to handle such cases by checking if the pointer exists.
http://www.libsdl.org/cgi/docwiki.cgi/SDL_LoadBMP
SDL_BlitSurface()
The process of writing an image unto another is called blitting. We use SDL_BlitSurface() to draw one surface unto another.
The first parameter is the source surface while the third parameter is the destination surface.
The second parameter determines which part of the source surface will be copied unto the destination. Setting this to null will draw the whole source unto the destination.
The fourth parameter is the location in the destination surface where the source will be drawn on. Setting this to null will draw the source on the upper left corner.
http://www.libsdl.org/cgi/docwiki.cgi/SDL_BlitSurface
SDL_FreeSurface()
SDL_FreeSurface() takes 1 parameter of type SDL_Surface*.
When a surface will no longer be used, it is preferred to free up the memory using SDL_FreeSurface(). Since the resource has been freed, it is common practice to set that pointer to 0. However, we didn’t do that because we’re expecting the program to end right after CleanUp().
http://www.libsdl.org/cgi/docwiki.cgi/SDL_FreeSurface
Adding a Color Key
To set the pink of the surface to transparent, we call SDL_SetColorKey() after we successfully loaded the image
SDL_SetColorKey(ship, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(ship->format, 255, 0, 255));
SDL_SetColorKey() takes 3 parameters – source surface, color key flags and a color
The color key flags can be OR’ed together to be set them together. SDL_SRCCOLORKEY indicates that the color specified will be transparent. SDL_RLEACCEL allows the color keyed surface to be drawn faster
The color is actually a 32 bit number that varies per operating machine. Thus, SDL_MapRGB() removes this dependency and generates that number for you using the format of the surface and the color value in red, green and blue.
http://www.libsdl.org/cgi/docwiki.cgi/SDL_SetColorKey
Sprites
See src/02/main.cpp for any clarifications
See the SDL API for more information on the aforementioned functions
http://www.libsdl.org/cgi/docwiki.cgi/SDL_API
React to Input
Input
Though SDL allows us to keep track of many different types of events, we will only take note of the arrow keys for now.
We will keep track of the state of each key using a bool variable. True means the key is pressed, False means it’s released.
Create the following enum enum direction_t { UP = 0, DOWN, LEFT, RIGHT };
Add the following global variable bool keys[4];
Input
Add the following cases inside the Input() case SDL_KEYDOWN: SetKey(e.key.keysym.sym, true); break; case SDL_KEYUP: SetKey(e.key.keysym.sym, false); break;
Input
Add the following function. Don’t forget to add the prototype as well void SetKey (SDLKey key, bool val) { switch (key) { case SDLK_DOWN: keys[DOWN] = val; break; case SDLK_UP: keys[UP] = val; break; case SDLK_LEFT: keys[LEFT] = val; break; case SDLK_RIGHT: keys[RIGHT] = val; break; } }
Update
We need to keep the location of the ship. Create the following global variables.
int shipX = 0; int shipY = 0;
To find out which keys are pressed, we simply check keys[] in the Update()
if (keys[RIGHT]) shipX++; else if (keys[LEFT]) shipX--; if (keys[DOWN]) shipY++; else if (keys[UP]) shipY--;
Output
Since the coordinates of our ship have changed, we need to specify where to draw it.
Replace the previous call with SDL_BlitSurface() with the following in the Output()
SDL_Rect destRect; destRect.x = shipX; destRect.y = shipY; SDL_BlitSurface(ship, NULL, screen, &destRect);
Note that SDL_Rect is a struct pre-defined in SDL. It contains x, y, w and h. However, as a fourth parameter in SDL_BlitSurface(), only the x and y are used.
Build and Run
Build and run the program
The ship should now be movable by pressing the arrow keys
Surface Coordinates
You may have noticed in the code that pressing down increases the y-coordinate of the ship. This is so because in surface coordinates, the upper left corner denotes (0, 0) and +x goes to the right while +y goes down.
This convention is commonly used for Screen Coordinates (but this is NOT a standard)
Ship Trails
You may have noticed that moving the ship around would leave a trail of its edges unto the background. This is because our screen buffer is drawn unto every frame and nothing is clearing it out.
One way to fix this is to clear the buffer using SDL_FillRect() and specify the desired color (not recommended)
The better solution is to draw a background every frame, making sure that the background is drawn first.
React to Input
See src/03/main.cpp for any clarifications
Timer
Purpose
In our previous example, the coordinates of the ship change for every frame that the keys are pressed. This would be undesirable because our games could be played in different machines with different frame speeds.
If one computer runs with 60 fps and another running at 30 fps, the computer with 60 fps would move faster than the other.
Instead of being frame-rate dependent, we will include a timer which will calculate the time between frames so that motion will be time-dependent.
Timer
Add the following global variables float timeSinceStart = 0; float dt = 0;
Add the following to the top portion of the Update() float newTime = SDL_GetTicks() * 0.001f; dt = newTime - timeSinceStart; timeSinceStart = newTime; Both values are in seconds. SDL_GetTicks() returns the
number of milliseconds since SDL_Init(). We are also keeping the time between frames (dt).
Ship Speed
Since our motion will now be time-dependent, we need to set a speed (pixels/sec) as a global variable.
float shipSpeed = 400;
In the Update(), in/decrement shipX or shipY by (shipSpeed * dt);
Note: shipX and shipY should now be float as well.
Build and Run
Build and run the program
Try changing the value of shipSpeed and see what happens.
See src/04/main.cpp for any clarifications
Asteroids
Asteroids
We will now add adversaries for our player.
The ship needs to dodge the asteroid. Colliding with the asteroid will cause the ship to spawn at the starting position.
The asteroid starts from the top of the screen, slowly moving downwards. Upon going past the bottom edge of the screen, the asteroid will reappear on top with a random x-coordinate.
Random Number
Since we will need a random number generator, include the <ctime> library
#include <ctime>
At the end of the Init(), right before return true;
srand(time(NULL));
To use, simply call rand() and this will generate a random integer. Limit the values using modulo.
Asteroid Properties
Create the following global variables SDL_Surface *asteroid = 0; float asteroidX = 0; float asteroidY = -128; // above the screen float asteroidSpeed = 200;
Asteroid Sprite
Load the image (Init()) asteroid = SDL_LoadBMP("images/asteroid.bmp"); if (!asteroid) { fprintf(stderr, "Error: %s", SDL_GetError()); } SDL_SetColorKey(asteroid, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(asteroid->format, 255, 0, 255));
As a practice, when coding the loading of a sprite, handle the
release of the resource immediately (CleanUp()) SDL_FreeSurface(asteroid);
Asteroid Logic
Add the following after the ship’s movement in the Update() asteroidY += (asteroidSpeed * dt); if (asteroidY > 480) { asteroidY = -128; asteroidX = rand() % 512; } This moves the asteroid downward until it reaches the end
and spawns on top upon going beyond the bottom edge.
Draw the Asteroid
Inside the Output() after drawing the ship; destRect.x = asteroidX; destRect.y = asteroidY; SDL_BlitSurface(asteroid, NULL, screen, &destRect);
Note that we’re recycling the same destRect used for the ship.
Test
Before we go to collision detection and reaction, now would be a good time to test it.
Build and run the program
The asteroid should come from the top, moving downwards and respawning above the top edge of the screen.
It would be advisable to change the initial coordinates of the ship to somewhere in the middle of the screen.
Circle – Circle Collision
For now, we will let our collision areas be a circle. Circle collisions are easy to understand
Two circles are colliding if the distance between their centers is less than the sum of their radii
Circle – Circle Collision
Given the position both circles; (posAX, posAY) and (posBX, posBY), and their radius; radiusA and radiusB, we can compute for the distance as follows:
distanceSq = (posAX – posBX) * (posAX – posBX) + (posAY – posBY) * (posAY – posBY)
Rather than getting the square root, we leave them as squared values because the sqrt() function is really slow
If (radiusA + radiusB) * (radiusA + radiusB) > distanceSq, then the 2 circles intersect
Asteroid – Ship Collision
Ship Collision Area Center: (shipX + 32, shipY + 32)
Radius: 16
Asteroid Collision Area Center: (asteroidX + 64, asteroidY + 64)
Radius: 48
Note that our collision areas aren’t perfect
Asteroid – Ship Collision
After colliding, the ship will return to its starting position. if (AreCirclesIntersecting(shipX + 32, shipY + 32, 16, asteroidX + 64, asteroidY + 64, 48)) { shipX = 288; shipY = 416; }
Note that this is in the last part of the Update()
Asteroids
Build and run your program.
Move the ship towards the asteroid and see what happens when they collide.
See src/05/main.cpp for any clarifications
Congratulations!
Hooray to you for your first attempt at making video games!
Wasn’t that a piece of cake?
Sure, installation and set up was hell but when you got that first window up, it was pure bliss.
Exercises
1. Limit the movement of the ship such that it cannot leave the boundaries of the stage
2. Put in 1 more asteroid and this one moves horizontally
3. Make the movement of the first asteroid to be alternating (moving down then up)
4. Add a boost for the ship (holding the space bar would double its speed)
Limitations
Realizations
1. Adding more game objects is a hassle to manage. Each game object will either be drawn (sprite, position, etc) or execute logic (react to input, collision, etc) or both.
2. Having a dynamic number of game objects during run time is currently not possible.
3. Resources are manually managed. (allocating, freeing, using)
4. The game loop is already constant. Changing the rules of the game doesn’t affect it.
5. Ideally, the modules are separate (Input and Timer)
6. How do we add a Title Screen and In-Game Menu?
7. How do we change levels?
8. Logging using fprintf() to stderr. What if I wanted to log to a file?
9. And so on…
Frameworks a.k.a. Game Engines
A Framework is an integration of different technologies to remove the constant parts of a program and lessen the hassle of work not directly related to the purpose of the software.
For example The Game Loop
Resource Management
Game Object Management
Separation of Modules