よく使われる処理をUnrealC++で書いてみようの巻。
こんにちくわ。
吉田です。初めましての方が多そうなので自己紹介をさせていただきます。
Profile : Kai Yoshida
今回は
qiita.comに参加させてもらいましたので、
自分なりに書きたいものを書きたいと思います('ω')
なお、本記事ではUE4 [4.14.0],Visual Studio Community 2015を使用しています。
Visual Studio Community 2015のダウンロードはこちらから。
www.visualstudio.com
今回のテーマはブログの題の通りです。
ごちゃごちゃ言っていても仕方がないので本題に入りましょう。
BeginOverlap,EndOverlap
こちらはBPでもおなじみCollisionの衝突判定のイベントです。
BPだとこんな感じ。
BP :
これを使わずしてゲームは作れないって程重要なノードです。まずはこいつを実装してみましょうか。
まず、自分は今こんな状況にいる!ってシチュエーションを作りましょう。
今回は、ドアの開閉スクリプトをBPじゃなくUnrealC++で作りたくて夜も朝までしか寝れない!ってシチュエーションにしましょう。
1 : DoorClassを作る。
はい。
ActorClassを継承したDoorClassを作りましょう。
コードを書きます。
まずは基本となる、StaticMeshをアサインする処理から。
Door.h
UPROPERTY(EditAnywhere,BlueprintReadOnly,Category = "Door", meta = (AllowPrivateAccess = "true")) class UStaticMeshComponent* DoorMesh; UPROPERTY(EditAnywhere,BlueprintReadOnly,Category = "Door", meta = (AllowPrivateAccess = "true")) class UStaticMeshComponent* DoorFrameMesh; UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category = "Door", meta = (AllowPrivateAccess = "true")) class UBoxComponent* DoorTrigger;
ひとつずつ解説していきます。
UPROPERTY([specifier, specifier, ...], [meta=(key=value, key=value, ...)]) Type VariableName;
UPROPERTY :
UPROPERTYを簡単に説明すると、エンジン内に存在するUPROPERTYマクロを使用した
変数の宣言です。こいつを使うことによってBPClassとの連携が取れます。
specifier :
specifier(指定子)はUPROPERTYマクロを宣言する際に記述することによって、宣言子に指定子を追加しさまざまな振る舞いを持たせることができるものです。
公式ドキュメントにくわーーーしく載っています。
docs.unrealengine.com
meta :
metaを一言で説明すると、パラメータの編集を制御できるようにするためのもの。です。
Dv7Pavilionさんが詳しく説明してくれてますので、詳しくはリンクをクリック
qiita.com
2 : ソースファイルのコンストラクタにComponentを追加し、階層を設定
Door.cpp
ADoor::ADoor() { DoorMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Door")); DoorFrameMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrame")); DoorTrigger = CreateDefaultSubobject<UBoxComponent>(TEXT("DoorTrigger")); // アタッチ RootComponent = DoorFrameMesh; DoorMesh->SetupAttachment(DoorFrameMesh); DoorTrigger->SetupAttachment(DoorMesh); }
Componentを追加はCreateDefaultSubobject,Attachに関してはSetupAttachmentです。
詳しく説明すると、今回の主題とはかけ離れてしまうので各自よろしくお願いします...
3 : Begin,EndOverlap関数の定義
Door.h
// BeginOverlap UFUNCTION(BlueprintCallable, Category = "Door", meta = (AllowPrivateAccess = "true")) void OnDoorTriggerBeginOverlap(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult); // EndOverlap UFUNCTION(BlueprintCallable, Category = "Door", meta = (AllowPrivateAccess = "true")) void OnDoorTriggerEndOverlap(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
なお、関数の引数ですがこれからのバージョンアップで増えたり減ったりすると思うので、あくまで動作を保証するバージョンは4.14.0
ということにさせてください。
UFUNCTIONマクロですが、BPClassと連携が取れる(関数Ver)とでも思っておいてください。
Door.cpp
// OverlapEvent Override DoorTrigger->OnComponentBeginOverlap.AddDynamic(this, &ADoor::OnDoorTriggerBeginOverlap); DoorTrigger->OnComponentEndOverlap.AddDynamic(this, &ADoor::OnDoorTriggerEndOverlap);
AddDynamicヘルパーマクロというものを用いることで、自動的に関数名の文字列を生成してくれるそうです。
これによってOverlapEventのBindingは完了です。
あとは関数を呼び出して好きなタイミングで使えばOK
Door.cpp
void ADoor::OnDoorTriggerBeginOverlap(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult) { // 好きな処理を書いてください。 } void ADoor::OnDoorTriggerEndOverlap(class UPrimitiveComponent* HitComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { // 好きな処理を書いてください。 }
Full Code
Door.h
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "GameFramework/Actor.h" #include "Door.generated.h" UCLASS() class SIDESHOOTERCPP_API ADoor : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties ADoor(); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick( float DeltaSeconds ) override; // BeginOverlap UFUNCTION(BlueprintCallable, Category = "Door", meta = (AllowPrivateAccess = "true")) void OnDoorTriggerBeginOverlap(class UPrimitiveComponent* HItComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult); // EndOverlap UFUNCTION(BlueprintCallable, Category = "Door", meta = (AllowPrivateAccess = "true")) void OnDoorTriggerEndOverlap(class UPrimitiveComponent* HItComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); // Add primitive UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Door") class UStaticMeshComponent* DoorMesh; UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Door") class UStaticMeshComponent* DoorFrameMesh; UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Door") class UBoxComponent* DoorTrigger; };
Door.cpp
// Fill out your copyright notice in the Description page of Project Settings. #include "SideShooterCpp.h" #include "Door.h" // Sets default values ADoor::ADoor() { // 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; // Create the Object DoorMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Door")); DoorFrameMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrame")); DoorTrigger = CreateDefaultSubobject<UBoxComponent>(TEXT("DoorTrigger")); // Attach process RootComponent = DoorFrameMesh; DoorMesh->SetupAttachment(DoorFrameMesh); DoorTrigger->SetupAttachment(DoorMesh); // OverlapEvent Override DoorTrigger->OnComponentBeginOverlap.AddDynamic(this, &ADoor::OnDoorTriggerBeginOverlap); DoorTrigger->OnComponentEndOverlap.AddDynamic(this, &ADoor::OnDoorTriggerEndOverlap); } // Called when the game starts or when spawned void ADoor::BeginPlay() { Super::BeginPlay(); } // Called every frame void ADoor::Tick( float DeltaTime ) { // 扉が開く処理 } void ADoor::OnDoorTriggerBeginOverlap(class UPrimitiveComponent* HItComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult) { // 好きな処理を書いてください。 } void ADoor::OnDoorTriggerEndOverlap(class UPrimitiveComponent* HItComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { // 好きな処理を書いてください。 }
・おまけ
BeginOverlap,EndOverlapをイベントにしBPへ公開したい場合は
Door.h
// BeginOverlap UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Door", meta = (AllowPrivateAccess = "true")) void OnDoorTriggerBeginOverlap(class UPrimitiveComponent* HItComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult); // EndOverlap UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Door", meta = (AllowPrivateAccess = "true")) void OnDoorTriggerEndOverlap(class UPrimitiveComponent* HItComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
Door.hのBegin,EndOverlap関数のUFUNCTIONにBlueprintImplementableEventを追加し、Door.cppの関数の定義を消せばOKです。
コンパイルしてDoorClassを継承したDoorBPClassを作り、
自分だけのイベントを作っちゃいましょう('ω')
これでOverlapEventに関しては終わり!
お次はAnimBPをC++から制御する処理についてです。
Animation
仕様 :
マウスの右クリックでADS(銃のサイトを覗き、安定させること)のAnimationをさせる。
1 : AnimBPを作成する。
ここでひとつ注意です。必ず AnimInstanceClass を継承したAnimBPClassを作成してください。
名前はなんでもOkです。
2 : AnimInstanceClassを継承したC++Classを作る
わかりやすい名前で作成の後、コードを書いていきましょう。
今回は TPSAnimInstance という名前で作成しました。
3 : 変数宣言
TPSAnimInsatnce.h
UCLASS(transient,Blueprintable,hideCategories=AnimInstance,BlueprintType) class SGRAYSHOOTER_API UTPSAnimInstance : public UAnimInstance { GENERATED_UCLASS_BODY() public: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Combat") bool bIsAiming; };
4 : コンストラクタで値の初期化処理
TPSAnimInstance.cpp
UTPSAnimInstance::UTPSAnimInstance(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // ここに変数の初期値を設定します。 bIsAiming = false; }
Full Code :
TPSAnimInstance.h
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "Animation/AnimInstance.h" #include "TPSAnimInstance.generated.h" /** * */ UCLASS(transient,Blueprintable,hideCategories=AnimInstance,BlueprintType) class SIDESHOOTERCPP_API UTPSAnimInstance : public UAnimInstance { GENERATED_UCLASS_BODY() public: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Combat") bool bIsAiming; };
TPSAnimInsatnce.cpp
// Fill out your copyright notice in the Description page of Project Settings. #include "プロジェクトの名前.h" #include "TPSAnimInstance.h" UTPSAnimInstance::UTPSAnimInstance(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { // ここに変数の初期値を設定します。 bIsAiming = false; }
5 : 使用しているCharacterClassで関数、変数を制作
ThirdPersonCharacter.h
UCLASS(config=Game) class ThirdPersonCharacter : public ACharacter { GENERATED_BODY() UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category = "Combat") class USkeletalMeshComponent* myMesh; protected: void Aim(); void StopAim(); };
6 : CharacterClassのコンストラクタで初期化処理
AThirdPersonCharacter::AThirdPersonCharacter() { // 上にごちゃごちゃ書いてあると思いますが気にせず。 myMesh = GetMesh(); }
7 : PlayerInputComponentを設定
BPからバインドされたインプットイベントを呼ぶにはノード一つで対応できますがC++だと少しだけ手間が必要になります。
(1) : ThirdPersonCharacterのソースファイルにコードを書きます。
ThirdPersonCharacter.cpp
void AThirdPersonCharacter::SetupInputComponent(class UInputComponent* PlayerInputComponent) { PlayerInputComponent->BindAction("ADS",IE_Pressed, this, &AThirdPersonCharacter::Aim); PlayerInputComponent->BindAction("ADS",IE_Released, this, &AThirdPersonCharacter::StopAim);
PlayerInputComponentのBindAction,BindAxis関数(今回使っていませんが皆さんのCharacterClassにあると思うので説明させていただきます。)ですが、
引数はこのようになっています。
void AYourCharacterClassName::SetupInputComponent(class UInputComponent* PlayerInputComponent) { PlayerInputComponent->BindAction(FName ActionName, EInput KeyEvent, AYourCharacterClassName *Object, void (AYourCharacterClassName:: *Func)); PlayerInputComponent->BindAxis(FName AxisName, AYourCharacterClassName *Object, void (AYourCharacterClassName:: *Func)); }
BindAction :
FName ActionName : Project Settings->
Inputに設定したイベントネームのことです。今回は "ADS"という名前のインプットアクションです。
EInput KeyEvent : 列挙型でKeyEventが6つ定義されています。
IE_Pressed : キーが押された時実行
IE_Released : キーが離された時実行
IE_Repeat : キーが押されている限り実行(ですが、他のキーの割り込みが入ると中断されるそうです。)
IE_DoubleClick : キーがダブルクリックされた時実行
IE_Axis : キーのAxisValueを送る
IE_MAX : すみません、不明です...。
AYourCharacterClassName *Object :
現在、使用しているCharacterClassを参照してあげてください。今回は自分自身なので this です。
void(AYourCharacterClassName:: *Func) :
キーを押したとき,離した時に呼ぶイベントを参照してください。今回はAim,StopAim関数を呼んでいます。
8 : Aim,StopAim関数を実装
ThirdPersonCharacter.cpp
void AThirdPersonCharacter::Aim() { // もしmyMeshがnullならreturn if(!myMesh) return; // UTPSAnimInstanceのAnimationを定義し、myMeshにアサインされているAnimBPにCast UTPSAnimInstance *Animation = Cast<UTPSAnimInstance>(myMesh->GetAnimInstance()); // もしCastに失敗したらreturn if(!Animation) return; // UTPSAnimInstanceのbIsAimingにtrueを代入 Animation->bIsAiming = true; } void AThirdPersonCharacter::StopAim() { // もしmyMeshがnullならreturn if(!myMesh) return; // UTPSAnimInstanceのAnimationを定義し、myMeshにアサインされているAnimBPにCast UTPSAnimInstance *Animation = Cast<UTPSAnimInstance>(myMesh->GetAnimInstance()); // もしCastに失敗したらreturn if(!Animation) return; // UTPSAnimInstanceのbIsAimingにfalseを代入 Animation->bIsAiming = false; }
Full Code :
ThirdPersonCharacter.h
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. #pragma once #include "GameFramework/Character.h" #include "ThirdPersonCharacter.generated.h" UCLASS(config=Game) class AThirdPersonCharacter : public ACharacter { GENERATED_BODY() UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category = "SkeletalMesh",meta = (AllowPrivateAccess = "true")) class USkeletalMeshComponent* myMesh; protected: void Aim(); void StopAim(); };
ThirdPersonCharacter.cpp
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. #include "YourProjectName.h" #include "ThirdPersonCharacter.h" #include "TPSAnimInstance.h" AThirdPersonCharacter::AThirdPersonCharacter() { myMesh = GetMesh(); } ////////////////////////////////////////////////////////////////////////// // Input void AThirdPersonCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { // set up gameplay key bindings PlayerInputComponent->BindAction("ADS", IE_Pressed, this, &AThirdPersonCharacter::Aim); PlayerInputComponent->BindAction("ADS", IE_Released, this, &AThirdPersonCharacter::StopAim); } void AThirdPersonCharacter::Aim() { if (!myMesh) return; UTPSAnimInstance *Animation = Cast<UTPSAnimInstance>(myMesh->GetAnimInstance()); if (!Animation) return; Animation->bIsAiming = true; } void AThirdPersonCharacter::StopAim() { if (!myMesh) return; UTPSAnimInstance *Animation = Cast<UTPSAnimInstance>(myMesh->GetAnimInstance()); if (!Animation) return; Animation->bIsAiming = false; }
ここまできたらAnimBPに飛びましょう。(コンパイルを忘れずに!)
9 : AnimBPでParentClassを再定義
変更が完了したらBPをコンパイルして下さい。
ちゃんと動作してるかテストしてみましょうか。
画面にtrueと表示されていればOKです。
10 : 実際にプレイヤーにAnimationさせる
・まずはStateMachineを用意します。なお、StateMachineの説明は本題からそれてしまうので、
docs.unrealengine.com
こちらをご覧ください('ω')
・StateEditorに飛んだら新しいStateを用意しましょう。
名前はなんでもいいと思いますが、今回は " idle " という名前のStateと " Aiming " という名前のStateを作ります。
・idle内に " Idle_Rifle_Hip " を追加してあげます。
・お次はAimingStateを追加します。
Aiming内に " Idle_Rifle_Ironsights " を追加します。
・遷移ルールを設定します
"idle" - "Aiming"
"Aiming" - "idle"
ちなみにUPROPERTYのCategoryはこんな感じで活躍できます。(便利!)
・Test!!
Characterがこんな感じでAnimationしていればOKです('ω')
Animation編・・・END!!
[LINETRACE]
おなじみのLineTraceです。
銃の発射処理やikなどにも使われているこの処理をUnrealC++でやってみましょう。
1 : ObjectClassを継承したClassを作る
今回は MyStaticLibrary という名前のClassを作りました。
2 : LineTrace関数を定義する
MyStaticLibrary.h
public: /** LineTrace */ static FORCEINLINE bool Trace( UWorld* World, AActor* ActorToIgnore, const FVector& Start, const FVector& End, FHitResult& HitOut, ECollisionChannel CollisionChannel = ECC_Pawn, bool ReturnPhysMat = false)
・FORCEINLINEマクロ
コードを強制的にインライン化するものです。(inlineの強いVerかな...?)
inlineについてはこちらを参照。
インライン関数 - Wikipedia
3 : 関数の中身を書いていく
MyStaticLibrary.h
public: /** LineTrace */ static FORCEINLINE bool Trace( UWorld* World, AActor* ActorToIgnore, const FVector& Start, const FVector& End, FHitResult& HitOut, ECollisionChannel CollisionChannel = ECC_Pawn, bool ReturnPhysMat = false) { if (!World) { return false; } FCollisionQueryParams TraceParams(FName(TEXT("VictoreCore Trace")), true, ActorToIgnore); TraceParams.bTraceComplex = true; //TraceParams.bTraceAsyncScene = true; TraceParams.bReturnPhysicalMaterial = ReturnPhysMat; //Ignore Actors TraceParams.AddIgnoredActor(ActorToIgnore); //Re-initialize hit info HitOut = FHitResult(ForceInit); //Trace! World->LineTraceSingleByChannel( HitOut, Start, End, CollisionChannel, TraceParams ); return (HitOut.GetActor() != NULL); }
・FCollisionQueryParams
Collisionの衝突判定に関する情報がいろいろと入っています。
今回の処理で言えばbTraceComplex(trueなら元のmeshのCollisionから衝突判定を取り、falseならmeshに割り当てられているcollisionから衝突判定を取る),
bReturnPhysicalMaterial(PhysicalMatを返すか否か),IgnoreActor(Traceを無視するActorを指定できる)などです。便利...
・FHitResult
おなじみのヒットリザルトです。
4 : コンストラクタの定義
MyStaticLibrary.cpp
UMyStaticLibrary::UMyStaticLibrary(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { //変数の初期化とかしてね!! }
ではCharacterClassに戻りLineTraceを呼び出す関数を定義し、PlayerInputComponentのBindActionでInputActionをBindしましょう!
5 : CharacterClassにLineTrace関数を定義
※ThirdPersonCharacterを使っていると仮定してのコードです。
ThirdPersonCharacter.h
UCLASS(config=Game) class AThirdPersonCharacter : public ACharacter { GENERATED_BODY() protected: void LineTrace(); }
他にいろいろ書かれていると思いますが気にしなくて大丈夫です。
6 : InputActionを設定
一度UE4のEditorに戻りInputActionを設定しましょう。
今回は "Fire" という名前のActionをマウスの左ボタンに割り当てました。こんな感じ
7 : cppにMyStaticLibraryをincludeし関数の呼び出し
ThirdPersonCharacter.cpp
#include "MyStaticLibrary.h" AThirdPersonCharacter::AThirdPersonCharacter() { void AThirdPersonCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { PlayerInputComponent->BindAction("Fire",IE_Pressed, this, &AThirdPersonCharacter::LineTrace); } void AThirdPersonCharacter::LineTrace() { ACharacter* myCharacter = UGameplayStatics::GetPlayerCharacter(AActor::GetWorld(),0); const FVector Start = myCharacter->GetActorLocation(); const FVector End = Start + myCharacter->GetActorRotation().Vector() * お好きな距離(5000とか); FHitResult HidData(ForceInit); if(UMyStaticLibrary::Trace(AActor::GetWorld(), myCharacter, Start, End, HitData) && HitData.GetActor()) { //お好きな処理をどぞ!! } }
・おまけ
Traceが当たった時にBlueprintでEventを呼び出すものを "実装" してみましょう。
ThirdPersonCharacter.h
UCLASS(config=Game) class AThirdPersonCharacter : public ACharacter { GENERATED_BODY() public: UFUNCTION(Category = "Trace", BlueprintImplementableEvent, BlueprintCallable) void OnTraceHit(FVector ImpactPoint, AActor* HitActor);
・次に先ほどの //お好きな処理をどぞ! の部分に先ほどの関数を呼び出してあげるだけで....
ThirdPersonCharacter.cpp
{ HitPoint = HitData.ImpactPoint; AActor* HitActor = HitData.GetActor(); OnTraceHit(HitPoint,HItActor); }
こんなことが可能に....素晴らしい。
以上で本ブログは終了になります。張り切ってかなり長く、見にくくなってしまい申し訳ありません...。
間違っている箇所がありましたら修正依頼をお願いします
いよいよ明日はクリスマスイブ、ぼっちを決め込む皆さんに demuyan さんの 「 何か裏っぽいネタを書きます 」が癒してくれることを願って終わりとさせていただきます。