Part 1の一部になります。
前回の続き
今回はGameplay Ability System (通称GAS)を使って、HPが0になったら死ぬを実装します。
お約束 | |
---|---|
この記事作成にあたって使用した主なUnreal Engine バージョン | UE4.27.2 / UE5 Preview1 |
本日のゴール
HPが0になったら死んでリスポーンを実装します。
参考文献
https://github.com/Pantong51/GASContent/blob/master/Tutorial_Attribute_Delegates.md
https://github.com/tranek/GASDocumentation/blob/master/README.md#412-setup-and-initialization
第一段階:HPが0になった時に発動するイベントを作る
雪玉が当たった時にGameplay Effect (GE) でHPを減らし、HPが0になったら死ぬを実装します。
まずキャラクター.hにいきまして、#includeの下にこれを宣言します
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FHealthChange, float, CharacterHealth);
Delegateの宣言ですね。
続いてpublic:の下にこれを加えます。
UFUNCTION(BlueprintNativeEvent, Category= "GameplayAbility|Die")
void Die();
virtual void Die_Implementation();
UPROPERTY(BlueprintAssignable, Category = "Delegates")
FHealthChange OnHealthChange;
UFUNCTION(BlueprintCallable, Category = "GameplayAbility|PlayerState")
bool IsAlive() const;
UFUNCTION(BlueprintCallable, Category = "GameplayAbility|Cancel")
virtual void CancelCharacterAllAbilities();
死んだ時に発生するイベントと、死の終わりの関数、先程のDelegateのインスタンス、生きてるかどうかの確認関数ですね。最後のCancelCharacterAllAbilities()は後で説明します。
死の終わりの関数は今の所使う気がないですが、まあいい…
続いてprotected:の下にこれを加えます。
FGameplayTag DeadTag;
FGameplayTag EffectRemoveOnDeathTag;
// Called when the game starts or when spawned
virtual void BeginPlay() override;
// Attribute changed callbacks
virtual void HealthAttributeUpdated(const FOnAttributeChangeData& Data);
もうBeginPlay()あるよっって方が多いと思うので、その場合はBeginPlay()についてはスルーしてください。(publicの方にあるかも)
このHealthAttributeUpdatedがHPが変わった時に実行される関数になります。
続いてキャラクター.cppを編集していきます。
まずはBeginPlay()から。
void Aキャラクター名称::BeginPlay()
{
Super::BeginPlay();
if (AbilitySystem)
{
AbilitySystem->GetGameplayAttributeValueChangeDelegate(UAttributeSet名称::GetHealthAttribute()).AddUObject(this, &Aキャラクター名称::HealthAttributeUpdated);
}
}
すでにBeginPlay()あるよって方は、if (AbilitySystem){ } の {}内に上記のうちの
AbilitySystem->GetGameplayAttributeValueChangeDelegate(UAttributeSet名称::GetHealthAttribute()).AddUObject(this, &Aキャラクター名称::HealthAttributeUpdated);
を加えてください。
ここではAbilitySystem->GetGameplayAttributeValueChangeDelegate()が、Attributeの値の変化が起こった時に、HealthAttributeUpdatedという作った関数を起動するという処理になります。
キャラクター.cppの最後に下記を加えます。
bool Aキャラクター名称::IsAlive() const
{
return GetHealth() > 0.0f;
}
void AToiroTemplateCharacter::Die_Implementation()
{
}
void Aキャラクター名称::HealthAttributeUpdated(const FOnAttributeChangeData& Data)
{
OnHealthChange.Broadcast(Data.NewValue);
if (!IsAlive() && !AbilitySystem->HasMatchingGameplayTag(DeadTag))
{
if (GetLocalRole() == ROLE_Authority){
//死のイベントは、サーバーでだけ実施
Die();
}
}
}
void Aキャラクター名称::CancelCharacterAllAbilities()
{
if (GetLocalRole() != ROLE_Authority || !AbilitySystem)
{
return;
}
AbilitySystem->CancelAllAbilities();
}
HealthAttributeUpdated() が、HPが変更時に呼び出される関数で、if内はHPが0 つまり IsAliveがFalseのときで、なおかつDeadTagがついていないときです。このときDie()関数を呼び出します。
Die()についてはこれから実装していきます。
まずはコンパイルして、エラーが出てないことを確かめましょう。
第二段階:Dieイベントの実装
死んだ時に何を処理すればいいか?ですね。
下準備で、GameModeを継承したBPを作ります。
※GameModeはすでに作っている方だったら不要です。
さて、自分の操作キャラのBPに移動しましょう。
関数のオーバーライド をクリックしてDieを呼び出しましょう |
このDieイベントからあとに死の実装をしていきます。まずはCancelCharacterAllAbilities()を呼び出します。これによってこのキャラが持っているすべてのGAをキャンセルします。
とりあえずラグドール化してみました。ラグドール化のやり方はぐぐるとポンポン出てくるので見てください。
サーバーとクライアント側で二回リスポーンしないように、Is Locally Controlledをつないでリスポーン処理を行います。リスポーン処理はサーバーで実施するので、RPC Run On Server「Do Repawn」を作りました。
でもこれ、RPC Run On Serverじゃなくて、はじめからSwitch Has Authority でサーバー側でやればいいんじゃない?と思うかもしれませんが、それだとClientのControllerを渡せないんですよね。これがマルチプレイヤーの面倒なところです。
作ったRPCの中身はこんな感じ
見て分かる通り、実際の処理はGameModeで実施します。理由は、GameModeはサーバーにしか存在しないので間違いが発生しないからです。
Get GameModeをCastして、GameMode内で作ったイベント「Respawn Player by GameMode」を起動します。このイベントには、使っているPlayer Controllerの情報と、Destroyするアクターの情報を渡してあげます。
このGameMode内のイベントは上の画像ではRun On Serverになってますが、普通のカスタムイベントでいいです。ちなみに、GameModeはAuthorityでしかアクセスできないので、GameModeにキャストする前にRPC Run On Serverを介する必要があります。
GameMode内で作ったイベント「Respawn Player by GameMode」 |
はい、中身はこんな感じです。Player Controllerが有効かを確かめて、有効なら新しいアクターをSpawnして、一回コントローラーをUn Possessしてその後、新しく作ったキャラにPossessします。ちなみにですがPossessもサーバー側でしか実施されないノードです。
これでどうでしょうか?実行してみます。
確かに無事リスポーンされるようになったのですが、クライアント側だけリスポーンするとなぜかGAが使えない状態になってます。とんだ縛りプレイだよ。
クライアント側だけがうまく動かないときは、Replicateの設定から見直す
これがマルチプレイヤーゲーム制作の原則です。誰がそう言ってたかって?
俺だよ!!
問題の発生の原因はGASのASC (Ability System Component)のReplicateでしょうね。GAS Documentationを見てみると、それっぽい手順が書いてあったので、早速実践します。(参考文献参照)
virtual void OnRep_PlayerState() override;
これをキャラクター.hファイルに加えます。protected:の下ですね。
次にキャラクター.cppファイルに下記を加えます。
void Aキャラクター名称::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
AbilitySystem->InitAbilityActorInfo(this, this);
}
これでコンパイルして、実行してみると、ちゃんとリスポーン後もASCが起動するようになりました。めでたしめでたし。(この結論にたどり着くまで6時間かかりました笑)
ちなみにですが、もしかすると「俺ASCは、PawnじゃなくてPlayerStateに紐づけているんだけど」という場合があるかもしれませんね。(Fortniteもそうらしいです。) これはRespawnの前後で有しているAbilityなどを保持したいかどうか、などによるみたいです。この場合は、上記のReplicationの記述をPlayerState側に記述する必要があります。
ちなみにちなみにですが、実際の実装では結局GameModeではなく、GameStateのBPを使ってキャラクターをスポーンさせることにしました。GameStateはAuthority以外でもいじれてしまうので、GameStateを使う場合にはSwitch Has Authorityノードを使ってあげると良きかなと思います。
(まあRPC Run On Serverを使っているので無問題とは思いますが…)
おわり
0 件のコメント:
コメントを投稿