Objects and classes in C++ "Transition from C to C++ and C++ Programming in Unreal Engine" tutorial②

1. Object Oriented Programming (OOP)

In the prologue we mentioned:

If we can classify each type of function of the program into categories, and use a certain type of program to solve a certain type of problem, the same type of function can be used by creating the same instance object from the same type, and when using a certain type of program, there are Corresponding access rights prevent other programmers from misusing, and then a program with similar functions to the program can be realized by adding, deleting, checking and modifying part of the content on the basis of the program, which can greatly reduce the programmer's thinking burden.

Object-oriented programming is to decompose the affairs that constitute a problem into various objects. The purpose of establishing an object is not to complete a step, but to describe the behavior of something in the entire problem-solving step.

In game development, we will find that there are a large number of things that have commonalities and can be classified into one category, such as players and enemies, both belong to characters, and they all have the same functions, blood volume, and attack power, such as movement and attack. parameters of the same type but different values.

If we use a process-oriented programming language to create the above functions, we need to write a function to control the movement of the character, and a function to control the movement of the enemy. The same function, because the target is different, we may need to write several functions . It is very troublesome to implement different moving functions.

Using an object-oriented programming language, we can first write the functions and attributes that both the player and the enemy have, that is, the code of the character class, then the code of the player class and the enemy class can inherit the code of the character class and based on it Adding, deleting, checking and modifying part of the content is realized. In this way, code reuse can be realized and the programmer's thinking burden can be reduced.

2. Classes in C++

Classes in C++ evolved from structures in C language. We learned from structures in C language that structures can package and organize some data together, and we can package some data in the program. In the structure, it is very convenient to add, delete, check and modify a large amount of data.

For example, when writing a Snake program, we can store the coordinate data of each node on the Snake body in a structure, and then we can easily assign the coordinate information of each node as a whole without manual Then perform a parameter one by one:

position[0][1]=position[1][1]

like this:

#include<stdio.h>
struct SnakeNode
{
	int position[2];//用以表示蛇节点的x,y轴坐标
};
int main()
{
	struct SnakeNode snake[100];
	snake[0].position[0] = 1;
	snake[0].position[1] = 2;
	snake[1] = snake[0];//让蛇的第二个节点坐标等于第一个节点的坐标
	printf("snake.position[0]=[%d,%d]\n", snake[0].position[0], snake[0].position[1]);
	printf("snake.position[1]=[%d,%d]\n", snake[1].position[0], snake[1].position[1]);
}

The output is: 

snake.position[0]=[1,2]
snake.position[1]=[1,2]

But we can find that, as a data structure, the structure of C language can only be used as a unit for storing information. In the structure of C language, only member variables can be defined, and "functions" or member methods cannot be defined, and they do not have operations themselves. Data, the ability to perform calculations, can only be modified by external code.

Compared with the structure of C language, the class in C++ adds member methods (or member functions) and access control, and has functions such as inheritance and rewriting. This means that it can not only store data like a C language structure, but also has the ability to manipulate data and perform calculations.

If there are two types of characters in the game we want to make, the player and the enemy, we can first write the functions and attributes that the player and the enemy have, that is, the code of the character class, and then inherit the player class and the enemy class from the character class, so that The player and the enemy have the member variables and member methods owned by their parent class, that is, the role class.

code show as below:

#include<iostream>
//class关键词用于声明类
class Character//玩家类和敌人类共有的父类——角色类
{
private://在此后面定义的方法和变量都是私有的
	float blood_;//玩家和敌人都会有的血量值
public://在此后面定义的方法和变量都是公有的
	//玩家和敌人类都需要的方法:
	float getBlood()
	{
		return blood_;
	}
	float setBlood(float blood)
	{
		blood_ = blood;
		return blood_;
	}
};
//注意这里是public继承方式
//后面会讲到几种继承方式的区别
class Player :public Character
{
};
class Enemy :public Character
{
};
int main()
{
	Character character = Character();
	character.setBlood(1.0);
	Player player = Player();
	player.setBlood(2.0);
	Enemy enemy = Enemy();
	enemy.setBlood(3.0);
	std::cout << character.getBlood() << std::endl;
	std::cout << player.getBlood() << std::endl;
	std::cout << enemy.getBlood() << std::endl;
}

The output is: 

1
2
3

 In C++, member methods and member variables have three access rights, namely:

(1)public:

The member methods and member variables declared after this keyword are public. Public members can be accessed not only within the class, but also outside the class and its derived classes (subclasses). access interface.

In the above code, we can see that after the code of the main function creates an instance of the Character class, namely character, the member methods of getBlood() and setBlood() can be accessed through the instance character.

(2)protected:

The member methods and member variables declared after this keyword are protected, and the protected members are also hidden outside the class, but for the derived classes (subclasses) of this class, they are equivalent to public members. In derived classes can be accessed.

(3)privated:

The member methods and member variables declared after this keyword are private. Private members can only be accessed within the class, and are hidden outside the class, and their derived classes (subclasses) cannot be accessed.

In a C++ class, when we do not declare any access rights, the default access rights of this member will be private.

Class inheritance method:

(1) public inheritance method

class B:public A
  • All public members in the base class are public properties in the derived class;
  • All protected members in the base class are protected attributes in the derived class;
  • All private members of the base class cannot be used in the derived class.


(2) protected inheritance method

class B:protected A
  • All public members in the base class are protected properties in the derived class;
  • All protected members in the base class are protected properties in the derived class;
  • All private members in the base class cannot be used in derived classes.


(3) private inheritance method

class B:private A
  • All public members in the base class are private properties in the derived class;
  • All protected members in the base class are private properties in the derived class;
  • All private members in the base class cannot be used in derived classes.

(4) Default inheritance method

class B:A
  •  It is the same as private inheritance, that is, the default inheritance method of C++ is private inheritance

Multiple Inheritance in C++

In the previous example, the derived class has only one base class, which is called single inheritance (Single Inheritance ) . In addition, C++ also supports multiple inheritance (Multiple Inheritance) , that is, a derived class can have two or more base classes.

Multiple inheritance tends to complicate code logic and confuse thinking. It has always been controversial and is rarely used in small and medium-sized projects. Later,  Java , C# , PHP  , etc. simply canceled multiple inheritance.

The syntax of multiple inheritance is also very simple, just separate multiple base classes with commas. For example, if class A, class B and class C have been declared, then derived class D can be declared like this:

class D: public A, private B, protected C
{
    //类D新增加的成员
};

3. C++ classes in Unreal Engine

I am using Unreal 4.27.2 for demonstration here. The same is true for Unreal 5, and the code is not much different.

In Unreal Engine, if you need to create a class, you need to click on the file in the upper left corner of the engine to create a C++ class to create

  We can see that when creating a C++ class in Unreal Engine, you will be asked to choose a parent class. Generally speaking, the classes in the gameplay layer (that is, the game logic code) in Unreal Engine generally inherit from the UObject class. The subclasses created by UObject for the parent class all have functions such as garbage collection (what is garbage collection, please Baidu), reflection (what is the reflection mechanism, please Baidu) and so on.

You can see this picture about the inheritance relationship of important base classes in Unreal Engine:

insert image description here

Here I have created an Actor derived class MyActor. After the code is successfully compiled, it will automatically open Visual Studio, and we can see the code just created:

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class CPPTESTPROJECT_API AMyActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AMyActor();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

};

We can find that the derived classes of the UObeject class in the Unreal Engine will have macro tags of UCLASS() and GENERATED_BODY() after creation, and classes with this tag will have the garbage collection and reflection functions of the Unreal Engine.

We also found that in the Unreal Engine, the subclass of AActor will be prefixed with A, indicating that it is a subclass of Actor, and the Actor class is a kind of content that can be placed in the game scene. After the scene, we can drag it out of the content browser and place it in the scene. The objects that exist in the general game space are all subclasses of the Actor class.

class constructor and destructor

In C++, every class has constructors and destructors by default, and they must all belong to the public member methods (or member functions) of the class, otherwise you cannot create an instance of this class. If you do not declare a constructor or destructor, the compiler will automatically add a no-argument constructor or destructor for you at compile time, and the compiler will not perform any operations in the no-argument constructor for you.

In game development, since we have written the player class and the enemy class, many players or enemies may be created when the game starts running, so we need to use the constructor to set the initial information of the player or enemy instance , to run them.

The destructor is a function that handles the aftermath of the object's death when we need to delete the object. It is used in game development to delete the character entity after death and so on.

The constructor of the class needs to have the same name as the class, and the name of the destructor is added before the class name~

Several ways of writing class constructor in C++

#include <iostream>
using namespace std;

class Student {
public:
    int m_age;
    int m_score;
    float m_money=0;

    //类的构造函数可以写多种不同参数的形式,调用时可以选择你想要的调用
    
    // 初始化列表方式,初始化列表
    //C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。
    //也就是说采用初始化列表的话,构造函数本体实际上不需要有任何操作,因此效率更高。
    Student(int age, int score) :
        m_age(age),
        m_score(score)
        {}

    // 内部赋值方式
    Student(int age, int score,float money) 
    {
        m_age = age;
        m_score = score;
        m_money=money;
    }
};

The destructor of a class in C++:

#include<iostream>
class A
{
	int a;
	A *b;
public:
	A()//A的无参构造函数
	{
		std::cout << "你创建了个A的实例" << std::endl;
	}
	A(int x)//构造函数可以有多种参数配置,可以选择你想要的方式构造类的实例
	{
		a = x;
		std::cout << a << std::endl;//输出A
		b = new A();//让指针b指向一个新创建的A类的一个实例
	}
	~A()//A的析构函数 注意析构函数都不能带参数
	{
		delete b;//删除b指针,即释放b指针指向的对象
	}
};
int main()
{
	A a = A(5);
	//调用A的构造方法创建A的实例a,
	//你也可以像数组一样创建一组A的实例:
	//例如A *a = new A[6](5);
	//或A a[2] = {A(5),A(6)};
}

The result of the operation is: 

5
你创建了个A的实例

Note that the destructor cannot have any formal parameters.

In the destructor, the work we often need to complete is to manually delete the pointer type variable in the member variable of the class, such as char* b in the above example, because the object pointed to by the pointer type variable may be in the process of running the program It is dynamically created using the new keyword, just like the malloc() function in C language, which is the object stored in the memory space dynamically allocated during the running of the program.

C++ does not have a garbage collection mechanism like Java, which can automatically delete unused objects. If you have created a large number of instances of class A during the running of the program, and have not deleted the objects pointed to by the pointers in their member variables when destructing them, then those objects that have not been deleted will always occupy your memory , but because you deleted the instance of class A, you cannot provide the instance of class A to access those objects and delete them, they will always occupy that memory until the program ends or the computer memory runs out, this is C++ Common memory leak issues.

Going back to our Unreal Engine, you will find that in addition to the construction function, the subclass of Actor in the Unreal Engine also adds 

	virtual void BeginPlay() override;

 BeginPlay() is a function that will be called when the first frame of the game level starts to run (also called loading frame). It is generally used to set the initial position of the Actor in the scene, etc., because especially the position, orientation, etc. of the Actor These information often need to be set after the scene world is created. Otherwise, the scene has not been created when the scene is generated, and the Actor will not know which scene to place, and the program will go wrong.

We found that it also has the virtual keyword, which means it is a virtual function. We will talk about the detailed explanation of virtual functions in the next tutorial.

We open the cpp file corresponding to MyActor to see the implementation of BeginPlay().

// Fill out your copyright notice in the Description page of Project Settings.


#include "MyActor.h"

// Sets default values
AMyActor::AMyActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

If we add a line of code to print Hello world in the void AMyActor::BeginPlay() function in Unreal Engine as follows.

// Called when the game starts or when spawned
void AMyActor::BeginPlay()
{
	Super::BeginPlay();
	GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("Hello world"));
}

At this time, go back to the editing interface of the Unreal Engine, click compile and press the compile button

 After the compilation is complete, drag MyActor from the content browser into the scene

 Click the run button

When we can start running, we can see the Hello world information printed on the screen

Because each Actor's BeginPlay() function will be called when the level starts running. So after starting to run, it prints Hello world

Congratulations on writing your first Unreal C++ program!

references:

[1] C++ Primer Plus (6th Edition) Chinese Edition

Guess you like

Origin blog.csdn.net/lifesize/article/details/128518898