game object

____
______
/ __ \
/ __/ /
/ / / /____ ___ _____ ____ ____ / /_/ /_ __
/ / / / ___/ __ `/ __ `/ __ \/ __ \/ /_/ / / / /
/ /_/ / / / /_/ / /_/ / /_/ / / / / / / / /_/ /
/_____/_/
\__,_/\__, /\____/_/ /_/_/ /_/\__, /
__ / /
___/ /
/____/
/____/
Chapter 4
Engine
Outline – Part I
•
•
•
•
Overview
Managers
Logfile Management
Game Management
(next)
Dragonfly Overview
Saucer: hit()
Hero: kbd()
GAME CODE
Star: eventHandler()
GameOver: step()
DrawCharacter
InsertObject
LoadSprite
DRAGONFLY
GetKey
MoveObject
SendEvent
Allocate memory
Clear display
COMPUTER PLATFORM
File open/close
Get keystroke
Dragonfly Classes
Managers
• Support systems that manage crucial tasks
– Handling input, Rendering graphics, Logging data
–…
• Many interdependent, so startup order matters
– e.g., Log file manager needed first since others log
messages
– e.g., Graphics manager may need memory allocated
for sprites, so needs Memory manager first
namespace df {
class Manager {
private:
std::string type; // Manager type identifier .
bool is_started; // True when started successfully.
protected:
// Set type identifier of Manager.
void setType(std::string type);
public:
Manager();
virtual ~Manager();
// Get type identifier of Manager.
std::string getType() const;
// Startup Manager.
// Return 0 if ok, else negative number.
virtual int startUp();
// Shutdown Manager.
virtual void shutDown();
// Return true when startUp() was executed ok, else false.
bool isStarted() const;
};
} // end of namespace df
Manager.h
Notes:
• No “Managers”
• Base class
• Other managers
inherit
• virtual ensures
derived calls
• Namespace (df)
to avoid conflicts
‒ Omitted in later
listings
• “type” helpful for
logging –
protected so
derived managers
can call
namespace df {
class Manager {
Avoids naming
collisions
private:
std::string type; // Manager type identifier .
bool is_started; // True when started successfully.
protected:
// Set type identifier of Manager.
void setType(std::string type);
public:
Manager();
virtual ~Manager();
Object attributes
not modified
// Get type identifier of Manager.
std::string getType() const;
// Startup Manager.
// Return 0 if ok, else negative number.
virtual int startUp();
// Shutdown Manager.
virtual void shutDown();
} // end of namespace df
Notes:
• No “Managers”
• Base class
• Other managers
inherit
• virtual ensures
derived calls
• Namespace (df)
to avoid conflicts
‒ Omitted in later
listings
Derived class can
override
// Return true when startUp() was executed ok, else false.
bool isStarted() const;
};
Manager.h
• “type” helpful for
logging –
protected so
derived managers
can call
Managers in C++: Global Variables?
• Need access from many parts of code  Could make
Managers global variables (e.g., outside of main())
// Outside main(), these are global variables.
df::LogManager log_manager;
df::DisplayManager graphics_manager;
main() {
...
}
• However, order of constructor/destructor
unpredictable
– Could call graphics_manager constructor before
log_manager constructor!
• Plus, explicit global variables difficult from library
– Names could be different in user code  brittle
Managers
• Often, want only 1 instance of each Manger
– e.g., Undefined if two objects managing graphics
• How to enforce one and only 1 instance in
C++?
Managers: C++ Singletons
• Idea – compiler won’t
allow creation (so have
only 1)
MySingleton s; // not allowed
• Instead:
MySingleton &s=
MySingleton::getInstance();
• Guarantees only 1 copy of
MySingleton will exist
– See next slide for static
class Singleton {
(a.k.a. Singleton
private:
Design Pattern)
Singleton();
Singleton(Singleton const &copy);
Singleton&(Singleton const &assign);
public:
static Singleton &getInstance();.
};
// Return the one and only instance of the class.
Singleton &Singleton::getInstance() {
// A static variable persists after method ends.
static Singleton single;
return single;
};
 Use for Dragonfly Managers
• However, also want to explicitly control when starts (not at
first getInstance() call)
 Use startUp() and shutDown() for each, not constructor
Managers in C++: Static Variables?
• Remember, static
variables retain value
after method terminates
• static variables inside
method not created until
method invoked
• Use inside Manager class
method to “create”
manager  the Singleton
void stuff() {
static int x = 0;
cout << x;
x++;
}
main() {
stuff(); // prints 0
stuff(); // prints 1
}
Outline – Part I
•
•
•
•
Overview
Managers
Logfile Management
Game Management
(done)
(done)
(next)
Game Engine Messages
• If all goes well, only want game output/graphics
• But during development, often not the case
– Even for players, may have troubles running game
• Generally, need help debugging
• Debuggers are useful tools, but some bugs not easy to find
in debugger
– Some bugs timing dependent, only happen at full speed
– Some bugs caused by long sequence of events, hard to trace by
hand
– Debuggers don’t trace multithreaded code well (e.g., SFML)
• Most powerful debug tool can still be “print” messages
(e.g., printf() or cout)
• However, standard printing difficult when graphical display
• One solution  Print to logfile
The LogManager – Functionality
• Manages output to logfile
– Upon startup  open file
– Upon shutdown  close file
• Attributes
– Need file handle
• What else?
– Method for general-purpose messages via
writeLog()
• E.g., “Player is moving”
• E.g., “Player is moving to (x,y)” with x and y passed in
– Could include time - game or wall clock (optional)
General Purpose Output
• Using printf() one of most versatile
– But using it requires pre-determined number of
arguments
printf(“Bob wrote 123 lines”);
printf(“%s wrote %d lines”, “Bob”, 123);
// 1 argument
// 3 arguments
• But printf() itself allows variable number or
arguments
• Solution  use printf()’s mechanism for variable
number of arguments passed in writeLog()
– Specify with “…”:
void writeLog(const char *fmt, …) {
…
}
General Purpose Output
• Need <stdarg.h>
• Create a va_list
– Structure gets initialized
with arguments
• va_start() with name
of last known argument
– Sets up va_list
• Can then do printf(),
but with va_list
 vfprintf()
• Uses macros to increment
across args
• va_end() when done
(“va” stands for “variable argument”)
#include <stdio.h>
#include <stdarg.h>
void writeMessage(const char *fmt, ...) {
fprintf(stderr, "Message: ");
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
Flushing Output
• Data written to file buffered before going to disk
• If process terminates without closing file, data
not written. e.g.,
fprintf(fp, “Doing stuff”);
// program crashes later (e.g., segfault)
– “Doing stuff” line passed, but won’t appear in file
• Can add option to fflush() after each write
– All user-buffered data goes to disk
– Note, incurs overhead, so use only when debugging 
remove for “gold master” release
• Could “compile in” with #ifdef directives (see below)
LogManager Attributes and Methods
private:
bool do_flush;
FILE *p_f;
// True if fflush after each write.
// Pointer to logfile structure.
public:
// If logfile is open, close it.
~LogManager();
// Start up the LogManager (open logfile "dragonfly.log").
int startUp();
// Shut down the LogManager (close logfile).
void shutDown();
// Set flush of logfile after each write.
void setFlush(bool do_flush=true);
// Write to logfile. Supports printf() formatting.
// Return number of bytes written, -1 if error.
int writeLog(const char *fmt, ...);
LogManager Attributes and Methods
private:
bool do_flush;
FILE *p_f;
// True if fflush after each write.
// Pointer to logfile structure.
public:
// If logfile is open, close it.
~LogManager();
// Start up the LogManager (open logfile "dragonfly.log").
int startUp();
// Shut down the LogManager (close logfile).
void shutDown();
// Set flush of logfile after each write.
void setFlush(bool do_flush=true);
// Write to logfile. Supports printf() formatting.
// Return number of bytes written, -1 if error.
int writeLog(const char *fmt, ...);
Allows calling with
no argument
Conditional Compilation
• LogManager used by many objects (status and
debugging). So, all #include “LogManager.h”
– e.g., Saucer.h and Object.h both include
• But then during compilation, header file
processed twice
– Likely causes error, e.g., when compiler sees class
definition second+ time
– Even if does not, wastes compile time
• Solution?
 Only compile in “LogManager.h” one time
Once-only Header Files
• When header included first
time, all is normal
– Defines FILE_FOO_SEEN
• When header included second
time, FILE_FOO_SEEN defined
– Conditional is then false
– So, preprocessor skips entire
contents  compiler will not
see it again, so no duplicate
// File foo.h
#ifndef FILE_FOO_SEEN
#define FILE_FOO_SEEN
// The entire foo file appears next.
class Foo{ … };
#endif // !FILE_FOO_SEEN
• Convention:
– User header file, name should not begin with _ (underline)
– System header file, name should begin with __ (double
underline) [Used for Dragonfly]
– Avoids conflicts with user programs
– For all files, name should contain filename and additional text
Platform-Specific Code
• Conditional compilation
also useful for platform#if defined (_WIN32 ) || defined (_WIN64)
specific code
– E.g., code needed for
Mac, but may not work
on Windows
– [Dragonfly uses for
Window, Mac, Linux]
// Windows specific code here.
#elif LINUX
// Linuxspecific code here.
#endif
#ifndef __LOG_MANAGER_H__
#define __LOG_MANAGER_H__
Complete header file
// System includes.
#include <stdio.h>
// Engine includes.
#include "Manager.h“
// Two-letter acronym for easier access to manager.
#define LM df::LogManager::getInstance()
const std::string LOGFILE_NAME "dragonfly.log"
class LogManager : public Manager {
private:
LogManager();
LogManager (LogManager const&);
void operator=(LogManager const&);
bool do_flush;
FILE *p_f;
public:
// If logfile is open, close it.
~LogManager();
// Private (a singleton).
// No copying.
// No assignment.
// True if fflush after each write.
// Pointer to logfile structure.
// Get the one and only instance of the LogManager.
static LogManager &getInstance();
// Start up the LogManager (open logfile "dragonfly.log").
int startUp();
// Shut down the LogManager (close logfile).
void shutDown();
// Set flush of logfile after each write.
void setFlush(bool do_flush=true);
// Write to logfile. Supports printf() formatting.
// Return number of bytes written, -1 if error.
int writeLog(const char *fmt, ...);
};
} // end of namespace df
#endif // __LOG_MANAGER_H__
LogManager.h
namespace df {
Using the LogManager (1 of 2)
// Get singleton instance of LogManager (can also just use “LM”).
df::LogManager &log_manager = df::LogManager::getInstance();
// Example call with 1 arg.
log_manager.writeLog("DisplayManager::startUp(): Current window set");
// Example call with 2 args.
log_manager.writeLog(
"WorldManager::isValid(): WorldManager does not handle ‘%s’",
event_name.c_str());
// Example call with 3 args.
log_manager.writeLog(
"DisplayManager::startUp(): max X is %d, max Y is %d", max_x, max_y);
// Sample logfile output:
LogManager started // Message from LogManager::startUp()
DisplayManager:: startUp (): Current window set
DisplayManager:: startUp (): max X is 80, max Y is 24
WorldManager::isValid(): WorldManager does not handle ‘mud event’
Development Checkpoint #1!
• Create project
– Use downloaded from Engine or Tutorial as basis
– Make sure works with “empty” game.cpp
• Create Manager base class
• Create LogManager derived class
• Implement writeLog()
– Initially, not part of LogManager, just function
– Test thoroughly, variable arguments
• Move writeLog() into LogManager
– Instantiate LogManager and use getInstance()
• Test  Make sure solid before continuing
– Many of your other objects will use!
Outline – Part I
•
•
•
•
Overview
Managers
Logfile Management
Game Management
– Game Loop
– Time
– GameManager
(done)
(done)
(done)
(next)
The Game Loop
• The Game Manager “runs” game:
10,000 foot view
of game loop
while (game not over) do
Get input // e.g., from keyboard/mouse
Update game world state
Draw current scene to back buffer
Swap back buffer to current buffer
end while
• Each iteration a “step” or a “tick” (as from a clock)
• How fast will above loop run?
– Note, early games just moved Objects fixed amount each loop
 On faster computers, objects moved faster! 
• How to slow it down?
The Game Loop with Timing
while (game not over) do
Get input // e.g., from keyboard/mouse
Update game world state
Draw current scene to back buffer
Swap back buffer to current buffer
Measure loop_time // how long above steps took
Sleep for (TARGET_TIME - loop_time)
end while
But what is value
of TARGET_TIME?
• Frame rate is how often images updated to player  Unit is Hertz
(Hz) or frames per second (f/s)
– 30 f/s typical of full-motion video, little visual value for going faster
• Time between frames is frame time (or delta time)
• At 30 f/s, frame time is 1/30 or 33.3 milliseconds  TARGET_TIME
– Milliseconds are common unit of time for game engines
• Why do we care about frame rate?  Often drives game loop rate
(not many reasons to go faster than frame display rate)
• Ok, how to measure computer time?
Measuring Computer Time (1 of 2)
• time() returns seconds since Jan 1, 1970
– Resolution of 1 second  far too coarse for game loop
• Modern CPUs have high-resolution timer
– Hardware register that counts CPU cycles
– 3 GHz processor, timer goes 3 billion times/sec, so resolution is
0.333 nanoseconds  Plenty of precision!
– 32-bit architecture  wrap around every ~1.4 seconds
– 64-bit architecture  wrap around every ~195 years
• System calls vary with platform. e.g.,
– Win32 API  QueryPerformanceCounter() to get value, and
QueryPerformanceFrequency() to get rate
• GetSystemTime() has millisecond accuracy – good enough for engine
– Xbox 360 and PS3  mftb (move from time base register)
– Linux  clock_gettime() to get value (link with –lrt)
Measuring Computer Time (2 of 2)
• Need to measure elapsed time
– Want milliseconds for game engine
• Basic method:
Record before time
Do processing stuff (get input, update ...)
Record after time
Compute elapsed time: after - before
 So, how to measure elapsed time on Linux?
Mac? Windows?
Elapsed Time – Linux
// Compile with ‐lrt
#include <time.h>
struct timespec before_ts, after_ts;
clock_gettime(CLOCK_REALTIME, &before_ts); // Start timing.
// Do whatever work that needs to be measured...
clock_gettime(CLOCK_REALTIME, &after_ts); // Stop timing.
// Compute elapsed time in milliseconds.
long int bfore_msec, after_msec;
bfore_msec = bfore_ts.tv_sec*1000 +bfore_ts.tv_nsec/1000000;
after_msec = after_ts.tv_sec*1000 +after_ts.tv_nsec/1000000;
long int elapsed_time = after_msec ‐ before_msec;
Elapsed Time – Mac
#include <sys/time.h>
struct timeval bfore_tv, after_tv;
gettimeofday(&bfore_tv, NULL); // Start timing.
// Do whatever work that needs to be measured...
gettimeofday(&after_tv, NULL); // Stop timing.
// Compute elapsed time in milliseconds.
long int bfore_msec =
bfore_tv.tv_sec*1000 + bfore_tv.tv_usec/1000;
long int after_msec =
after_tv.tv_sec*1000 + after_tv.tv_usec/1000;
long int elapsed_time = after_msec ‐ bfore_msec;
Note: granularity of gettimeofday() varies with
system. Could consider mach_absolute_time()
Elapsed Time – Windows
#include <Windows.h>
SYSTEMTIME before_st, after_st;
GetSystemTime(&before_st);
// Do whatever work that needs to be measured...
GetSystemTime(&after_st);
// Compute elapsed time in milliseconds.
long int before_msec = (before_st.wMinute * 60 * 1000)
+ (before_st.wSecond * 1000)
+ (before_st.wMilliseconds);
long int after_msec = (after_st.wMinute * 60 * 1000)
+ (after_st.wSecond * 1000)
+ (after_st.wMilliseconds);
long int elapsed_time = after_msec - before_msec;
The Clock Class
• Engine needs convenient access to highresolution timing
– Sometimes useful for game programmer, too
• Game loop  find elapsed time since last call
– For Dragonfly, this is sufficient
• More general purpose could separate “game time” from
“real time”. This would allow time scaling for times engine
could not “keep up” (more later)
• Know how long game loop took
– Can then pause/sleep for right amount
• Remainder of frame time (TARGET_TIME)
Clock.h
// The clock, for timing (such as the game loop)
class Clock {
private:
long int previous_time;
// Previous time delta() called (micro sec).
public:
// Sets previous_time to current time.
Clock();
// Return time elapsed since delta() was last called, -1 if error.
// Units are microseconds.
long int delta(void);
// Return time elapsed since delta() was last called.
// Units are microseconds.
long int split(void) const;
};
Sleeping
• At end of game loop, need to pause/sleep for
whatever is remaining (elapsed - delta)
– Need milliseconds of granularity
– sleep() only has seconds of granularity
• On Linux and Mac, use nanosleep()
struct timespec sleep_time
sleep_time.tv_sec = 0
sleep_time.tv_nsec = 20000000 // 20 milliseconds
nanosleep(&sleep_time , NULL)
Need <time.h>
• On Windows, use Sleep()
int sleep_time = 20
Sleep(sleep_time) // 20 milliseconds
Need <Windows.h>
Game Loop with Clock
Clock clock
while (game not over) do
clock.delta()
// Get input (e.g., from keyboard/mouse)
// Update world state
// Draw new scene to back buffer
// Swap back buffer to current buffer
loop_time = clock.split()
sleep(TARGET_TIME - loop_time)
end while
(as appropriate for platform, Windows or Linux)
Additional Timing Topics
• What happens if game engine cannot keep up (i.e.,
loop_time > TARGET_TIME)?
– Generally, displayed frame rate must go down
– But does gameplay (e.g., Hero’s bullet fire rate)?
• Could have GameManager provide “step” event more than
once, as required
– But note, if processing step events are taking the most time, this
could exacerbate problem!
• Could have elapsed time so game objects could adjust
accordingly. e.g., for Hero bullet fire rate:
fire_countdown -= ceil(elapsed / TARGET_TIME)
 Note, Dragonfly does nothing extra, but game code still
could
Outline – Part I
•
•
•
•
Overview
Managers
Logfile Management
Game Management
– Game Loop
– Time
– GameManager
(done)
(done)
(next)
(done)
(done)
(done)
GameManager.h (1 of 2)
#include "Manager.h"
const int FRAME_TIME_DEFAULT 33
// In milliseconds.
class GameManager : public Manager {
private:
GameManager();
// Private (a singleton).
GameManager (GameManager const&);
// No copying.
void operator=(GameManager const&); // No assignment.
bool game_over;
// True, then game loop should stop.
int frame_time;
// Target time per game loop (in milliseconds).
public:
// Get the singleton instance of the GameManager.
static GameManager &getInstance();
GameManager.h (2 of 2)
// Startup all GameManager services.
int startUp();
// Shut down GameManager services.
void shutDown();
// Run game loop.
void run();
// Set game over status to indicated value.
// If true (the default), will stop game loop.
void setGameOver(bool game_over=true);
// Get game over status.
bool getGameOver() const;
// Return frame time.
// Frame time is target time for game loop, in milliseconds.
int getFrameTime() const;
};
Development Checkpoint #2!
• Create Clock class
– Clock.h and Clock.cpp with stubs
– Add to project and compile
• Test with program via sleep() and {nanosleep() or Sleep()}
– Use printf(), cout or LogManager for output
• Create GameManager class
– GameManager.h and GameManager.cpp with stubs
– Add to project and compile
• startup() starts LogManager, shutdown() stops LogManager
– Test
• Implement game loop inside GameManager run()
– Uses Clock: delta(), split() and sleep
– Test with printf(), cout or LogManager for output
• Add additional functionality to GameManager
– Frame time
• Make sure solid before going on
– Will drive entire game!
Outline – Part I
•
•
•
•
Overview
Managers
Logfile Management
Game Management
(done)
(done)
(done)
(done)
Outline – Part II
•
•
•
•
•
•
The Game World
Game Objects
Lists of Objects
Updating Game Objects
Events
WorldManager
(next)
The Game World
• Full of objects: bad guys, walls for buildings,
trees and rocks, …
• Game programmer needs access
– e.g., “all objects within radius” or “all objects that
are Saucers”
• Game world needs to manage
– Store and access
– Update: move, collide, …
• Fundamental is game object
Game Object
• Fundamental game programmer abstraction for
items in game
–
–
–
–
–
Opponents (e.g., Saucers)
Player characters (e.g., Hero)
Obstacles (e.g., Walls)
Projectiles (e.g., Bullets)
Other (e.g., Explosions, Score indicator, …)
• Game engine needs to access (e.g., to get
position) and update (e.g., change position)
Core attribute is location in world, so create Vector
class Vector {
// Horizontal coordinate in 2d world.
// Vertical coordinate in 2d world.
public:
// Create Vector with (x,y).
Vector(float init_x, float init_y);
// Default 2d (x,y) is (0,0).
Vector();
// Get/set horizontal component.
void setX(float new_x);
float getX() const;
// Get/set vertical component.
void setY(float new_y);
float getY() const;
// Set horizontal & vertical components.
void setXY(float new_x, float new_y);
Vector.h (1 of 2)
private:
float x;
float y;
// Normalize vector.
void normalize();
// Scale vector.
void scale(float s);
Support routines
useful when
moving (used later)
// Add two Vectors, return new Vector.
Vector operator+(const Vector &other) const;
};
Vector.h (2 of 2)
// Return magnitude of vector.
float getMagnitude() const;
void Vector::getMagnitude()
float mag = sqrt(x*x + y*y)
return mag
void Vector::scale(float s)
x = x * s
y = y * s
void Vector::normalize()
length = getMagnitude()
if length > 0 then
x = x / length
y = y / length
end if
Vector Vector::operator+(const Vector
&other) const {
Vector v
// Create new vector.
v.x = x + other.x // Add x components.
v.y = y + other.y // Add y components.
return v
// Return new vector.
#include <string>
#include "Vector.h"
class Object {
private:
int id;
std::string type;
Vector pos;
// Unique Object identifier.
// User-defined identification.
// Position in game world.
In constructor:
• Set id based on static
• Set position (0,0)
• Set type as “undefined”
// Set object id.
void setId(int new_id);
// Get object id.
int getId() const;
// Set type identifier of Object.
void setType(std::string new_type);
// Get type identifier of Object.
std::string getType() const;
// Set position of object.
void setPosition(Vector new_pos);
// Get position of object.
Vector getPosition() const;
};
Object.h
public:
Object();
virtual ~Object();
#include <string>
#include "Vector.h"
class Object {
private:
int id;
std::string type;
Vector pos;
// Unique Object identifier.
// User-defined identification.
// Position in game world.
// Set object id.
void setId(int new_id);
Needed since deletion
inside engine
// Get object id.
int getId() const;
Object.h
public:
Object();
virtual ~Object();
// Set type identifier of Object.
void setType(std::string new_type);
// Get type identifier of Object.
std::string getType() const;
// Set position of object.
void setPosition(Vector new_pos);
// Get position of object.
Vector getPosition() const;
};
Note: will add
more attributes
later.
 Object
becomes largest
class!
Outline – Part II
•
•
•
•
•
•
The Game World
Game Objects
Lists of Objects
Updating Game Objects
Events
WorldManager
(done)
(done)
(next)
Lists of Game Objects (1 of 3)
• Different kinds of lists might want. e.g.,
– List of all solid Objects
– List of all Objects within radius of explosion
– List of all Saucer Objects
• Lists should be efficient (e.g., avoid copying Objects)
• Updating Objects in lists should update Objects in
game world
– i.e., not just copy of Object
• Using library could be option (e.g., STL list class)
– But, for Dragonfly, want to understand implementation
implications of Object lists since significant impact on
performance  build your own
Lists of Game Objects (2 of 3)
• Different implementation choices possible,
both for the list and for storing the object
– What are some?
– Pros and cons?
Lists of Game Objects (2 of 3)
• Different implementation choices possible,
both for the list and for storing the object
– What are some?
– Pros and cons?
• Array: 1) ease of implementation, 2)
performance
• Object pointer: 1) needed for polymorphism,
2) useful for changing game object, 3)efficient
Integer example
Lists of Game Objects (3 of 3)
private:
int item[MAX];
int count;
// Construct with empty list.
IntList::IntList() {
count = 0;
}
// same for List::clear()
// Add item to list.
bool IntList::insert(int x) {
// Check if room.
if (count == MAX)
return false;
item[count] = x;
count++;
return true;
}
// Remove item from list.
bool IntList::remove(int x) {
for (int i=0; i<count; i++) {
if (item[i] == x) { // Found...
// ...so scoot over.
for (int j=i; j<count-1; j++)
item[j] = item[j+1];
count--;
return true; // Found.
}
}
return false; // Not found.
}
Basically, replace “int” with “Object *”
ObjectList.h (1 of 2)
const int MAX_OBJECTS = 5000;
#include "Object.h"
#include "ObjectListIterator.h"
class ObjectListIterator;
class ObjectList {
private:
int count;
Object *p_obj[MAX_OBJECTS];
// Count of objects in list.
// Array of pointers to objects.
public:
friend class ObjectListIterator;
ObjectList();
ObjectList.h (1 of 2)
const int MAX_OBJECTS = 5000;
Needed by compiler
for forward reference
#include "Object.h"
#include "ObjectListIterator.h"
class ObjectListIterator;
class ObjectList {
private:
int count;
Object *p_obj[MAX_OBJECTS];
// Count of objects in list.
// Array of pointers to objects.
public:
friend class ObjectListIterator;
ObjectList();
What is an
iterator?
ObjectList.h (2 of 2)
// Insert object pointer in list.
// Return 0 if ok, else -1.
int insert(Object *p_o);
// Remove object pointer from list.
// Return 0 if found, else -1.
int remove(Object *p_o);
// Clear list (setting count to 0).
void clear();
// Return count of number of objects in list.
int getCount() const;
// Return true if list is empty, else false.
bool isEmpty() const;
// Return true if list is full, else false.
bool isFull() const;
};
Iterators
• Iterators “know” how to traverse through container class
– Decouples container implementation with traversal
• Can have more than one iterator instance for given list,
each keeping position
• Note, adding or deleting to list while iterating may cause
unexpected results
– Should not “crash”, but may skip items
• Steps to create:
1.
2.
3.
Understand container class (e.g., IntList)
Design iterator class for container class
Add iterator as friend of container
• Steps to use:
1.
2.
Create iterator object, giving it container object
Use first(), isDone(), next(), and currentItem() to
access container
Iterator for List Class (1 of 3)
class IntListIterator {
// Iterate to next item.
void next() {
index++;
}
private:
const IntList *p_list; // Pointer to IntList.
int index;
// Index of current item.
// Return true if done iterating, else false.
bool isDone() {
return index == (p_list -> count);
}
public:
IntListIterator(const IntList *p_l) {
p_list = p_l;
first();
}
// Set iterator to first item.
void first() {
index = 0;
}
1.
2.
Want to error
check index
// Return current item.
int currentItem() {
return p_list -> item[index];
}
};
Understand container class: e.g., IntList uses array.
Design iterator class for container class: e.g., need index
Iterator for List Class (2 of 3)
…
// [Inside IntList class (i.e., IntList.h)]
friend class IntListIterator;
…
3. Add iterator as friend of container
Iterator for List Class (3 of 3)
IntListIterator li(&my_list);
// Iterator with while() loop
li.first();
while (!li.isDone()) {
int item = li.currentItem();
li.next();
}
// Iterator with for() loop
for (li.first(); !li.isDone(); li.next())
int item = li.currentItem();
Steps to use:
1.
2.
Indicate list to iterate on when creating iterator (e.g., &my_list)
Use first(), isDone(), next(), and currentItem() to access
#include "Object.h "
#include "ObjectList.h"
class ObjectList;
private:
ObjectListIterator();
// Must be given list when created.
int index;
// Index into list.
const ObjectList *p_list; // List iterating over.
public:
// Create iterator, over indicated list.
ObjectListIterator(const ObjectList *p_l);
void first();
void next();
bool isDone() const;
// Set iterator to first item in list.
// Set iterator to next item in list.
// Return true if at end of list.
// Return pointer to current Object, NULL if done/empty.
Object *currentObject() const;
};
ObjectListIterator.h
class ObjectListIterator {
#include "Object.h "
#include "ObjectList.h"
class ObjectList;
private:
ObjectListIterator();
// Must be given list when created.
int index;
// Index into list.
const ObjectList *p_list; // List iterating over.
Default construction
public:
// Create iterator, over indicated list.
not allowed
ObjectListIterator(const ObjectList *p_l);
void first();
void next();
bool isDone() const;
// Set iterator to first item in list.
// Set iterator to next item in list.
// Return true if at end of list.
// Return pointer to current Object, NULL if done/empty.
Object *currentObject() const;
};
ObjectListIterator.h
class ObjectListIterator {
Needed by compiler
for forward reference
Array Bounds in C++
Tip #6!
• Remember:
‒ Arrays begin with 0
‒ Arrays end with size minus 1
‒ e.g., int item[MAX]:
‒ First element is item[0]
‒ Last element is item[MAX-1]
‒ When testing and debugging, check all boundary
conditions!
Development Checkpoint #3!
• Create Vector and Object classes
– Add to project or Makefile
– Implement and test (mostly containers)
• Create ObjectList
– Stub out and add to project or Makefile
– Implement and then test insert() and remove()
– Test limits (empty list and full list)
• Create ObjectListIterator
– Stub out and add to project or Makefile
– Implement and then test by having Objects “move”
– Test limits (empty list, full list)
Outline – Part II
•
•
•
•
•
•
The Game World
Game Objects
Lists of Objects
Updating Game Objects
Events
WorldManager
(done)
(done)
(done)
(next)
Updating Game Objects
• Every engine updates game objects – one of its
core functionalities
• Makes game interactive – game objects respond
to player input
– e.g., Game object moves North when player presses
up arrow
• Makes game dynamic – game objects can change
– e.g., Game object takes damage when collides with
another object
Game Loop with Update
ObjectList world_objects
while (game not over) {
...
// Update world state.
ObjectListIterator li(&world_objects)
li.first()
while not li.isDone() do
li.currentObject() -> Update()
li.next()
end while
...
}
• Declare
Update() as
virtual
• Seems ok,
right? But
devil in the
details…
Single Phase Update
// Update saucer (should be called once per game loop).
void Saucer::Update()
WorldManager move(this)
WorldManager drawSaucer(this)
• What’s wrong with the above?
– Consider occlusion or destruction
– Consider dependencies (e.g., turret on vehicle)
• Inefficiency can be handled with sub-systems
to provide phased updates
Phased Updates
ObjectList world_objects
while (game not over) do
...
// Phase 1: Have objects update themselves.
ObjectListIterator li(&world_objects)
for (li.first(); not li.isDone(); li.next())
li.currentObject() -> Update()
end for
// Phase 2: Move all objects.
for (li.first(); not li.isDone(); li.next())
WorldManager move(li.currentObject())
end for
// Phase 3: Draw all objects.
for (li.first(); not li.isDone(); li.next())
WorldManager draw(li.currentObject())
end for
...
end while
• Updates done
in phases
• Allows
subsequent
phases to take
advantage
• Could even be
sub-phases
(not shown)
Outline – Part II
•
•
•
•
•
•
The Game World
Game Objects
Lists of Objects
Updating Game Objects
Events
WorldManager
(done)
(done)
(done)
(done)
(next)
Events
• Games are inherently event-driven
• An event is anything that happens that game object may
need to take note of
– e.g., passing of time (game loop or timer)
– e.g., explosion, pickup health pack, run into enemy
• Generally, engine must
A) Notify interested objects
B) Arrange for those objects to respond
 Call this event handling
• Different objects respond in different ways (or not at all)
– e.g., Car collides with rock then car stops, but rock unchanged
– e.g., Keyboard is pressed so Hero fires missile, but Saucer ignores
keypress
• So, how to manage event handling?
Simple Approach
• Notify game object
that event occurs by
calling method in
each object
• e.g., explosion, send
event to all objects
within radius
– virtual function
named
onExplosion()
void Explosion::Update()
...
if (explosion_went_off) then
// Get list of all objects in range.
ObjectList damaged_objects =
getObjectsInRange(radius)
// Have them each react to explosion.
ObjectListIterator li(&damaged_objects)
for (li.first(); not li.isDone(); li.next())
li.currentObject() -> onExplosion()
end for
...
end if
• Statically typed late binding
– “Late binding” since compiler doesn’t know which  only known at
runtime
– “Statically typed” since knows which type when object known
• E.g. Tank  Tank::onExplosion()
Crate  Crate::onExplosion()
So, what’s the problem?
Statically-Typed is Inflexible
• Objects must declare onExplosion(), even if not all
game objects will use
– If want engine to support, all game objects for all games … even
games with no explosions!
• Worse  base Object must declare virtual functions for all
possible events in game!
• Makes difficult to add new events since must be known at
engine compile time
– Can’t make events in game code or even with World editor
• Instead, want to use dynamically typed late binding
– Some languages support natively (e.g., C# delegates)
– Others (e.g., C++) must implement manually
• How to implement?
 Add notion of event as object parameter and pass around
– Often called message passing
Encapsulating Event in Object
Core Components
Advantages
• Type (e.g., explosion, health • Single event handler
– Since type encapsulated, only method
pack, collision …)
needed is
virtual int eventHandler(Event *p_e);
• Attributes (e.g., damage,
• Persistence
healing, with what …)
– Event data can be retained say, in
queue, and handled later
• Blind forwarding
– An object can pass along event without
even “knowing” what it does (the
engine does this!)
– e.g., “dismount” event not understood
by jeep, can be passed to all occupants
Event Types (1 of 3)
• One approach is to match each
type to integer (e.g., enum EventType)
– Simple and efficient (integers are
fast)
• Problem
enum EventType {
LEVEL_STARTED,
PLAYER_SPAWNED,
ENEMY_SPOTTED,
EXPLOSION,
BULLET_HIT,
…
}
– Events are hard-coded, meaning adding new events hard
– Enumerators are indices so order dependent
• If someone adds one in middle data stored in files gets messed
up
– Event types harder to discern at run time (only see
number)
• This works usually for small games but doesn’t scale
well for larger games
Event Types (2 of 3)
• Encode via strings (e.g., std::string event_type)
• Good:
– Totally free form (e.g., “explosion” or “collision” or “boss ate my
lunch”) so easy to add
– Dynamic – can be parsed at run-time, nothing pre-bound
• Bad:
– Potential name conflicts (e.g., game code inadvertently uses
same name as engine code) (e.g., game programmers use same
name)
– Events would fail if simple typo (compiler could not catch)
– Strings “expensive” compared to integers (but typically only for
large strings)
• Overall, extreme flexibility makes worth “risk” by many
engines
Event Types (3 of 3)
• To help avoid name collision problems,
developers can build tools
– Central dbase of all event types  GUI used to add
new types
– Conflicts automatically detected
– When adding event, could “paste” in automatically, to
avoid human typing errors
• Before setting up such tools weigh benefits with
costs carefully (e.g., may be low tech way)
• Dragonfly convention has “df::” for name
– e.g., “df::step”
Event Arguments
• Easiest is have new type of event class for
each unique event class Event {
string event_type;
};
Note: get() and
set() for event type
not shown
class EventExplosion : public Event {
Vector location;
int damage;
float radius;
};
• Objects get parent Event, but can check type
to see if this is, say, an “explosion event”  if
so, treat incoming object as EventExplosion
Chain of Responsibility (1 of 2)
• Game objects often dependent upon each other
– e.g., “explosion” event given to jeep then passed to
every rider in jeep
– e.g., “heal” event given to jeep, passes to soldier and
does not need to go to backpack
• Can draw graph of relationship
– e.g., Vehicle  Soldier  Backpack  Pistol
• May want to pass events along from one in chain
to another
– Passing stops at end of chain
– Passing stops if event is “consumed” (can be indicated
by return type in event handler)
Chain of Responsibility (2 of 2)
bool someGameObject::eventHandler(Event *p_event)
// Call base class' handler first.
if (BaseClass::eventHandler(p_event)) then
return true // If base consumed, then done.
// Otherwise, try to handle event myself.
if p_event -> getType() == EVENT_DAMAGE then
takeDamage(p_event -> getDamageInfo())
return false // Responded to event, but ok to forward.
end if
if p_event -> getType() == EVENT_HEALTH_PACK then
doHeal(p_event -> getHealthInfo())
return true // Consumed event, so don't forward.
end if
...
return false // Didn't recognize this event.
}
(Almost right  actually, need cast the event call – see later slides)
#include <string>
const std::string UNDEFINED_EVENT "df::undefined";
class Event {
private:
std::string event_type;
// Holds event type.
// Destructor.
virtual ~Event();
// Set event type.
void setType(std::string new_type);
// Get event type.
std::string getType() const;
};
Event.h
public:
// Create base event.
Event();
Dragonfly Events
• Built-in events derive from base Event class
• Game programmer can define others
– e.g., “nuke”
• Will define most as needed, but do EventStep
now
EventStep.h
#include "Event.h"
• Generated by
GameManager every
game loop
– Send to all game
Objects
• Constructor sets type
to STEP_EVENT
– Remember,
event_type is
private to Event
• Game loop iteration
number stored in
step_count
• Acted upon in
eventHandler()
const std::string STEP_EVENT "df::step";
class EventStep : public Event {
private:
int step_count; // Iteration number of game loop.
public:
EventStep();
EventStep(int init_step_count);
void setStepCount(int new_step_count);
int getStepCount() const;
};
int Points::eventHandler(const Event *p_e) {
...
// If step, increment score every second (30 steps).
if (p_e -> getType() == df::STEP_EVENT) {
if (p_e -> getStepCount() % 30 == 0)
setValue(getValue() + 1);
...
}
Dereferencing Pointers
Tip!
• Dereferencing uses “*”
e.g., int *p_x can use cout “x is ” << *p_x
• Access to method uses “.”
e.g., Event e then e.getType()
• But together, typically use “->”. So, Event *p_e:
(*p_e).getType() same as p_e->getType();
Sending Step Events
• In GameManager, inside game loop
// Send step event to all objects.
all_objects = WorldManager getAllObjects()
create EventStep s(game_loop_count)
create ObjectListIterator li on all_objects list
while not li.isDone() do
li.currentObject() -> eventHandler() with s
li.next()
end while
• Except … eventHandler() needs to take
Event * instead of EventStep *
 Need casting
Runtime Type Casting
• C++ strongly typed  conversion to another type
needs to be made explicit
– Called type-casting or casting
• Cast from base class to derived class
class Base {};
class Derived : public Base {};
Base *p_b = new Base;
Derived *p_d = static_cast <Derived *> (p_b);
• From Saucer Shoot:
int Bullet::eventHandler(const Event *p_e)
if p_e->getType() is df::COLLISION_EVENT then
EventCollision *p_collision_event = static_cast <EventCollision *> (p_e)
hit(p_collision_event)
return 1
end if
Ok, What Do We Have?
•
•
•
•
•
Game objects
Lists of game objects
Iterators for game objects
Events
Means of passing them to game objects
 Ready for World Manager!
Outline – Part II
•
•
•
•
•
•
The Game World
Game Objects
Lists of Objects
Updating Game Objects
Events
WorldManager
(done)
(done)
(done)
(done)
(done)
(next)
WorldManager.h (1 of 2)
#include "Manager.h"
#include "ObjectList.h"
class WorldManager : public Manager {
private:
WorldManager();
// Private (a singleton).
WorldManager (WorldManager const&);
// No assignment.
void operator=(WorldManager const&); // No copying.
ObjectList updates;
// List of all Objects in world to update.
ObjectList deletions;
// List of all Objects in world to delete.
public:
// Get the one and only instance of the WorldManager.
static WorldManager &getInstance();
// Startup game world (initialize everything to empty).
// Return 0.
int startUp();
WorldManager.h (2 of 2)
// Shutdown game world (delete all game world Objects).
void shutDown();
// Add Object to world. Return 0 if ok, else -1.
int insertObject(Object *p_o);
// Remove Object from world. Return 0 if ok, else -1.
int removeObject(Object *p_o);
// Return list of all Objects in world.
ObjectList getAllObjects() const;
// Update world.
// Delete Objects marked for deletion.
void update();
// Indicate object is to be deleted at end of current game update.
// Return 0 if ok, else -1.
int markForDelete(Object *p_o);
};
WorldManager
startUp() and shutDown()
// Startup game world (initialize everything to empty).
// Return 0.
void WorldManager::startUp()
updates.clear()
deletions.clear()
Manager::startUp()
// Shutdown game world (delete all game world Objects).
void WorldManager::shutDown()
// Delete all game objects.
ObjectList ol = updates
// Copy list so can delete during iteration.
ObjectListIterator li(&ol)
for (li.first(); not li.isDone(); li.next())
delete li.currentObject()
end for
Manager::shutDown()
Extensions to Object
• Event handler
// Handle event (default is to ignore everything).
// Return 0 if ignored, else 1 if handled.
virtual int eventHandler(const Event *p_e);
• Constructor
// Add self to game world.
WorldManager insertObject(this)
• Destructor
// Remove self from game world.
WorldManager removeObject(this)
Remember in Saucer Shoot?
new Saucer;
// Without grabbing return value
 Now you know how!
Need for Deferred Deletion
• Each step of game loop, iterate over all Objects
 send “step” event
• Object may be tempted to delete itself or another
– e.g., when collision occurs
– e.g., after fixed amount of time
• But may be in middle of iteration! Other Object
may still act on same event
– e.g., for collision, eventHandler() for both objects
called, even if one “deletes” another
Implement deferred deletion  WorldManager::markForDelete()
WorldManager markForDelete()
// Indicate Object is to be deleted at end of current game loop.
// Return 0 if ok, else -1.
int WorldManager::markForDelete(Object *p_o)
// Object might already have been marked, so only add once.
create ObjectListIterator li on deletions list
while not li.isDone() do
if li.currentObj() is p_o then // Object already in list.
return 0
end if
li.next()
end while
// Object not in list, so add.
deletions.insert(p_o)
WorldManager update()
// Update world.
// Delete Objects marked for deletion.
void WorldManager::update()
// Delete all marked objects.
create ObjectListIterator li on deletions list
while not li.isDone() do
delete li.currentObject()
li.next()
end while
// Clear list for next update phase.
deletions.clear()
Will add to update() later (e.g., movement and collisions)
Program Flow for Game Objects
Object Creation
1. Game program invokes
new, say new Saucer
2. Saucer Saucer() calls
Object Object()
3. Object Object() calls
WorldManager
insertObject()
4. WorldManager
insertObject() calls
updates.insert()
Object Destruction
1.
2.
3.
4.
5.
6.
7.
Game program (e.g., in Saucer)
calls WorldManager
markForDelete()
WorldManager
markForDelete() calls
deletions.insert()
GameManager run() calls
WorldManager update()
WorldManager update() iterates
through deletions, calling
delete on each (delete triggers
destructor)
Saucer ~Saucer() calls Object
~Object()
Object ~Object() calls
WorldManager removeObject()
WorldManager removeObject()
calls updates.remove()
Development Checkpoint #4!
•
•
•
•
•
•
Create base Event
Create derived EventStep
Extend Object with eventHandler()
Create WorldManager
Extend Object to add to WorldManager
Write WorldManager markForDelete() and
update()
• Have Game Manager
– Get all objects from WorldManager
– Send step event to each
• Test thoroughly!  Used by rest of engine a lot
Outline – Part II
•
•
•
•
•
•
The Game World
Game Objects
Lists of Objects
Updating Game Objects
Events
WorldManager
(done)
(done)
(done)
(done)
(done)
(done)
Ready for Dragonfly Egg!
• Start GameManager
– Starts LogManager
– Starts WorldManager
• Populate world
– Create some game objects (derive
from base Object class)
• Add themselves to WorldManager
automatically
• Objects handle step event
– Game objects can change position
• Should be able to shutdown
– GameManager setGameOver()
• Gracefully shutdown Managers
• All of this “observable” from
logfile (“dragonfly.log”)
– Can set Object positions
• Run GameManager
– Run game loop with controlled timing
– Each iteration,
• Sends step event to each Object
• Call WorldManager update
• WorldManager update()
– Iterates through deletions, removing
each
• Construct game code that shows
all this working
– Can be more than one “game”
– Include as part of your project
• Make sure to test thoroughly!
– Foundational code for rest of
engine
Outline – Part III
•
•
•
•
•
Sending Events
Managing Graphics
Managing Input
Moving Objects
Misc
(next)
Manager onEvent()
•
// Send event to all Objects.
// Return count of number of events sent.
int Manager::onEvent(const Event *p_event)
count = 0
Want to send event ?
− e.g., step in engine
− e.g., “nuke” in game code
all_objects = WM.getAllObjects()
create ObjectListIterator li on all_objects list
while not li.isDone() do
li.currentObject() -> eventHandler() with p_event
li.next()
increment count
end while
return count
To use:
// Provide step event to all Objects.
EventStep s;
onEvent(&s);
Outline – Part III
• Sending Events
• Managing Graphics
– SFML
– DisplayManager
• Managing Input
• Moving Objects
• Misc
(done)
(next)
Simple and Fast Multimedia
Library - SFML Overview
• Does window creation
(OpenGL) and input
• Does 2d graphics
• Text rendering (using
FreeType)
• Audio (using OpenAL)
• Networking (basic TCP and
UDP)
• Available for Windows,
Linux, Mac
• Free, open source software
• Developed since 2007 (v1.0)
to now (v2.4.0 in August
2016)
• Written in C++, with
bindings for other
languages
– e.g., Java, Python, Ruby
Dragonfly with SFML
• SFML provides more than needed for Dragonfly
 We’ll learn just what is needed for game
engine
• Generally, need to link SFML libraries
– sfml-system, sfml-window, sfml-graphics, sfml-main
– sfml-audio (when add audio) and Winmm (Windows)
– sfml-network (for completeness, but not used)
• Complete documentation
– http://www.sfml-dev.org/documentation/2.4.0/
SFML Window
#include <SFML/Graphics.hpp>
...
// Create SFML window (size 1024x768, with Titlebar).
int window_horizontal = 1024
int window_vertical = 768
sf::RenderWindow window(sf::VideoMode(horizontal, vertical),
"Title - Dragonfly", sf::Style::Titlebar)
// Turn off mouse cursor for window (useful for Dragonfly).
window.setMouseCursorVisible(false)
// Synchronize refresh rate with monitor (call only once, prevents tearing).
window.setVerticalSyncEnabled(true)
// When done... (close window, destroy all attached resources).
window.close()
SFML Font
• Before drawing text, need to load font
sf::Font font
if (font.loadFromFile("df-font.ttf") == false) then
// Error
end if
• Note, direct path must be loaded – cannot
automatically access any installed fonts
• Recommend: Anonymous Pro (df-font.ttf)
– Fixed-width design for coders, Open Font License
– http://www.marksimonson.com/fonts/view/anonymous-pro
• If cannot load, check working director (e.g.,
Debug in Windows)
SFML Text
sf::Text text
// Select a pre-loaded font (see previous slide).
text.setFont(font)
// Set display string.
text.setString("Hello world")
// Set character size (in pixels).
text.setCharacterSize(24)
// Set color.
text.setFillColor(sf::Color::Red)
// Set style (an example shown below) .
text.setStyle(sf::Text::Bold | sf::Text::Underlined)
// Set position on window (in pixels).
text.setPosition(100, 50)
SFML Drawing Text
• Clear window back buffer first
• Draw all text on back buffer
• Display by swapping back buffer with current
// Clear window and draw text.
window.clear()
// Erases back buffer
window.draw(text) // Draws text to back buffer
window.display()
// Swaps current buffer to back buffer
SFML Hello, World! for Dragonfly
(1 of 2)
#include <iostream> // for std::cout
#include <SFML/Graphics.hpp>
int main() {
// Load font.
sf::Font font;
if (font.loadFromFile("df-font.ttf") == false) {
std::cout << "Error! Unable to load font 'df-font.ttf'." << std::endl;
return -1;
}
// Setup text to display.
sf::Text text;
text.setFont(font); // Select font.
text.setString("Hello, world!"); // Set string to display.
text.setCharacterSize(32); // Set character size (in pixels).
text.setFillColor(sf::Color::Green); // Set text color.
text.setStyle(sf::Text::Bold); // Set text style.
text.setPosition(96,134); // Set text position (in pixels).
// Create window to draw on.
sf::RenderWindow *p_window =
new sf::RenderWindow(sf::VideoMode(400, 300), "SFML - Hello, world!");
if (!p_window) {
std::cout << "Error! Unable to allocate RenderWindow." << std::endl;
return -1;
}
SFML Hello, World! for Dragonfly
(1 of 2)
#include <iostream> // for std::cout
#include <SFML/Graphics.hpp>
int main() {
// Load font.
sf::Font font;
if (font.loadFromFile("df-font.ttf") == false) {
std::cout << "Error! Unable to load font 'df-font.ttf'." << std::endl;
return -1;
}
// Setup text to display.
sf::Text text;
text.setFont(font); // Select font.
text.setString("Hello, world!"); // Set string to display.
text.setCharacterSize(32); // Set character size (in pixels).
text.setFillColor(sf::Color::Green); // Set text color.
text.setStyle(sf::Text::Bold); // Set text style.
text.setPosition(96,134); // Set text position (in pixels).
Note, doesn’t have to be
pointer, but is more like
DisplayManager
// Create window to draw on.
sf::RenderWindow *p_window =
new sf::RenderWindow(sf::VideoMode(400, 300), "SFML - Hello, world!");
if (!p_window) {
std::cout << "Error! Unable to allocate RenderWindow." << std::endl;
return -1;
}
SFML Hello, World! for Dragonfly
(2 of 2)
// Turn off mouse cursor for window.
p_window -> setMouseCursorVisible(false);
// Synchronize refresh rate with monitor.
p_window -> setVerticalSyncEnabled(true);
// Repeat as long as window is open.
while (1) {
// Clear window and draw text.
p_window -> clear();
p_window -> draw(text);
p_window -> display();
// See if window has been closed.
sf::Event event;
while (p_window -> pollEvent(event)) {
if (event.type == sf::Event::Closed) {
p_window -> close();
delete p_window;
return 0;
}
} // End of while (event).
} // End of while (1).
} // End of main().
Will describe SFML events
later, during input
Can use this program test if SFML
is working for your system!
Outline – Part III
• Sending Events
• Managing Graphics
– SFML
– DisplayManager
• Managing Input
• Moving Objects
• Misc
(done)
(done)
(next)
Life is Better with Color
• Colors: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA,
CYAN, WHITE
• Will map game programmer color to SFML color
// Colors Dragonfly recognizes.
enum Color {
UNDEFINED_COLOR = -1,
BLACK = 0,
RED,
GREEN,
YELLOW,
BLUE,
MAGENTA,
CYAN,
WHITE,
};
// If color not specified, will use this.
const Color COLOR_DEFAULT = WHITE;
<SFML/Graphics.hpp>
"Color.h"
"Manager.h"
“Vector.h"
Could add:
|sf::Style::Close
// Defaults for SFML window.
const int WINDOW_HORIZONTAL_PIXELS_DEFAULT = 1024;
const int WINDOW_VERTICAL_PIXELS_DEFAULT = 768;
const int WINDOW_HORIZONTAL_CHARS_DEFAULT = 80;
const int WINDOW_VERTICAL_CHARS_DEFAULT = 24;
const int WINDOW_STYLE_DEFAULT = sf::Style::Titlebar;
const sf::Color WINDOW_BACKGROUND_COLOR_DEFAULT = sf::Color::Black;
const std::string WINDOW_TITLE_DEFAULT = "Dragonfly";
const std::string FONT_FILE_DEFAULT = "df-font.ttf";
class DisplayManager : public Manager {
private:
DisplayManager();
DisplayManager (DisplayManager const&);
void operator=(DisplayManager const&);
sf::Font font;
// Font used for ASCII graphics.
sf::RenderWindow *p_window;
// Pointer to SFML window.
int window_horizontal_pixels; // Horizontal pixels in window.
int window_vertical_pixels;
// Vertical pixels in window.
int window_horizontal_chars; // Horizontal ASCII spaces in window.
int window_vertical_chars;
// Vertical ASCII spaces in window.
public:
static DisplayManager &getInstance();
DisplayManager.h (1 of 2)
#include
#include
#include
#include
// Close graphics window.
void shutDown();
// Draw a character at screen location (x,y) with color.
// Return 0 if ok, else -1.
int drawCh(Vector world_pos, char ch, Color color) const;
// Return window's horizontal maximum (in characters).
int getHorizontal() const;
// Return window's vertical maximum (in characters).
int getVertical() const;
// Return window's horizontal maximum (in pixels).
int getHorizontalPixels() const;
// Return window's vertical maximum (in pixels).
int getVerticalPixels() const;
// Render current display buffer.
// Return 0 if ok, else -1.
int swapBuffers();
// Return pointer to SFML drawing window.
sf::RenderWindow *getWindow() const;
};
DisplayManager.h (2 of 2)
// Open graphics window ready for text-based display.
// Return 0 if ok, else -1.
int startUp();
// If window already created, do nothing.
if p_window is not NULL then
return 0
end if
create window // an sf::RenderWindow for drawing
turn off mouse cursor
synchronize refresh rate with monitor
load font
if everything successful then
invoke Manager::startUp()
return ok
else
return error
end if
Diplay Manager startUp()
// Open graphics window ready for text-based display.
// Return 0 if ok, else return -1.
int DisplayManager::startUp()
DisplayManager Drawing Helpers
• SFML pixel-based, but Dragonfly text-based
– Make helper functions to convert, not part of
DisplayManager
– Declare in DisplayManager.h, not utility.h
// Compute character height, based on window size and font.
float charHeight();
// Compute character width, based on window size and font.
float charWidth();
// Convert ASCII spaces (x,y) to window pixels (x,y).
Vector spacesToPixels(Vector spaces);
// Convert window pixels (x,y) to ASCII spaces (x,y).
Vector pixelsToSpaces(Vector pixels);
DisplayManager Drawing Helpers –
charHeight() and spacesToPixels()
// Compute character height, based on window size and font.
float charHeight()
return DisplayManager getVerticalPixels() /
DisplayManager getVeritical()
// Convert ASCII spaces (x,y) to window pixels (x,y).
Vector spacesToPixels(Vector spaces)
return Vector (spaces.getX() * charWidth(),
spaces.getY() * charHeight())
charWidth() and pixelsToSpaces() similar
Warning!
Make sure to
cast division as
float,
otherwise will
get integer
division
// Make sure window is allocated.
if p_window is NULL then
return -1
end if
// Convert spaces (x,y) to pixels (x,y).
Vector pixel_pos = spacesToPixels(pos)
// Draw background rectangle since text is "see through" in SFML.
static sf::RectangleShape rectangle
rectangle.setSize(sf::Vector2f(charWidth(), charHeight()- 1))
rectangle.setFillColor(WINDOW_BACKGROUND_COLOR_DEFAULT)
rectangle.setPosition(pixel_pos.getX() + charWidth()/10,
pixel_pos.getY() + charHeight()/5)
p_window -> draw(rectangle)
// Create character text to draw.
static sf::Text text("", font)
text.setString(ch)
text.setStyle(sf::Text::Bold) // Make bold, since looks better.
DisplayManager drawCh() (1 of 2)
// Draw a character at window location (x,y) with color.
// Return 0 if ok, else -1.
int DisplayManager::drawCh(Vector pos, char ch, Color color)
Note, multiplier of 2
for typical terminal
character.
Can use 1 for square
character.
// Set SFML color based on Dragonfly color.
switch (color)
case YELLOW:
text.setFillColor(sf::Color::Yellow)
break;
case RED:
text.setFillColor(sf::Color::Red)
break;
...
end switch
// Set position in window (in pixels).
text.setPosition(pixel_pos.getX(), pixel_pos.getY())
// Draw character.
p_window -> draw(text)
DisplayManager drawCh() (2 of 2)
// Scale to right size.
if (charWidth() < charHeight()) then
text.setCharacterSize( charWidth() * 2 )
else
text.setCharacterSize( charHeight() * 2 )
end if
DisplayManager swapBuffers()
// Render current display buffer.
// Return 0 if ok, else -1.
int DisplayManager::swapBuffers()
// Make sure window is allocated.
if p_window is NULL then
return -1
end if
// Display current window.
p_window -> display()
Note, graphics typically 2 buffers
Display one, draw on other
When ready  swap
// Clear window to get ready for next draw.
p_window -> clear()
return 0 // Success.
DisplayManager drawString() (1 of 2)
enum Justification {
LEFT_JUSTIFIED,
More than one character (i.e., string)
CENTER_JUSTIFIED,
 Used for HUD (ViewObjects) later
RIGHT_JUSTIFIED,
};
...
class DisplayManager : public Manager {
...
// Draw string at screen location (x,y) with color.
// Justified LEFT, CENTER or RIGHT.
// Return 0 if ok, else -1.
int drawString(Vector world_pos, std::string str,
Justification just, Color color) const;
...
};
// Get starting position.
Vector starting_pos = world_pos
switch (just)
case CENTER_JUSTIFIED:
starting_pos.setX(world_pos.getX() - str.size()/2)
break
case RIGHT_JUSTIFIED:
starting_pos.setX(world_pos.getX() - str.size())
break
case LEFT_JUSTIFIED:
default:
break
end switch
// Draw string character by character.
for i = 0 to str.size()
Vector temp_pos(starting_pos.getX() + i, starting_pos.getY())
drawCh(temp_pos, str[i], color)
end for
return ok
DisplayManager
drawString() (2 of 2)
// Draw string at screen location (x,y) with color.
// Justified LEFT, CENTER or RIGHT.
// Return 0 if ok, else -1.
int drawString(Vector world_pos, std::string str,
Justification just, Color color) const;
DisplayManager Methods not
Described
• getInstance()
– As for other singletons
• shutdown()
– Close SFML window
• getWindow()
– Return SFML drawing window (in case programmer
wants to use)
• getHorizontal(), getVertical()
– Return horizontal and vertical dimensions of terminal
window
– Pixel versions, too
Using the DisplayManager (1 of 3)
• Can be called explicitly. e.g., could put
drawing code in eventHandler()
Vector p1(12,10), p2(1,3);
DM.drawCh(p1, ‘*’, df::RED);
DM.drawCh(p2, ‘M’, df::BLUE);
• But for Objects, better for game programmer
if handled automatically  Extend Object
public:
virtual void draw()
Using the DisplayManager (2 of 3)
• Draw method does nothing in base class, but
game code can override. e.g., Star:
void Star::draw()
DM.drawCh(pos, STAR_CHAR, df::WHITE);
• Add draw() method to WorldManager:
// Draw all objects.
void WorldManager::draw()
create ObjectListIterator i on updates // all game objects
while not i.isDone() do
invoke i.currentObject() -> draw()
i.next()
end while
Using the DisplayManager (3 of 3)
• Modify GameManager, game loop
Clock clock
while (game not over) do
clock.delta()
// Get input from keyboard/mouse.
WorldManager update()
WorldManager draw()
DisplayManager swapBuffers()
loop_time = clock.split()
sleep(TARGET_TIME - loop_time)
end while
New
• Later, will add support for Sprites in
DisplayManager
Drawing in Layers
• Up to now, no easy way to make sure one Object
drawn before another
– e.g., If tried Saucer Shoot, Star may be on top of Hero
• Provide means to control levels of Object’s
display order  Altitude
• Draw “low altitude” Objects before higher
altitude Objects
– Higher altitude objects in same location overwrite
lower ones before screen refresh
• Is this 3rd dimension? Not really since all in same
plane for collisions
Object Extensions
to Support Altitude
private:
int altitude; // 0 to MAX supported (lower drawn first).
public:
// Set object altitude.
// Checks for in range [0, MAX_ALTITUDE].
// Return 0 if ok, else -1.
int setAltitude(int new_altitude);
// Return object altitude.
int getAltitude() const;
Provide const int MAX_ALITITUDE = 4 in WorldManager.h
WorldManager Extensions
to Support Altitude
// In draw()
...
for alt = 0 to MAX_ALTITUDE
// Normal iteration through all objects.
...
if p_temp_go -> getAltitude() is alt then
// Normal draw.
...
end if
end for // Altitude outer loop.
Q: What is the “cost” of doing altitude?
– Can fix later with SceneGraph
Development Checkpoint #5!
• Create DisplayManager (stub out and add to
Makefile)
• Write startUp(), shutDown(), getBuffer(),
swapBuffers()
– Refer to SFML examples
• Implement drawCh() and then drawString()
• Add draw() to Object and WorldManager draw()
• Modify GameManager game loop to call
WorldManager draw() and DisplayManager
swapBuffers()
• Implement drawing in layers. Test with game objects
at different heights
Outline – Part III
• Sending Events
• Managing Graphics
• Managing Input
– Overview
– SFML for Input
– InputManager
– Input Events
• Moving Objects
• Misc
(done)
(done)
(next)
The Need to Manage Input
• Game could poll device directly. e.g., see if press “space”
then perform “jump”
• Positives
– Simple
• Drawbacks
– Device dependent. If device swapped (e.g., for joystick), game
won’t work
– If mapping changes (e.g., “space” becomes “fire”), game must
be recompiled
– If duplicate mapping (e.g., “left-mouse” also “jump”), must
duplicate code
• Role of game engine is to avoid such drawbacks, specifically
in InputManager
Input Workflow
1. User provides input via device (e.g., button
press)
2. Engine detects input has occurred
– Determines whether to process at all (e.g., perhaps
not during cut-scene)
3. If input is to be processed, decode data from
device
‒ May mean dealing with device specific details (e.g.,
degrees of rotation on analog stick)
4. Encode into abstract, device-independent form
suitable for game
Managing Input
• Must receive from device
• Must notify objects (objects provide action)
• Manager must “understand” low level details
of device to produce meaningful Event
• Event must include enough details specific for
device
– e.g., keyboard needs key value pressed
– e.g., mouse needs location, button action
Input in Dragonfly
• Many choices, but uses SFML
– Already used for output/graphics
• Provides non-blocking input
– Game won’t block for “enter” key (unlike, e.g.,
cin, scanf())
• Handles keyboard
– Key press, key down, key release
• Handles mouse
– Left and right buttons, mouse location/movement
SFML for Game-Type Input (1 of 2)
• Assume have SFML window open
• Only setup needed:
// Disable keyboard repeat (only do once).
p_window -> setKeyRepeatEnabled(false)
• Then, during game:
// Go through all Window events, extracting recognized ones.
sf::Event event
while (p_window -> pollEvent(event)) do // event passed as reference
// Key was pressed.
if event.type is sf::Event::KeyPressed then
if event.key.code is sf::Keyboard::A
// Do A key pressed stuff.
end if
end if
// Key was released.
if event.type is sf::Event::KeyReleased then
// Do key released stuff.
end if
SFML for Game-Type Input (2 of 2)
// Mouse was moved.
if event.type is sf::Event::MouseMoved then
// Do mouse moved stuff.
end if
// Mouse was clicked.
if event.type is sf::Event::MouseButtonPressed then
if event.mouseButton.button is sf::Mouse::Right
// Do right mouse clicked stuff.
end if
end if
end while
// Key is pressed (currently held down).
if sf::Keyboard::isKeyPressed(keycode) then
// Do key is pressed stuff.
end if
// Mouse button is pressed (currently held down).
if sf::Mouse::isButtonPressed(button) then
// Do mouse is pressed stuff.
end if
#include "Manager.h"
class InputManager : public Manager {
public:
// Get the one and only instance of the InputManager.
static InputManager &getInstance();
// Get terminal ready to capture input.
// Return 0 if ok, else return -1.
int startUp();
// Revert back to normal terminal mode.
void shutDown();
// Get input from the keyboard and mouse.
// Pass event along to all Objects.
void getInput();
};
InputManager.h
private:
InputManager();
InputManager (InputManager const&);
void operator=(InputManager const&);
Checking StartUp Status
• Note, SFML window needs to be created before
InputManager can start
 New start up dependency order for Dragonfly
1. LogManager
2. DisplayManager
3. InputManager
• Can check startup status in base Manager class
bool isStarted()
InputManager startUp()
// Get window ready to capture input.
// Return 0 if ok, else return -1.
int InputManager::startUp()
if DisplayManager is not started then
return error
end if
sf::RenderWindow window = DisplayManager getWindow()
disable key repeat in window
invoke Manager::startUp()
InputManager shutDown()
• Turn on key repeat
• Note: assume InputManager shuts down
before DisplayManager, so InputManager
doesn’t close SFML window
• Set is_started to false
– Call Manager::shutDown()
if key press then
create EventKeyboard (key and action)
send EventKeyboard to all Objects
else if key release then
create EventKeyboard (key and action)
send EventKeyboard to all Objects
else if mouse moved then
create EventMouse (x, y and action)
send EventMouse to all Objects
else if mouse clicked then
InputManager getInput() (1 of 2)
// Get input from the keyboard and mouse.
// Pass event along to all Objects.
void InputManager::getInput()
// Check past window events.
while event do
end if
end while // Window events.
// Check current key press events for each key.
if key pressed (sf::Keyboard::Up) then
create EventKeyboard (key and action)
send EventKeyboard to all Objects
end if
...
// Check current mouse press events for each button.
if mouse button pressed (sf::Mouse::Left) then
create EventMouse (x, y and action)
send EventMouse to all Objects
end if
...
InputManager getInput() (2 of 2)
create EventMouse (x, y and action)
send EventMouse to all Objects
InputManager Misc
• Modify GameManager run() game loop
…
// (Inside GameManager’s run())
// Get input.
InputManager getInput()
…
Now, need events for keyboard and mouse
 passed to Objects
#include "Event.h"
// Types of keyboard actions Dragonfly recognizes.
enum EventKeyboardAction {
UNDEFINED_KEYBOARD_ACTION = -1, // Undefined
KEY_PRESSED,
// Was down
KEY_RELEASED,
// Was released
KEY_DOWN,
// Is down
};
// Keys Dragonfly recognizes.
namespace Keyboard { // New namespace for clarity
enum Key {
UNDEFINED_KEY = -1,
SPACE, RETURN, ESCAPE, TAB, LEFTARROW, RIGHTARROW,
UPARROW, DOWNARROW, PAUSE, MINUS, PLUS, TILDE, PERIOD,
COMMA, SLASH, LEFTCONTROL, RIGHTCONTROL, LEFTSHIFT,
RIGHTSHIFT, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11,
F12, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R,
S, T, U, V, W, X, Y, Z, NUM1, NUM2, NUM3, NUM4, NUM5, NUM6,
NUM7, NUM8, NUM9, NUM0,
};
} // end of namespace input
EventKeyboard.h (1 of 2)
const std::string KEYBOARD_EVENT = "df::keyboard";
private:
Keyboard::Key key_val;
EventKeyboardAction keyboard_action;
// Key value.
// Key action.
public:
EventKeyboard();
// Set key in event.
void setKey(Keyboard::Key new_key);
// Get key from event.
Keyboard::Key getKey() const;
// Set keyboard event action.
void setKeyboardAction(EventKeyboardAction new_action);
// Get keyboard event action.
EventKeyboardAction getKeyboardAction() const;
};
EventKeyboard.h (2 of 2)
class EventKeyboard : public Event {
private:
Keyboard::Key key_val;
EventKeyboardAction keyboard_action;
public:
EventKeyboard();
// Set key in event.
void setKey(Keyboard::Key new_key);
// Get key from event.
Keyboard::Key getKey() const;
// Key value.
// Key action.
Typically used by game
code (e.g., to see what key
was pressed)
Typically used by
InputManager only
// Set keyboard event action.
void setKeyboardAction(EventKeyboardAction new_action);
// Get keyboard event action.
EventKeyboardAction getKeyboardAction() const;
};
EventKeyboard.h (2 of 2)
class EventKeyboard : public Event {
#include "Event.h"
const std::string MOUSE_EVENT = "df::mouse";
class EventMouse : public Event {
private:
EventMouseAction mouse_action;
Mouse::Button mouse_button;
Vector mouse_xy;
// Mouse action.
// Mouse button.
// Mouse (x,y) coordinates.
EventMouse.h (1 of 2)
// Set of mouse buttons recognized by Dragonfly.
namespace Mouse {
enum Button {
UNDEFINED_MOUSE_BUTTON = -1,
LEFT,
RIGHT,
MIDDLE,
};
}
// Set of mouse actions recognized by Dragonfly.
enum EventMouseAction {
UNDEFINED_MOUSE_ACTION = -1,
Note, all (x,y)
CLICKED,
coordinates in Dragonfly,
PRESSED,
including the mouse, are
MOVED,
in spaces not pixels.
};
// Load mouse event's action.
void setMouseAction(EventMouseAction new_mouse_action);
// Get mouse event's action.
EventMouseAction getMouseAction() const;
// Set mouse event's button.
void setMouseButton(Mouse::Button new_mouse_button);
// Get mouse event's button.
Mouse::Button getMouseButton() const;
// Set mouse event's position.
void setMousePosition(Vector new_mouse_xy);
// Get mouse event's y position.
Vector getMousePosition() const;
};
EventMouse.h (2 of 2)
public:
EventMouse();
What Have We Done?
The Complete Game Loop!
Clock clock
// Section 4.4.3, p73.
while (game not over) do
// Line 11, Listing 4.24, p77.
clock.delta()
// Line 13, Listing 4.19, p73.
InputManager getInput()
// Listing 4.88, p132.
WorldManager update()
// Listing 4.61, p107.
WorldManager draw()
// Listing 4.76, p122.
DisplayManager swapBuffers()
// Listing 4.74, p121.
loop_time = clock.split()
// Line 17, Listing 4.19, p73.
sleep(TARGET_TIME - loop_time) // Listing 4.20-4.21, p74.
end while
Development Checkpoint #6!
• Create InputManager (stub out and add to
project or Makefile)
• Write startUp() and shutDown()
– Verify DisplayManager start up dependency
• Create EventKeyboard and EventMouse (add
to project or Makefile)
• Integrate with GameManager
Can now have player “control” game Object! e.g., ‘wasd’ keys.
See Hero from Saucer Shoot, for example.
Outline – Part III
•
•
•
•
Filtering Events
Managing Graphics
Managing Input
Moving Objects
– Velocity
– Collisions
– World boundaries
• Misc
(done)
(done)
(done)
(next)
Kinematics
• Up to now, need to
move by updating
location in game code
 every step
// Move 1 space every 4 steps (ticks).
int Saucer::eventHandler(const Event *p_e){
if (p_e->getType() == df::STEP_EVENT) {
// See if time to move.
countdown--;
if (countdown == 0) {
pos.setX(pos.getX() - 1);
countdown = 4;
}
return 1; // Event was handled
}
}
• Instead, have Engine do same work
Automatically update Object positions based on
velocity (direction and speed)
Kinematics – physics that describes motion of
objects with accounting for forces or masses
private:
Vector direction; // Direction vector.
float speed;
// Object speed in direction.
// Get speed of Object.
float getSpeed() const;
// Set direction of Object.
void setDirection(Vector new_direction);
// Get direction of Object.
Vector getDirection() const;
// Set direction and speed of Object.
void setVelocity(Vector new_velocity);
// Get velocity of Object based on direction and speed.
Vector getXVelocity() const;
// Predict Object position based on speed and direction.
// Return predicted position.
Vector predictPosition()
Object Extensions to
Support Kinematics
public:
// Set speed of Object.
void setSpeed(float speed);
private:
Vector direction; // Direction vector.
float speed;
// Object speed in direction.
Note, don’t store
velocity but can
compute
Object Extensions to
Support Kinematics
public:
// Set speed of Object.
void setSpeed(float speed);
// Get speed of Object.
float getSpeed() const;
// Set direction of Object.
void setDirection(Vector new_direction);
// Get direction of Object.
Vector getDirection() const;
// Set direction and speed of Object.
void setVelocity(Vector new_velocity);
// Get velocity of Object based on direction and speed.
Vector getXVelocity() const;
// Predict Object position based on speed and direction.
// Return predicted position.
Vector predictPosition()
Called
each step
Object predictPosition()
// Predict Object position based on speed and direction.
// Return predicted position.
Vector Object::predictPosition()
// Add velocity to position.
Vector new_pos = position + getVelocity()
// Return new position.
return new_pos
Note: although relatively simple as-is,
future work could incorporate more
complex physics into this method.
WorldManager Extensions to
update() to Support Velocity
// Update object positions based on their velocities.
// Iterate through all objects.
create ObjectListIterator li on updates list
while not li.isDone() do
// Add velocity to position.
Vector new_pos = p_o -> predictPosition()
// If Object should change position, then move.
if new_pos != getPosition() then
moveObject() to new_pos
end if
li.next()
end while // End iterate.
...
WorldManager Extensions to
update() to Support Velocity
// Update object positions based on their velocities.
// Iterate through all objects.
create ObjectListIterator li on updates list
while not li.isDone() do
// Add velocity to position.
Vector new_pos = p_o -> predictPosition()
// If Object should change position, then move.
if new_pos != getPosition() then
moveObject() to new_pos
end if
li.next()
end while // End iterate.
...
Discussed next
section (collisions)
Using Velocity – Example
// Move 1 space every 4 steps (ticks).
int Saucer::eventHandler(Event *p_e) {
if (p_e->getType() == STEP_EVENT) {
// See if time to move.
countdown--;
if (countdown == 0) {
pos.setX(pos.getX() - 1);
countdown = 4;
}
return 1; // Event was handled
}
}
// Set movement in horizontal direction.
// 1 space left every 4 frames.
setXVelocity(-0.25);
• No need to handle “step” event just for movement
• No need for velocity controls in game code
• Future work could extend to acceleration and other
forces
– e.g., Newton’s 2nd law: F = m a
Outline – Part III
•
•
•
•
Filtering Events
Managing Graphics
Managing Input
Moving Objects
– Velocity
(done)
– Collisions
(next)
– World boundaries
• Misc
(done)
(done)
(done)
Collision Detection
• Determining if Objects collide not as easy as it seems
– Geometry can be complex (beyond squares or spheres)
– Objects can move fast
– Can be many objects (say, n)
• Naïve solution O(n2) time complexity
 every object can potentially collide with every other
• Basic technique used by many game engines (and
Dragonfly)  overlap testing
– Detects whether collision has already occurred
Overlap Testing
• Most common technique used in games
– Relatively easy (conceptually and in code)
• Concept
– Every step, test every pair of game objects to see
if volumes overlap. If yes  collision!
– Report that event occurred to both game objects
– Easy for simple volumes like spheres, harder for
polygonal models
• Will show methods to help with this shortly
• May exhibit more errors! (see next slide)
– Alternative is intersection testing (not discussed)
Overlap Testing Limitations
• Fails if game objects move too fast relative to size
>--t1
>--t2
|
|
|
>--t3
 Arrow passes through window without breaking it!
• Possible solutions?
1.
2.
Game design constraint on speed of game objects (e.g., fastest Object
moves smaller distance (per step) than thinnest Object)
‒ May not be practical for all games
Reduce game loop step size (adjust game object speed accordingly)
‒ Adds overhead since more computation
‒ Or, could have different step size for different objects (more complex)
Dealing with Complexity
• Complex geometry must be simplified
– Complex 3d object can have 100’s or 1000’s of
polygons
– Testing intersection of each costly
• Reduce number of Object pair tests
– There can be 100’s or 1000’s of game objects
– Remember, if test all, O(n2) time complexity
Complex Geometry: Bounding
Volume (1 of 3)
• Bounding volume is simple geometric shape that
approximates game object
– e.g., approximate “spikey”
Game object with ellipsoid
game
object
bounding
volume
• Note, does not need to completely encompass,
but might mean some contact not detected
– May be ok for some games
Complex Geometry: Bounding
____
Volume (2 of 3) \ /__o_\
• Testing cheaper
– If no intersection with bounding volume
~==/
?
 no more testing required
– If is intersection, then could be collision
 could do more refined testing next
• Commonly used bounding volumes
– Sphere – if distance between centers less
than sum of Radii then no collision
– Box – used in Dragonfly
\
~==/
##
##
##(O)##
#\|/#
## | ##
## | ##
|
|
____
\ /__o_\
~==/
?
Complex Geometry: Bounding
Volume (3 of 3)
• For complex object, can fit several bounding
volumes around unique parts
– e.g., For avatar, boxes around torso and limbs,
sphere around head
• Can use hierarchical bounding volume
– e.g., Large sphere around whole avatar
• If intersect, refine with more refined bounding boxes
Collision Resolution
• Once detected, must take action to resolve
– But effects on trajectories and objects can differ
• e.g., Two billiard balls collide
– Calculate ball positions at time of impact
– Impart new velocities on balls
– Play “clinking” sound effect
• e.g., Rocket slams into wall
– Rocket disappears
– Explosion spawned and explosion sound effect
– Wall charred and area damage inflicted on nearby characters
• e.g., Character walks through invisible wall
– Magical sound effect triggered
– No trajectories or velocities affected
Note, many of the above actions are done in game code, not engine code.
Collisions in Dragonfly
Detection
• Overlap testing
– Dragonfly Naiad single “point”
objects
• Collision between Objects means
they occupy same space
• Dragonfly simplifies geometry with
bounding box
– Collision when boxes overlap, no
refinement
• Detection only when moving Object
– Note: alternative could have Objects
move themselves, then engine would
test all Objects each step
Resolution
• Disallow move
– Only for some types of objects
(next slide)
– Object stays in original location
• Report collision event to both
Objects
Collidable Entities
• Not all game objects are collidable entities
– e.g., HUD elements (player menus, scores)
– e.g., Stars in Saucer Shoot
• Add notion of “solidness”
– Collisions only occur between solid objects
• Object that is solid automatically gets collisions
• Solid Objects that are HARD cannot occupy same
space, while solid Objects that are SOFT can (but
still collide)
• Non-solid Objects are SPECTRAL (don’t collide)
Solidness States in Object.h
enum Solidness {
HARD,
// Objects cause collisions and impede.
SOFT,
// Objects cause collisions, but don't impede.
SPECTRAL // Objects don't cause collisions.
};
• Collisions only happen between solid Objects
– HARD or SOFT, not SPECTRAL
• HARD cannot occupy same space as another HARD
– Basically, movement not allowed (but collision occurs)
• HARD can occupy space with 1+ SOFT
• 1+ SOFT Objects can occupy a space
Extensions to Object to Support
Solidness
private:
Solidness solidness; // Solidness of object.
public:
bool isSolid(); // True if HARD or SOFT, else false.
// Set object solidness, with checks for consistency.
// Return 0 if ok, else -1.
int setSolidness(Solidness new_solid);
// Return object solidness.
Solidness getSolidness() const;
EventCollision.h (1 of 2)
#include "Event.h"
#include "Object.h"
const std::string COLLISION_EVENT "df::collision";
class EventCollision : public Event {
private:
Vector pos;
// Where collision occurred.
Object *p_obj1; // Object moving, causing collision.
Object *p_obj2; // Object being collided with.
public:
// Create collision event at (0,0) with obj1 and obj2 NULL.
EventCollision();
// Create collision event between o1 and o2 at position p.
// Object o1 `caused' collision by moving into object o2.
EventCollision(Object *p_o1, Object *p_o2, Vector p);
EventCollision.h (2 of 2)
// Return object that caused collision.
Object *getObject1() const;
// Set object that caused collision.
void setObject1(Object *p_new_o1);
// Return object that was collided with.
Object *getObject2() const;
// Set object that was collided with.
void setObject2(Object *p_new_o2);
// Return position of collision.
Vector getPosition() const;
// Set position of collision.
void setPosition(Vector new_pos);
};
Extensions to WorldManager to
Support Collisions
public:
// Return list of Objects collided with at position `where'.
// Collisions only with solid Objects.
// Does not consider if p_o is solid or not.
ObjectList isCollision(Object *p_o, Vector where) const;
// Move Object.
// If no collision with solid, move ok else don't move.
// If p_go is Spectral, move ok.
// Return 0 if move ok, else -1 if collision with solid.
int moveObject(Object *p_o, Vector where);
Utility positionsIntersect()
// Return true if two positions intersect, else false.
bool positionsIntersect(Vector p1, Vector p2)
if abs( p1.getX() - p2.getX() ) < 1 and
abs( p1.getY() - p2.getY() ) < 1
return true
else
return false
end if
• Useful for WorldManager isCollision()
• May seem trivial, but will replace with
boxIntersectsBox() later
// Make empty list.
ObjectList collision_list
// Iterate through all objects.
create ObjectListIterator i on updates list
while not i.isDone() do
Object *p_temp_o = i.currentObj()
if p_temp_o is not p_o then
// Do not consider self.
// Same location and both solid?
if positionsIntersect(p_temp_o->getPosition(), where)
and p_temp_o -> isSolid() then
add p_temp_o to collision_list
end if // No solid collision.
end if // Not self.
i.next()
end while // End iterate.
return collision_list
Extensions to WorldManager
to Support Collisions
// Return list of Objects collided with at position `where'.
// Collisions only with solid Objects.
// Does not consider if p_o is solid or not.
ObjectList isCollision(Object *p_o, Vector where)
// Get collisions.
ObjectList list = isCollision(p_o, where)
if not list.isEmpty() then
// Iterate over list.
create ObjectListIterator i on list
while not i.isDone() do
Object *p_temp_o = i.currentObj()
// Create collision event.
EventCollision c(p_o, p_temp_o, where)
// Send to both objects.
p_o -> eventHandler(&c)
p_temp_o -> eventHandler(&c)
// If both HARD, then cannot move.
if p_o is HARD and p_temp_o is HARD then
do_move = false
end if
i.next()
end while // End iterate.
if do_move is false then
return -1 // Move not allowed.
end if
end if // No collision.
end if // Object not solid.
// If here, no collision so allow move.
p_o -> setPosition(where)
return ok // Move was ok.
If no collision with solid
move Object
If collision with solid
send collision event to all
if both HARD
don't move.
otherwise
move Object
WorldManager moveObject()
int moveObject(Object *p_o, Vector where)
if p_o -> isSolid() then // Need to be solid for collisions.
Outline – Part III
•
•
•
•
Filtering Events
Managing Graphics
Managing Input
Moving Objects
– Velocity
(done)
– Collisions
(done)
– World boundaries (next)
• Misc
(done)
(done)
(done)
World Boundaries
• Generally, game objects expected to stay within
world
– May be “off screen” but still within game world
• Object that was inside game world boundary that
moves out receives “out of bounds” event
– Move still allowed
– Objects can ignore event
– e.g., useful for game object not to go on forever (e.g.,
Bullet)
• Create “out of bounds” event  EventOut
EventOut.h
#include "Event.h"
const std::string OUT_EVENT "df::out";
class EventOut : public Event {
public:
EventOut();
};
Extensions to WorldManager to
Support Boundaries
• In moveObject()
• If Object can move, check against
DisplayManager getHorizontal() and
getVertical()
...
// Generate out of bounds event and
• If inside & moves outside
// send to object.
– Generate EventOut
• If outside & moves outside
EventOut ov
p_go -> eventHandler(&ov)
...
– Nothing (i.e., only one event)
• If outside & moves inside
– Nothing (i.e., only event when goes out)
Development Checkpoint #7!
• Extend Object and World Manager to support
velocity.
– Test with range of velocities (faster than 1, less than 1)
• Extend WorldManager to support collisions
– Test extensively, using Objects at known positions
• Create EventOut and add necessary “out of
bounds” code to WorldManager moveObject()
– Test Objects going out, staying out, coming in
Ready for Dragonfly Naiad!
• Manager method to send
events to all objects
• Objects can draw
themselves
– 2d graphics in color
• Objects can get input
from keyboard, mouse
• Engine moves Objects 
velocity
• Objects that move out of
world get “out of bounds”
event
• Objects have solidness
– soft, hard, spectral
• Objects that collide get
collision event
– Can react accordingly
– Non-solid objects don’t get
• Objects can appear
higher/lower than others
– 5 layers
Can be used to make a game!
e.g., Consider Saucer Shoot Naiad (next slide)
Saucer Shoot Naiad
• No sprites  mostly single character
• No ViewObjects  no GameStart, Points or Nuke display
• No registering for events (all Objects get all events)
Available online
Outline – Part IV
• Resource Management
– Runtime Issues
– ResourceManager
•
•
•
•
•
Using Sprites
Boxes
Camera Control
Audio
View Objects
(next)
Runtime Resource Management
• One copy of each resource in memory
– Manage memory resources
• Manage lifetime (remove if not needed)
• Handle composite resources
– e.g., 3d model with mesh, skeleton, animations…
• Custom processing after loading (if needed)
• Provide single, unified interface which other
engine components can access
• Handle streaming (asynchronous loading) if
engine supports
Runtime Resource Management
• “Understand” format of data
– e.g., PNG or Text-sprite file
• Globally-unique identifier
– So assets can be accessed by objects
• Usually load when needed (but sometimes in advance)
• Removing hard (when done?)
– e.g., some models used in multiple levels
 Can use reference count
– e.g., load level and all models with count for each. As level
exits, decrease reference count. When 0, remove.
Resource Management in Dragonfly
• Assets are sprites, sounds, music  start with sprites
– Text-based files
– “Understands” (custom) format
• No offline management tools
– Such tool could help build, then save in right format
• At runtime, must understand format and load
• Need data structures (classes) for
– Frames (dimensions and data)
– Sprites (identifiers and frames)
• Then, ResourceManager
Frames
• Text
• Variable sizes (width and height)
– Rectangular
____
/__o_\
.**
**.**
*.*
• Note, in Dragonfly, frames don’t have color
(nor do individual characters)
– But could be extended to support
\
~==/
_____
_____ __
__
/ ___/____ ___ __________ _____
/ ___// /_ ____ ____ / /_
\__ \/ __ `/ / / / ___/ _ \/ ___/
\__ \/ __ \/ __ \/ __ \/ __/
___/ / /_/ / /_/ / /__/ __/ /
___/ / / / / /_/ / /_/ / /_
/____/\__,_/\__,_/\___/\___/_/
/____/_/ /_/\____/\____/\__/
Arrow keys to move, Spacebar to fire, Enter for one nuke
#include <string>
class Frame {
private:
int width;
int height;
std::string frame_str;
// Width of frame.
// Height of frame.
// All frame characters stored as string.
// Create frame of indicated width and height with string.
Frame(int new_width, int new_height, std::string frame_str);
// Set width of frame.
void setWidth(int new_width);
// Get width of frame.
int getWidth() const;
// Set height of frame.
void setHeight(int new_height);
// Get height of frame.
int getHeight() const;
// Set frame characters (stored as string).
void setString(std::string new_frame_str);
// Get frame characters (stored as string).
std::string getString() const;
};
Frame.h
public:
// Create empty frame.
Frame();
#include <string>
class Frame {
private:
int width;
int height;
std::string frame_str;
// Width of frame.
// Height of frame.
// All frame characters stored as string.
Frames are stored as 1-d strings
// Create frame of indicated width and height with string.
Frame(int new_width, int new_height, std::string frame_str);
// Set width of frame.
void setWidth(int new_width);
// Get width of frame.
int getWidth() const;
// Set height of frame.
void setHeight(int new_height);
// Get height of frame.
int getHeight() const;
// Set frame characters (stored as string).
void setString(std::string new_frame_str);
// Get frame characters (stored as string).
std::string getString() const;
};
Frame.h
public:
// Create empty frame.
Frame();
Sprites
• Sequence of Frames
– No color
\
~==/
\
==/
• In Dragonfly, Sprites have color
– Same color for all frames and characters
• Note, Sprites are just repository for data
– Don’t know how to “draw” themselves
– Nor even what display rate is
– (That functionality with game Objects)
• Need dimensions, number of frames, and
ability to add/retrieve frames
____
/____\
____
/___o\
____
/__o_\
____
/_o__\
____
/o___\
#include <string>
#include "Frame.h"
class Sprite {
// Sprite width.
// Sprite height.
// Maximum number of frames sprite can have.
// Actual number of frames sprite has.
// Optional color for entire sprite.
// Array of frames.
// Text label to identify sprite.
// Sprite constructor always has one arg.
public:
// Destroy sprite, deleting any allocated frames.
~Sprite();
// Create sprite with indicated maximum number of frames.
Sprite(int max_frames);
// Set width of sprite.
void setWidth(int new_width);
// Get width of sprite.
int getWidth() const;
Sprite.h (1 of 2)
private:
int width;
int height;
int max_frame_count;
int frame_count;
Color color;
Frame *frame;
std::string label;
Sprite();
#include <string>
#include "Frame.h"
Needed to understand Frame class
class Sprite {
// Sprite height.
// Maximum number of frames sprite can have.
// Actual number of frames sprite has.
// Optional color for entire sprite.
// Array of frames.
// Text label to identify sprite.
// Sprite constructor always has one arg.
public:
// Destroy sprite, deleting any allocated frames.
~Sprite();
// Create sprite with indicated maximum number of frames.
Sprite(int max_frames);
// Set width of sprite.
void setWidth(int new_width);
// Get width of sprite.
int getWidth() const;
Must provide
number of Frames
when create. e.g.,
Sprite(5)
Sprite.h (1 of 2)
private:
int width;
int height;
int max_frame_count;
int frame_count;
Color color;
Frame *frame;
std::string label;
Sprite();
Frames are array,
but dynamically
allocated
// Sprite
width.
// Set height of sprite.
void setHeight(int new_height);
// Get height of sprite.
int getHeight() const;
// Get sprite color.
int getColor() const;
// Get total count of frames in sprite.
int getFrameCount();
// Add frame to sprite.
// Return -1 if frame array full, else 0.
int addFrame(Frame new_frame);
// Get next sprite frame indicated by number.
// Return empty frame if out of range [0, frame_count].
Frame getFrame(int frame_number) const;
// Set label associated with sprite.
void setLabel(std::string new_label);
// Get label associated with sprite.
std::string getLabel() const;
};
Sprite.h (2 of 2)
// Set sprite color.
void setColor(int new_color);
Sprite Constructor
// Create sprite with indicated maximum number of frames.
Sprite::Sprite(int max_frames)
set frame_count to 0
set width to 0
set height to 0
frame = new Frame [max_frames]
set max_frame_count to max_frames
set color to COLOR_DEFAULT
Sprite Destructor
// Destroy sprite, deleting any allocated frames.
Sprite::~Sprite()
if frame is not NULL then
delete [] frame
end if
Note delete syntax  frame is an array
Sprite addFrame()
// Add a frame to the sprite.
// Return -1 if frame array full, else 0.
int Sprite::addFrame(Frame new_frame)
// Sprite is full?
if frame_count is max_frame_count then
return error
else
frame[frame_count] = new_frame
increment frame_count
end if
Sprite getFrame()
// Get next sprite frame indicated by number.
// Return empty frame if out of range [0, frame_count].
Frame Sprite::getFrame(int frame_number) const
if (frame_number < 0) or
(frame_number >= frame_count) then
Frame empty // Make empty frame.
return empty // Return it.
end if
return frame[frame_number]
Outline – Part IV
• Resource Management
– Issues, Frames, Sprites
– ResourceManager
•
•
•
•
•
Using Sprites
Boxes
Camera Control
Audio
View Objects
(done)
(next)
#include "Manager.h"
#include "Sprite.h"
// Maximum number of unique sprites in game.
const int MAX_SPRITES = 1000;
// Delimiters used to parse Sprite files // the ResourceManager `knows' file format.
const std::string FRAMES_TOKEN "frames";
const std::string HEIGHT_TOKEN "height";
const std::string WIDTH_TOKEN "width";
const std::string COLOR_TOKEN "color";
const std::string END_FRAME_TOKEN "end";
const std::string END_SPRITE_TOKEN "eof";
class ResourceManager : public Manager {
private:
ResourceManager();
// Private (a singleton).
ResourceManager (ResourceManager const&); // Don't allow copy.
void operator=(ResourceManager const&);
// Don't allow assignment.
Sprite *p_sprite[MAX_SPRITES]; // Array of (pointers to) sprites.
int sprite_count;
// Count of number of loaded sprites.
ResourceManager.h (1 of 2)
#include <string>
#include "Manager.h"
#include "Sprite.h"
// Maximum number of unique sprites in game.
const int MAX_SPRITES = 1000;
Tokens for parsing
sprite files
// Delimiters used to parse Sprite files // the ResourceManager `knows' file format.
const std::string FRAMES_TOKEN "frames";
const std::string HEIGHT_TOKEN "height";
const std::string WIDTH_TOKEN "width";
const std::string COLOR_TOKEN "color";
const std::string END_FRAME_TOKEN "end";
const std::string END_SPRITE_TOKEN "eof";
class ResourceManager : public Manager {
private:
ResourceManager();
// Private (a singleton).
ResourceManager (ResourceManager const&); // Don't allow copy.
void operator=(ResourceManager const&);
// Don't allow assignment.
Sprite *p_sprite[MAX_SPRITES]; // Array of (pointers to) sprites.
int sprite_count;
// Count of number of loaded sprites.
ResourceManager.h (1 of 2)
#include <string>
// Get the one and only instance of the ResourceManager.
static ResourceManager &getInstance();
// Get manager ready for resources.
int startUp();
// Shut down manager, freeing up any allocated Sprites.
void shutDown();
// Load Sprite from file.
// Assign indicated label to sprite.
// Return 0 if ok, else -1.
int loadSprite(std::string filename, std::string label);
// Unload Sprite with indicated label.
// Return 0 if ok, else -1.
int unloadSprite(std::string label);
// Find Sprite with indicated label.
// Return pointer to it if found, else NULL.
Sprite *getSprite(std::string label) const;
};
ResourceManager.h (2 of 2)
public:
~ResourceManager();
Reading Sprite from File
frames 5
width 6
height 2
color green
____
/____\
end
____
/___o\
end
____
/__o_\
end
____
/_o__\
end
____
/o___\
end
eof
• Typical that image file has
specific format
– Header
– Body
– Closing
• Parse in pieces
Tip #12!
Many ways to read from a file in C++ 
Listing next slide is but one example
// Example of reading text file.
// Read one line at a time, writing each line to stdout.
#include <iostream>
#include <fstream>
#include <string>
using
using
using
using
std::string;
std::ifstream;
std::cout;
std::endl;
int main() {
string line;
ifstream myfile ("example.txt");
// Open file.
if (myfile.is_open()) {
// Read line from file.
// Display line to screen.
}
// Close file when done.
myfile.close();
} else
// If here, unable to open file.
cout << "unable to open file" << endl;
}
• getline()
reads a line at
a time
– Strips off \n
// Repeat until end of file.
while (myfile.good()) {
getline(myfile, line);
cout << line << endl;
Example of
C++ File Input
• good()
returns true if
file has data
– When end of
file, returns
false
ResourceManager loadSprite()
// Load Sprite from file.
// Assign indicated label to sprite.
// Return 0 if ok, else -1.
int ResourceManager::loadSprite(string filename, string label)
open file filename
read header
new Sprite (with frame count)
while not eof do
read frame
Error check adding
add frame to Sprite
frame to Sprite
end while
close file
add label to Sprite
Note, should error check and provide
descriptive errors throughout
Error line 12. Line width 7, expected 6.
Line is: "/o___\ "
Extra ` ‘, tough to see
Helper Functions for Loading Sprites
// Read single line `tag number', return number
int readLineInt(ifstream *p_file, int *p_line_number, const char *tag);
// Read single line `tag string', return string.
std::string readLineStr(ifstream *p_file, int *p_line_number, const char *tag);
// Read frame until `eof', return Frame.
Frame readFrame(ifstream *p_file, int *p_line_number, int width, int height);
• Place directly in ResourceManager.cpp, but not part
of class
– Nor in utility.h since game programmer won’t use
• Take in pointer to line number so can increment
accordingly
– Use to report in logfile in case of error
readLineInt()
// Read single line of form `tag number', return number.
int readLineInt(ifstream *p_file, int *p_line_number, const char *tag)
string line
getline() into line
if not line.compare(line, tag) then
return error // Not right tag.
end if
number = atoi() on line.substr()
increment line number
atoi() – converts string
(char *) to number  (needs
<stdlib.h>)
return number
• Can call above with tags of: “frame”, “width”, and “height”
• Do similar for readLineStr()
// Read frame until `eof', return Frame.
Frame readFrame(ifstream *p_file, int *p_line_number,
int width, int height)
string line, frame_str
getline() into line // Error check.
if line width > width then
return error (empty Frame)
end if
frame_str += line
Error check
p_file->good()
end for
getline() into line // Error check.
if line is not "end" then
return error (empty Frame)
end if
create frame (width, height, frame_str)
return frame
readFrame()
for i = 1 to height
ResourceManager unloadSprite()
// Unload Sprite with indicated label.
// Return 0 if ok, else -1.
int ResourceManager::unloadSprite(std::string label)
for i = 0 to sprite_count-1
if label is p_sprite[i] -> getLabel() then
delete p_sprite[i]
// scoot over remaining sprites
for j = i to sprite_count-2
p_sprite[j] = p_sprite[j+1]
end for
decrement sprite_count
return success
end if
end for
return error
// sprite not found
ResourceManager getSprite()
// Find Sprite with indicated label.
// Return pointer to it if found, else NULL.
Sprite *ResourceManager::getSprite( std::string label) const
for i = 0 to sprite_count-1
if label is p_sprite[i] -> getLabel() then
return p_sprite[i]
end if
end for
return NULL // Sprite not found.
Development Checkpoint #8!
• Make Frame class (stub out and add to project or Makefile),
then implement
– Test outside of Dragonfly
• Make Sprite class (stub out and add to project or Makefile),
then implement simple attributes
• Implement constructor and addFrame()
• Make ResourceManager
• Build helper functions
– Test with sprite files from Saucer Shoot
• Implement ResourceManager loadSprite(),
getSprite() and unloadSprite()
• Test all thoroughly
Outline – Part IV
•
•
•
•
•
•
Resource Management
Using Sprites
Boxes
Camera Control
Audio
View Objects
(done)
(next)
Using Sprites
• Can load sprites
// Load saucer sprite
ResourceManager &resource_manager = ResourceManager::getInstance();
resource_manager.loadSprite("sprites/saucer-spr.txt", "saucer");
• But not draw (yet). Need to extend:
– Graphics Manager (draw a Frame)
– Object draw() (handle Sprites)
– World Manager (to animate)
// Centered? Then offset (x,y) by 1/2 (width,height).
if centered then
x_offset = frame.getWidth() / 2
y_offset = frame.getHeight() / 2
else
x_offset = 0
y_offset = 0
end if
// Frame data stored in string.
std::string str = frame.getString()
// Draw row by row, character by character.
for y = 1 to frame.getHeight()
for x = 1 to frame.getWidth()
Vector temp_pos(world_pos.getX() - x_offset + x,
world_pos.getY() - y_offset + y)
drawCh(temp_pos, str[y * frame.getWidth() + x], color)
end for // x
end for // y
DisplayManager drawFrame()
// Draw single sprite frame at screen location (x,y) with color.
// If centered true then center frame at (x,y).
// Return 0 if ok, else -1.
int DisplayManager::drawFrame(Vector world_pos, Frame frame,
bool centered, Color color) const
// Error check empty string.
if frame is empty then
return error
end if
Extensions to Object to Support Sprites
(1 of 2)
private:
Sprite *p_sprite;
bool sprite_center;
int sprite_index;
int sprite_slowdown;
int sprite_slowdown_count;
// Sprite associated with object.
// True if sprite centered on object.
// Current index frame for sprite.
// Slowdown rate (1 = no slowdown, 0 = stop).
// Slowdown counter.
public:
// Set object Sprite to new one.
// If set_box is true, set bounding box to size of Sprite.
void setSprite(Sprite *p_new_sprite, bool set_box=true);
// Return pointer to Sprite associated with this object.
Sprite *getSprite() const;
// Set Sprite to be centered at object position (pos).
void setCentered(bool centered=true);
// Indicates if sprite is centered at object position (pos).
bool isCentered() const;
Extensions to Object to Support Sprites
(1 of 2)
private:
Sprite *p_sprite;
bool sprite_center;
int sprite_index;
int sprite_slowdown;
int sprite_slowdown_count;
// Sprite associated with object.
// True if sprite centered on object.
// Current index frame for sprite.
// Slowdown rate (1 = no slowdown, 0 = stop).
// Slowdown counter.
public:
// Set object Sprite to new one.
// If set_box is true, set bounding box to size of Sprite.
void setSprite(Sprite *p_new_sprite, bool set_box=true);
// Return pointer to Sprite associated with this object.
Sprite *getSprite() const;
// Set Sprite to be centered at object position (pos).
void setCentered(bool centered=true);
// Indicates if sprite is centered at object position (pos).
bool isCentered() const;
Defaults to setting
bounding box to
Sprite size (next)
Extensions to Object to Support Sprites
(2 of 2)
// Set index of current Sprite frame to be displayed.
void setSpriteIndex(int new_sprite_index);
// Return index of current Sprite frame to be displayed.
int getSpriteIndex() const;
// Slows down sprite animations.
// Sprite slowdown is in multiples of GameManager frame time.
void setSpriteSlowdown(int new_sprite_slowdown);
int getSpriteSlowdown() const;
void setSpriteSlowdownCount(int new_sprite_slowdown_count);
int getSpriteSlowdownCount() const;
// Draw single sprite frame.
// Drawing accounts for: centering, slowdown, advancing Sprite Frame.
virtual void draw();
Extensions to Object to Support Sprites
(2 of 2)
// Set index of current Sprite frame to be displayed.
void setSpriteIndex(int new_sprite_index);
// Return index of current Sprite frame to be displayed.
int getSpriteIndex() const;
// Slows down sprite animations.
// Sprite slowdown is in multiples of GameManager frame time.
void setSpriteSlowdown(int new_sprite_slowdown);
int getSpriteSlowdown() const;
void setSpriteSlowdownCount(int new_sprite_slowdown_count);
int getSpriteSlowdownCount() const;
// Draw single sprite frame.
// Drawing accounts for: centering, slowdown, advancing Sprite Frame.
virtual void draw();
Allows game
objects to redefine (e.g., Star)
// Draw single sprite frame.
// Drawing accounts for: centering, slowdown, advancing Sprite Frame.
virtual void Object::draw()
// Ask graphics manager to draw current frame.
DisplayManager drawFrame(position, p_sprite->getframe(index),
p_sprite->getColor())
// If slowdown is 0, then animation is frozen.
if getSpriteSlowdown() is 0 then
return
end if
// Increment counter.
count = getSpriteSlowdownCount()
increment count
// Advance sprite index, if appropriate.
if count >= getSpriteSlowdown() then
count = 0
// Reset counter.
increment index
// Advance frame.
// If at last frame, loop to beginning.
if index >= p_obj -> getFrameCount() then
index = 0
end if
setSpriteIndex(index)
// Set index for next draw().
end if
setSpriteSlowdownCount(count) // Set counter for next draw().
Object draw()
// If sprite not defined, don’t continue further.
if p_sprite is NULL then
return
end if
index = getSpriteIndex()
Note, draw() still virtual
 Game programmer can
define own, calling
Object::draw() if needed
(e.g., put health bar above)
Development Checkpoint #9!
• Extend DisplayManager with drawFrame()
– Test with game Object draw()
• Extend Object to support Sprites
• Write revised Object draw()
– Debug with screen (visual) and logfile messages
• Test with variety of Sprites
– Saucer Shoot tutorial or new
– Verify advance speed, looping, stopped
Outline – Part IV
•
•
•
•
•
•
Resource Management
Using Sprites
Boxes
Camera Control
Audio
View Objects
(done)
(done)
(next)
Boxes
• Can use boxes for several features
– Determine bounds of game object for collisions
– World boundaries
– Screen boundaries (for camera control)
• Create 2d box class
#include “Vector.h"
class Box {
private:
Vector corner;
float horizontal;
float vertical;
// Upper left corner of box.
// Horizontal dimension.
// Vertical dimension.
public:
// Create box with (0,0) for the corner, and 0 for horiz and vert.
Box();
// Get upper left corner of box.
Vector getCorner() const;
// Set upper left corner of box.
void setCorner(Vector new_corner);
Box.h
// Create box with an upper-left corner, horiz and vert sizes.
Box(Vector init_corner, float init_horizontal, float init_vertical);
// Get horizontal size of box.
float getHorizontal() const;
// Set horizontal size of box.
void setHorizontal(float new_horizontal);
// Get vertical size of box.
float getVertical() const;
// Set vertical size of box.
void setVertical(float new_vertical);
};
Mostly a
“container”
class
Bounding Boxes
• Used for “size” of an Object
– Known as bounding box since “bounds” the
borders of game object (also known as “hitbox”
since determines if game object is “hit”)
– Determines space Object occupies for collision
computation
• Need to extend Object to have bounding box
and then WorldManager to use it
Extensions to Object to Support
Bounding Boxes
private:
Box box;
// Box for sprite boundary & collisions.
public:
// Set object's bounding box.
void setBox(Box new_box);
// Get object's bounding box.
Box getBox() const;
• box initialized to (0, 0)
• But change setSprite() to set box to size
of Sprite Frames
Collisions with Boxes
• In WorldManager, replace positionsIntersect()
with boxIntersectsBox()
• How to determine if boxes intersect?
If left edge of B within A
AND
If top edge of B within A
 Intersect!
(Ditto for A within B)
boxIntersectsBox()
// Return true if boxes intersect, else false.
bool boxIntersectsBox(Box A, Box B)
// Test horizontal overlap (x_overlap).
Bx1 <= Ax1 <= Bx2
// Either left side of A in B?
Ax1 <= Bx1 <= Ax2
// Or left side of B in A?
// Test vertical overlap (y_overlap).
By1 <= Ay1 <= By2
// Either top side of A in B?
Ay1 <= By1 <= Ay2
// Or top side of B in A?
if x_overlap and y_overlap then
return true
// Boxes do intersect.
else
return false
// Boxes do not intersect.
end if
getWorldBox()
• Object boxes relative to Object position
– e.g., 3 character Sprite Object has corner at (-1.5,-1.5)
In utility.cpp
• Need to convert to world-relative position for collisions
// Convert relative bounding Box for Object to absolute world Box.
Box getWorldBox(const Object *p_o)
Box temp_box = p_o -> getBox()
Vector corner = temp_box.getCorner()
corner.setX(corner.getX() + p_o->getPosition().getX())
corner.setY(corner.getY() + p_o->getPosition().getY())
temp_box.setCorner(corner)
return temp_box
// Convert relative bounding Box for Object to absolute world Box
// at position where.
Box getWorldBox(const Object *p_o, Vector where)
getWorldBox()
• Object boxes relative to Object position
– e.g., 3 character Sprite Object has corner at (-1.5,-1.5)
In utility.cpp
• Need to convert to world-relative position for collisions
// Convert relative bounding Box for Object to absolute world Box.
Box getWorldBox(const Object *p_o)
Box temp_box = p_o -> getBox()
Vector corner = temp_box.getCorner()
corner.setX(corner.getX() + p_o->getPosition().getX())
corner.setY(corner.getY() + p_o->getPosition().getY())
temp_box.setCorner(corner)
return temp_box
Does not
change Object
attributes
// Convert relative bounding Box for Object to absolute world Box
// at position where.
Box getWorldBox(const Object *p_o, Vector where)
WorldManager isCollision() with
Bounding Boxes
// Return list of Objects collided with at position `where'.
// Collisions only with solid Objects.
// Does not consider if p_o is solid or not.
ObjectList isCollision(const Object *p_o, Vector where) const
...
// World position bounding box for object at where
Box b = getWorldBox(p_o, where)
// World position bounding box for other object
Box b_temp = getWorldBox(p_temp_o)
if boxIntersectsBox(b, b_temp) and p_temp_o -> isSolid() then
...
Was positionsIntersect()
Outline – Part IV
•
•
•
•
•
•
Resource Management
Using Sprites
Boxes
Camera Control
Audio
View Objects
(done)
(done)
(done)
(next)
Boxes for Boundaries
• Some games, player can see entire world (e.g.,
Chess, Pac-man)
• But other games, player only sees part of world
(e.g., Legend of Zelda, Super Mario Bros.)
– Screen acts as “viewport” into world, a “camera”
• Needed:
1. World boundary
• Limits of game world
2. View boundary
• Limits of what player can see (usually, size of window)
3. Translation of world coordinates to view coordinates
Extensions to WorldManager to
Support Views
private:
Box boundary;
Box view;
// World boundary.
// Player view of game world.
public:
// Get game world boundary.
Box getBoundary() const;
// Set game world boundary.
void setBoundary(Box new_boundary);
// Get player view of game world.
Box getView() const;
// Set player view of game world.
void setView(Box new_view);
Extensions to WorldManager to
Support Views
private:
Box boundary;
Box view;
// World boundary.
// Player view of game world.
public:
// Get game world boundary.
Box getBoundary() const;
// Set game world boundary.
void setBoundary(Box new_boundary);
// Get player view of game world.
Box getView() const;
// Set player view of game world.
void setView(Box new_view);
Set in GameManager
startUp()
Default to size of SFML
window (obtain from
DisplayManager)
Refactor moveObject() for EventOut
With World boundary and bounding boxes, need to fix EventOut
// Move Object.
// ...
// If moved from inside world boundary to outside, generate EventOut.
int WorldManager::moveObject(Object *p_o, Vector where)
...
// Do move.
Box orig_box = getWorldBox(p_o) // original bounding box
p_o -> setPosition(where)
// move object
Box new_box = getWorldBox(p_o)
// new bounding box
// If object moved from inside to outside world, generate
// "out of bounds" event.
if boxIntersectsBox(orig_box, boundary) and
not boxIntersectsBox(new_box, boundary)
EventOut ov
p_o -> eventHandler(&ov)
end if
...
// Was in bounds?
// Now out of bounds?
// Create "out of bounds" event
// Send to Object
Views
• Game Objects have world (x,y)  need to
translate to view/screen (x,y)
– In DisplayManager before drawing on screen
To get screen (x,y)  compute
distance from view origin (i.e.,
subtract view origin from position)
A  (5,7) and draw
B  (-2, 2) don’t draw
(x value too small)
C  (15, -1) don’t draw
(x value too large,
y value too small)
worldToView()
// Convert world position to view position.
Vector worldToView(Vector world_pos)
Vector view_origin = WorldManager getView().getCorner()
view_x = view_origin.getX()
view_y = view_origin.getY()
Vector view_pos(world_pos.getX() - view_x,
world_pos.getY() - view_y)
return view_pos
• Put in utility.cpp
• Re-factor DisplayManager to call worldToView()
right before drawing each character
int DisplayManager::drawCh(Vector world_pos,
char ch, Color color) const
Vector view_pos = worldToView(world_pos)
...
Extensions to WorldManager draw()
to Support Views (1 of 2)
• Not all Objects drawn every loop (e.g., B and C
in previous example)
– Add inside “altitude loop”
// Bounding box coordinates are relative to Object,
// so convert to world coordinates.
Box temp_box = getWorldBox(p_temp_o)
// Only draw if Object would be visible on screen (intersects view).
if boxIntersectsBox(temp_box, view) then
p_temp_o -> draw()
end if
Extensions to WorldManager draw()
to Support Views (2 of 2)
private:
Object *p_view_following;
// Object view is following.
public:
// Set view to center screen on position view_pos.
// View edge will not go beyond world boundary.
void setViewPosition(Vector view_pos);
// Set view to center screen on Object.
// Set to NULL to stop following.
// If p_new_view_following not legit, return -1 else return 0.
int setViewFollowing(Object *p_new_view_following);
• Allow programmer to control views
– Explicit via setViewPosition()
– Implicit via setViewFollowing()
// Make sure horizontal not out of world boundary.
x = view_pos.getX() - view.getHorizontal()/2
if x + view.getHorizontal() > boundary.getHorizontal() then
x = boundary.getHorizontal() - view.getHorizontal()
end if
if x < 0 then
x = 0
end if
// Make sure vertical not out of world boundary.
y = view_pos.getY() - view.getVertical()/2
if y + view.getVertical() > boundary.getVertical() then
y = boundary.getVertical() - view.getVertical()
end if
if y < 0 then
y = 0
end if
// Set view.
Vector new_corner(x, y)
view.setCorner(new_corner)
WorldManager setViewPosition()
// Set view to center screen on position view_pos.
// View edge will not go beyond world boundary.
void WorldManager::setViewPosition(Vector view_pos)
// Set to NULL to turn `off' following.
if p_new_view_following is NULL then
p_view_following = NULL
return ok
end if
// ...
// Iterate over all Objects. Make sure p_new_view_following
// is one of the Objects, then set found to true.
// ...
// If found, adjust attribute accordingly and set view position.
if found then
p_view_following = p_new_view_following
setViewPosition(p_view_following -> getPosition)
return ok
end if
// If we get here, was not legit. Don't change current view.
return -1
WorldManager setViewFollowing()
// Set view to follow Object.
// Set to NULL to stop following.
// If p_new_view_following not legit, return -1 else return 0.
int WorldManager::setViewFollowing(Object *p_new_view_following)
Extension to WorldManager
moveObject() to Support Views
// If view is following this object, adjust view.
if p_view_following is p_o then
setViewPosition(p_o -> getPosition())
end if
• Put at end, after change position
Using Views
df::Vector corner(0,0)
df::Box boundary(corner, 80, 50)
df::WorldManager setBoundary(boundary)
df::Box view(corner, 80, 24)
df::WorldManager setView(view)
• Set world
size 80x50
• Set view
size 80x24
// Always keep Hero centered in screen.
void Hero::move(float dy)
// Move as before...
// Adjust view.
df::Box new_view = WorldManager getView();
df::Vector corner = new_view.getCorner();
corner.setY(corner.getY() + dy);
new_view.setCorner(corner);
df::WorldManager setView(new_view);
Explicit
control
// Always keep the Hero centered in screen.
void Hero::Hero()
// ...
df::WorldManager setViewFollowing(this);
Implicit
control
OR
Development Checkpoint #10!
• Add Box (to project or Makefile, build and test)
• Extend Object to support bounding boxes
– Modify setSprite() to set box
• Write boxIntersectsBox() and replace
positionsIntersect()
– Test with multi-character Object
• Add views to WorldManager (view and boundary)
• Write and test worldToView() and use in
DisplayManager drawCh()
• Extend WorldManager draw() and moveObject()
• Test views with Saucer Shoot
Outline – Part IV
•
•
•
•
•
Resource Management
Using Sprites
Boxes
Camera Control
Audio
– SFML for audio
– Sound
– Music
• View Objects
(done)
(done)
(done)
(done)
(next)
SFML for Audio
• Provides for 2 distinct types
– Sounds – small, so fit in memory, typically used for
sound effects for games (e.g., Bullet “fire”)
sf::Sound
– Music – larger, keep on disk, often played
continuously in background (e.g., GameStart
music)
sf::Music
• Both in <SFML/Audio.hpp>
SFML Playing Sound
• Sound stored in separate buffer
(sf::SoundBuffer)
• Sound buffer loaded from file (e.g., .wav)
sf::SoundBuffer buffer;
if (buffer.loadFromFile("sound.wav") == false)
// Error!
sf::Sound sound;
sound.setBuffer(buffer);
sound.play();
• Once loaded, played via play()
– Sounds can be played simultaneously, too
SFML Playing Music (1 of 2)
• Sound not pre-loaded, but streamed from file
sf::Music music;
if (music.openFromFile("music.wav") == false)
// Error!
music.play();
• Both sound/music
– Pause with pause()
– Stop with stop()
– Loop with setLoop() as true or false
SFML Playing Music (2 of 2)
• Note, cannot copy or assign sf::Music
sf::Music music;
sf::Music music_copy = music; // Error!
void makeItSo(sf::Music music_parameter) {
...
}
makeItSo(music); // Error!
• Above code will have compile-time errors
Dragonfly Audio
• Sound class
– Wrap SFML sound functionality
• Music class
– Wrap SFML music functionality
• Like other SFML features, do not expose game
programmer to interface
• Make use consistent with Dragonfly sprites
// System includes.
#include <string>
#include <SFML/Audio.hpp>
class Sound {
private:
sf::Sound sound;
// The SFML sound.
sf::SoundBuffer sound_buffer; // SFML sound buffer associated with sound.
std::string label;
// Text label to identify sound.
// Load sound buffer from file.
// Return 0 if ok, else -1.
int loadSound(std::string filename);
// Set label associated with sound.
void setLabel(std::string new_label);
// Get label associated with sound.
std::string getLabel() const;
// Play sound.
// If loop is true, repeat play when done.
void play(bool loop=false);
// Stop sound.
void stop();
// Pause sound.
void pause();
// Return SFML sound.
sf::Sound getSound() const;
};
Sound.h
public:
Sound();
~Sound();
// System includes.
#include <string>
#include <SFML/Audio.hpp>
class Sound {
private:
sf::Sound sound;
// The SFML sound.
sf::SoundBuffer sound_buffer; // SFML sound buffer associated with sound.
std::string label;
// Text label to identify sound.
Need to call sound.resetBuffer()
to avoid Debug Assertion on Windows
// Load sound buffer from file.
// Return 0 if ok, else -1.
int loadSound(std::string filename);
// Set label associated with sound.
void setLabel(std::string new_label);
// Get label associated with sound.
std::string getLabel() const;
// Play sound.
// If loop is true, repeat play when done.
void play(bool loop=false);
// Stop sound.
void stop();
// Pause sound.
void pause();
// Return SFML sound.
sf::Sound getSound() const;
};
Sound.h
public:
Sound();
~Sound();
// System includes.
#include <string>
#include <SFML/Audio.hpp>
class Music {
private:
sf::Music music;
std::string label;
Music (Music const&);
void operator=(Music const&);
// The SFML music.
// Text label to identify music.
// SFML doesn't allow music copy.
// SFML doesn't allow music assignment.
// Associate music buffer with file.
// Return 0 if ok, else -1.
int loadMusic(std::string filename);
// Set label associated with music.
void setLabel(std::string new_label);
// Get label associated with music.
std::string getLabel() const;
// Play music.
// If loop is true, repeat play when done.
void play(bool loop=true);
// Stop music.
void stop();
// Pause music.
void pause();
// Return SFML music.
sf::Music *getMusic();
};
Music.h
public:
Music();
// System includes.
#include <string>
#include <SFML/Audio.hpp>
class Music {
private:
sf::Music music;
std::string label;
Music (Music const&);
void operator=(Music const&);
Private since sf::Music
attribute doesn’t allow
// Associate music buffer with file.
// Return 0 if ok, else -1.
int loadMusic(std::string filename);
// Set label associated with music.
void setLabel(std::string new_label);
// Get label associated with music.
std::string getLabel() const;
// Play music.
// If loop is true, repeat play when done.
void play(bool loop=true);
// Stop music.
void stop();
// Pause music.
void pause();
// Return SFML music.
sf::Music *getMusic();
};
Must be a pointer here
since can’t copy sf::Music
Music.h
public:
Music();
// The SFML music.
// Text label to identify music.
// SFML doesn't allow music copy.
// SFML doesn't allow music assignment.
Extend ResourceManager
const int MAX_SOUNDS = 128;
const int MAX_MUSICS = 128;
private:
Sound sound[MAX_SOUNDS];
int sound_count;
Music music[MAX_MUSICS];
int music_count;
// Array of sound buffers.
// Count of number of loaded sounds.
// Array of music buffers.
// Count of number of loaded musics.
public:
// Load Sound from file.
// Return 0 if ok, else -1.
int loadSound(std::string filename, std::string label);
// Remove Sound with indicated label.
// Return 0 if ok, else -1.
int unloadSound(std::string label);
// Find Sound with indicated label.
// Return pointer to it if found, else NULL.
Sound *getSound(std::string label);
// Associate file with Music.
// Return 0 if ok, else -1.
int loadMusic(std::string filename, std::string label);
// Remove label for Music with indicated label.
// Return 0 if ok, else -1.
int unloadMusic(std::string label);
// Find Music with indicated label.
// Return pointer to it if found, else NULL.
Music *getMusic(std::string label);
ResourceManager loadSound()
// Return 0 if ok, else -1.
int ResourceManager::loadSound(std::string filename, std::string label)
if sound_count is MAX_SOUNDS then
writeLog("Sound array full")
return -1 // Error.
end if
if sound[sound_count].loadSound(filename) == -1 then
writeLog("Unable to load from file")
return -1 // Error
end if
// All set.
sound[sound_count].setLabel(label)
increment sound_count
return 0
loadMusic() similar
Sound/Music Methods Similar to
Sprite Methods
Audio
Sprites
•
•
•
•
• loadSprite()
• unloadSprite()
• getSprite()
unloadSound()
unloadMusic()
getSound()
getMusic()
Except:
1) No resources to “destroy” when remove
2) Music cannot be copied, so when “unload”, just
set label to empty (“”)
Using Audio – e.g., Saucer Shoot
// Load music.
RM.loadMusic( "sounds/start-music.wav" , "start music" );
// Load sounds.
RM.loadSound(
RM.loadSound(
RM.loadSound(
RM.loadSound(
"sounds/fire.wav" , "fire" );
"sounds/explode.wav" , "explode" );
"sounds/nuke.wav" , "nuke" );
"sounds/game-over.wav" , "game over" );
// Play “start music”.
df::Music *p_music = RM.getMusic("start music");
p_music->play();
// Play "fire" sound.
df::Sound *p_sound = RM.getSound("fire");
p_sound->play();
Development Checkpoint #11!
• Make Sound and Music classes, stub out and
add to Makefile or project
• Implement and test outside of engine
– Use Saucer Shoot audio files
• Extend ResourceManager for sound
– Test
• Extend ResourceManager for music
– Test
Outline – Part IV
•
•
•
•
•
•
Resource Management
Using Sprites
Boxes
Camera Control
Audio
View Objects
(done)
(done)
(done)
(done)
(done)
(next)
Different Object Types
• “Game” objects
– live in game world
– things that interact with each other in game world
(e.g., Saucers, Bullets, Hero)
• “View” objects
– live in view world, or display
– things that don’t interact with game world objects and
only display information to player (e.g., Score, Lives)
• Create ViewObject
– Inherits from base Object class
#include <string>
// General location of ViewObject on screen.
enum ViewObjectLocation {
TOP_LEFT, TOP_CENTER, TOP_RIGHT,
CENTER_LEFT, CENTER_CENTER, CENTER_RIGHT,
BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT,
};
class ViewObject : public Object {
private:
std::string view_string;
int value;
bool border;
Color color;
ViewObjectLocation location;
// Label for value (e.g., "Points").
// Value displayed (e.g., points).
// True if border around display.
// Color for text.
// Location of ViewObject.
public:
// Construct ViewObject.
// Object settings: SPECTRAL, max alt.
// ViewObject defaults: border, top_center, default color.
ViewObject();
// Draw view string and value.
virtual void draw();
ViewObject.h (1 of 2)
#include "Object.h"
#include "Event.h"
#include <string>
// General location of ViewObject on screen.
enum ViewObjectLocation {
TOP_LEFT, TOP_CENTER, TOP_RIGHT,
CENTER_LEFT, CENTER_CENTER, CENTER_RIGHT,
BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT,
};
class ViewObject : public Object {
virtual
for the same
reason as
for Object
private:
std::string view_string;
int value;
bool border;
Color color;
ViewObjectLocation location;
// Label for value (e.g., "Points").
// Value displayed (e.g., points).
// True if border around display.
// Color for text.
// Location of ViewObject.
public:
// Construct ViewObject.
// Object settings: SPECTRAL, max alt.
// ViewObject defaults: border, top_center, default color.
ViewObject();
// Draw view string and value.
virtual void draw();
ViewObject.h (1 of 2)
Pre-defined
positions, relative to
screen not world
#include "Object.h"
#include "Event.h"
// General location of ViewObject on screen.
void setLocation(ViewObjectLocation new_location);
// Set view value.
void setValue(int new_value);
// Get view value.
int getValue() const;
// Set view border (true = display border).
void setBorder(bool new_border);
// Get view border (true = display border).
bool getBorder();
// Set view color.
void setColor(Color new_color);
// Get view color.
Color getColor() const;
// Set view display string.
void setViewString(std::string new_view_string);
// Get view display string.
std::string getViewString() const;
};
ViewObject.h (2 of 2)
// Handle `view' event if tag matches view_string (others ignored).
// Return 0 if ignored, else 1 if handled.
virtual int eventHandler(const Event *p_e);
ViewObject Constructor
// Construct ViewObject.
// Object settings: SPECTRAL, max altitude, type `ViewObject'.
// ViewObject defaults: border, top_center, default color, value 0.
ViewObject::ViewObject()
// Initialize Object attributes.
setSolidness(SPECTRAL)
setAltitude(MAX_ALTITUDE)
setType("ViewObject")
// Initialize ViewObject attributes.
setValue(0)
setBorder(true)
setLocation(TOP_CENTER)
setColor(COLOR_DEFAULT)
// Set new position based on location.
switch (new_location)
case TOP_LEFT:
p.setXY(WorldManager getView().getHorizontal() * 1/6, 1)
if getBorder() is false then
delta = -1
end if
break
case TOP_CENTER:
p.setXY(WorldManager getView().getHorizontal() * 3/6, 1)
if getBorder() is false then
delta = -1
end if
break
...
end switch
// Shift, as needed, based on border.
p.setY(p.getY() + delta)
// Set position of object to new position.
setPosition(p)
// Set new location.
location = new_location
ViewObject setLocation()
// General location of ViewObject on screen.
ViewObject::setLocation(ViewObjectLocation new_location)
ViewObject setBorder()
• Straightforward, but need to setLocation() to account
for new border setting
(And an example of why getters/setters used)
// Set view border (true = display border) .
void ViewObject::setBorder(bool new_border)
if border != new_border then
border = new_border
// Reset location to account for bordersetting.
setLocation(getLocation())
end if
ViewObject draw()
// Draw view string and value.
void ViewObject::draw()
// Display view string + value.
if border is true then
temp_str = " " + getViewString() + " " + intToString(value) + " "
else
temp_str = getViewString() + " " + intToString(value)
end if
Remember – view object in view (x,y)
Must convert to world (x,y) before drawing
// Draw centered at position.
Vector pos = viewToWorld(getPosition())
DisplayManager drawString(pos, temp_str, CENTER_JUSTIFIED,
getColor())
if border is true then
// Draw box around display.
...
end if
intToString() utility defined next slide
intToString() Utility
#include <sstream>
using std::stringstream;
// Convert int to a string, returning string.
std:string intToString(int number) {
stringstream ss; // Create stringstream.
ss << number;
// Add number to stream.
return ss.str(); // Return string with contents of stream.
}
• Put in utility.cpp
Extension to WorldManager draw()
to Support ViewObjects
...
// Only draw if Object would be visible (intersects view).
if boxIntersectsBox(box, view) or
dynamic_cast <ViewObject *> (p_temp_o))
p_temp_o -> draw()
end if
...
// Object in view,
// or is ViewObject.
• ViewObjects on screen (not world positions)
– Always draw
• Dynamic cast indicates type (next slide)
#include "Event.h"
const std::string VIEW_EVENT "df::view";
public:
// Create view event with tag VIEW_EVENT, value 0 and delta false.
EventView();
// Create view event with tag, value and delta as indicated.
EventView(std::string new_tag, int new_value, bool new_delta);
// Set tag to new tag.
void setTag(std::string new_tag);
// Get tag.
std::string getTag() const;
// Set value to new value.
void setValue(int new_value);
// Get value.
int getValue() const;
// Set delta to new delta.
void setDelta(bool new_delta);
// Get delta.
bool getDelta() const;
};
ViewEvent.h
class EventView : public Event {
private:
std::string tag; // Tag to associate.
int value;
// Value for view.
bool delta;
// True if change in value, else replace value.
// See if this is a `view' event.
if p_event->getType() is VIEW_EVENT then
EventView *p_ve = static_cast <EventView *> p_event
// See if this event is meant for this object.
if p_ve -> getTag() is getViewString() then
if p_ve -> getDelta() then
setValue(getValue() + p_ve->getValue())
else
setValue(p_ve->getValue())
end if
// Event was handled.
return 1
end if
end if
// If here, event was not handled.
return 0
// Change in value.
// New value.
ViewObject eventHandler()
// Handle `view' events if tag matches view_string (others ignored).
// Return 0 if ignored, else 1 if handled.
int ViewObject::eventHandler(const Event *p_e)
Using ViewObjects
// Before starting game...
df::ViewObject *p_vo = new ViewObject; // Used for points.
p_vo -> setViewString("Points");
p_vo -> setValue(0);
p_vo -> setLocation(df::TOP_RIGHT);
p_vo -> setColor(df::COLOR_YELLOW);
...
// In destructor of enemy object...
df::EventView ev("Points", 10, true);
df::WorldManager onEvent(&ev);
Development Checkpoint #13!
• Create ViewObject class (add to project or
Makefile, stub out and compile)
• Write ViewObject constructor and
setLocation()
• Write intToString()
• Write ViewObject draw()
• Create ViewEvent class and define ViewObject
eventHandler()
• Test with variety of ViewObjects and events at a
variety of locations on screen
Ready for Dragonfly!
• Game objects have
sprites
– Animation
• Game objects have
bounding boxes
– Sprite sized
• Collisions for boxes
• View objects for display
– Have values, updatable via
events
– HUD-type locations
• Have camera control for
world
– Subset of world
– Move camera, display
objects relative to world
• Audio
– Sound effects
– Music
Saucer Shoot Dragonfly
• Saucer Shoot with no optional elements
– Transparency, Event Filtering, Inactive Objects
Available online
Dragonfly Optional Elements
•
•
Human-friendly Time Strings
Controlling Verbosity
•
•
Overloading + for Object List
Dynamically-sized Lists of Objects
•
•
•
•
Disallow Movement onto Soft Objects
Invisible Objects
Inactive Objects
Disallow Soft Movement
•
•
•
•
Random Seeds
•
SceneGraph
•
Fine-tuning Game Loop
•
Sprite Transparency
•
Utility Functions
•
Handle Ctrl-C
•
Splash Screen
•
Closing Game Window
•
HUD Elements – TextEntry,
Buttons
Event Filtering (registerInterest())
Colored Backgrounds
View Dynamics (“slack”)
See book for details.