Game Engine Architecture Spring 2017 03. Event Systems for Game Engines
Total Page:16
File Type:pdf, Size:1020Kb
Game Engine Architecture Spring 2017 03. Event systems for game engines Juha Vihavainen University of Helsinki [ McShaffryMcShaffry,, Ch. 6: Game actors and component architecture ,, Ch. 11: Game event management ]] [ Gregory, Ch. 5.4: Strings, unique ids ,, localization , etc., 274, Ch.15.2: Runtime object model architectures , 873 Ch. 15.7: Events and message passing, 933 ]] [Meyers (2015): Effective Modern C ++++]] Lecture outline On events and event handling using Observer (MVC) and Command design patterns Updates are not enough for game software Typed Message design pattern (Vlissides, 1997) breaking dependencies between senders & receivers Queueing of messages for later delivery Using unique object ids for game entities and types 24.2.2017 Juha Vihavainen / University of Helsinki 22 The Observer design pattern Problem certain objects need to be informed about the changes occurring in other objects a subject has to be observed by one or more observers decouple as much as possible and reduce the dependencies Solution define a oneone--toto--manymany dependency between objects so that when one object changes its statestate,, all its dependents are automatically notified and updated A cornerstone of the ModelModel--ViewView--ControllerController architectural design, where the Model implements the mechanics of the program, and the Views are implemented as Observers that are kept uncoupled from the Model components Modified from Joey Paquet, 20072007--20142014 24.2.2017 University of Helsinki 33 Erich Gamma et al., Design Patterns ( 1994) Observer pattern: class design ""View "" classes ""ModelModel""classesclasses Updates multiple ( seems Notifies all its observers complicated..) observers on changes 24.2.2017 University of Helsinki 44 The participants Subject --abstractabstract class defining the operations for attaching and dede-- attaching observers to the clientclient;; often referred to as " Observable "" ConcreteSubject -- concrete Subject class. It maintains the state of the observed object and when a change in its state occurs it notifies the attached Observers. If used as part of MVC, the ConcreteSubjeConcreteSubjectct classes are the Model classes that have Views attached to thethemm Observer --interfaceinterface or abstract class defining the operations to be used to notify the registered Observer objects ConcreteObserver -- concrete observer subclasses that are attached to a particular subject class There may be multiple different concrete observers attached to a single subject that will provide a different view of that subject 24.2.2017 University of Helsinki 55 On behaviour of Observer pattern The client class instantiates the ConcreteObservable object Then it instantiates and attaches the concrete observers to it usiusingng the methods defined in the Observable interface Each time the (observable) state of the subject is changing, it notifies all the attached observers using the methods defined in the Observer interface When a new Observer is added to the application, all we need to do is to instantiate it in the client class and to attach it to the Observable object The classes already created (framework) will remain mostly unchanged 24.2.2017 University of Helsinki 66 77 Observer pattern: interface classes // Observable.h #pragma once // Visual Studio include guard class Observer ; // name stub needed only // Observer.h class Observable { // an abstract class #pragma once public : virtual void attach ( Observer *); class Observer { // an abstract class virtual void detach ( Observe r *); public : virtual void notify (); virtual void update () = 0; virtual ~Observable (); virtual void detach () = 0; // added Observable (Observable const&) = delete; virtual ~Observer (); ... Observer (Observer const&) = delete; protected : ... Observable (); // called by subclass protected : private : Observer (); // called by subclass struct Observers ; // name stub only }; Observers * observers_; }; Note . If a destructor is declared, the generation of default copy operations is deprecated (to be removed from standard). Pimpl idiom hides std::list <Observer **> The compiler may give a warning. For now we need delete .. (C++11) Meyers S. (2015): On C++ technicalities Effective Modern C++, p. 115 The special member functions are those compilers may generate on their own (if needed, i.e., called): default constructor , destructor , copy operations , and move operations Move operations (T &&) are generated only for classes lacking explicitly declared move operations, copy operations, and a destructor The copy constructor (T const &) is generated only for classes lacking an explicitly declared copy constructor, and it’s deleted if a move operation is declared The copy assignment operator == (T const &) ) is generated only for classes lacking an explicitly declared copy assignment operator, and it’s deleted if a move operation is declared Generation of the copy operations in classes with an explicitly declared destructor is deprecated (i.e., to be removed from C++ standard) Member function templates never suppress generation of special member functions (even if templates could produce these copy operations) 24.2.2017 University of Helsinki 88 99 Observer PatternPattern:: Concrete Subject // ClockTimer.cpp #include "ClockTimer.h " // ClockTimer.h ClockTimer :: ClockTimer () #pragma once : hour_( 0), minute_(0 ), second_(0 ) {} #include "Observable.h " void ClockTimer :: start ( int time ) { class ClockTimer : public Observable for (int i = 1; i <= time ; ++i) { tick (); public : } ClockTimer (); void ClockTimer :: tick () { // by one sec int hour () const { return hour_; } ++second_; int minute () const { return minute_; } if (second_>= 60 ) { int second () const { return second_; } ++minute_; second_= 0; void start ( int time); if (minute_>= 60 ) { void tick (); ++hour_; minute_= 0; private : if (hour_>= 24 ) int hour_; hour_= 0; int minute_; } int second_; } }; // the Observable object notifies // all its registered observers notify (); } 1010 Observer PatternPattern:: // DigitalClock.cpp Concrete Observer #include "DigitalClock.h " #include "ClockTimer.h " // DigitalClock.h #include <iostream > #pragma once #include "Observer.h " DigitalClock :: DigitalClock ( ClockTimer * s) { class ClockTimer; // name stub if (s != nullptr ) s ->attach ( this ); subject _ = s; class DigitalClock : public Observer } { DigitalClock ::~ DigitalClock () { public : if (subject_!= nullptr) void update () override; subject_ ->detach (this); void detach () override; } void display () const; void DigitalClock ::update () { DigitalClock ( ClockTimer * s display (); = nullptr); } ~DigitalClock () override; void DigitalClock ::detach () { private : subject_= nullptr; ClockTimer * subject_; } }; void DigitalClock :: display () const { int hour = subject_ ->hour (); int minute = subject_ ->minute (); int second = subject_ ->second (); Note . Strong dependencies: if one is std::cout << hour << ":" << minute << ":" destroyed, must update other . << second << std::endl ; } Observer patternpattern:: driver program // ClockDriver.cpp #include "ClockTimer.h " #include "DigitalClock.h " #include <iostream > // standard IO streams int main () Objects can be local { // create a ClockTimer to be observed ClockTimer timer; // create a DigitalClock that is connected to the ClockTimer DigitalClock digitalClock (&timer ); // advancing the ClockTimer updates the DigitalClock // as tick() calls Update() after it changed its state int secs ; std::cout << "Enter number of seconds to count: " ; std::cin >> secs ; timer.start ( secs ); int j; std::cin >> j; // pause at end of program } 24.2.2017 University of Helsinki 1111 Implementation of the Observer pattern ( Solution )) // Observable.cpp #include "Observable.h " Note . Should #include "Observer.h " additionally enclose #include <list> // only here, not in Observable .h file everything in our custom namespace.. struct Observable::Observers : std::list < Observer *> {}; // define implementation class Observable :: Observable () : observers_(new Observers) { } Observable ::~ Observable () { for (auto& x : *observers_) detach (x); delete observers_; } // Observer.cpp void Observable ::attach ( Observer * o) { #include "Observer.h " observers_ ->push_back (o); Observer :: Observer () {} } Observer ::~ Observer () {} void Observable ::detach ( Observer * o) { observers_ ->remove (o); o->detach (); // will break link to subject } void Observable ::notify () { for (auto& x : *observers_) x ->update (); } Background: updates are not enough Games are inherently very much eventevent--drivendriven an explosion goes off the player is sighted by an enemy, or enters a trigger area a health pack is getting picked up Games need to notify interested game objects when an event occurs arrange those objects to respond to significant events Different kinds of game objects will respond in different ways to an event; for example, a Pong ball is governed/affected both by updates in its physical "state", determined by its velocity hitting another object (paddle/wall) and changing its velocity ((raterate and direction of changechange)) being missed by one of the players --andand hitting the back wall 24.2.2017 Juha Vihavainen / University of Helsinki 1313 Common game events [McShaffry, p. 325325--326]326] 1 General game events a game object has moved the event system is ready a collision has occurred the sound system is ready a character has changed states the network system is ready player character