TIL: Learn to Code in C++ by Developing Your First Game (Section 4, Lectures 120-124)

It’s been a busy week for me! I finished a play-through of Final Fantasy Tactics War of The Lions last week. It’s my first time through that particular version of the game (though I have to admit logging hundreds of hours into the PS1 version back in the 90’s). Play it, if you can. It’s one of the best games of all time IMO. I also have finished up my first ever play-through of Endless Legend. T’was an alright game… but I didn’t enjoy as much as the Civ series.

With the Steam Summer sale in gear, I’ve been using Steam Idle Master to start farming my backlog for Steam trading cards. I figure I may as well build up my Steam Wallet and see if I can’t get a game or two added to my backlog. That sad… sad… ever growing backlog. So far I’ve got $8 into my account and have only managed to buy a single game (Material Girl [Don’t judge me. Don’t you dare judge me.]). I have not gotten around to playing it yet. Not sure what year it will be before I do.

Anyways, I’ve been slowly working through my gaming backlog this year, and I’ve currently got 8 more titles left on my to-do list for the year! I’m not 100% sure what I’ll tackle next… but it might be Middle Earth: Shadow of Mordor. For now though, I’m going to stick with the tutorials this particular blog post is supposed to be about, getting back into a regular bouldering regime, reading through The Ultimate Guide to Video Game Writing and Design, developing an upcoming Houdini curriculum I intend on unleashing to you this year and (of course) practicing both French and Japanese. Busy, busy, busy.

And now for my notes…

Section: 4 – Battle Tank – A Fun Tank Battle with Mortars

Lecture 120: Delegating to Components
Lecture 121: Using Virtual and Override
Lecture 122: Dynamic vs Static Polymorphism
Lecture 123: Dynamic Polymorphism & Vtables
Lecture 124: Creating an AI Controller Class

Lecture 120: Delegating to Components

– tank will be controlled either by AI or us
– ai will try to aim at the player
– tank will otherwise aim where we point it
– tank aiming component will create an event in blueprint
    – it’s easier to use asset references in blueprint so that we can easily refine connections
– presently the Tank Player Controller is based on the Player Controller parent class (we want our own custom class)
    – created in c++ (yyyyyeah, having done nothing in c++ since December… I don’t remember this off the top of my head)
        – done by simply adding a new class (player controller) in the content browser
        – the controller will need to know what kind of tank it is controlling, so we need to create a way of Getting the Pawn “GetPawn()” to return the pawn the character is posessing
        – we work with a TankPlayerController.cpp as well as a Tank.cpp (Tank.h) which we will create.
            – Tank will be a pawn class (we’re going to be using Public classes for this game)
                – if you accidentally create something and need to delete it, you’ll need to Build -> Clean Solution, otherwise the entry won’t be removed from the Content Browser (there is no delete option in UE for cpp files
                – Once they’ve been created, you need to re-parent the tank controller and tank blue prints to the cpp files

Lecture 121: Using Virtual and Override

– The goal here is to understand Inheritance Hierarchy
– we can test to see if things are working by attaching debug info to tick ((print string)) or to any event
– but it’s ideal to to include debug info in our class, not just our blueprints
– Children inherit methods from their parents
– This means, we ideally want to ensure that if we add things like BeginPlay() it overrides child’s methods
– check if you’re overriding using overrid:

void BeginPlay() override;

– if the method was declared virtual, it can be overridden by any ancestor in the future
– if something is already virtual, specifying it a second time has no extra effect

To override a virtual method:
1. Find the signature in the API reference
2. copy the signature, postfix override in the .h file
3. Define it in the .cpp without either virtual or override
4. Usually call “Super::” in the first line of code


void ATankPlayerController::BeginPlay()
{
    Super::BeginPlay();
}

We can then do things like report the name of our tank:


auto ControlledTank = GetControlledTank();
     if (!ControlledTank)
     {
         UE_LOG(LogTemp, Warning, TEXT("PlayerController not possessing a tank"));
     }
     else
     {
         UE_LOG(LogTemp, Warning, TEXT("Player Controller possessing: %s"), *(ControlledTank->GetName()));
     }

Lecture 122: Dynamic vs Static Polymorphism

– you can test c++ online! cpp.sh
– Polymorphism -> calling a different function depending on the type
     – there are times when one needs to return a string if one thing happens, or a float if another thing happens… a function might need to return one or the other.
– We can also create sub classes


class Animal
{
     public:
     void MakeNoise() {
         std::cout << "I'm an animal" << std::endl;
     }
};

class Cat : Animal
{
     public:
     void MakeNoise() {
         std::cout << "Meow" << std::endl;
    }
};

class Dog : Animal
{
     public:
     void MakeNoise() {
         std::cout << "Woof" << std::endl;
     }
};

int main()
{
     Animal animal;
     animal.MakeNoise();
     Cat cat;
     cat.MakeNoise();
     Dog dog;
     dog.MakeNoise();
}

– The above is “static polymorphism”. It’s defined in the code.
– “dynamic polymorphism” is when the type is only known at run-time.


int main()
{
     Cat cat;
     Dog dog;
     Animal animals[] = {cat, dog};
     animals[0].MakeNoise();
     animals[1].MakeNoise();
}

-the above would only return the “I’m an animal” as we have no way of knowing what is in the array. Therefore it can only call the “animal.MakeNoise()” not cat or dog.MakeNoise().
-in order to make it dynamic, we need to make use of “virtual” and “override”. the void MakeNoise() in the class Animal would need a “virtual” while the MakeNoise()es in Cat and Dog would use “overridde”
    -the thing to be aware of here though, is we still aren’t providing enough of a hint to the program about what is in our array. cat and dog, are copies inside the array, and thus are of type Animal.
    -we use pointers with arrays, and so we must make cat and dog pointers inside the array.


int main()
{
     Cat cat;
     Dog dog;
     Animal* animals[] = {&cat, &dog}; //Animal* is a pointer, while &cat is the address of cat
     animals[0]->MakeNoise();
     animals[1]->MakeNoise();
}

Lecture 123: Dynamic Polymorphism & Vtables

godbolt.org – a look at what chunks of c++ are doing in assembly language
-I… I have to admit, I’m not completely understanding what the point of this lesson was other than to illustrate that C++ tries to be efficient under the hood. I guess this is some background info that Sam felt was worth adding in, which Ben didn’t originally feel was important enough to add in? Or didn’t know how to work it in?

Lecture 124: Creating an AI Controller Class

-The PlayerController possesses one tank when we start, but at the moment, there is no AI. We need to create an AIController that will possess its own tank.
-The AI c++ class won’t appear by default when creating a new class. You need to toggle “Show All Classes”
-We only have one tank, so it’s on the tank where we will set it’s AI controller
    -under the details view for the Tank BP, we leave the “Auto Possess Player” as disabled. This is because we don’t want to 100% of the time possess any tank in the level by the player.
    -if a tank is in the level, and not possessed by the player, it should be possessed by AI. We therefore leave it so that it will be Auto Possessed by AI when Placed in World.
    -set the AI Controller Class to the one we’ve created
-Since both the earlier created PlayerController, and the newly created AIController are children of the Controller class, it means that we can use similar code (IE: reference BeginPlay().)