Difference between revisions of "Intermediate C++ Game Programming Tutorial 18"
From Chilipedia
(→Video Timestamp Index) |
(→Video Timestamp Index) |
||
(88 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] | ||
+ | <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] | * 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 | ** 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 | ||
+ | </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] | * 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] | * 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 | ** 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] | * 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 | ** This makes the base class an Abstract Class which cannot be instantiated | ||
** It enforces that the virtual function is overridden in all sub-classes | ** 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] | ** 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] | ||
− | * We have now | + | </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 | ** 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 | ||
− | * Applying Polymorphism to ... [https://youtu.be/4Vvc1YurUYA?t= | + | ** 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] | ||
− | * | + | <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 47: | 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.
Contents
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
- Creating a "Virtual Function" in the
MemeFighter
class 0:23
- Create a free function
DoSpecials()
that callsSpecialMove()
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
- Create a free function
- 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
- In this case, we create two
- 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
- Making the destructor in the parent class
- Review of termonology and concepts learned 19:49
- 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
- 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
- We add a free function
- Recap of advantage of the inheritance-composition construct 14:00
- It avoids having to change a fighter into a different type when exchanging weaponry
- One more real world example used in RPG Project Twin: a finite "State Machine" for a Boss Battle 14:28
- 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 classEntity
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
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()
andWeapon::GetRank()
(pure) virtual functions (this would reduce the amount of per-instance data to just the vtable ptr)