C++ のオブジェクトとクラス「Unreal Engine での C から C++ および C++ プログラミングへの移行」チュートリアル②

1.オブジェクト指向プログラミング (OOP)

プロローグで次のように述べました。

プログラムの機能の種類ごとにカテゴリを分類し、特定の種類の問題を解決するために特定の種類のプログラムを使用できる場合、同じ種類から同じインスタンス オブジェクトを作成することで、同じ種類の関数を使用できます。ある種類のプログラムを利用することで、他のプログラマによる悪用を防止するための対応するアクセス権があり、そのプログラムを基に内容の一部を追加、削除、確認、修正することで、そのプログラムと同様の機能を持つプログラムを実現することができ、これにより、プログラマーの思考負担を大幅に軽減できます。

オブジェクト指向プログラミングは、問題を構成する事柄をさまざまなオブジェクトに分解することです. オブジェクトを確立する目的は、ステップを完了することではなく、問題解決ステップ全体における何かの動作を記述することです.

ゲーム開発では、プレイヤーと敵、どちらもキャラクターに属し、すべて同じ機能、血液量、攻撃など、共通点があり、1 つのカテゴリに分類できるものが多数あることがわかります。移動や攻撃などのパワー パラメータは同じタイプで値が異なります。

プロセス指向のプログラミング言語で上記の関数を作る場合、キャラクターの動きを制御する関数と敵の動きを制御する関数を書く必要がありますが、同じ関数でも対象が違うので、いくつかの関数を書く必要があるかもしれません.異なる移動関数を実装するのは非常に面倒です.

オブジェクト指向プログラミング言語を使用して、まずプレイヤーと敵の両方が持つ機能と属性、つまりキャラクター クラスのコードを記述し、次にプレイヤー クラスと敵クラスのコードを継承することができます。文字クラスとそれに基づくコンテンツの追加、削除、チェック、および一部の変更が実現されます。このようにして、コードの再利用が実現され、プログラマーの思考負担が軽減されます。

2. C++ のクラス

C++ のクラスは C 言語の構造体から発展しました. C 言語の構造体から, 構造体がいくつかのデータをパッケージ化してまとめて整理できることを学びました. プログラム内のいくつかのデータをパッケージ化することができます. 構造体では, 追加したり, 削除したり,大量のデータをチェックして修正します。

たとえば、Snake プログラムを作成する場合、Snake 本体の各ノードの座標データを構造体に格納できます。その後、各ノード全体の座標情報をマニュアルなしで簡単に割り当てることができます。その後、パラメーターを 1 つずつ実行します。 :

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

このような:

#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]);
}

出力は次のとおりです。 

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

しかし、データ構造として、C言語の構造は情報を格納する単位としてのみ使用できることがわかります.C言語の構造では、メンバー変数のみを定義でき、「関数」またはメンバーメソッドは定義できません.定義されており、操作自体はありません. 計算を実行する機能であるデータは、外部コードによってのみ変更できます.

C言語の構造に比べて、C++のクラスはメンバーメソッド(またはメンバー関数)やアクセス制御が追加され、継承や書き換えなどの機能を持っています。これは、C 言語の構造体のようにデータを格納できるだけでなく、データを操作して計算を実行する機能も備えていることを意味します。

作りたいゲームにプレイヤーとエネミーの2種類のキャラクターがいる場合、まずプレイヤーとエネミーが持つ機能や属性、つまりキャラクタークラスのコードを書いて継承すればよいプレイヤー クラスと敵クラスはキャラクター クラスから取得されるため、プレイヤーと敵は、親クラス、つまりロール クラスが所有するメンバー変数とメンバー メソッドを持ちます。

コードは以下のように表示されます:

#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;
}

出力は次のとおりです。 

1
2
3

 C++ では、メンバー メソッドとメンバー変数には次の 3 つのアクセス権があります。

(1)公開:

このキーワードの後に​​宣言されたメンバー メソッドとメンバー変数はパブリックです。パブリック メンバーは、クラス内だけでなく、クラスおよびその派生クラス (サブクラス) の外部からもアクセスできます。インターフェイスにアクセスします。

上記のコードでは、main 関数のコードが Character クラスのインスタンス、つまり character を作成した後、getBlood() および setBlood() のメンバー メソッドにインスタンス character を介してアクセスできることがわかります。

(2)保護:

このキーワードの後に​​宣言されたメンバー メソッドとメンバー変数は保護され、保護されたメンバーもクラスの外部に隠されますが、このクラスの派生クラス (サブクラス) に対しては、パブリック メンバーと同等の派生クラスにアクセスできます。

(3)非公開:

このキーワードの後に​​宣言されたメンバー メソッドとメンバー変数はプライベートです。プライベート メンバーはクラス内でのみアクセスでき、クラスの外部には隠され、派生クラス (サブクラス) にはアクセスできません。

C++ クラスでは、アクセス権を宣言しない場合、このメンバーのデフォルトのアクセス権はプライベートになります。

クラスの継承方法:

(1) 公開継承方式

class B:public A
  • 基本クラスのすべてのパブリック メンバーは、派生クラスのパブリック プロパティです。
  • 基本クラスの保護されたメンバーはすべて、派生クラスの保護された属性です。
  • 基本クラスのすべてのプライベート メンバーを派生クラスで使用することはできません。


(2) 保護継承方式

class B:protected A
  • 基本クラスのすべてのパブリック メンバーは、派生クラスの保護されたプロパティです。
  • 基本クラスの保護されたメンバーはすべて、派生クラスの保護されたプロパティです。
  • 基本クラスのすべてのプライベート メンバーは、派生クラスでは使用できません。


(3) 私的相続方式

class B:private A
  • 基本クラスのすべてのパブリック メンバーは、派生クラスのプライベート プロパティです。
  • 基本クラスのすべての保護されたメンバーは、派生クラスのプライベート プロパティです。
  • 基本クラスのすべてのプライベート メンバーは、派生クラスでは使用できません。

(4) デフォルトの継承方法

class B:A
  •  プライベート継承と同じです。つまり、C++ のデフォルトの継承方法はプライベート継承です。

C++ での多重継承

前の例では、派生クラスには基本クラスが 1 つしかなく、これは単一継承 (Single Inheritance )と呼ばれます。さらに、C++ は多重継承 (Multiple Inheritance)もサポートしています。つまり、派生クラスは 2 つ以上の基本クラスを持つことができます。

多重継承はコード ロジックを複雑にし、思考を混乱させる傾向があります. それは常に物議を醸しており、小規模および中規模のプロジェクトではめったに使用されません. その後、  Java , C# , PHP  などは単純に多重継承を廃止しました.

多重継承の構文も非常に単純で、複数の基本クラスをコンマで区切るだけです。たとえば、クラス A、クラス B、およびクラス C が宣言されている場合、派生クラス D は次のように宣言できます。

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

3. Unreal Engine の C++ クラス

ここではデモンストレーションに Unreal 4.27.2 を使用していますが、Unreal 5 でも同じことが言え、コードもそれほど変わりません。

Unreal Engine でクラスを作成する必要がある場合は、エンジンの左上隅にあるファイルをクリックして、作成する C++ クラスを作成する必要があります。

  Unreal Engine で C++ クラスを作成するときに、親クラスを選択するように求められることがわかります.一般的に言えば、Unreal Engine のゲームプレイ レイヤー (つまり、ゲーム ロジック コード) のクラスは、通常、UObject クラスから継承されます。親クラスの UObject によって作成されたサブクラスはすべて、ガベージ コレクション (ガベージ コレクションとは何ですか。バイドゥでお願いします)、リフレクション (リフレクション メカニズムとは何ですか。バイドゥでお願いします) などの機能を備えています。

Unreal Engine の重要な基本クラスの継承関係については、次の図を参照してください。

ここに画像の説明を挿入

Here I have created an Actor 派生クラス MyActor. コードが正常にコンパイルされると、Visual Studio が自動的に開き、作成されたばかりのコードが表示されます。

// 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;

};

Unreal Engine の UObeject クラスの派生クラスは、作成後に UCLASS() と GENERATED_BODY() のマクロ タグを持ち、このタグを持つクラスは Unreal Engine のガベージ コレクションとリフレクション機能を持つことがわかります。

また、Unreal Engine では、AActor のサブクラスに A というプレフィックスが付けられ、それが Actor のサブクラスであることを示し、Actor クラスはゲーム シーンに配置できる一種のコンテンツであることもわかりました。シーンの後、コンテンツ ブラウザからドラッグしてシーンに配置できます。一般的なゲーム空間に存在するオブジェクトはすべて Actor クラスのサブクラスです。

クラスのコンストラクタとデストラクタ

C++ では、すべてのクラスに既定でコンストラクターとデストラクターがあり、それらはすべてクラスのパブリック メンバー メソッド (またはメンバー関数) に属している必要があります。そうでない場合、このクラスのインスタンスを作成できません。コンストラクタまたはデストラクタを宣言しない場合、コンパイラはコンパイル時に引数なしのコンストラクタまたはデストラクタを自動的に追加し、コンパイラは引数なしのコンストラクタで操作を実行しません。

ゲーム開発では、プレイヤークラスと敵クラスを書いているので、ゲームの実行を開始すると多くのプレイヤーまたは敵が作成される可能性があるため、コンストラクターを使用してプレイヤーまたは敵インスタンスの初期情報を設定し、実行する必要があります。彼ら。

デストラクタは、オブジェクトを削除する必要があるときに、オブジェクトの死の余波を処理する関数で、ゲーム開発で死後にキャラクター エンティティを削除する場合などに使用されます。

クラスのコンストラクタはクラスと同じ名前にする必要があり、デストラクタの名前はクラス名の前に追加されます~

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;
    }
};

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)};
}

操作の結果は次のとおりです。 

5
你创建了个A的实例

デストラクタには正式なパラメータを指定できないことに注意してください。

デストラクタでは、ポインタ型変数が指すオブジェクトがプログラムの実行過程 C 言語の malloc() 関数と同様に、 new キーワードを使用して動的に作成され、プログラムの実行中に動的に割り当てられたメモリ空間に格納されるオブジェクトです。

C++ には、未使用のオブジェクトを自動的に削除できる Java のようなガベージ コレクション メカニズムがありません。プログラムの実行中にクラス A の多数のインスタンスを作成し、それらを破棄するときにメンバー変数内のポインターが指すオブジェクトを削除していない場合、削除されていないオブジェクトは常にあなたのmemory ですが、クラス A のインスタンスを削除したため、クラス A のインスタンスを提供してこれらのオブジェクトにアクセスして削除することはできません。プログラムが終了するか、コンピューターのメモリがなくなるまで、それらは常にそのメモリを占有します。これは C++ 共通メモリです。漏れの問題。

Unreal Engine に戻ると、構築関数に加えて、Unreal Engine の Actor のサブクラスも追加されていることがわかります。 

	virtual void BeginPlay() override;

 BeginPlay() は、ゲーム レベルの最初のフレームが実行を開始したときに呼び出される関数です (ロード フレームとも呼ばれます)。これは一般に、シーン内のアクタの初期位置の設定などに使用されます。アクターの位置、向きなど これらの情報は、多くの場合、シーン ワールドが作成された後に設定する必要があります. そうしないと、シーンが生成されたときにシーンが作成されておらず、アクターはどのシーンを配置すればよいかわかりません。プログラムがおかしくなります。

仮想関数であることを意味する virtual キーワードも含まれていることがわかりました. 仮想関数の詳細な説明については、次のチュートリアルで説明します.

MyActor に対応する cpp ファイルを開き、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);

}

次のように、Unreal Engine の void AMyActor::BeginPlay() 関数で Hello world を出力するコード行を追加するとします。

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

この時点で、Unreal Engine の編集インターフェイスに戻り、[コンパイル] をクリックして [コンパイル] ボタンを押します。

 コンパイルが完了したら、MyActor をコンテンツ ブラウザからシーンにドラッグします。

 実行ボタンをクリックします

実行を開始できるようになると、Hello world の情報が画面に表示されます。

レベルの実行が開始されると、各アクタの BeginPlay() 関数が呼び出されるためです。したがって、実行を開始すると、Hello worldが出力されます

初めての Unreal C++ プログラムの作成、おめでとうございます!

参照:

[1] C++ Primer Plus (第 6 版) 中国語版

おすすめ

転載: blog.csdn.net/lifesize/article/details/128518898