Playerの作成
ダメージとダメージ処理
ダメージ処理の作成です。 以下の手順で作成します。
- ダメージアニメをアニメーションモンタージュで作成する
- アニメーションBPでアニメーションモンタージュをできるように修正
- AnmStateComponent.hをアニメーションモンタージュが呼び出せるように修正
- ダメージを受信しダメージアニメーションを再生するキャラクタを作成する
ダメージアニメションの作成
アニメーションはアニメーションモンタージュで作成します。
このアニメーションは連続再生が可能です。
アニメーション→モンタージュを選び、対応するスケルトンを選びます。
<モンタージュを選択>
<スケルトンを選択>
名前はMAnm_Damage
にしました。ダブルクリックで編集を開始します。
再生アニメーションの選択と調整
再生したいアニメーションを選択してドロップします。
詳細パネルから開始時間と終了時間を設定します。
次に、アニメーション通知からAnm State NotifyでStartPackとEndを設定します。
<通知の設定>
<通知内容の設定:画像ではendを設定>
アニメーションの作成はひとまず終了です。
アニメーションブループリントの修正
アニメーションモンタージュの再生を行うためにアニメーションブループリントにslotノードを追加します。
AnmStateComponent.hの修正
AnmStateComponent.hにアニメーションモンタージュの読み込みと再生処理を追加します。
追加部分
protected:
UPROPERTY()
UAnimInstance* AnimInst; //!< アニメーションインスタンス
UPROPERTY()
TMap<FString, UAnimMontage*> MontagesAnims; //!< アニメーションモンタージュのリスト
//-------------------------------------------
/// アニメーションインスタンスの取得
//-------------------------------------------
void InitAnimInst() {
if (AnimInst) return;
AActor* ac = GetOwner();
if (!ac) return;
auto Mesh = ac->FindComponentByClass<USkeletalMeshComponent>();
if (!Mesh) return;
AnimInst = Mesh->GetAnimInstance();
}
public:
//-------------------------------------------
/// アニメーションインスタンスを取得する
//-------------------------------------------
UAnimInstance* GetAnmInst(void) {
return AnimInst;
}
//-------------------------------------------
/// アニメーションモンタージュの登録
//-------------------------------------------
void EntryMontageAnim(FString AnimName, FString AssetPath) {
UAnimMontage** anm = MontagesAnims.Find(AnimName);
if (anm == nullptr) {
UAnimMontage* Obj = LoadObject<UAnimMontage>(NULL, *AssetPath, NULL, LOAD_None, NULL);
if (Obj) {
MontagesAnims.Add(AnimName, Obj);
}
}
}
//-------------------------------------------
/// アニメーションモンタージュの取得
//-------------------------------------------
UAnimMontage* GetMontagesAnim(FString AnimName) {
return MontagesAnims[AnimName];
}
//-------------------------------------------
/// アニメーションモンタージュの再生
//-------------------------------------------
float PlayMontage(FString AnimName,
float InPlayRate = 1.f,
EMontagePlayReturnType ReturnValueType = EMontagePlayReturnType::MontageLength,
float InTimeToStartMontageAt = 0.f,
bool bStopAllMontages = true
) {
return AnimInst->Montage_Play(GetMontagesAnim(AnimName),
InPlayRate,
ReturnValueType,
InTimeToStartMontageAt,
bStopAllMontages);
}
EntryMontageAnim()
を使ってアニメーションを読み込みます。
UAnimInstance
が必要になるのでOwner
のMeshから取得します。
再生はPlayMontage()
で行います。
全文
/**
* @file AnmStateComponent.h
* @brief アニメーション状態
* @author inuvatico
* @date 2020/11/08
* @version 1.0
* @copyright 2018 inuvatico
* Released under the MIT license.
* see https://opensource.org/licenses/MIT
* @par (new/Add/Change : 2020/10/17)
*
*/
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "AnmStateComponent.generated.h"
//---------------------------------------------------------------------
// アニメーションモンタージュインデックス
//---------------------------------------------------------------------
UENUM(BlueprintType)
enum class UAnmMtIndex : uint8 {
AMTID_NONE UMETA(DisplayName = "None"),
AMTID_DAMAGE UMETA(DisplayName = "Damage"),
};
//---------------------------------------------------------------------
// アニメーションインデックス
//---------------------------------------------------------------------
UENUM(BlueprintType)
enum class UAnmIndex : uint8 {
ANMID_NONE UMETA(DisplayName = "None"),
ANMID_IDLE_RUN UMETA(DisplayName = "IdleRun"),
ANMID_ATTACK1 UMETA(DisplayName = "Attack1"),
ANMID_ATTACK2 UMETA(DisplayName = "Attack2"),
ANMID_DAMAGE UMETA(DisplayName = "Damage"),
ANMID_DEAT UMETA(DisplayName = "Death"),
ANMID_TESTANM1 UMETA(DisplayName = "Test1"),
ANMID_TESTANM2 UMETA(DisplayName = "Test2"),
ANMID_TESTANM3 UMETA(DisplayName = "Test3"),
//Num UMETA(Hidden)
};
//---------------------------------------------------------------------
/// アニメーションイベント
//---------------------------------------------------------------------
UENUM(BlueprintType)
enum class UAnmEvent : uint8 {
ST_START UMETA(DisplayName = "Start"),
ST_END UMETA(DisplayName = "End"),
ST_SHOOT1 UMETA(DisplayName = "Shoot1"),
ST_SHOOT2 UMETA(DisplayName = "Shoot2"),
};
//---------------------------------------------------------------------
/// アニメーションの状態
//---------------------------------------------------------------------
USTRUCT(BlueprintType)
struct FBPAnmState
{
GENERATED_USTRUCT_BODY();
UPROPERTY(BlueprintReadOnly, Category = "BPA")
UAnmIndex AnmIndex = UAnmIndex::ANMID_NONE; //!< 実行中のアニメーション
UPROPERTY(BlueprintReadOnly, Category = "BPA")
float Speed = 0; //!< アニメーションスペースのレート 0~1
UPROPERTY(BlueprintReadOnly, Category = "BPA")
UAnmMtIndex AnmMtIndex = UAnmMtIndex::AMTID_DAMAGE; //!< 実行中のアニメーションモンタージュ
FBPAnmState() {
AnmIndex = UAnmIndex::ANMID_NONE;
Speed = 0;
}
};
//---------------------------------------------------------------------
/// アニメーション管理用コンポーネント
//---------------------------------------------------------------------
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class UAnmStateComponent : public UActorComponent
{
GENERATED_BODY()
//-------------------------------------------
/// コンストラクタ
//-------------------------------------------
UAnmStateComponent()
:AnimInst(nullptr)
{}
public:
UPROPERTY(BlueprintReadOnly, Category = "Variable|Blueprint")
FBPAnmState BPAnmState; //!< アニメーションの状態
//-------------------------------------------
/// BeginPlay
//-------------------------------------------
void BeginPlay()
{
Super::BeginPlay();
InitAnimInst();
}
public:
//-------------------------------------------
/// 指定のイベントをクリア
//-------------------------------------------
void ClearEventNo(int No) {
AnmEventBit &= ~(1 << No);
}
//-------------------------------------------
/// アニメーションイベントを受信
//-------------------------------------------
void ReceiveEvent(UAnmEvent AnmEvent) {
if (AnmEvent == UAnmEvent::ST_START) {
if (Started == 0) {
Started = 1;
Ended = 0;
AnmEventBit = 0;
}
}
if (AnmEvent == UAnmEvent::ST_END) {
Started = 0;
Ended = 1;
}
AnmEventBit |= (1 << (uint32)AnmEvent); //受信イベントを保存
//if (GEngine) {
// GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Orange, FString::Printf(TEXT("AnmEvent=%d"), (int)AnmEvent));
//}
}
//-------------------------------------------
/// アニメーションの変更要求
//-------------------------------------------
void SetAnmIndex(UAnmIndex AnmIndex) {
AnmEventBit = 0;
BPAnmState.AnmIndex = AnmIndex;
//if (GEngine) {
// GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Orange, FString::Printf(TEXT("ANMIDX=%d"), (int)BPAnmState.AnmIndex));
//}
}
uint32 AnmEventBit = 0; //!< 通知されたイベント
protected:
UPROPERTY()
UAnimInstance* AnimInst; //!< アニメーションインスタンス
UPROPERTY()
TMap<FString, UAnimMontage*> MontagesAnims; //!< アニメーションモンタージュのリスト
//-------------------------------------------
/// アニメーションインスタンスの取得
//-------------------------------------------
void InitAnimInst() {
if (AnimInst) return;
AActor* ac = GetOwner();
if (!ac) return;
auto Mesh = ac->FindComponentByClass<USkeletalMeshComponent>();
if (!Mesh) return;
AnimInst = Mesh->GetAnimInstance();
}
public:
//-------------------------------------------
/// アニメーションインスタンスを取得する
//-------------------------------------------
UAnimInstance* GetAnmInst(void) {
return AnimInst;
}
//-------------------------------------------
/// アニメーションモンタージュの登録
//-------------------------------------------
void EntryMontageAnim(FString AnimName, FString AssetPath) {
UAnimMontage** anm = MontagesAnims.Find(AnimName);
if (anm == nullptr) {
UAnimMontage* Obj = LoadObject<UAnimMontage>(NULL, *AssetPath, NULL, LOAD_None, NULL);
if (Obj) {
MontagesAnims.Add(AnimName, Obj);
}
}
}
//-------------------------------------------
/// アニメーションモンタージュの取得
//-------------------------------------------
UAnimMontage* GetMontagesAnim(FString AnimName) {
return MontagesAnims[AnimName];
}
//-------------------------------------------
/// アニメーションモンタージュの再生
//-------------------------------------------
float PlayMontage(FString AnimName,
float InPlayRate = 1.f,
EMontagePlayReturnType ReturnValueType = EMontagePlayReturnType::MontageLength,
float InTimeToStartMontageAt = 0.f,
bool bStopAllMontages = true
) {
return AnimInst->Montage_Play(GetMontagesAnim(AnimName),
InPlayRate,
ReturnValueType,
InTimeToStartMontageAt,
bStopAllMontages);
}
private:
int Started;
int Ended;
};
//-------------------------------------------
/// アニメーション管理用コンポーネント
//-------------------------------------------
UCLASS()
class UAnmStateNotify : public UAnimNotify {
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
UAnmEvent AnmEvent;
void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override {
if (AActor* Actor = MeshComp->GetOwner()) {
auto AnmStateComponent = Actor->FindComponentByClass<UAnmStateComponent>();
if (AnmStateComponent) {
AnmStateComponent->ReceiveEvent(AnmEvent);
}
}
}
};
ダメージを受信しダメージアニメーションを再生するキャラクタを作成する
プレイヤーとは別にダメージを受信しダメージアニメーションを再生するキャラクタを作成します。
エネミーコントローラーの作成
エネミーコントローラー(EnemyIController01.h)はプレイヤーコントローラーに似ています。
実装するアクションは
- Init
- IdleRun
- Damage
の3つです。
アクターはACppPlyBaseを流用します。
EnemyIController01.h
/**
* @file EnemyIController01.h
* @brief エネミーコントローラー
* @author inuvatico
* @date 2021/01/17
* @version 1.0
* @copyright 2020 inuvatico
* Released under the MIT license.
* see https://opensource.org/licenses/MIT
* @par (new/Add/Change : 2020/10/17)
*
*/
#pragma once
#include "CoreMinimal.h"
#include "AIController.h"
#include "EnemyIController01.generated.h"
class ACppPlyBase;
class UDamageComponent;
using AEnemy01 = ACppPlyBase;
/**
* エネミーコントローラー
*/
UCLASS()
class AEnemyIController01 : public AAIController
{
GENERATED_BODY()
public:
AEnemyIController01();
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
public:
enum class CallType {
INIT,
ACT,
TERM,
MAX
};
//-------------------------------------------------------------
/// Actionの定義
//-------------------------------------------------------------
enum class ActType : int {
Init,
IdleRun,
Damage,
MAX,
};
//-------------------------------------------------------------
/// Actionメンバーの定義
//-------------------------------------------------------------
void ActInit(CallType type);
void ActIdleRun(CallType type);
void ActDamage(CallType type);
//-------------------------------------------------------------
/// Actionの切り替え
//-------------------------------------------------------------
using AThisController = AEnemyIController01;
void SetAction(ActType actionID) {
using fnc = void (AThisController::*)(CallType type);
static const fnc fncList[ActType::MAX] = {
&AThisController::ActInit
,&AThisController::ActIdleRun
,&AThisController::ActDamage
};
if ((int)actionID >= (int)ActType::MAX)
return;
fnc NewAction = fncList[(int)actionID];
if (TermAction) {
(this->*TermAction)(CallType::TERM);
}
TermAction = NewAction;
(this->*NewAction)(CallType::INIT);
CurrentAction = NewAction;
}
private:
void (AThisController::* CurrentAction)(CallType type) = nullptr;
void (AThisController::* TermAction)(CallType type) = nullptr;
private:
AEnemy01* MyActor = nullptr;; //!< 操作対象のActor
UDamageComponent* DamageComponent = nullptr;//!< ダメージを受信するコンポーネントのポインタ
};
続いて実装です。
BeginPlay()
でアニメーションの読み込みを行います。Tick()
でダメージの受信をチェックしています。
ダメージを受信するとSetAction(ActType::Damage);
を
呼び出してダメージアニメションを再生します。
EnemyIController01.cpp
/**
* @file EnemyIController01.cpp
* @brief エネミーコントローラー
* @author inuvatico
* @date 2021/01/17
* @version 1.0
* @copyright 2020 inuvatico
* Released under the MIT license.
* see https://opensource.org/licenses/MIT
* @par (new/Add/Change : 2020/10/17)
*
*/
#include "Components/SkeletalMeshComponent.h"
#include "UObject/ConstructorHelpers.h"
#include "../ply/CppPlyBase.h"
#include "EnemyIController01.h"
//-------------------------------------------------
//! コンストラクタ
//-------------------------------------------------
AEnemyIController01::AEnemyIController01() {
}
//-------------------------------------------------
//! BeginPlay()
//-------------------------------------------------
void AEnemyIController01::BeginPlay() {
Super::BeginPlay();
MyActor = Cast <ACppPlyBase>(GetCharacter());
if (!MyActor) return;
DamageComponent = MyActor->FindComponentByClass<UDamageComponent>();
//ダメージアニメーションの登録
{
#define ANIMMONTAGE_PATH TEXT("AnimMontage'/Game/app/grp/chr/chr00/AnimMontage_00.AnimMontage_00'")
#define ANMMON_DAMAGE "DAMAGE"
MyActor->AnmStateComponent->EntryMontageAnim(ANMMON_DAMAGE, ANIMMONTAGE_PATH);
}
SetAction(ActType::Init);
}
//-------------------------------------------------
//! Tick
//-------------------------------------------------
void AEnemyIController01::Tick(float DeltaTime)
{
if (!MyActor) return;
if (DamageComponent) {
if (DamageComponent->RcvDamage) {
DamageComponent->RcvDamage = 0;
SetAction(ActType::Damage);
}
}
if (CurrentAction) {
(this->*CurrentAction)(CallType::ACT);
}
}
//==============================================================
// Actionメンバー
//==============================================================
void AEnemyIController01::ActInit(CallType type){
if (type == CallType::ACT) {
SetAction(ActType::IdleRun);
}
}
void AEnemyIController01::ActIdleRun(CallType type) {
if (CallType::INIT == type) {
MyActor->AnmStateComponent->SetAnmIndex(UAnmIndex::ANMID_IDLE_RUN);
}if (CallType::ACT == type) {
}
}
void AEnemyIController01::ActDamage(CallType type) {
if (CallType::INIT == type) {
MyActor->AnmStateComponent->PlayMontage(ANMMON_DAMAGE);
}if (CallType::ACT == type) {
if (MyActor->AnmStateComponent->AnmEventBit & (1 << static_cast<int>(UAnmEvent::ST_END))) {
SetAction(ActType::IdleRun);
return;
}
}
}
エネミーブループリントの作成
エネミーブループリントはプレイヤーブループリント(BP_CppPlyBase)を流用して作成します。
BP_CppPlyBaseを複製してBP_CppEnemyとします。
BP_CppEnemyを開いてコントローラーをAEnemyIController01に変更します。
最後にマップに配置して実行です!