Object oriented Game Development -P5 doc

30 295 0
Object oriented Game Development -P5 doc

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

// MyClass.hpp #include "package\package_Type.hpp" class MyClass : public package_Type { public: // Your operations here. }; If this is a bit too abstract, let’s look at a real-life example. One project had a package to handle game controllers. These game controllers could come in vari- ous flavours: joypad, keyboard, mouse, etc. The package defined a polymorphic handler class that accepted different types of input data: class ctrl_InputHandler { public: virtual void HandleJoy( ctrl_Joypad & aJoy, ??? ); // Other controller types handled similarly. }; The idea is that the client writes their own input handler to do whatever they require it to do. However, there are a couple of things we needed to specify: something about who was using the controller, and something about what was being controlled. This was done by using two Strawman classes: class ctrl_User { public: ctrl_User() {} virtual ~ctrl_User() {} }; class ctrl_Target { public: ctrl_Target() {} virtual ~ctrl_Target() {} }; The handler class now has member functions of the form: void HandleJoy( ctrl_Joy & aData, ctrl_User * pUser, ctrl_Target * pTarget ); Object-oriented game development106 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 106 At the game level, we had a player owning a controller and using it to move a cursor to drive it around the screen (Figure 4.5). The game’s input handler code looked something like this: // GameInputHandler.hpp #include "controller\ctrl_InputHandler.hpp" class GameInputHandler : public ctrl_InputHandler { public: void HandleJoy( ctrl_Joy& aData, ctrl_User * pUser, ctrl_Target * pTarget ); }; // GameInputHandler.cpp #include "GameInputHandler.hpp" #include "Player.hpp" #include "Cursor.hpp" void GameInputHandler::HandleJoy( ctrl_Joy& aData, ctrl_User * pUser, ctrl_Target * pTarget ) { // This upcast is fine because we know what // the final user type is at the game level. Player * pPlayer = static_cast<Player *>(pUser); // Similarly, at this level that target is always // the cursor. Cursor * pCursor = static_cast<Cursor *>(pTarget); // Code to do things with players and cursors… } Object-oriented design for games 107 ctrl_InputHandler Controller GameInputHandler Game ctrl_User Player ctrl_Target Cursor Figure 4.5 The game subclasses of the abstract component classes. 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 107 4.3.7 Prototype A prototype is similar to a factory, in as much as it creates new object instances. However, it does this not by taking some type identifier and returning an associ- ated class, but by cloning itself: class PrototypeBase { public: virtual PrototypeBase * Clone() = 0; }; class Prototype1 : public PrototypeBase { public: // Cloning without copying makes no sense! Prototype1( const Prototype1 & that ) { // … } // Note the use of the ‘covariant return type’ // rule now supported by most C++ compilers: a // virtual function that returns a base class // pointer or reference // with an override in a subclass can declare its // return type to be the subclass type. Prototype1 * Clone() { return new Prototype1( *this ); } }; Another factory-like class then uses the prototype thus: class PrototypeFactory { public: void SetPrototype( PrototypeBase * pBase ) { delete m_pPrototype; m_pPrototype = pBase; } PrototypeBase * Create() { return( m_pPrototype->Clone() ); } Object-oriented game development108 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 108 private: PrototypeBase * m_pPrototype; }; The prototype is useful because it can be used to set default values in a dynamic way: PrototypeFactory MyFactory; Prototype1 * pProto1 = new Prototype1( 10 ); MyFactory.SetPrototype( pProto1 ); PrototypeBase * pInstance1 = MyFactory.Create(); Prototype1 * pProto2 = new Prototype1( 20 ); MyFactory.SetPrototype( pProto2 ); PrototypeBase * pInstance2 = MyFactory.Create(); Here’s an example of a prototype system in action. We have a game in which a player has a weapon that can shoot various types of bullets (see Figure 4.6 for some lines and bubbles). This translates into the following code skeleton: // Weapon.hpp class Bullet; class Weapon { public: Bullet * CreateBullet(); void SetBulletPrototype( Bullet * pBullet ); private: Bullet * m_pBulletPrototype; }; Object-oriented design for games 109 BulletStandard Weapon Bullet BulletArmourPiercing BulletPrototype Figure 4.6 Class diagram for weapon and bullet management system. 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 109 // Weapon.cpp #include "Weapon.hpp" #include "Bullet.hpp" void Weapon::SetBulletPrototype( Bullet * pProto ) { delete m_pBulletPrototype; m_pBulletPrototype = pProto; } /*virtual*/ Bullet * Weapon::CreateBullet() { return( m_pBulletPrototype->Clone() ); } // // Bullet.hpp class Bullet { public: Bullet(); Bullet( const Bullet & that ); virtual Bullet * Clone() = 0; // and other methods. private: // some data fields. }; // // BulletStandard.hpp (BulletArmourPiercing is similar) #include "Bullet.hpp" #include "maths\maths_Vector3.hpp" class BulletStandard : public Bullet { public: BulletStandard(); BulletStandard( const BulletStandard & that ); BulletStandard * Clone(); private: maths_Vector3 m_vPosition; // and other fields }; // Object-oriented game development110 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 110 // BulletStandard.cpp #include "BulletStandard.hpp" BulletStandard:: BulletStandard( const BulletStandard & that ) : Bullet( that ) , m_vPosition( that.m_vPosition ) { } /*virtual*/ BulletStandard * BulletStandard::Clone() { return( new BulletStandard( *this ) ); } Now the player (say) can change the type of ammo their weapon shoots by writing: Weapon * pWeapon = GetWeapon(); pWeapon->SetBulletPrototype( new BulletStandard ); 4.3.8 Russian doll The aim of this mini-pattern is to eliminate global-like objects, by which we mean real globals (which are anathema) or singletons (which are tolerable but not aesthetically satisfying). I was tempted to call this pattern a Trojan horse, but that had certain nega- tive connotations. However, the idea is much the same – allow an object type to hide encapsulated details of other objects from a system that it passes through on its way into another system further down the hierarchy. The bypassed system therefore remains oblivious – and therefore independent – of anything contained in the object itself (the ‘Trojans’, if you will). We will call the global-like objects ‘services’ and consider a non-horizontal object hierarchy where the objects all depend on a selection of those services, a bit like that shown in Figure 4.7. Object-oriented design for games 111 Object Subclass 1 Subclass 2 Service 1 Service 2s2 s1 Figure 4.7 The ‘Russian doll’ pattern works well for object hierarchies that look like this. 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 111 Let’s look at how the constructors of the subclasses might appear: // Subclass1.cpp Subclass1::Subclass1( Service1 * pService1 ) : m_pService1( pService1 ) { /* Service1 assumed to be private */ } // Subclass2.cpp Subclass2:: Subclass2( Service2 *pService2, Service1 *pService1 ) : Subclass1( pService1 ) , m_pService2( pService2 ) { } Notice that the deeper the hierarchy gets, the more arguments the constructors need. Long argument lists aren’t just ugly; they are also a fertile source of mis- takes because of things like automatic type conversion. Furthermore, notice that Subclass2 gets to see a Service1 pointer even though it never uses or cares about it. To create a Subclass2, we’d write something like: // Things.cpp #include "Service1.hpp" #include "Service2.hpp" //… Service1 * pService1 = Service1::Instance(); Service2 * pService2 = Service2::Instance(); Subclass2 * pThing = new Subclass2(pService1, pService2); Those global-like objects have a habit of popping up everywhere and getting passed around everywhere. We can tame this sort of creeping disease quite simply by using a container class – a Russian doll: // ServiceProvider.hpp class ServiceProvider { public: Service1 * GetService1(); Service2 * GetService2(); Object-oriented game development112 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 112 private: Service1 * m_pService1; Service2 * m_pService2; }; Now we can write our constructors like this: // Subclass1.cpp #include "ServiceProvider.hpp" Subclass1::Subclass1( ServiceProvider & rServices ) : m_pService1( rServices.GetService1() ) { } // Subclass2.cpp #include "ServiceProvider.hpp" Subclass2::Subclass2( ServiceProvider & rServices ) : Subclass1( rServices ) , m_pService2( rServices.GetService2() ) { } No more growing argument lists, and only the classes that actually require par- ticular services need request and use them. Case study 4.3: patterns in practice – the ‘state manager’ Having talked quite abstractly about patterns, we’ll now demonstrate their use in a case study. We’ll discuss in detail the development of a state manager for a game (or indeed any other sort of application). First, what (in this context) is a state? It is a single mode of behaviour that deter- mines what the game is doing at any one time. For example, the game may be playing a cut scene, in a menu hierarchy, running the main game or paused. Each of these behaviours we call a state; Figure 4.8 shows some state classes. As always, we start by factoring out common behaviours: what is there that is similar to all states? Looking at the examples above, we can infer that there is a concept of time passing (a cut scene plays or the game evolves): the state can be updated with respect to time. How do we measure time? In a game, we can either count elapsed frames or measure actual elapsed time. Which is better? That’s another argument, which we’ll deal with in a later chapter. For now, let’s just assume we have a type Time that rep- resents the elapsed interval since the last update. Object-oriented design for games 113 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 113 What else is common to state? We’d certainly like to be able to see some visual dif- ference between states, so we’d like to Draw what’s going on. Again, for argument’s sake, we’ll assume we have a Renderer class that can draw stuff: class state_State { public: state_State(); virtual ~state_State(); virtual void Update( Time dt ) = 0; virtual void Draw( Renderer * pRenderer ) const = 0; }; Note that the Draw member is declared const: it shouldn’t modify the state. What else can we say about states? Well, our application will, at some point, have to change state. Zero or one state will be outgoing and another one other will be incom- ing. These states may require initialisation and clean-up operations, so to this end we extend the interface by adding two virtual methods, OnEnter() and OnLeave(): class state_State { public: state_State(); virtual ~state_State(); virtual void Update( Time dt ) = 0; virtual void Draw( Renderer * pRenderer ) const = 0; virtual void OnEnter() { ; } virtual void OnLeave() { ; } }; Object-oriented game development114 CutSceneState MenuState OptionsMenuStateMainMenuState State PauseStateGameState Figure 4.8 Examples of state subclasses. 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 114 We have avoided passing the outgoing and incoming states as parameters into these member functions, even though that might give more power and flexibility. Why? Because at this relatively low level, binding states to each other makes it harder to write a higher-level state that is as autonomous as it needs to be. If we really need to imple- ment this sort of behaviour, then we can still do so further down the class hierarchy. The sequence of operations involved in a state change is detailed in this code fragment: void ChangeState( state_State *pFrom, state_State *pTo ) { if ( pFrom != 0 ) { pFrom->OnLeave(); } pTo->OnEnter(); } Simple enough. (Of course, production code would be more robust – what happens if pTo is NULL? Nothing good, that’s for sure, so the validity of the parameters needs to be asserted on entry.) Now, a more awkward question: who owns the states? Who is responsible for creat- ing, deleting and using states? The most obvious candidate is the application itself, as shown in Figure 4.9. But – as ever – ‘obvious’ is not necessarily best. Anyone can create and destroy states arbitrarily, and that can lead anywhere from crashes – immediate or latent – to memory leaks, and we would like to avoid these if possible. This is just the sort of situation where a manager can help, centralising the point of creation and helping us to enforce the correct transitions from state to state. Object-oriented design for games 115 Application Application State ConcreteState *StatesCurrent state_State Figure 4.9 The application subclasses of the abstract state class. 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 115 [...]... fmwk_View gui_InputReceiver GameState GameView GameInputRecvr Game Input receiver Figure 4.21 The intermediate Framework component avoids logically or physically binding the View and State components 8985 OOGD_C04.QXD 134 1/12/03 2:32 pm Page 134 Object- oriented game development 4.4 Summary ● Object orientation fits well with game development ● C++ may not be the most object- oriented of languages, but... care 8985 OOGD_C05.QXD 1/12/03 2:38 pm Page 135 The component model for game development 5 5.1 Definition: game engine A series of modules and interfaces that allows a development team to focus on product game- play content rather than technical content This chapter is about game engines Or rather, it is about avoiding game engines Game engines, as we may infer from the above definition, tend to be monolithic... Input map gui_Menu *Gadgets input_InputMap Gadget View gui_Gadget View view_View Focus View 8985 OOGD_C04.QXD 1/12/03 132 2:32 pm Page 132 Object- oriented game development Strictly speaking, this isn’t ideal, allowing lower-level objects (gadgets) to talk to higher-level objects (menus) Also note an implicit circularity (menu has a gadget, gadget has a menu via the input map) and an explicit circularity... State View state_State *Views view_View 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 133 Object- oriented design for games 133 Figure 4.20 A better way to associate states and views Framework fmwk_State State *Views fmwk_View View state_State view_View So let’s assume that there is a class called GameState that updates and draws the game Figure 4.21 shows how the classes might relate This works exactly as required... abstract object (Figure 4.16) However, there is one item of data that we associate with all flavours of input data, and that is an owner tag This allows us to distinguish where the data came from, and it should be unique for every potential source of input data We’ll use an integer tag field here, though others are feasible 127 8985 OOGD_C04.QXD 1/12/03 128 2:32 pm Page 128 Object- oriented game development. .. RemoveAllChildren(); Figure 4.13 Basic view classes View view_Rectangle view_View *Children 8985 OOGD_C04.QXD 122 1/12/03 2:32 pm Page 122 Object- oriented game development private: std::list m_Children; }; // File: view_View.cpp #include "view_View.hpp" #include // Function objects that perform per-view tasks struct ViewDeleter { inline void operator()( view_View * pView ) { delete pView; } };... class RenderContext : public view_Context { public: rndr_Renderer & GetRenderer() { return m_Renderer; } 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 127 Object- oriented design for games private: rndr_Renderer m_Renderer; }; Second, we need to rewrite the function object that does the rendering in view_View.cpp, given that we have added a parameter: class ViewRenderer { public: // ‘explicit’ stops the compiler... ConcreteStateFactory state_Factory ConcreteState 5 For the sake of sanity, ensure case-independent comparison of names *States state_State Current 8985 OOGD_C04.QXD 1/12/03 118 2:32 pm Page 118 Object- oriented game development Figure 4.12 Custom state management Application State Application State manager state_StateManager AppStateManager Factory ConcreteStateFactory state_Factory ConcreteState *States... /* = 2 */ 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 129 Object- oriented design for games enum ExtendedThing : public Thing { THREE, /* = 3 */ FOUR, /* = 4 */ }; But since this isn’t allowed, and it could have horrendous side effects if it were, then it’s better to abandon enums in favour of our flexible friend polymorphism The idea is to have a separate object that the application defines and that handles... thing – you want it to call the right function depending not only on the type of input map but also on the type of input In the lingo, only the 129 8985 OOGD_C04.QXD 130 1/12/03 2:32 pm Page 130 Object- oriented game development static type of arguments can be used to select the function to call (at compile time), not the dynamic type (at run time like you need) Thankfully, there are work-arounds One possibility . with players and cursors… } Object- oriented design for games 107 ctrl_InputHandler Controller GameInputHandler Game ctrl_User Player ctrl_Target Cursor Figure 4.5 The game subclasses of the abstract. aData, ctrl_User * pUser, ctrl_Target * pTarget ); Object- oriented game development1 06 8985 OOGD_C04.QXD 1/12/03 2:32 pm Page 106 At the game level, we had a player owning a controller and using. that shown in Figure 4.7. Object- oriented design for games 111 Object Subclass 1 Subclass 2 Service 1 Service 2s2 s1 Figure 4.7 The ‘Russian doll’ pattern works well for object hierarchies that look

Ngày đăng: 01/07/2014, 15:20

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan