Learned This Week
Overview
It's week 2, and I've focused this week on Object-Oriented Programming and how it applies to C++ (classes, objects, inheritance, etc). Finishing up the C++ Essential Training LinkedIn course, I learned helpful information about classes and templates in C++, as well as how to use constructors, destructors, and overloading to achieve a lot of usability and flexibility for classes.
I've begun a course called Programming Foundations: Object-Oriented Design, which centers around the design principles of OOP and not necessarily their implementation in a specific language. This course will help me to apply my learning from previous courses and languages to C++ while still keeping an objective eye on the fundamentals being learned. This week, I went over the basics of OOP and the APIE acronym (see Key Points for more info).
In addition, I began poking around a little bit in Unreal this week, woohoo! I have a passion for tinkering and learning new ways to do so and then putting those into action resonates with me. I expect it'll get a lot more complex as I start delving into actually implementing code, but I'm ready and excited for the challenge!
Key Points:
- C++ Classes
- Constructors and destructors
- Called when an object is created and destroyed, respectively
- Can be overloaded for specific situations
- Overloading
- The implementation of Polymorphism in C++, overloading allows functions to be called with the same name but different signatures in order to activate different code for different circumstances
- Object-Oriented Programming
- There are four pillars of OOP: Abstraction, Polymorphism, Inheritance, and Encapsulation (A PIE! 🥧)
- Abstraction: Classes focus on the definition of a thing, but not on its specific characteristics - that's up to the objects of the class (i.e. a class may have a parameter for defining what color it is, but only an object will say what that color is - although default values are acceptable)
- Polymorphism: Classes are able to implement their methods in different forms - in C++ this is a static/compile-time polymorphism, meaning functions can be overloaded to be implemented in different ways based on their function signatures
- Inheritance: Classes should inherit common functions and data types from each other to allow for more flexibility should changes need to be made later
- Encapsulation: Each object should operate on itself, and interactions between objects should use interfaces/public functions in order to avoid directly manipulating internal workings - this also permits internal changes to a class' code without needing large-scale refactoring as long as the implementation in other parts of the program stays the same
- OOP is a way of approaching code that is different to typical static code, and allows for easy extension and edits to code created with it
- When using OOP to approach an application, the last thing you should do is code - first, you need to analyze what the application needs to do and design a way to do it
- Blueprint Inheritance
- Blueprints and C++ classes operate on separate "layers", meaning C++ code can directly interact with other C++ code and the same with blueprints, but they cannot interact with the other layer
- An exception to this: during runtime they can interact with each other (through interfaces)
- Blueprints can be reparented to a different class after creation (phew!)
Nitty-Gritty
- C++ Essential Training LinkedIn Course - Jump to Top
- Classes and Objects
- Classes in C++ as with most object-oriented setups are made up of data members and function members, also called variables and methods
- C++ classes have constructors and destructors, called when an object is created or destroyed
- Constructors are named with the name of the class, and destructors are named with a ~ and the name of the class (i.e. Class1 would have a constructor Class1() and a destructor ~Class1())
- Class members default to private
- Class definition example:
- Members are accessed on an object with dot notation
- Class functions can be defined outside of the definition of the class, which is the way that Unreal C++ objects are designed (function declarations in the header file, definitions in the .cpp file)
- An example of the above class functions defined outside the class would be:
- Classes typically keep their data members private and use getters and setters (accessors) - public functions - to access and manipulate their data (for encapsulation purposes)
- const-qualified objects must have member functions which are const-qualified as well in order to call those functions. Functions with the same name can be overloaded to include a const version of the function, and const-qualified objects will only be able to (and only will) call the const version of that function
- Constructors can be overloaded for different use cases (i.e. base initialization, initializing without any specified data (defaults), copying data from an existing object of the same type, etc.
- Overloading can apply to operators within classes as well (i.e. class object + class object can be overloaded to redirect to data handling versions of the operators)
- This can also be used to "disallow" certain operators for a class such as disallowing the assignment operator to protect a created object of the class
- Chapter Quiz
- Templates
- Templates are a way of defining type-agnostic code
- Once a template is defined, the compiler defines a version/specialization of each function for each data type in order to keep the language and compiled code strongly typed
- The downsides to using templates are:
- they take up a lot more executable/file size for each definition, as there needs to be a version for each data type
- debugging can be challenging due to nebulous error messages (type-agnostic)
- they can lead to longer compile times
- Templates are widely used to implement containers and/or generic programming functions
- Template argument deduction allows the compiler to implicitly know what type the arguments of a template function are when called - if the compiler can't figure it out or you get runtime errors, you need to specify directly by putting the type in angle brackets after the function call (i.e. maxof<int>(7, 9);)
- Chapter Quiz
- Standard Library
- Common functions for system tasks (file i/o, character formatting, error handling, etc)
- Most work similarly to Python, but with specific headers
- Chapter Quiz
- Standard Template Library
- Provides basic containers and operators such as vectors, lists, sets, maps, stacks, queues, & decks, strings, i/o streams, etc.
- Vectors
- template sequence container that supports random access of its elements
- declared using template syntax with a type identifier
- has many methods to operate on its data
- can be used in range-based for loops
- can be initialized from an existing c-array
- Handling exceptions
- exceptions can be caught by using a try-catch block, putting whatever is being tested into the try{} section and the code that will run if an exception is caught after the catch{} section
- exception classes can be defined and used in your own code
- Chapter Quiz
- Course complete!
- Object-Oriented Programming LinkedIn Course - Jump to Top
- Object-Oriented Fundamentals
- O-O design isn't just a specific type of code, it's a design process/programming paradigm which you can go through to gear your code for its intended audience and purpose
- OOP defines objects that can be used and re-used for its intended purpose - applying this to Unreal and other game engines, what this boils down to is that each object should be doing its own work
- Objects are things that can be described with a "the", for example: the bank account, the mug, the ceiling, but not the running - that's not an object
- Objects have a name (identifier), attributes (variables), and behaviors (methods).
- The fundamental concepts of OOP are Abstraction, Polymorphism, Inheritance, and Encapsulation (APIE) 🥧
- Abstraction
- We focus on the essential qualities of a thing, but not on the specifics - abstraction is what you're doing when you make a class of something
- Classes have data and methods inside them, i.e. what they are and what they can do, but they don't have specifics for what those are - that would be a specific object of that class
- Polymorphism
- Means having different forms - ironically, polymorphism also has different forms:
- Dynamic polymorphism
- Different types of objects use the same interface for methods even though their data may be of different types - but they all implement the same method in different ways
- Overriding, not overloading
- Static or compile-time polymorphism
- Different versions of the same method exist on a class, allowing the compiler to select the correct method based on what the method needs to do (refer back to the section above here about function overloading, this is that concept)
- Overloading, not overriding
- Inheritance
- Enables the use of existing classes to create new classes
- For example, if you have an employee and a customer that have class definitions like this, a lot of their variables and some of their methods are the same:
- So maybe we should be inheriting each class from a different more ambiguous class that has all of those on it, then implement unique variables and methods on the inherited classes, like so:
- Works on an "is-a" relationship, meaning you can put "is a" between the inherited class and the class it inherits from, i.e. "A Customer is a Person"
- Inheritance is denoted between "superclasses" and "subclasses" or the "base class" and "derived class"
- Updates that should be propagated across all derived classes only need to be added to the base class, and new derived classes can easily be created
- Encapsulation
- Each part of the whole works on itself
- Methods and data stay inside and native to their own objects/classes, meaning data stays private and can be accessed and changed using the methods of an object, but not directly manipulated by the program at large
- For example, a cookie jar has a number of cookies and a method to request a cookie, but the application at large doesn't modify the number of cookies directly, the method does
- Black boxing can also be applied to encapsulation, meaning the inner workings of an object are known only to that object - in the cookie jar example, the application at large never knows what the method to request a cookie actually does under the hood, just that it is called and returns either a cookie or an error if there are no cookies left to request
- Encapsulation is about reducing dependencies from one part of the application to another - meaning if the class needs to change, the application doesn't need to change unless the methods it needs to call are different
- OOP is usually broken down into three (or two) steps: analysis, design, and programming (analysis and design are often spoken of together)
- Analysis
- What is the problem you need to solve?
- Gather the requirements of your program
- Describe the application
- Design
- How are you going to do it? (No coding yet!)
- Identify the main objects
- Describe the interactions between objects
- Create a class diagram
- Programming
- Enact your plan and code
- Chapter Quiz
- Requirements
- What does your project need to do? What problem does it need to solve?
- Legalities, performance, support, security, updatability, adaptability
- Take the time to understand the why between requirements in order to break them down into more specific needs
- Functional requirements can be defined as a "must be able to", i.e. "the system must be able to bake a cake", and "the system must be able to keep track of its available ingredients"
- Non-functional requirements can be defined as a "should be", i.e. "the system should be available 24/7", and "the system should be compatible with common household ovens"
- Chapter Quiz
- Use Cases and User Stories
- Use cases
- Meant to outline what a program or application needs to do without any technical wording
- Should be broken up into three main parts per use case
- Title: What needs to be done?
- Primary Actor: Who/what needs to do it? (This can be a person or another program)
- Success Scenario: How is it done? (Not technical, but rather an everyday language description of actions)
- Extensions can also be added to a use case to define additional details for what could go wrong and how it would be addressed by the system
- Identifying the actors
- What different types of users will need to interact with the application?
- Are there other systems that the application will need to interact with?
- Identifying the scenarios
- Define user-focused goals that will encompass multiple steps, i.e. "cook meal" rather than "turn on microwave"
- Focus on typical situations that can occur, and move on to extensions afterwards
- Omit needless words - use simple and concise wording
- Diagramming use cases
- Lay out the actors and the use cases, then connect who will need to do what within the application
- User stories
- Quick sentences to outline a scenario a user would be in when interacting with your application
- Broken down into three parts:
- As a (type of user), I want (goal) so that (reason)
- For example, "as a college student, I want to heat up my ramen so that I can eat a warm meal"
- Chapter Quiz
- C++ for Python Programmers Runestone Course - Jump to Top
- Chapter 3: Control Structures
- if, ifelse, and elif
- switch-case
- break statements are needed in each case - otherwise, if the statement is missing, it will move on to the next case automatically
- while loops
- for loops
- for (declaration, condition, post-loop statement){}
- Chapter 4: Functions
- Functions are declared with their return type as C++ is strongly typed
- Passing arguments to a function
- pass-by-value is default, but you can pass-by-reference to directly change the value of the variable
- Function overloading
- the C++ compiler selects functions by their function signature (a combination of the function name and the number, order, and type of its parameters), so functions with different signatures but the same name can be used to call the same thing in different situations
- two signatures must not match or the program will not compile
- Chapter 5: Collection Data Types
- Arrays
- ordered collection of elements of the same data type
- can be allocated to memory in a static or dynamic manner
- static arrays are declared with a type and a size to allocate, for example:
int arr1[15];- dynamic arrays are declared with a type and their elements , for example:
int arr2{1, 2, 3, 4};- Vectors
- similar to Python lists, C++ vectors are used to store dynamically allocated indexed data
- available through the standard template library -
#include <vector> - has multiple operators that can be used in tandem with it such as push_back, size, insert, erase, etc
- Strings
- c-strings in C++ are technically an array of char elements with a null terminator (0) at the end
- C++ also includes a string type in its <string> library which comes along with a few useful methods which are similar to the vector methods
- Hash Tables/Maps
- key-value pairs in an unordered composition (though ordered maps also exist)
- dot notation is used to access methods on maps
- Sets
- keyed values in an unordered composition (though ordered sets also exist)
- similar to a hash table/map but only stores keys
- good for O(1) access of unique elements
- Programming with C++ UE Documentation - Jump to Top
- Programming Quick Start
- Actors and their functions are declared in C++ header files, then the function definitions exist within the .cpp files associated with the actor
- Variables defined in C++ code as UPROPERTYs with the EditAnywhere tag can be edited in-engine in order to change how the C++ code reacts
- UPROPERTY tags using BlueprintReadWrite can also be changed by blueprint code
- Variations of C++ code actors can be made in-engine by creating blueprints from the C++ class - these blueprint actors will derive all variables and methods from the C++ code but can be further edited
- Live Coding
- Allows for live recompilation of changed C++ code while the engine is running, when playing in editor, or when running a packaged version of the application when attached to the editor
- Object reinstancing is enabled by default, meaning large-scale changes to actors in C++ will trigger the removal and replacement of their associated actors in the editor
- UnrealVS Extension
- Visual Studio extension which allows for easy access to common actions in C++ UE code
- Managing Game Code
- Unreal manages the creation and compilation of game code so you don't have to
- All classes can be created from the editor under File > New C++ Class
- Visual Studio files can be generated for .uproject files which also generates intellisense data
- Programming Tools
- The Low-Level Memory Tracker (LLM)
- Tool used within Unreal for memory tracking at runtime, and can display what memory is being allocated where by the engine
- Displayed and modified through console commands during runtime
- Similar to other
statcommands - Sparse Class Data system (SPD)
- Utilized in order to cut down on the non-relevant data inside an Actor class object
- Currently in beta
- Properties can be identified as SPD properties if (taken from here):
- It is a member of an Actor class that has many instances in-game at the same time, meaning that a significant amount of memory is tied up in redundant copies.
- It does not change on placed Actor instances. In other words, the property does not need the EditInstanceOnly or EditAnywhere UProperty Specifiers, because no instance of the Actor overrides or changes its base value.
- C++ code does not modify it. Any C++ code that directly accesses the variable must be replaced with calls to an accessor function.
- Only C++ variables can be used with SPD - if a blueprint variable should be in SPD, it needs to be migrated to C++
- The properties are then shared across the actors instead of consistently copying them, saving memory space
- Converting Blueprint to C++ Unreal Course - Jump to Top
- Introduction
- What will this course teach?
- How to write C++ for use in UE
- How to convert existing blueprint code to C++
- How to integrate both and use them in tandem
- C++ strengths (over blueprints)
- Extremely quick
- Can do (almost) anything - allows you to access the whole of the engine
- Works very well with version control
- Very concise
- More maintainable at a large scale
- Starter Kit Overview
- Setting up the default project files that accompany the course
- Going over the main architecture of the program - the GameMode controls the majority of the interactions in the project
- Creating C++ Base Classes
- Inheritance between BP and C++
- The easiest way to enable collaboration between BP and C++ is to have your blueprint class inherit from a C++ base class
- Blueprints must inherit from C++, you can't inherit C++ from a blueprint as C++ can't connect back to blueprints at compile time
- However, runtime calls are not restricted between C++ or BPs, they can interact with one another (still need to be backed up by interfaces in the same language)
- Re-parenting a blueprint class
- Existing BPs can be re-parented from one C++ class to another, although they should be of the same parent type (i.e. the BP_Grabber blueprint is currently parented to a SceneComponent, so the new parent should also be of a SceneComponent class)
- The class in C++ must be blueprintable, which can be done by adding the "Blueprintable" class specifier in the UCLASS declaration in the header file:
- The Blueprintable class specifier is inheritable, meaning that if the parent class was Blueprintable to begin with, we wouldn't need to add it here
- Blueprint classes can be re-parented in their Class Settings