Presentation is loading. Please wait.

Presentation is loading. Please wait.

Chapter 4 Engine.

Similar presentations


Presentation on theme: "Chapter 4 Engine."— Presentation transcript:

1 Chapter 4 Engine

2 Outline – Part I Overview (next) Managers Logfile Management
Game Management

3 Dragonfly Overview GAME CODE DRAGONFLY COMPUTER PLATFORM Saucer: hit()
Hero: kbd() Star: eventHandler() GameOver: step() DRAGONFLY DrawCharacter InsertObject LoadSprite GetKey MoveObject SendEvent COMPUTER PLATFORM Allocate memory Clear display File open/close Get keystroke

4 Dragonfly Classes

5 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

6 Manager.h Notes: No “Managers” Base class Other managers inherit
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 Avoids naming collisions 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 Object attributes not modified Derived class can override

7 Dragonfly Header files
Tip #1! Header files available for download: Warning! Might see complete, full-featured engine Not starting point Instead, construct header files by hand However, listings also available for download (e.g., Manager.h in Listing 4.1)

8 Managers in C++: Global Variables?
Need access from many parts of code  Could make Managers global variables (e.g., outside of 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 // Outside main(), these are global variables. df::LogManager log_manager; df::DisplayManager graphics_manager; main() { ... } Gregory, Chapter 5

9 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++?

10 Managers: C++ Singletons
class Singleton { private: 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; (a.k.a. Singleton Design Pattern) 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 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

11 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

12 Outline – Part I Overview (done) Managers (done)
Logfile Management (next) Game Management

13 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

14 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)

15 General Purpose Output
Using printf() one of most versatile But using it requires pre-determined number of arguments printf(“Bob wrote 123 lines”); // 1 argument printf(“%s wrote %d lines”, “Bob”, 123); // 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, …) { }

16 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); }

17 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)

18 LogManager - Attributes and Methods
private: bool do_flush; // True if fflush after each write. FILE *p_f; // 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

19 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

20 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

21 Platform-Specific Code
Conditional compilation also useful for platform-specific code E.g., code needed for Mac, but may not work on Windows [Dragonfly uses for Window, Mac, Linux] #if defined (_WIN32 ) || defined (_WIN64) // Windows specific code here. #elif LINUX // Linuxspecific code here. #endif

22 LogManager.h Complete header file #ifndef __LOG_MANAGER_H__
#define __LOG_MANAGER_H__ // System includes. #include <stdio.h> // Engine includes. #include "Manager.h“ // Two-letter acronym for easier access to manager. #define LM df::LogManager::getInstance() namespace df { const std::string LOGFILE_NAME "dragonfly.log" class LogManager : public Manager { private: LogManager(); // Private (a singleton). LogManager (LogManager const&); // No copying. void operator=(LogManager const&); // No assignment. bool do_flush; // True if fflush after each write. FILE *p_f; // Pointer to logfile structure. public: // If logfile is open, close it. ~LogManager(); // 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__ Complete header file LogManager.h

23 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. "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’

24 Using the LogManager (2 of 2)
Tip #2! When calling writeLog() include information: Class name Method name // Sample logfile output: Log Manager started DisplayManager::startUp(): Current window set DisplayManager::startUp(): max X is 80, max Y is 24

25 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!

26 Outline – Part I Overview (done) Managers (done)
Logfile Management (done) Game Management (next) Game Loop Time GameManager

27 The Game Loop The Game Manager “runs” game:
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 10,000 foot view of game loop 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?

28 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?

29 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 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) Example of Windows high-resolution timing at:

30 Measuring Computer Time (2 of 2)
Need to measure elapsed time Want milliseconds for game engine Basic method:  So, how to measure elapsed time on Linux? Mac? Windows? Record before time Do processing stuff (get input, update ...) Record after time Compute elapsed time: after - before

31 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/ ; after_msec = after_ts.tv_sec*1000 +after_ts.tv_nsec/ ; long int elapsed_time = after_msec ‐ before_msec;

32 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* bfore_tv.tv_usec/1000; long int after_msec = after_tv.tv_sec* 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()

33 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;

34 The Clock Class Engine needs convenient access to high-resolution 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)

35 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. long int split(void) const; };

36 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() On Windows, use Sleep() struct timespec sleep_time sleep_time.tv_sec = 0 sleep_time.tv_nsec = // 20 milliseconds nanosleep(&sleep_time , NULL) Need <time.h> int sleep_time = 20 Sleep(sleep_time) // 20 milliseconds Need <Windows.h>

37 (as appropriate for platform, Windows or Linux)
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)

38 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

39 Outline – Part I Overview (done) Managers (done)
Logfile Management (done) Game Management Game Loop (done) Time (done) GameManager (next)

40 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();

41 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; };

42 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 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!

43 Timing from Shell/Terminal
Tip #4! Can measure how long command takes from shell Time (Linux/Mac) Measure-Command (Windows) e.g., time myprogram.exe or time a.out Output example (bash, also /usr/bin/time): real 0m3.566s user 0m0.154s sys 0m0.248s Use for testing game loop! e.g., “Game” iterates 100 times Exit  Verify with time takes 3.3 seconds

44 Outline – Part I Overview (done) Managers (done)
Logfile Management (done) Game Management (done)

45 Outline – Part II The Game World (next) Game Objects Lists of Objects
Updating Game Objects Events WorldManager

46 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

47 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

48 Vector.h (1 of 2) class Vector { private:
float x; // Horizontal coordinate in 2d world. float y; // 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)

49 Support routines useful when moving (used later)
// Return magnitude of vector. float getMagnitude() const; // Normalize vector. void normalize(); // Scale vector. void scale(float s); // Add two Vectors, return new Vector. Vector operator+(const Vector &other) const; }; Support routines useful when moving (used later) Vector.h (2 of 2) 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.

50 Object.h In constructor: Set id based on static Set position (0,0)
#include <string> #include "Vector.h" class Object { private: int id; // Unique Object identifier. std::string type; // User-defined identification. Vector pos; // Position in game world. public: Object(); virtual ~Object(); // 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; }; In constructor: Set id based on static Set position (0,0) Set type as “undefined” Object.h

51 Needed since deletion inside engine
#include <string> #include "Vector.h" class Object { private: int id; // Unique Object identifier. std::string type; // User-defined identification. Vector pos; // Position in game world. public: Object(); virtual ~Object(); // 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 Needed since deletion inside engine Note: will add more attributes later.  Object becomes largest class!

52 Outline – Part II The Game World (done) Game Objects (done)
Lists of Objects (next) Updating Game Objects Events WorldManager

53 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

54 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?

55 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

56 Lists of Game Objects (3 of 3)
private: int item[MAX]; int count; // 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. Integer example // 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; } Basically, replace “int” with “Object *”

57 ObjectList.h (1 of 2) const int MAX_OBJECTS = 5000;
#include "Object.h" #include "ObjectListIterator.h" class ObjectListIterator; class ObjectList { private: int count; // Count of objects in list. Object *p_obj[MAX_OBJECTS]; // Array of pointers to objects. public: friend class ObjectListIterator; ObjectList();

58 Needed by compiler for forward reference
ObjectList.h (1 of 2) const int MAX_OBJECTS = 5000; #include "Object.h" #include "ObjectListIterator.h" class ObjectListIterator; class ObjectList { private: int count; // Count of objects in list. Object *p_obj[MAX_OBJECTS]; // Array of pointers to objects. public: friend class ObjectListIterator; ObjectList(); Needed by compiler for forward reference What is an iterator?

59 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; };

60 Pointer Naming Convention
Tip #5! Debugging pointer errors can be tricky (challenging, frustrating) Better to avoid in both design and coding Naming convention can help remind that variables is pointer Dragonfly uses p_ for all. e.g., Object *p_o Recommended!

61 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: Understand container class (e.g., IntList) Design iterator class for container class Add iterator as friend of container Steps to use: Create iterator object, giving it container object Use  first(), isDone(), next(), and currentItem() to access container

62 Iterator for List Class (1 of 3)
class IntListIterator { private: const IntList *p_list; // Pointer to IntList. int index; // Index of current item. public: IntListIterator(const IntList *p_l) { p_list = p_l; first(); } // Set iterator to first item. void first() { index = 0; // Iterate to next item. void next() { index++; } // Return true if done iterating, else false. bool isDone() { return index == (p_list -> count); // Return current item. int currentItem() { return p_list -> item[index]; }; Want to error check index Understand container class: e.g., IntList uses array. Design iterator class for container class: e.g., need index

63 Iterator for List Class (2 of 3)
// [Inside IntList class (i.e., IntList.h)] friend class IntListIterator; Add iterator as friend of container

64 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: Indicate list to iterate on when creating iterator (e.g., &my_list) Use  first(), isDone(), next(), and currentItem() to access

65 ObjectListIterator.h #include "Object.h " #include "ObjectList.h"
class ObjectList; class ObjectListIterator { 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(); // Set iterator to first item in list. void next(); // Set iterator to next item in list. bool isDone() const; // Return true if at end of list. // Return pointer to current Object, NULL if done/empty. Object *currentObject() const; }; ObjectListIterator.h

66 ObjectListIterator.h Needed by compiler for forward reference
#include "Object.h " #include "ObjectList.h" class ObjectList; class ObjectListIterator { 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(); // Set iterator to first item in list. void next(); // Set iterator to next item in list. bool isDone() const; // Return true if at end of list. // Return pointer to current Object, NULL if done/empty. Object *currentObject() const; }; Needed by compiler for forward reference ObjectListIterator.h Default construction not allowed

67 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!

68 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 Implement and then test by having Objects “move” Test limits (empty list, full list)

69 Outline – Part II The Game World (done) Game Objects (done)
Lists of Objects (done) Updating Game Objects (next) Events WorldManager

70 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

71 Game Loop with Update Declare Update() as virtual
Seems ok, right? But devil in the details… 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 }

72 Single Phase Update What’s wrong with the above?
// 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

73 Phased Updates Updates done in phases
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. WorldManager move(li.currentObject()) // Phase 3: Draw all objects. WorldManager draw(li.currentObject()) end while Updates done in phases Allows subsequent phases to take advantage Could even be sub-phases (not shown)

74 Outline – Part II The Game World (done) Game Objects (done)
Lists of Objects (done) Updating Game Objects (done) Events (next) WorldManager

75 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?

76 Simple Approach 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 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() 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?

77 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

78 Encapsulating Event in Object
Core Components Advantages Type (e.g., explosion, health pack, collision …) Attributes (e.g., damage, healing, with what …) Single event handler Since type encapsulated, only method needed is virtual int eventHandler(Event *p_e); Persistence 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

79 Event Types (1 of 3) One approach is to match each
enum EventType { LEVEL_STARTED, PLAYER_SPAWNED, ENEMY_SPOTTED, EXPLOSION, BULLET_HIT, } One approach is to match each type to integer (e.g., enum EventType) Simple and efficient (integers are fast) Problem 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

80 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

81 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”

82 Event Arguments Easiest is have new type of event class for each unique event Objects get parent Event, but can check type to see if this is, say, an “explosion event”  if so, treat incoming object as EventExplosion class Event { string event_type; }; class EventExplosion : public Event { Vector location; int damage; float radius; Note: get() and set() for event type not shown

83 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)

84 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. ... return false // Didn't recognize this event. } (Almost right  actually, need cast the event call – see later slides)

85 Event.h #include <string>
const std::string UNDEFINED_EVENT "df::undefined"; class Event { private: std::string event_type; // Holds event type. public: // Create base event. Event(); // Destructor. virtual ~Event(); // Set event type. void setType(std::string new_type); // Get event type. std::string getType() const; }; Event.h

86 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

87 EventStep.h Generated by GameManager every game loop
#include "Event.h" 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; }; 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() 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); }

88 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();

89 Sending Step Events In GameManager, inside game loop
Except … eventHandler() needs to take Event * instead of EventStep *  Need casting // 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

90 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 From Saucer Shoot: class Base {}; class Derived : public Base {}; Base *p_b = new Base; Derived *p_d = static_cast <Derived *> (p_b); 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

91 Do Not Ignore Compiler Warnings
Tip #7! Compilers can allow conversion of one pointer type to another (e.g., Event * to EventStep *) They are both addresses after all But compiler will provide warning message  Do not ignore! Usually, warnings because something unintended or dangerous is happening If intend on conversion, make explicit with cast

92 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!

93 Outline – Part II The Game World (done) Game Objects (done)
Lists of Objects (done) Updating Game Objects (done) Events (done) WorldManager (next)

94 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();

95 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); };

96 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()

97 Extensions to Object Event handler Constructor Destructor
// Handle event (default is to ignore everything). // Return 0 if ignored, else 1 if handled. virtual int eventHandler(const Event *p_e); // Add self to game world. WorldManager insertObject(this) Remember in Saucer Shoot? new Saucer; // Without grabbing return value  Now you know how! // Remove self from game world. WorldManager removeObject(this)

98 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()

99 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)

100 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)

101 Program Flow for Game Objects
Object Creation Object Destruction Game program invokes new, say new Saucer Saucer Saucer() calls Object Object() Object Object() calls WorldManager insertObject() WorldManager insertObject() calls updates.insert() 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()

102 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

103 Outline – Part II The Game World (done) Game Objects (done)
Lists of Objects (done) Updating Game Objects (done) Events (done) WorldManager (done)

104 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 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 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”) 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

105 Version Control Tip #8! Code getting large  Version control system
For teams, facilitates concurrent development But even for individuals, helps manage changes, associating files by time and revision names If local version control system (e.g., RCS), make sure to backup to another disk (networked or local)

106 Outline – Part III Sending Events (next) Managing Graphics
Managing Input Moving Objects Misc

107 Manager onEvent() To use: Want to send event ? e.g., step in engine
e.g., “nuke” in game code // Send event to all Objects. // Return count of number of events sent. int Manager::onEvent(const Event *p_event) count = 0 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 // Provide step event to all Objects. EventStep s; onEvent(&s); To use:

108 Outline – Part III Sending Events (done) Managing Graphics (next)
SFML DisplayManager Managing Input Moving Objects Misc

109 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

110 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

111 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()

112 SFML Font Before drawing text, need to load font
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 If cannot load, check working director (e.g., Debug in Windows) sf::Font font if (font.loadFromFile("df-font.ttf") == false) then // Error end if

113 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)

114 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

115 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; Note, doesn’t have to be pointer, but is more like DisplayManager

116 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!

117 Outline – Part III Sending Events (done) Managing Graphics
SFML (done) DisplayManager (next) Managing Input Moving Objects Misc

118 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;

119 DisplayManager.h (1 of 2) #include <SFML/Graphics.hpp>
#include "Color.h" #include "Manager.h" #include “Vector.h" // 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(); Could add: |sf::Style::Close DisplayManager.h (1 of 2)

120 // Open graphics window ready for text-based display.
// Return 0 if ok, else -1. int startUp(); // Close graphics window. void shutDown(); // Draw a character at screen location (x,y) with color. 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. int swapBuffers(); // Return pointer to SFML drawing window. sf::RenderWindow *getWindow() const; }; DisplayManager.h (2 of 2)

121 Graphics Manager startUp()
// Open graphics window ready for text-based display. // Return 0 if ok, else return -1. int DisplayManager::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 Graphics Manager startUp()

122 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);

123 DisplayManager Drawing Helpers – charHeight() and spacesToPixels()
// Compute character height, based on window size and font. float charHeight() return DisplayManager getVerticalPixels() / DisplayManager getVeritical() Warning! Make sure to cast division as float, otherwise will get integer division // 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

124 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) // 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)

125 DisplayManager drawCh() (2 of 2)
// Scale to right size. if (charWidth() < charHeight()) then text.setCharacterSize( charWidth() * 2 ) else text.setCharacterSize( charHeight() * 2 ) end if // Set SFML color based on Dragonfly color. switch (color) case YELLOW: text.setFillColor(sf::Color::Yellow) break; case RED: text.setFillColor(sf::Color::Red) ... end switch // Set position in window (in pixels). text.setPosition(pixel_pos.getX(), pixel_pos.getY()) // Draw character. p_window -> draw(text) Note, multiplier of 2 for typical terminal character. Can use 1 for square character. DisplayManager drawCh() (2 of 2)

126 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() // Clear window to get ready for next draw. p_window -> clear() return 0 // Success. Note, graphics typically 2 buffers Display one, draw on other When ready  swap

127 DisplayManager drawString() (1 of 2)
enum Justification { LEFT_JUSTIFIED, CENTER_JUSTIFIED, 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; More than one character (i.e., string)  Used for HUD (ViewObjects) later

128 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; // 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()) case LEFT_JUSTIFIED: default: 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)

129 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

130 Using the DisplayManager (1 of 3)
Can be called explicitly. e.g., could put drawing code in eventHandler() But for Objects, better for game programmer if handled automatically  Extend Object Vector p1(12,10), p2(1,3); DM.drawCh(p1, ‘*’, df::RED); DM.drawCh(p2, ‘M’, df::BLUE); public: virtual void draw()

131 Using the DisplayManager (2 of 3)
Draw method does nothing in base class, but game code can override. e.g., Star: Add draw() method to WorldManager: void Star::draw() DM.drawCh(pos, STAR_CHAR, df::WHITE); // 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

132 Using the DisplayManager (3 of 3)
Modify GameManager, game loop Later, will add support for Sprites in DisplayManager 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

133 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

134 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

135 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

136 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

137 Develop and Test in Isolation
Tip #8! #include <unistd.h> // for sleep() #include "DisplayManager.h" int main() { DM.drawCh(df::Vector(10,5), '*',df::WHITE); DM.swapBuffers(); sleep(2); // sleep for 2 seconds DM.shutDown(); } Pop open SFML window Draw ‘*’ at (10,5) Close after 2 seconds

138 Outline – Part III Sending Events (done) Managing Graphics (done)
Managing Input (next) Overview SFML for Input InputManager Input Events Moving Objects Misc

139 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

140 Input Workflow User provides input via device (e.g., button press)
Engine detects input has occurred Determines whether to process at all (e.g., perhaps not during cut-scene) 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) Encode into abstract, device-independent form suitable for game

141 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

142 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

143 SFML for Game-Type Input (1 of 2)
Assume have SFML window open Only setup needed: Then, during game: // Disable keyboard repeat (only do once). p_window -> setKeyRepeatEnabled(false) // 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 // Key was released. if event.type is sf::Event::KeyReleased then // Do key released stuff.

144 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 while // Key is pressed (currently held down). if sf::Keyboard::isKeyPressed(keycode) then // Do key is pressed stuff. // Mouse button is pressed (currently held down). if sf::Mouse::isButtonPressed(button) then // Do mouse is pressed stuff.

145 InputManager.h #include "Manager.h"
class InputManager : public Manager { private: InputManager(); InputManager (InputManager const&); void operator=(InputManager const&); 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

146 Checking StartUp Status
Note, SFML window needs to be created before InputManager can start  New start up dependency order for Dragonfly LogManager DisplayManager InputManager Can check startup status in base Manager class bool isStarted()

147 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()

148 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()

149 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 if key press then create EventKeyboard (key and action) send EventKeyboard to all Objects else if key release then 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)

150 InputManager getInput() (2 of 2)
create EventMouse (x, y and action) send EventMouse to all Objects 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 ... // Check current mouse press events for each button. if mouse button pressed (sf::Mouse::Left) then InputManager getInput() (2 of 2)

151 Now, need events for keyboard and mouse
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

152 EventKeyboard.h (1 of 2) #include "Event.h"
const std::string KEYBOARD_EVENT = "df::keyboard"; // 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)

153 EventKeyboard.h (2 of 2) class EventKeyboard : public Event { private:
Keyboard::Key key_val; // Key value. EventKeyboardAction keyboard_action; // 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)

154 class EventKeyboard : public Event {
private: Keyboard::Key key_val; // Key value. EventKeyboardAction keyboard_action; // 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; }; Typically used by game code (e.g., to see what key was pressed) EventKeyboard.h (2 of 2) Typically used by InputManager only

155 #include "Event.h" const std::string MOUSE_EVENT = "df::mouse"; // 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, CLICKED, PRESSED, MOVED, class EventMouse : public Event { private: EventMouseAction mouse_action; // Mouse action. Mouse::Button mouse_button; // Mouse button. Vector mouse_xy; // Mouse (x,y) coordinates. EventMouse.h (1 of 2) Note, all (x,y) coordinates in Dragonfly, including the mouse, are in spaces not pixels.

156 EventMouse.h (2 of 2) public: EventMouse();
// 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)

157 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 , p74. end while

158 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.

159 Outline – Part III Filtering Events (done) Managing Graphics (done)
Managing Input (done) Moving Objects (next) Velocity Collisions World boundaries Misc

160 Kinematics Up to now, need to move by updating location in game code
// 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 Up to now, need to move by updating location in game code  every step 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

161 Object Extensions to Support Kinematics
private: Vector direction; // Direction vector. float speed; // Object speed in direction. 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() Object Extensions to Support Kinematics

162 Object Extensions to Support Kinematics
private: Vector direction; // Direction vector. float speed; // Object speed in direction. 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() Note, don’t store velocity but can compute Object Extensions to Support Kinematics Called each step

163 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.

164 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)

165 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

166 Outline – Part III Filtering Events (done) Managing Graphics (done)
Managing Input (done) Moving Objects Velocity (done) Collisions (next) World boundaries Misc

167 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

168 Overlap Testing Most common technique used in games Concept
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)

169 Overlap Testing Limitations
Fails if game objects move too fast relative to size  Arrow passes through window without breaking it! Possible solutions? 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) | > > | >--- t t2 | t3

170 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

171 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 Note, does not need to completely encompass, but might mean some contact not detected May be ok for some games \ \\/\/ \// \_\/ |\_/ \ ||/ \|/ ||_/ ||| /|||\ game object bounding volume

172 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)## #\|/# ## | ## ## | ## | \ ~==- /

173 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

174 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.

175 Collisions in Dragonfly
Detection Resolution 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 Disallow move Only for some types of objects (next slide) Object stays in original location Report collision event to both Objects

176 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)

177 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

178 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;

179 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);

180 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); };

181 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 getCollisions(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);

182 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 getCollisions() May seem trivial, but will replace with boxIntersectsBox() later

183 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 getCollisions(Object *p_o, Vector where) // 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

184 WorldManager moveObject()
int moveObject(Object *p_o, Vector where) if p_o -> isSolid() then // Need to be solid for collisions. // Get collisions. ObjectList list = getCollisions(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 // 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 WorldManager moveObject()

185 Outline – Part III Filtering Events (done) Managing Graphics (done)
Managing Input (done) Moving Objects Velocity (done) Collisions (done) World boundaries (next) Misc

186 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

187 EventOut.h #include "Event.h" const std::string OUT_EVENT "df::out";
class EventOut : public Event { public: EventOut(); };

188 Extensions to WorldManager to Support Boundaries
In moveObject() If Object can move, check against DisplayManager getHorizontal() and getVertical() If inside & moves outside Generate EventOut If outside & moves outside Nothing (i.e., only one event) If outside & moves inside Nothing (i.e., only event when goes out) ... // Generate out of bounds event and // send to object. EventOut ov p_go -> eventHandler(&ov)

189 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

190 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)

191 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 on Project 2b Web page

192 Outline – Part IV Resource Management (next) Using Sprites Boxes
Runtime Issues ResourceManager Using Sprites Boxes Camera Control Audio View Objects

193 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

194 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.

195 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

196 Frames Text Variable sizes (width and height)
____ /__o_\ Text Variable sizes (width and height) Rectangular Frames can draw themselves (given location) 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 \ ~==- /

197 Frames are stored as 1-d strings
#include <string> class Frame { private: int width; // Width of frame. int height; // Height of frame. std::string frame_str; // All frame characters stored as string. public: Frame(); // Create empty frame. // 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; // Draw self centered at position (x,y) with color. // Don't draw transparent characters (0 means none). Return 0 if ok, else -1. int draw(Vector position, Color color, char transparency) const; }; Frames are stored as 1-d strings Frame.h

198 Frame draw() // Draw self centered at position (x,y) with color.
// Return 0 if ok, else -1. // Note: top-left coordinate is (0,0). int Frame::draw(Vector world_pos, Color color) const // Error check empty string. if frame is empty then return error end if // Determine offset since centered x_offset = frame.getWidth() / 2 y_offset = frame.getHeight() / 2 // 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 Frame draw()

199 Sprites Sequence of Frames In Dragonfly, Sprites have color
____ /____\ /___o\ /__o_\ /_o__\ /o___\ Sequence of Frames No color In Dragonfly, Sprites have color Same color for all frames and characters Sprites also: Can “draw” themselves (given location) Know animation rate Need dimensions, number of frames, ability to add/retrieve frames, and animation rate \ ~==- / ==-

200 Sprite.h (1 of 3) #include <string> #include "Frame.h"
class Sprite { private: int width; // Sprite width. int height; // Sprite height. int max_frame_count; // Maximum number of frames sprite can have. int frame_count; // Actual number of frames sprite has. Color color; // Optional color for entire sprite. Frame *frame; // Array of frames. std::string label; // Text label to identify sprite. 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 3)

201 Sprite.h (1 of 3) #include <string> #include "Frame.h"
class Sprite { private: int width; // Sprite width. int height; // Sprite height. int max_frame_count; // Maximum number of frames sprite can have. int frame_count; // Actual number of frames sprite has. Color color; // Optional color for entire sprite. int m_slowdown; // Animation slowdown (1=no slowdwn, 0=stop). Frame *frame; // Array of frames. std::string label; // Text label to identify sprite. 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 3)

202 Sprite.h (1 of 3) Needed to understand Frame class
#include <string> #include "Frame.h" class Sprite { private: int width; // Sprite width. int height; // Sprite height. int max_frame_count; // Maximum number of frames sprite can have. int frame_count; // Actual number of frames sprite has. Color color; // Optional color for entire sprite. int m_slowdown; // Animation slowdown (1=no slowdwn, 0=stop). Frame *frame; // Array of frames. std::string label; // Text label to identify sprite. 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; Frames are array, but dynamically allocated Sprite.h (1 of 3) Must provide number of Frames when create. e.g., Sprite(5)

203 Sprite.h (2 of 3) // Set height of sprite.
void setHeight(int new_height); // Get height of sprite. int getHeight() const; // Set sprite color. void setColor(int new_color); // 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 3)

204 Sprite.h (3 of 3) // Set animation slowdown value.
// Value in multiples of GameManager frame time. void setSlowdown(int new_sprite_slowdown); // Get animation slowdown value. int getSlowdown() const; // Draw indicated frame centered at position (x,y). // Return 0 if ok, else -1. // Note: top-left coordinate is (0,0). int draw(int frame_number, Vector position) const; }; Sprite.h (3 of 3)

205 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

206 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

207 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

208 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]

209 Outline – Part IV Resource Management Using Sprites Boxes
Issues, Frames, Sprites (done) ResourceManager (next) Using Sprites Boxes Camera Control Audio View Objects

210 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; // Delimiters used to parse Sprite files - // the ResourceManager `knows' file format. const std::string HEADER_TOKEN = "HEADER"; const std::string BODY_TOKEN = "BODY"; const std::string FOOTER_TOKEN = "FOOTER"; 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 VERSION_TOKEN = "version"; 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)

211 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; // Delimiters used to parse Sprite files - // the ResourceManager `knows' file format. const std::string HEADER_TOKEN = "HEADER"; const std::string BODY_TOKEN = "BODY"; const std::string FOOTER_TOKEN = "FOOTER"; 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 VERSION_TOKEN = "version"; 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. Tokens for parsing sprite files ResourceManager.h (1 of 2)

212 ResourceManager.h (2 of 2)
public: ~ResourceManager(); // 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. 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)

213 Reading Sprite from File
<HEADER> frames 2 width 4 height 3 color blue slowdown 3 </HEADER> <BODY> \ ~==- / end ==- </BODY> <FOOTER> version 1 </FOOTER> Typical that image file has specific format Header Body Footer Parse in pieces Header Body Tip #12! Many ways to read from a file in C++  Listing provides one example Footer

214 Example of C++ File Input
// Example of reading text file. // Read one line at a time, writing each line to stdout. #include <iostream> #include <fstream> #include <string> using std::string; using std::ifstream; using std::cout; using std::endl; int main() { string line; ifstream myfile ("example.txt"); // Open file. if (myfile.is_open()) { // Repeat until end of file. while (myfile.good()) { getline(myfile, line); // Read line from file. cout << line << endl; // Display line to screen. } // Close file when done. myfile.close(); } else // If here, unable to open file. cout << "unable to open file" << endl; Example of C++ File Input getline() reads a line at a time Strips off \n good() returns true if file has data When end of file, returns false

215 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 all header lines // header has sprite format data parse header read all body lines // body has frame data new Sprite (with frame count) for each frame parse frame add frame to Sprite end for read all footer lines parse footer close file add label to Sprite Create utility functions to help parse sprite file

216 Helper Functions for Loading Sprites
// Get next line from file, with error checking ("" means error). std::string getLine(std::ifstream *p_file); // Read in next section of data from file as vector of strings. // Return vector (empty if error). std::vector<std::string> readData(std::ifstream *p_file, std::string delimiter); // Match token in vector of lines (e.g., "frames 5"). // Return corresponding value (e.g., 5) (-1 if not found). // Remove any line that matches from vector. int matchLineInt((std::vector<std::string> *p_data, const char *token); // Match token in vector of lines (e.g., "color green"). // Return corresponding string (e.g., "green") ("" if not found). std::string matchLineInt(std::vector<std::string> *p_data, const char *token); // Match frame lines until "end", clearing all from vector. // Return Frame. Frame readFrame(std::vector<std::string> *p_data, int width, int height); Place directly in ResourceManager.cpp, but not part of class Nor in utility.h since game programmer won’t use

217 getLine() // Get next line from file, with error checking ("" means error). std::string getLine(std::ifstream *p_file) string line getline(*p_file, line) if not (p_file -> good()) then return error // File input error. return "" end if return line

218 readData() // Read in next section of data from file as vector of strings. // Return vector (empty if error). std::vector<std::string> readData(std::ifstream *p_file, std::string delimiter) begin = "<" + delimiter + ">" // Section beginning end = "</" + delim + ">" // Section ending // Check for beginning. s = getLine() if s not equal begin then return error end if // Read in data until ending (or not found). while (s not equal end) and (not s.empty()) do push_back(s) onto data end while // If ending not found, then error. if s.empty() then return data

219 matchLineInt() // Match token in vector of lines (e.g., "frames 5"). // Return corresponding value (e.g., 5) (-1 if not found). // Remove any line that matches from vector. int matchLineInt((std::vector<std::string> *p_data, const char *token) // Loop through all lines. auto i = p_data -> begin() // vector iterator while i not equals p_data -> end() if i equals token then number = atoi() on line.substr() i = p_data -> erase(i) // clear from vector else increment i end if end while return number atoi() – converts string (char *) to number  (needs <stdlib.h>) Can call above with tokens of: “frames”, “width”, “height”, “slowdown” Do similar for readLineStr() Can with token of: “color”

220 // Match frame lines until "end", clearing all from vector.
// Return Frame. Frame matchFrame(std::vector<std::string> *p_data, int width, int height) string line, frame_str for i = 1 to height line = p_data -> front() if line width not equal width then return error (empty Frame) end if frame_str += line end for if line is not "end" then create frame (width, height, frame_str) return frame matchFrame()

221 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 return error // sprite not found

222 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.

223 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

224 Outline – Part IV Resource Management (done) Using Sprites (next)
Boxes Camera Control Audio View Objects

225 Using Sprites Can load sprites But cannot draw (yet). Need to:
Control active animation (new class) Extend Object to support // Load saucer sprite RM.loadSprite("sprites/saucer-spr.txt", "saucer");

226 Animation.h (1 of 2) // System includes. #include <string>
// Engine includes. #include "Sprite.h" class Animation { private: Sprite *m_p_sprite; // Sprite associated with Animation. std::string m_name; // Sprite name in ResourceManager. int m_index; // Current index frame for Sprite. int m_slowdown_count; // Slowdown counter. public: // Animation constructor Animation(); // Set associated Sprite to new one. // Note, Sprite is managed by ResourceManager. // Set Sprite index to 0 (first frame). void setSprite(Sprite *p_new_sprite); // Return pointer to associated Sprite. Sprite *getSprite() const;

227 Animation.h (2 of 2) // Set Sprite name (in ResourceManager).
void setName(std::string new_name); // Get Sprite name (in ResourceManager). std::string getName() const; // Set index of current Sprite frame to be displayed. void setIndex(int new_index); // Get index of current Sprite frame to be displayed. int getIndex() const; // Set animation slowdown count (-1 means stop animation). void setSlowdownCount(int new_slowdown_count); int getSlowdownCount() const; // Draw single frame centered at position (x,y). // Drawing accounts for slowdown, and advances Sprite frame. // Return 0 if ok, else -1. int draw(Vector position); };

228 Animation draw() (1 of 2) // Draw single frame centered at position (x,y). // Drawing accounts for slowdown, and advances Sprite frame. // Return 0 if ok, else -1. int draw(Vector position) // If sprite not defined, don't continue further. if m_p_sprite is NULL then return end if // Ask Sprite to draw current frame. index = getIndex() Sprite draw(index, pos) // If slowdown count is -1, then animation is frozen. if getSlowdownCount() is -1 then

229 Animation draw() (1 of 2) // Increment counter.
count = getSlowdownCount() increment count // Advance sprite index, if appropriate. if count >= getSlowdown() then count = 0 // Reset counter. increment index // Advance frame. // If at last frame, loop to beginning. if index >= p_sprite -> getFrameCount() then index = 0 end if // Set index for next draw(). setIndex(index) // Set counter for next draw(). setSlowdownCount(count)

230 Development Checkpoint #9!
Create Animation class Implement Animation draw() Debug with screen (visual) and logfile messages Extend Object to support Sprites Write revised Object draw() Test with variety of Sprites Saucer Shoot tutorial or new Verify advance speed, looping, stopped

231 Outline – Part IV Resource Management (done) Using Sprites (done)
Boxes (next) Camera Control Audio View Objects

232 Boxes Can use boxes for several features Create 2d box class
Determine bounds of game object for collisions World boundaries Screen boundaries (for camera control) Create 2d box class

233 Mostly a “container” class
#include “Vector.h" class Box { private: Vector corner; // Upper left corner of box. float horizontal; // Horizontal dimension. float vertical; // Vertical dimension. public: // Create box with (0,0) for the corner, and 0 for horiz and vert. Box(); // Create box with an upper-left corner, horiz and vert sizes. Box(Vector init_corner, float init_horizontal, float init_vertical); // Get upper left corner of box. Vector getCorner() const; // Set upper left corner of box. void setCorner(Vector new_corner); // 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); }; Box.h Mostly a “container” class

234 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

235 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

236 Collisions with Boxes In WorldManager, replace positionsIntersect() with boxIntersectsBox() How to determine if boxes intersect? ???

237 Box Intersects Box – Cases

238 Box Intersects Box – Step 1
Ax1 Bx1 Ax2 Bx1 Ax1 Bx2 OR

239 Box Intersects Box – Step 2
Ay1 By1 Ay2 Box Intersects Box – Step 2 OR By1 Ay1 By2

240 Box Intersects Box – Step 1 and Step 2
If left edge of B within A OR if left edge of A within B AND Step 2 If top edge of B within A OR if top edge of B within A THEN  Intersect!

241 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

242 getWorldBox() Object boxes relative to Object position
e.g., 3 character Sprite Object has corner at (-1.5,-1.5) 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 In utility.cpp // Convert relative bounding Box for Object to absolute world Box // at position where. Box getWorldBox(const Object *p_o, Vector where)

243 getWorldBox() Object boxes relative to Object position
e.g., 3 character Sprite Object has corner at (-1.5,-1.5) 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 In utility.cpp 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)

244 WorldManager getCollisions() 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 getCollisions(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()

245 Visualizing Bounding Boxes for Debugging (1 of 2)
// Draw corners of Sprite box, with middle position. float x = getPosition().getX(); float y = getPosition().getY(); float corner_x = x + getBox().getCorner().getX(); float corner_y = y + getBox().getCorner().getY(); float horizontal = getBox().getHorizontal(); float vert = getBox().getVertical(); Vector ul(corner_x, corner_y); Vector ur(corner_x + horizontal, corner_y); Vector ll(corner_x, corner_y + vert); Vector lr(corner_x + horizontal, corner_y + vert); Vector mi(x, y); DM.drawCh(ul, '+', WHITE); DM.drawCh(ur, '+', WHITE); DM.drawCh(ll, '+', WHITE); DM.drawCh(lr, '+', WHITE); DM.drawCh(mi, 'x', RED); Tip! Can debug with log messages, but sometimes seeing is easier Add visual box on top of sprite Corners with “+”, position with “x” (See next slide)

246 Visualizing Bounding Boxes for Debugging (2 of 2)
Saucer Shoot with visual bounding boxes. Note, only solid objects shown with box.

247 Outline – Part IV Resource Management (done) Using Sprites (done)
Boxes (done) Camera Control (next) Audio View Objects

248 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: World boundary Limits of game world View boundary Limits of what player can see (usually, size of window) Translation of world coordinates to view coordinates

249 Extensions to WorldManager to Support Views
private: Box boundary; // World boundary. Box view; // 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);

250 Extensions to WorldManager to Support Views
private: Box boundary; // World boundary. Box view; // 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)

251 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 // Was in bounds? not boxIntersectsBox(new_box, boundary) // Now out of bounds? EventOut ov // Create "out of bounds" event p_o -> eventHandler(&ov) // Send to Object end if

252 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)

253 worldToView() Put in utility.cpp
// 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) ...

254 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

255 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()

256 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) // 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 // 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() if y < 0 then y = 0 // Set view. Vector new_corner(x, y) view.setCorner(new_corner) WorldManager setViewPosition()

257 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) // 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) // If we get here, was not legit. Don't change current view. return -1 WorldManager setViewFollowing()

258 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

259 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 OR // Always keep the Hero centered in screen. void Hero::Hero() // ... df::WorldManager setViewFollowing(this); Implicit control

260 Player Camera Control Tip #14!
Player camera control with invisible avatar Create SPECTRAL game object No sprite Responds to arrow keys (left, right, up, down) Tell Dragonfly to follow via setViewFollowing() See: Camera – Demonstration of a Player-controlled View

261 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

262 Outline – Part IV Resource Management (done) Using Sprites (done)
Boxes (done) Camera Control (done) Audio (next) SFML for audio Sound Music View Objects

263 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>

264 SFML Playing Sound Sound stored in separate buffer (sf::SoundBuffer)
Sound buffer loaded from file (e.g., .wav) Once loaded, played via play() Sounds can be played simultaneously, too sf::SoundBuffer buffer; if (buffer.loadFromFile("sound.wav") == false) // Error! sf::Sound sound; sound.setBuffer(buffer); sound.play();

265 SFML Playing Music (1 of 2)
Sound not pre-loaded, but streamed from file Both sound/music Pause with pause() Stop with stop() Loop with setLoop() as true or false sf::Music music; if (music.openFromFile("music.wav") == false) // Error! music.play();

266 SFML Playing Music (2 of 2)
Note, cannot copy or assign sf::Music Above code will have compile-time errors sf::Music music; sf::Music music_copy = music; // Error! void makeItSo(sf::Music music_parameter) { ... } makeItSo(music); // Error!

267 Dragonfly Audio Sound class Music 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

268 Sound.h // 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. public: Sound(); ~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

269 Sound.h Need to call sound.resetBuffer()
// 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. public: Sound(); ~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; }; Need to call sound.resetBuffer() to avoid Debug Assertion on Windows Sound.h

270 Music.h // System includes. #include <string>
#include <SFML/Audio.hpp> class Music { private: sf::Music music; // The SFML music. std::string label; // Text label to identify music. Music (Music const&); // SFML doesn't allow music copy. void operator=(Music const&); // SFML doesn't allow music assignment. public: Music(); // 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

271 Private since sf::Music attribute doesn’t allow
// System includes. #include <string> #include <SFML/Audio.hpp> class Music { private: sf::Music music; // The SFML music. std::string label; // Text label to identify music. Music (Music const&); // SFML doesn't allow music copy. void operator=(Music const&); // SFML doesn't allow music assignment. public: Music(); // 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(); }; Private since sf::Music attribute doesn’t allow Music.h Must be a pointer here since can’t copy sf::Music

272 Extend ResourceManager
const int MAX_SOUNDS = 128; const int MAX_MUSICS = 128; private: Sound sound[MAX_SOUNDS]; // Array of sound buffers. int sound_count; // Count of number of loaded sounds. Music music[MAX_MUSICS]; // Array of music buffers. int music_count; // 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. 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. int loadMusic(std::string filename, std::string label); // Remove label for Music with indicated label. int unloadMusic(std::string label); // Find Music with indicated label. Music *getMusic(std::string label);

273 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 // All set. sound[sound_count].setLabel(label) increment sound_count return 0 loadMusic() similar

274 Sound/Music Methods Similar to Sprite Methods
Audio Sprites unloadSound() unloadMusic() getSound() getMusic() loadSprite() unloadSprite() getSprite() Except: No resources to “destroy” when remove Music cannot be copied, so when “unload”, just set label to empty (“”)

275 Using Audio – e.g., Saucer Shoot
// Load music. RM.loadMusic( "sounds/start-music.wav" , "start music" ); // Load sounds. RM.loadSound( "sounds/fire.wav" , "fire" ); RM.loadSound( "sounds/explode.wav" , "explode" ); RM.loadSound( "sounds/nuke.wav" , "nuke" ); RM.loadSound( "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();

276 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

277 Outline – Part IV Resource Management (done) Using Sprites (done)
Boxes (done) Camera Control (done) Audio (done) View Objects (next)

278 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

279 ViewObject.h (1 of 2) #include <string> #include "Object.h"
#include "Event.h" // 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; // Label for value (e.g., "Points"). int value; // Value displayed (e.g., points). bool border; // True if border around display. Color color; // Color for text. ViewObjectLocation location; // 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)

280 #include <string>
#include "Object.h" #include "Event.h" // 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; // Label for value (e.g., "Points"). int value; // Value displayed (e.g., points). bool border; // True if border around display. Color color; // Color for text. ViewObjectLocation location; // 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(); Pre-defined positions, relative to screen not world ViewObject.h (1 of 2) virtual for the same reason as for Object

281 // 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); // 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)

282 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)

283 ViewObject setLocation()
// General location of ViewObject on screen. ViewObject::setLocation(ViewObjectLocation new_location) // 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) ... 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()

284 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

285 ViewObject draw() intToString() utility defined next slide
// 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 // Draw centered at position. Vector pos = viewToWorld(getPosition()) DisplayManager drawString(pos, temp_str, CENTER_JUSTIFIED, getColor()) // Draw box around display. ... Remember – view object in view (x,y) Must convert to world (x,y) before drawing intToString() utility defined next slide

286 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

287 Extension to WorldManager draw() to Support ViewObjects
... // Only draw if Object would be visible (intersects view). if boxIntersectsBox(box, view) or // Object in view, dynamic_cast <ViewObject *> (p_temp_o)) // or is ViewObject. p_temp_o -> draw() end if ViewObjects on screen (not world positions) Always draw Dynamic cast indicates type (next slide)

288 #include <iostream>
class Object { virtual int draw() {}; }; class ViewObject : public Object { main() { Object o; Object *p_o; ViewObject vo; // Note: returns false. p_o = &o; if (dynamic_cast <ViewObject *> (p_o)) std::cout << "True" << std::endl; else std::cout << "False" << std::endl; // Note: returns true. p_o = &vo; } Dynamic Cast Tip #15! Dynamic casts can be used to ensure type conversion is valid Polymorphic (derived with virtual function) will return true, else false

289 ViewEvent.h #include "Event.h"
const std::string VIEW_EVENT "df::view"; 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. 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

290 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) // 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()) // Change in value. else setValue(p_ve->getValue()) // New value. end if // Event was handled. return 1 // If here, event was not handled. return 0 ViewObject eventHandler()

291 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);

292 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

293 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

294 Saucer Shoot Dragonfly
Saucer Shoot with no optional elements Transparency, Event Filtering, Inactive Objects Available on Project 2c Web page

295 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 Event Filtering (registerInterest()) Colored Backgrounds View Dynamics (“slack”) Random Seeds SceneGraph Fine-tuning Game Loop Sprite Transparency Utility Functions Handle Ctrl-C Splash Screen Closing Game Window HUD Elements – TextEntry, Buttons See book for details.


Download ppt "Chapter 4 Engine."

Similar presentations


Ads by Google