AI好きな管理人が個人で勉強しているAIの技術を備忘録代わりとして色々と書いていくwikiです

UE4ではUnityと比べてAIに関するサポートが格段に良く、非常に作りやすくなっています。
この章ではUE4とUE4がサポートしている言語であるC++とBluePrintを用いて「プレイヤーを追跡する」という単純なAIを作っていきます。

使用するUE4のバージョンは「4.7.6」です。バージョン4.6やバージョン4.8 PreviewではC++の書き方に多少の差異が生じる可能性がありますので、4.7以外のバージョンを使う方は留意して頂けますと幸いです。
もう一つ、ソースコードエディタはVisualStudio2013を使用します。

まずはプロジェクトを作成します。
使用するテンプレートは「C++」の「Third Person」です。
保存するフォルダ名は「AI_Traning」でプロジェクト名は「AI_Traning」です。
Starter Contentは特に必要ありませんので、「No Starter Content」とします。


無事にプロジェクトが作成できたら、ちょっとエディタの設定を変更します。
UE4には一定時間経つと自動でセーブをする機能がありますが、個人的にその機能で良い思いをしたことがないので自動セーブ機能をオフにします。
ツールバーのEdit→Editor Preferences...→Loading & Saving→Enable AutoSaveのチェックを外します。


次にAIのアニメーションのアセットをMarket Placeからダウンロードします。
レベルエディタツールバーからMarketPlaceをクリックしてください。


表示されたウィンドウから「キャラクター・アニメーション」を探し、その中にある「アニメーションスターターパック」を見つけ、クリックします。


ダウンロードされていない方はアセットをダウンロードし、先ほど作成したプロジェクトを指定して追加してください。
無事追加出来たら、Content BrowserのContentフォルダ直下に「AnimStarterPack」というフォルダが追加されているはずです。


ここまで準備が終わったら、早速AIを作っていきます。
エディタのツールバーからFile→Add Code to Project...をクリックします。
「どのクラスを親クラスにしますか?」というウィンドウが表示されますので、「Character」を選択し「Next」をクリック。


ファイル名は「MyBot_Character」として「Create Class」をクリック


次にMyBot_Characterを動かすコントローラクラスを作りますが、その前に修正するべき箇所があります。
画像中の赤で囲まれている「[ProjectName].Build.cs」です。

初期状態だと以下の様な内容のはずです。
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class AI_Traning : ModuleRules
{
	public AI_Traning(TargetInfo Target)
	{
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore"});
	}
}
この中の「"InputCore"」の後ろに「"AIModule"」を追加します
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;

public class AI_Traning : ModuleRules
{
	public AI_Traning(TargetInfo Target)
	{
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "AIModule" });
	}
}
この「AIModule」を追加しないと、AIに関連するコンポーネントを参照する際にコンパイルエラーが必ず発生してしまいますので、AIをC++で構築する場合には注意してください。

では、AIModuleを追加したところでMyBot_Characterをコントロールするコントローラークラスを作成します。
MyBot_Characterを作成した手順と同様にFile→Add Code to Project...をクリック。
ウィンドウ右上にある「Show All Classes」のチェックボックスにチェックを入れて、検索バーに「AIController」と打ち込みます。
そうすると「AIController」が表示されますので、それをクリックして「Next」をクリックします。

ファイル名は「MyBot_Controller」とします。

無事に両方のクラスを作成できたら、まず最初に「MyBot_Character.h」に対し、AIの振る舞い(ビヘイビア)をBluePrint側から指定出来るように変更します。
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/Character.h"
#include "MyBot_Character.generated.h"

UCLASS()
class AI_TRANING_API AMyBot_Character : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	AMyBot_Character();

	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	
	// Called every frame
	virtual void Tick( float DeltaSeconds ) override;

	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

	// ↓ 追加
	UPROPERTY(EditAnywhere, Category = Behavior)
	class UBehaviorTree* BotBehavior;

};
パブリックな変数としてUBehaviorTreeのポインタ型で「BotBehavior」を宣言します。
この時UPROPERTYでEditAnywhereを指定することによりBluePrint側からBotBehaviorに格納するBehaviorTreeを指定出来ます。
次に「MyBot_Character.cpp」を変更します。
// Fill out your copyright notice in the Description page of Project Settings.

#include "AI_Traning.h"
#include "MyBot_Character.h"
#include "MyBot_Controller.h"	// ← 追加

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

	// ↓ 追加
	AIControllerClass = AMyBot_Controller::StaticClass();
}
変更するのはコンストラクタの部分だけです。
ここでは親クラスである「ACharacter」の親クラスである「APawn」クラスにて定義されている変数の「AIController」に対し先ほど作成した自作のコントローラクラスである「AMyBot_Controller」クラスのインスタンスを渡しています。
ここまで変更が完了したらコンパイルをしてみましょう。エラーが出ずにコンパイルが終了すれば、次は「MyBot_Controller.h/cpp」を編集していきます。MyBot_ControllerではAIの実際の振る舞いを定義していきます。
UE4側が生成してくれたMyBot_Controller.h/cppにはMyBot_Characterと違ってほとんど空っぽな状態です。
その中に以下のようにコードを追加します。
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "AIController.h"
#include "MyBot_Controller.generated.h"

/**
 * 
 */
UCLASS()
class AI_TRANING_API AMyBot_Controller : public AAIController
{
	GENERATED_BODY()
	
public:
	AMyBot_Controller(const class FObjectInitializer& ObjectInitializer);

	// UProperty 値が自動的に書き込みまたは読み出されることを許可しない
	UPROPERTY(Transient)
	class UBlackboardComponent* BlackboardComp;

	UPROPERTY(Transient)
	class UBehaviorTreeComponent* BehaviorComp;

	virtual void Possess(class APawn* InPawn);

	// BlackboardCompのKeyに対し値を設定する
	void SetEnemy(class APawn* InPawn);

	// BluePrint側でこの関数を呼び出せるように設定
	UFUNCTION(BlueprintCallable, Category = Behavior)
		void SearchForEnemy();

protected:
	uint8 EnemyKeyID;		// BlackBoardで定義したKeyID
	uint8 EnemyLocationID;	// BlackBoardで定義したKeyID
};
次にCPP側を変更していきます。
// Fill out your copyright notice in the Description page of Project Settings.

#include "AI_Traning.h"
#include "MyBot_Controller.h"

// ↓ 追加
#include "MyBot_Character.h"
#include "AI_TraningCharacter.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BehaviorTreeComponent.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Object.h"
#include "BehaviorTree/Blackboard/BlackboardKeyType_Vector.h"
// ====== ここまで ====== 

AMyBot_Controller::AMyBot_Controller(const class FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	BlackboardComp = ObjectInitializer.CreateDefaultSubobject<UBlackboardComponent>(this, TEXT("BlackBoardComp"));

	BehaviorComp = ObjectInitializer.CreateDefaultSubobject<UBehaviorTreeComponent>(this, TEXT("BehaviorComp"));
}

void AMyBot_Controller::Possess(class APawn* InPawn)
{
	Super::Possess(InPawn);

	AMyBot_Character* Bot = Cast<AMyBot_Character>(InPawn);

	if (Bot && Bot->BotBehavior)
	{
		BlackboardComp->InitializeBlackboard(*Bot->BotBehavior->BlackboardAsset);

		EnemyKeyID = BlackboardComp->GetKeyID("Enemy");
		EnemyLocationID = BlackboardComp->GetKeyID("Destination");

		BehaviorComp->StartTree(*Bot->BotBehavior);
	}
}

void AMyBot_Controller::SearchForEnemy()
{
	APawn* MyBot = GetPawn();
	if (MyBot == NULL)
		return;

	const FVector MyLoc = MyBot->GetActorLocation();
	float BestDistSq = MAX_FLT;
	AAI_TraningCharacter* BestPawn = NULL;

	for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
	{
		AAI_TraningCharacter* TestPawn = Cast<AAI_TraningCharacter>(*It);


		if (TestPawn)
		{
			const float DistSq = FVector::Dist(TestPawn->GetActorLocation(), MyLoc);

			if (DistSq < BestDistSq)
			{
				BestDistSq = DistSq;
				BestPawn = TestPawn;
			}
		}
	}

	if (BestPawn)
	{
		SetEnemy(BestPawn);
	}
}

void AMyBot_Controller::SetEnemy(class APawn* InPawn)
{
	BlackboardComp->SetValue<UBlackboardKeyType_Object>(EnemyKeyID, InPawn);
	BlackboardComp->SetValue<UBlackboardKeyType_Vector>(EnemyLocationID, InPawn->GetActorLocation());
}
ここまで変更出来たら、コンパイルしてみましょう。無事成功すればOKです。
MyBot_Controllerクラスでは色々定義しました。簡単に定義した内容を説明します。
Possess関数ではBlackboardCompを初期化し、初期化したBlackboardCompから「Enemy」、「Destination」という名前を持つキーを取得して保持して、AMyBot_Characterクラスで定義した「BotBehavior」をStartTree関数で起動します。
SearchForEnemy関数は察しの良い方は気づかれたかもしれません。
非常に単純なワールド上にいるプレイヤーを探し出すロジックです。
for (FConstPawnIterator It = GetWorld()->GetPawnIterator(); It; ++It)
でワールド上にいる全てのPawnを取得します。
		AAI_TraningCharacter* TestPawn = Cast<AAI_TraningCharacter>(*It);


		if (TestPawn)
		{
			const float DistSq = FVector::Dist(TestPawn->GetActorLocation(), MyLoc);

			if (DistSq < BestDistSq)
			{
				BestDistSq = DistSq;
				BestPawn = TestPawn;
			}
		}
	}

	if (BestPawn)
	{
		SetEnemy(BestPawn);
	}

プレイヤーが実際に操作するAAI_TraningCharacterに変換して、AI自身との距離を測り、BestDistSqより距離が小さければ「追跡するべきプレイヤー」として保持します。
ワールド上に1つしかプレイヤーの操作するPawnが無ければfor文は不要なのですが、複数人のプレイヤーが存在する場合には上記のような処理が必要になるでしょう。
SetEnemy関数ではPossess関数で取得したBlackboardのキーに対しベストなプレイヤーのポインタと、プレイヤーの位置を格納しています。

MyBot_Controller.h/cppでAIが追跡するべきプレイヤーの情報を取得、保持することが出来ました。
しかし「そのプレイヤーを追跡する」という処理は書かれていないので、これをそのままワールド上に放り出したところで何もしてくれません。プレイヤーを追跡するという処理はC++で書かず、BluePrintで書きます。

BluePrintでプレイヤーを追跡する処理を書く前にまた色々と準備するものがありますので、準備をしていきましょう。
Content BrowserのContentフォルダ内に新しくフォルダを作成します。
フォルダ名は「MyContent」とします。
フォルダが作成出来たら、MyContentフォルダをダブルクリックして「Add New」ボタンをクリックし「BluePrint Class」をクリックします。


ウィンドウ下部の「All Classes」の検索バーで「MyBot_Character」を入力するとその下に「MyBot_Character」が表示されますので、クリックしSelectを押します。
BluePrintの名前を「MyBot」に変更します。
作成出来た「MyBot」をダブルクリックし、編集画面へと移動します。
左上にある「Components」から「Mesh」を選択し、右側の「Details」にて「Mesh」を最初に追加したアニメーションスターターパックの「HeroTPP」を選択します。
DetailsのMeshの上にある「Animation」にある「Anim Blueprint Generated Class」で「ASP_HeroTPP_AnimBlueprint」を選択します。
その後、中心のViewportでメッシュの位置と向きを調整します。

設定が終わったら、編集画面前の画面に戻ってください。
今度はBlackboardを追加します。
MyBotを追加した手順と同様ですが、追加するのはBluePrintではなく「MiscllaneousのBlackboard」です。
名前は「MyBot_BB」とします。

追加したMyBot_BBをダブルクリックし、編集画面へと移動します。
ここではAIが参照する変数を定義していきます。
左上の「New Key」をクリックし「Object」をクリックします。名前は「Enemy」とします。この名前はMyBot_Controller.cppのPossess関数で指定した
		EnemyKeyID = BlackboardComp->GetKeyID("Enemy");
「"Enemy"」の事です。
追加したObject型のEnemyの設定を少し変更します。
Enemyをクリックして右側の「Blackboard Details」に表示される「Key Type」を展開し「Base Class」を「AI_TraningCharacter」に変更します。
AI_TraningCharacterはプレイヤーが実際に操作しているキャラクターの事です。


Enemyを追加したので次は
		EnemyLocationID = BlackboardComp->GetKeyID("Destination");
こちらの「Destination」を追加しましょう。
手順はEnemyの時と同様ですが、今度はObject型ではなく「Vector」型とします。
名前は「Destination」です。
Destinationは何も変更しないので追加し終えたらそのままでOKです。

Blackboardを作成し変数を追加し終えたら次は「BehaviorTree」を追加します。
Blackboardを追加した時と同様の手順で「Miscllaneous→BehaviorTree」をクリックします。
BehaviorTreeの名前は「MyBot_Brain」とします。
無事に追加出来たら、次はBehaviorTreeで用いるBehaviorTree用のノードを一つだけ作成します。
「AddNew→Blueprint Class」をクリックしAll Classesの検索バーで「BTService_BlueprintBase」をクリックしSelectをクリックします。

名前は「FollowPlayer」にしておきます。
作成できたら「FollowPlayer」をダブルクリックし、編集画面へと移動します。
ここでは以下の画像のようにノードを作成&配置してください。


何もエラーが出なければOKなので、今度はBehaviorTreeを編集していきます。
作成した「MyBot_Brain」をダブルクリックして編集画面へと移動します。
「ROOT」ノードからピンを引き出し「Selectorノード」を追加します。
追加したSelectorノードを右クリックして「Add Service→Follow Player」をクリックします。

Selectorノードのピンを引き出して「MoveTo」ノードを追加し、画面右の「Details」にある「Blackboard Key」で「Enemy」を設定すればOKです。
これでプレイヤーを追跡する処理が完成しました。
しかし、もう一つ設定するべき項目があります。
「MyBot」をダブルクリックして編集画面へと移動します。
画面左側の「Components」の「MyBot(self)」をクリックし右側の「Details」にある「Bot Behavior」を「MyBot_Brain」に設定します。

この設定により「MyBot」が「MyBot_Brain」で定義した振る舞い(ビヘイビア)を実行するようになります。

次はワールド上に作成したAIを出して動かしてみましょう。
ワールド上に出現させる方法は非常に簡単です。Content BrowserのMyBotをワールドにドラッグ・アンド・ドロップするだけです。
では、「Playボタン」を押して実行してみましょう!

しかし、残念な事にAIは動いてくれません。
これは単なるバグやタイプミス、設定ミス等ではなくちゃんとした理由があります。
「NavMeshが無い」事が原因です。
では早速NavMeshをワールド上に追加しましょう。
UE4でのNavMeshは「Nav Mesh Bouds Volume」という名前で画面左上の「Modes」から選択出来ます。

これをワールド上にドラッグ・アンド・ドロップして、地面に半分埋まるように配置してください。
配置出来たら「Pキー」を押します。そうするとワイヤーフレームのボックス内に緑のエリアが表示されるはずです。

あとはこのワイヤーフレームのボックスをワールド全体を覆うようにサイズを変更します。
ワールド内全てのオブジェクトに緑色のエリアが表示されればOKです。

AIがワールド内を動き回るにはこの「NavMesh」が必要不可欠です。
AIがワールド内を動き回る処理を書いたのにうんともすんとも言わないと言う場合には先ず最初に「NavMeshを設定したか否か」を確認してください。

では再度実行してプレイヤーを追いかけてくれば成功です!お疲れ様でした。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

メンバーのみ編集できます