Difference between revisions of "Intermediate C++ Game Programming Tutorial 18"

From Chilipedia
Jump to: navigation, search
(Video Timestamp Index)
(Video Timestamp Index)
 
(120 intermediate revisions by 2 users not shown)
Line 15: Line 15:
 
== Video Timestamp Index ==
 
== Video Timestamp Index ==
 
[https://www.youtube.com/watch?v=4Vvc1YurUYA Tutorial 18.1]
 
[https://www.youtube.com/watch?v=4Vvc1YurUYA Tutorial 18.1]
* Create a free function that calls <code>SpecialMove()</code> on the instances of derived classes of MemeFighter [https://youtu.be/4Vvc1YurUYA?t=1m17s 0:23]
+
<div class="mw-collapsible mw-collapsed"><br />
 +
* Creating a "Virtual Function" in the <code>MemeFighter</code> class [https://youtu.be/4Vvc1YurUYA?t=0m23s 0:23]
 +
<div class="mw-collapsible-content">
 +
** Create a free function <code>DoSpecials()</code> that calls <code>SpecialMove()</code> on the instances of the derived classes of MemeFighter
 
**  This requires the member function <code>SpecialMove()</code> to be added to the base class...
 
**  This requires the member function <code>SpecialMove()</code> to be added to the base class...
 
** ... and the signatures of these member functions in the derived classes to match up
 
** ... and the signatures of these member functions in the derived classes to match up
* Using <code>virtual</code> on the member function in the base class [https://youtu.be/4Vvc1YurUYA?t=3m17s 3:17]
+
</div>
 +
* Enabling "Dynamic Dispatch" by using <code>virtual</code> on the member function in the base class [https://youtu.be/4Vvc1YurUYA?t=3m17s 3:17]
 +
<div class="mw-collapsible-content">
 
** This enables the overridden functions in the child classes to be called
 
** This enables the overridden functions in the child classes to be called
 
** When you refer to a derived class object using a pointer or a reference to the base class, you can call a virtual function for that object and execute the derived class's version of the function
 
** When you refer to a derived class object using a pointer or a reference to the base class, you can call a virtual function for that object and execute the derived class's version of the function
 +
</div>
 +
* Applying the <code>override</code> keyword to increase code safety [https://youtu.be/4Vvc1YurUYA?t=5m17s 5:17]
 +
<div class="mw-collapsible-content">
 +
** Tells the compiler that you are intending to override something virtual in a base class
 +
</div>
 +
* Making a base class member function "Pure Virtual" using <code>virtual void Func(...) = 0;</code> [https://youtu.be/4Vvc1YurUYA?t=7m08s 7:08]
 +
<div class="mw-collapsible-content">
 +
** This makes the base class an Abstract Class which cannot be instantiated
 +
** It enforces that the virtual function is overridden in all sub-classes
 +
** Whether you make a pure virtual function or whether you supply a default implementation depends on your situation [https://youtu.be/4Vvc1YurUYA?t=8m47s 8:47]
 +
</div>
 +
* We have now applied the concept of "Polymorphism" [https://youtu.be/4Vvc1YurUYA?t=9m16s 9:16]
 +
<div class="mw-collapsible-content">
 +
** C++ polymorphism means that a call to a member function will cause a different function to be executed depending on the underlying type of object that invokes the function
 +
** Polymorphism creates the power to manage different types in a single container
 +
</div>
 +
* Applying Polymorphism to a container of pointers to the shared base type of different derived class objects [https://youtu.be/4Vvc1YurUYA?t=9m46s 9:46]
 +
<div class="mw-collapsible-content">
 +
** In this case, we create two <code>std::vector<MemeFighter*></code>s to enable team fights
 +
** These vectors contain pointers to the base type, this allows us to have a single vector that can manage objects of different types, as long as they all fall within the same hierarchy
 +
** To manage gameplay, we <code>#include <algorithm></code>
 +
** Use <code>std::any_of()</code> with a predicate (Lambda function) that tests if team members are still alive [https://youtu.be/4Vvc1YurUYA?t=11m26s 11:26]
 +
** Use <code>std::random_shuffle()</code> to get different match-ups in every round of gameplay [https://youtu.be/4Vvc1YurUYA?t=11m46s 11:46]
 +
** Use <code>std::partition()</code> to ensure the maximum number of live matchups [https://youtu.be/4Vvc1YurUYA?t=11m57s 11:57]
 +
** Implementing the game battle loops [https://youtu.be/4Vvc1YurUYA?t=12m34s 12:34]
 +
</div>
 +
* Applying Polymorphism to objects created on the heap (dynamic memory management) [https://youtu.be/4Vvc1YurUYA?t=13m46s 13:46]
 +
<div class="mw-collapsible-content">
 +
** Create vectors with pointers to dynamically created objects on the heap
 +
** When done, free the memory by calling <code>delete</code> on the pointers in the containers [https://youtu.be/4Vvc1YurUYA?t=14m18s 14:18]
 +
</div>
 +
* "Virtual Destructors": Managing destructors in base and derived classes [https://youtu.be/4Vvc1YurUYA?t=14m48s 14:48]
 +
<div class="mw-collapsible-content">
 +
** Making the destructor in the parent class <code>virtual</code> ensures that the destructors of the children objects are called (it enables dynamic dispatch so the appropriate child class destructor is called)
 +
** The Construction and Destruction procedure and the order in which ojects are created/destroyed (RAII) [https://youtu.be/4Vvc1YurUYA?t=16m31s 16:31]
 +
</div>
 +
* Review of termonology and concepts learned [https://youtu.be/4Vvc1YurUYA?t=19m49s 19:49]
 +
</div>
  
 
[https://youtu.be/cdOB_gKnJOM Tutorial 18.2]
 
[https://youtu.be/cdOB_gKnJOM Tutorial 18.2]
* WORK-IN-PROGRESS
+
<div class="mw-collapsible mw-collapsed"><br />
 +
* Design choices in class hierarchy: inheritance vs. composition [https://youtu.be/cdOB_gKnJOM?t=0m43s 0:43]
 +
<div class="mw-collapsible-content">
 +
** The MemeFighter game is expanded such that fighters can hold weapons
 +
** Chili argues why this situation best lends itself to a composition relationship: the abstract fighter class can have a pointer to an abstract weapon class (which has derived classes for various types of weapons)
 +
</div>
 +
* Adjusting the MemeFighter code to include the <code>Weapon</code> class [https://youtu.be/cdOB_gKnJOM?t=4m20s 4:20]
 +
<div class="mw-collapsible-content">
 +
** Adding MemeFighter member functions GiveWeapon(), PilferWeapon() [https://youtu.be/cdOB_gKnJOM?t=8m03s 8:03]
 +
** Adding derived classes of the Weapon class to repepresent Fist, Knife, Bat [https://youtu.be/cdOB_gKnJOM?t=9m34s 9:34]
 +
</div>
 +
* Add feature where one fighter can take the other fighter's weapon [https://youtu.be/cdOB_gKnJOM?t=11m36s 11:36]
 +
<div class="mw-collapsible-content">
 +
** We add a free function <code>TakeWeaponIfDead()</code> that handles the weapon exchange
 +
</div>
 +
* Recap of advantage of the inheritance-composition construct [https://youtu.be/cdOB_gKnJOM?t=14m00s 14:00]
 +
<div class="mw-collapsible-content">
 +
** It avoids having to change a fighter into a different type when exchanging weaponry
 +
</div>
 +
* One more real world example used in [[RPG Project Twin]]: a finite "State Machine" for a Boss Battle [https://youtu.be/cdOB_gKnJOM?t=14m28s 14:28]
 +
<div class="mw-collapsible-content">
 +
** A State Machine models patterns of behavior (like attack, evade, wander, find aid, etc.)
 +
** You can use virtual functions that control the boss (depending on type of behavior) and transition from one behavior to another, based on some condition
 +
** Very common pattern in game programming, e.g. using AI to model the behavior of enemies
 +
</div>
 +
* Presenting the template class <code>Behavior</code> within the class <code>Entity</code> [https://youtu.be/cdOB_gKnJOM?t=15m22s 15:22]
 +
* How to transition between behavioral states? [https://youtu.be/cdOB_gKnJOM?t=16m35s 16:35]
 +
<div class="mw-collapsible-content">
 +
** Associate each state with a stack of successor states that are consecutively called
 +
** The last state on this stack can then lead into a new chain (fill up another stack)
 +
** This again represents a combination of inheritance and composition: states are composed of other states as part of their "stack of states"...
 +
** ... avoiding an explosion of (derived) classes that would be needed to define this behavior with just inheritance
 +
</div>
 +
* The performance impact of using Virtual Functions [https://youtu.be/cdOB_gKnJOM?t=19m36s 19:36]
 +
<div class="mw-collapsible-content">
 +
** The direct impact: If you use virtual functions in a class, all objects of that class will have to store a (hidden) pointer to a virtual function table ("vtable")
 +
** The v-table holds pointers to code for each virtual function supported by the object. It is used as a lookup table to determine which functions are being called at runtime
 +
** An indirect impact: because the compiler does not know which function will be called at runtime, it can't inline any virtual functions that are being dynamically bound
 +
** The difference in performance is generally not problematic, except for specific (rare) cases
 +
</div>
 +
* An example of where performance actually matters: The SpriteEffect system [https://youtu.be/cdOB_gKnJOM?t=21m07s 21:07]
 +
<div class="mw-collapsible-content">
 +
** The SpriteEffect system (see [[Intermediate C++ Game Programming Tutorial 14|Intermediate Tutorial 14]]) uses functors to define drawing behavior. These functors are called on heavily as they contain PutPixel calls
 +
** In theory, virtual functions would be nicer, as you could swap out different effects dynamically at runtime (can't do that with a functor).
 +
** However, in that scenario the use of virtual functions would introduce performance issues
 +
** The choice depends on the estimated frequency of function calls (relevant when greater than O(1E6) calls/second)
 +
</div>
 +
</div>
  
 
== Source Code ==
 
== Source Code ==
Line 36: Line 126:
 
* [[Intermediate C++ Game Programming Tutorial 19|Next in series (Tutorial 19)]]
 
* [[Intermediate C++ Game Programming Tutorial 19|Next in series (Tutorial 19)]]
 
* [[Intermediate C++ Game Programming Series]]
 
* [[Intermediate C++ Game Programming Series]]
 +
* [[RPG Project Twin]]

Latest revision as of 03:55, 15 October 2019

Another two-parter here, and we got the real stuff now. Virtual functions allow you to unlock the true potential of inheritance in C++. You need to know this shit.

Topics Covered

Part 1

  • How to create a virtual function
  • Using the override keyword
  • Creating a pure virtual function
  • Using a container of pointers to manage a heterogeneous collection of objects
  • virtual destructors

Part 2

  • Using inheritance and composition together
  • Basic idea of a polymorphic state machine and its application to entity behavior

Video Timestamp Index

Tutorial 18.1


  • Creating a "Virtual Function" in the MemeFighter class 0:23
    • Create a free function DoSpecials() that calls SpecialMove() on the instances of the derived classes of MemeFighter
    • This requires the member function SpecialMove() to be added to the base class...
    • ... and the signatures of these member functions in the derived classes to match up
  • Enabling "Dynamic Dispatch" by using virtual on the member function in the base class 3:17
    • This enables the overridden functions in the child classes to be called
    • When you refer to a derived class object using a pointer or a reference to the base class, you can call a virtual function for that object and execute the derived class's version of the function
  • Applying the override keyword to increase code safety 5:17
    • Tells the compiler that you are intending to override something virtual in a base class
  • Making a base class member function "Pure Virtual" using virtual void Func(...) = 0; 7:08
    • This makes the base class an Abstract Class which cannot be instantiated
    • It enforces that the virtual function is overridden in all sub-classes
    • Whether you make a pure virtual function or whether you supply a default implementation depends on your situation 8:47
  • We have now applied the concept of "Polymorphism" 9:16
    • C++ polymorphism means that a call to a member function will cause a different function to be executed depending on the underlying type of object that invokes the function
    • Polymorphism creates the power to manage different types in a single container
  • Applying Polymorphism to a container of pointers to the shared base type of different derived class objects 9:46
    • In this case, we create two std::vector<MemeFighter*>s to enable team fights
    • These vectors contain pointers to the base type, this allows us to have a single vector that can manage objects of different types, as long as they all fall within the same hierarchy
    • To manage gameplay, we #include <algorithm>
    • Use std::any_of() with a predicate (Lambda function) that tests if team members are still alive 11:26
    • Use std::random_shuffle() to get different match-ups in every round of gameplay 11:46
    • Use std::partition() to ensure the maximum number of live matchups 11:57
    • Implementing the game battle loops 12:34
  • Applying Polymorphism to objects created on the heap (dynamic memory management) 13:46
    • Create vectors with pointers to dynamically created objects on the heap
    • When done, free the memory by calling delete on the pointers in the containers 14:18
  • "Virtual Destructors": Managing destructors in base and derived classes 14:48
    • Making the destructor in the parent class virtual ensures that the destructors of the children objects are called (it enables dynamic dispatch so the appropriate child class destructor is called)
    • The Construction and Destruction procedure and the order in which ojects are created/destroyed (RAII) 16:31
  • Review of termonology and concepts learned 19:49

Tutorial 18.2


  • Design choices in class hierarchy: inheritance vs. composition 0:43
    • The MemeFighter game is expanded such that fighters can hold weapons
    • Chili argues why this situation best lends itself to a composition relationship: the abstract fighter class can have a pointer to an abstract weapon class (which has derived classes for various types of weapons)
  • Adjusting the MemeFighter code to include the Weapon class 4:20
    • Adding MemeFighter member functions GiveWeapon(), PilferWeapon() 8:03
    • Adding derived classes of the Weapon class to repepresent Fist, Knife, Bat 9:34
  • Add feature where one fighter can take the other fighter's weapon 11:36
    • We add a free function TakeWeaponIfDead() that handles the weapon exchange
  • Recap of advantage of the inheritance-composition construct 14:00
    • It avoids having to change a fighter into a different type when exchanging weaponry
    • A State Machine models patterns of behavior (like attack, evade, wander, find aid, etc.)
    • You can use virtual functions that control the boss (depending on type of behavior) and transition from one behavior to another, based on some condition
    • Very common pattern in game programming, e.g. using AI to model the behavior of enemies
  • Presenting the template class Behavior within the class Entity 15:22
  • How to transition between behavioral states? 16:35
    • Associate each state with a stack of successor states that are consecutively called
    • The last state on this stack can then lead into a new chain (fill up another stack)
    • This again represents a combination of inheritance and composition: states are composed of other states as part of their "stack of states"...
    • ... avoiding an explosion of (derived) classes that would be needed to define this behavior with just inheritance
  • The performance impact of using Virtual Functions 19:36
    • The direct impact: If you use virtual functions in a class, all objects of that class will have to store a (hidden) pointer to a virtual function table ("vtable")
    • The v-table holds pointers to code for each virtual function supported by the object. It is used as a lookup table to determine which functions are being called at runtime
    • An indirect impact: because the compiler does not know which function will be called at runtime, it can't inline any virtual functions that are being dynamically bound
    • The difference in performance is generally not problematic, except for specific (rare) cases
  • An example of where performance actually matters: The SpriteEffect system 21:07
    • The SpriteEffect system (see Intermediate Tutorial 14) uses functors to define drawing behavior. These functors are called on heavily as they contain PutPixel calls
    • In theory, virtual functions would be nicer, as you could swap out different effects dynamically at runtime (can't do that with a functor).
    • However, in that scenario the use of virtual functions would introduce performance issues
    • The choice depends on the estimated frequency of function calls (relevant when greater than O(1E6) calls/second)

Source Code

Inheritance Github Repository

Errata

  • Forgot the virtual destructor for class Weapon! (this one hurts)
  • In the children, the function signatures should be: int CalculateDamage( const Attributes& attr,Dice& d ) const override
  • Though not technically an error, it might have been a better decision to make Weapon::GetName() and Weapon::GetRank() (pure) virtual functions (this would reduce the amount of per-instance data to just the vtable ptr)

See also