はじめに
アドベントカレンダー最終日25日....本日でとうとう終わりですね(藁)
そして今日は何といってもクリスマス!
といっても論文執筆の日々が続いております...orz。筆者はまったく楽しめないと思います。
本題
今回は、ネットワークアプリケーション(マルチキャスト)を使ったGameSystemAbilityにて苦戦したことについてつらつら書いていきたいと思います。
使用したバージョン
- Unreal Engine 5.6.1
- Visual Studio 2022
小話
とうとう Unreal Engine 5.7 が出ましたね。GASとネットワーク関連については特になかった気がします。少しずつ新機能を触ってみたいです。
手順2
「YourProjectEditor.Target.cs」を開きます。
手順3
DefaultBuildSettings = BuildSettingsVersion.V5;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_6;
以上の文を
DefaultBuildSettings = BuildSettingsVersion.V6;
IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
に変更します。
(「YourProject.Target.cs」ではないのでご注意を!中身がほとんど同じですが、それだけ直してもうまくいきません。)
手順4
(先にツールバーから「Build」>「Clean Solution」を押してみてください。)
ツールバーから「Build」>「Rebuild Solution」を押して、コンパイルを成功させてください。
手順5
エクスプローラに戻って、3つのファイルを消します。(なかなか消しずらいですが...)
「Binaries」、「Intermediate」、(あれば、「DerivedDataCache」)
手順6
「YourProject.uproject」を右クリック、「Generated Visutal Studio projects files」を押します。
手順7
「YourProject.Target.cs」を開きます。
手順8
(先にツールバーから「Build」>「Clean Solution」を押してみてください。)
ツールバーから「Build」>「Rebuild Solution」を押して、コンパイルを成功させてください。
手順8
「YourProject.uproject」を開きます
開けたら手順9を行ってください。開けなかった場合は、手順2~手順8までを試行錯誤してください。
手順9
ツールバー>[ツール]>[Visual Studioを更新」を押してみてください。(念のため)
手順10
.uprojectを削除してしまったら?
手順1
ファイルで以下の文章を作成します。保存名を「YourProject.uproject」にします。(拡張子を変更します)
{
"FileVersion": 3,
"EngineAssociation": "5.7",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "YourProject",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
// この部分はslnファイルで設定してあるmodule名
// 「YourProject.build.cs」に
// PublicDependencyModuleNames.AddRange
// で定義されているものをとりあえず入れます。
]
}
],
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
},
{
// 「YourProject.build.cs」に定義されている
// プラグインを有効にするためのものです。
// 有効になっていないと開けません。
// とりあえず定義しておいて、
// 不必要なものはエラーが出るのでそのたびに削除してください。
},
]
}
手順2
作成したファイルを「YourPorjct.sln」が置いてあるところに置きます。
手順3
上の手順2「手順2 「YourProject.Target.cs」を開きます。」からやってみてください
緒言
私は最初始めた際に GameAbilitySystem(GAS) どころか、マルチキャストにすら振れたことがなかったためいろいろな場面で苦戦しました。
マルチキャストのところからお話しします。
私が検証しているNetModeは Play As Listen Serverです。
1人サーバー、その他クライアントで起動するタイプです。
ほかにも Play As Client(すべて人がクライアント)にもできるみたいです。
1.サーバーとクライアントの同期
私が一番苦戦したことは、サーバーとクライアントをどのようにして同期させるかという部分です。
サーバーとクライアントの同期ができない!!!
って言葉が基本でした。
1章 サーバーで実行、クライアントで実行
本章では,マルチキャスト入門として,サーバー/クライアントで同期する通信(プロトコル,規約)Remote Procedure Call通信(RPC通信)について解説をしています。
詳しい説明は,Historia さんの公式ブログにて
[UE4]マルチプレイでの所有権とPRC
新しく出てきた言葉
UPROPERTYマクロ
- Replicated
- ReplicatedUsing=FuncName
UFUNCTIONマクロ
- Server
- Reliable
- UnReliable
- NetMulticast
その他マクロ
- DOREPLIFETIMEマクロ
仮想関数
- GetLifetimeReplicatedProps関数
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const;
変数
- bReplicates(bool型)
関数
- HasAuthority 関数(サーバーなのかクライアントなのかを確認)
1-1. 値の共有
以下のソースコードでは、クライアント(別のユーザー)へ反映されないコードです。
どこが違うのかわかりますでしょうか?(質問?疑問?)
UPROPERTY(ReplicatedUsing=OnRep_Value,VisibleAnywhere)
float value = 40.0f;
// -- 省略 --
public:
ASample();
UFUNCTION()
void OnRep_Value(float NewValue);
protected:
virtual void GetLifetimeReplicatedProps(const TArray<FLifetimeProperty> & OutLifetimeProps) const override;
// -- 省略 --
ASample::ASample()
{
bReplicates = true; // 共有を有効にする
}
void ASample::OnRep_Value(float NewValue)
{
// 値を変更する(サーバーにも共有してくれる!)
value = NewValue;
}
void ASample::GetLifetimeReplicatedProps(const TArray<FLifetimeProperty> & OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ASample,value);
}
正解は、ReplicatedUsing=OnRep_Valueの関数は、サーバー共有後に呼ばれる(通知される)クライアント用の関数とのこと。(詳しく説明すると、サーバー共有後に自動的に呼ばれるコールバック関数です。
道理で、共有されないわけだ...。
あと、そもそもクライアントの通知は自動的に呼ばれて、値更新されるので、
UFUNCTION()
void OnRep_Value(float NewValue);
自体が間違っています。
正しくは、
UFUNCTION()
void OnRep_Value();
考え方は以下の通りです。
1. トリガー(発火)が発生
2. サーバーを更新
3. クライアントを更新
サーバーはどうやって呼ぶべきなのか
某動画配信サイトを視聴していたところ、(その方はBPを使用)「サーバーで実行」、「クライアントで実行」で行いましょう。
「クライアントで実行」は自動で行われるため、「サーバーへ通知」する関数をつくって呼んであげればいいのです!(確信)
UPROPERTY(ReplicatedUsing=OnRep_Value,VisibleAnywhere)
float Value = 4.0f;
UFUNCTION()
// サーバーで実行する関数(サーバーを呼び出す)
UFUNCTION(Server,Reliable) // Reliableは通信ができたかを保証する(確認するものです。UnReliableは逆に保証しません。)
void CallServer(float NewValue);
UFUNCTION()
void OnRep_Value();
// トリガーを引く関数(適当)
void Trigger();
virtual void GetLifetimeReplicatedProps(const TArray<FLifetimeProperty> & OutLifetimeProps) const override;
ASample::ASample()
{
bReplicates = true;
}
ASample::Trigger()
{
// 60にへんこうする!
CallServer(60.0f);
}
void ASample::GetLifetimeReplicatedProps(const TArray<FLifetimeProperty> & OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ASample,Value);
}
void ASample::CallServer_Implementation(float NewValue) // 末尾に _Implementation をつける必要があります
{
Value = NewValue; // サーバーを呼ぶよ!
}
void ASample::OnRep_Value()
{
FString Message = TEXT("やったね!値更新されたね!");
UKismetSystemLibrary::PrintString(this,Message,true,true,FColor::Cyan,-1.0f); // 文字化ける
}
void ASample::GetLifetimeReplicatedProps(const TArray<FLifetimeProperty> & OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ASample,Value);
}
1-2. 値以外を共有する場合の時
値ならサーバーを送るだけでいいですが、アニメーションやUIの同期は残念ながら値での共有では難しそうです。
1. クライアントで実行する関数を作ります。
2. サーバーで実行する関数でクライアントで実行する関数呼び出します。
3. クライアントで反映させたいことをやります。
(多分)
Sample();
UFUNCTION(NetMulticast)
void CallClient();
UFUNCTION(Server,Reliable)
void CallServer();
void DoSomething();
ASample::ASample()
{
bReplicates = true;
}
void ASample::CallClient_Implementation()
{
// やりたいことを呼び出す
DoSomething();
}
void ASample::CallServer_Implementation()
{
// クライアントを呼びましょう
CallClient();
}
void ASample::DoSomething()
{
}
GetLifetimeReplicatedProps関数について
virtual void GetLifetimeReplicatedProps(const TArray<FLifetimeProperty> & OutLifetimeProps) const override;
は値共有出ない場合オーバーライドする必要はないみたいです。
コンストラクタの Replicates 変数の有効化は必要らしいです。
ASample::ASample()
{
bReplicates = true;
}
実はこれには欠点があるみたいです。
今回のやり方RPC実行では、弱点があるとのことで以下のように悪いとのことです。
1. クライアント側に反映されるまでタイムラグが起こる。
2. 通信負荷が上がる。
3. 再送処理や紛失が起こりやすい。
開発状況によって、いろいろな対策方法があるみたい(Replication Graphとか)ですが、今回は機能の1つである Game Ability System (GAS) を活用することにしました!(断言)
2章 Game Ability System (GAS)
前章通りに進めていくと、「GAS=ネットワーク機能」と考えてしまうのですが、実は「GAS = ネットワーク機能」がではないとのことです。
ゲームプレイ アビリティ システム は、アクタが保有し、トリガーできるアビリティとインタラクションを構築するためのフレームワークです。
GameAbilitySystemについてはまだまだ知らないことだらけですので、
以下の2つを参考に書き連ねていきたいと思います。
- Zhi Kang Shao,Your First 60 Minutes with Game Ability System,Epic,2025
- ポジTA,Gameplay Ability System,Unral Engine R & D ~ポジLab~,2025
今回出てくるGAS機能
クラス
- AttributeSet(UAttributeSetクラス)
- GameplayAbility(UGameplayAbitiltyクラス)
- GameplayEffect(UGameplayEffectクラス)
- GameplayCue(UGameplayCueクラス)
コンポーネント
- AbitiySystemComponent(UAbilitySystemComponentクラス):ASCともいわれたり?
2.1 AttributeSet(属性) UAttributeSetクラス
UAttributeSetで出てくる用語と単語
アクセサ
- ATTRIBUTE_ACCESSORS(ClassName,PropertyName)[FGameplayAttributeData型変数のSetter,Getter,Initterを自動生成してくれる]
- GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName,PropertyName)[FGameplayAttribute専用のGetter]
- GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName)[float型の専用のGetter]
- GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName)
- GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
仮想関数
- PostAttributeChange(属性が変更されたときすぐ実行)
virtual void PostAttributeChange(const FGameplayAttribute& Attribute,float OldValue,float NewValue);
- PostGameplayEffectExecute(GameplayEffectの属性が変更されたとき)
virtual void PostGameplayEffectExecute(const FGameplayEffectCallbackData& Data);
変数
- FGameplayAttributeData 変数(属性、float型のみの対応)
- FGameplayEffectModCallbackData (引数)
- TArray<FLifetimeProperty>& (引数)
その他
- DOREPLIFETIMEマクロ(クライアント側に変数を複製するマクロ)
- DOREPLIFETIME_CONDITION_NOTIFYマクロ(複製条件や通知ポリシーを指定できるクライアント複製マクロ)
- GAMEPLAYATTRIBUTE_REPNOTIFYマクロ(GAS内部のレプリケーション通知を一括で行ってくれるマクロ)
アクセサは、属性を取得したり、設定することができる(Setter,Getter)関数を定義してくれる関数です。
2.2 GameplayEffect (UGameplayEffectクラス)
AttributeSetクラスと連携を図るクラス(本当は AbilitySystemComponent[のちに解説?] を経由して)で、属性の変化をどう扱うかを決めるクラスです。
UGameplayEffects でよく扱われる変数
DurationPolicy(EGameplayEffectDurationType型)
即効性を持たせるのか持続性を持たせるのかが設定できる値です。(コンストラクタで呼びます)
定義は以下のようになっています。
UENUM()
enum class EGameplayEffectDurationType : uint8
{
Instant,
Infinite,
HasDuration,
};
- Instant(即時・即効性)すぐに属性を変更します。与えたGameplayEffectが消えても属性は元に戻りません。元に戻したいなどは、別にGampeplayEffectクラスで変更する必要があります。
- Infinite(無限)GameEffectクラスが削除されるまで属性を変更しておくことです。削除されたら属性は元に戻ります。
- HasDuration(一定期間)一定期間属性を変更します。既定の時間たったら属性の値は元に戻ります。非同期処理。
値の設定の方法は2種類。
1. 実行時(GameplayEffectを適応させるとき)に行う場合
AbilitySystemComponent経由で変更・実行します。
(小話)
レベルって言ってますが、最初なんのことだかさっぱりわかりませんでした。Map(プレイヤーがいる場所)のことだと思ってました。あれだってレベルっていうじゃん... 今冷静に考えれば、属性ですもんね...効果スケーリングのことです。#include "SampleGameplayEffect.h"
void ASample::ApplyGameplayEffect()
{
// Context情報(被害者[EventInstigatorActor]・加害者[DamageCausorActor])
FGameplayEffectContextHandle GEContext = AbilitySystemComponent->MakeEffectContext();
GEContext.AddInstigator(EventInstigatorActor, DamageCausorActor);
// エフェクトクラス、レベル、Context情報(加害者、被害者情報とか)
FGameplayEffectSpecHandle SampleSpec = AbilitySystemComponent->MakeOutgoingSpec(USampleGameplayEffect::StaticClass(), 1.0f, GEContext);
// 効果時間を設定する
SampleSpec.SetDuration(5.0); // 5秒間だけ持続させる
if(SampleSpec.IsValid())
{
// GO!
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SampleSpec.Data.Get());
}
2. GameplayEffectクラスで設定する場合
USampleGameplayEffect::USampleGameplayEffect()
{
// 5秒に設定する
DurationMagnitude = FGameplayEffectModifier::Magnitude(FScalableFloat(5.0f));
}
Piriodの設定
Piriodは1回こっきりではなく一定時間何度も効果を実行させるものです。
設定はGameplayEffectクラスで行います。
USampleGameplayEffect::USampleGameplayEffect()
{
// 5秒に設定する
Period = 5.0f; // 5秒周期で実行される
bExecutePeriodicEffectOnApplication = true; // 効果実行期間を有効にする
}
私が実証した際は、ApplyGameplayEffectSpecToSelf関数を実行する関数にサーバーで実行をする[UFUNCTION(Server,Reliable)]をつけなくても動作できました。
必ずしも必須とのことではないとのことでした。
対象の属性を指定する
FGameplayModifierInfo型で、どの属性を対象とするのか設定することができます。
FGameplayModifierInfoの属性(クラスのメンバ変数の意味の属性)
- Attribute:FGampelayAttribute型(対象の属性)
- ModifierOp:EGameplayModOp型(演算方法)
- ModifierMagnitude:FGameplayEffectModifierInfo型(大きさ):カーブテーブルは割愛します。
Modiers:TArray → UGameplayEffectクラスのメンバ変数
特にここの部分に注目すると、以下のように宣言されています。
EGampelayModOp::Type
GameplayEffectTypes.hUENUM(BlueprintType) namespace EGameplayModOp { enum Type : int { AddBase, MultiplayAddtive, DivideAddive, MultiplyCompound = 4 AddFinal, Max, Additive = 0, Multiplicitive = 1, Division = 2, Override = 3, }; }
いっぱいありそうな気がするのですが、
C++で実際に使えるのは4つとのことです。
1.Additive(加算、マイナスを入れれば減算)
2.MultiplyCompound(乗算)
3.Division(除算)
4.Override(値の置き換え)
使用できる列挙型について
EGameplayModOp::Type について,有識者の方いらっしゃいましたら教えてください。
2.3 GameplayCue(UGampelayCueクラス)
GameplayCueは VFXやサウンド関連などの演出として 使用するクラスです。
C++のファイルどれ使う?
GameplayEffectと違い、GameplayCueにはGameplayCue.h(UGameplayCueクラス)がありません。
以下の表に沿って使い分ける必要があるみたいです。
| カテゴリ | クラス名 | 説明 / 用途 |
|---|---|---|
| 補助クラス | GameplayCueFunctionLibrary(UGameplayCueFunctionLibrary) | BlueprintからGameplayCueを呼び出すための補助クラス |
| GameplayCueInterface(UGameplayCueInterface) | ||
| 補助クラス | GameplayCueManager(UGameplayCueManager) | GameplayCueの管理・登録・呼び出しを担当するクラス |
| 効果演出クラス | GameplayCueNotify_Actor(AGameplayCueNotify_Actor) | Actorのようにインスタンス化できるクラス。 |
| 効果演出クラス | GameplayCueNotify_Burst(UGameplayCueNotify_Burst) | 瞬間的な演出を出すためのクラス。複数のインスタンス化をすることが可能。 |
| 効果演出クラス | GameplayCueNotify_BurstLatent,(AGameplayCueNotify_BurstLatent) | 時間差のある演出などに適しているクラス |
| 効果演出クラス | GameplayCueNotify_HitImpact,(UGameplayCueNotify_HitImpact) | ヒットに特化した演出用のクラス |
| 効果演出クラス | GameplayCueNotify_Looping,(AGameplayCueNotify_Looping) | ループ演出するためのクラス |
| 効果演出クラス | GameplayCueNotify_Static,(UGameplayCueNotify_Static) | シンプルな演出を作り出すクラス |
| 補助クラス | GameplayCueSet,(UGameplayCueSet) | 複数のGameplayCueを管理するクラス |
| 補助クラス | GameplayCueTranslator(UGameplayCueTranslator) | GameplayCueと実際のNotifyを関連付けるクラス |
すべてを使ったわけではないので,すべて記述することはできないです。解説猛者求ム。
GameplayEffectsのクラスは
- GameplayEffects,(UGameplayEffects)
- GameplayEffectAttributeCaptureDefinition,(FGameplayEffectAttributeCaptureDefinition)
- GameplayEffectCalculation,(UGameplayEffectCalculation)
- GameplayEffectComponent,(UGameplayEffectComponent)
- GameplayEffectCustomApplicationRequirement,(UGameplayEffectCustomApplicationRequirement)
- GameplayEffectExecutionCalculation,(UGameplayEffectExecutionCalculation)
- GameplayEffectUIData,(UGameplayEffectUIData)
- GameplayEffectUIData_TextOnly,(UGameplayEffectUIData_TextOnly)
多分特に使われるのが、
AGameplayCueNotify_Actorクラス(Actorクラスのように使いたい場合、AActorクラスの派生)、
UGameplayCueNotify_Burstクラス(瞬間的な効果音とかに使う:複数のインスタンス化可能)、
AGameplayCueNotify_BurstLatentクラス(非同期処理や時間差があるとき、AGameplayCueNotify_Actorクラスの派生)、
UGameplayCueNotify_HitImpactクラス(ヒット時のエフェクト処理を行う)、
UGameplayCueNotify_Static(シンプルなGameplayCue、Burstクラスより使い勝手がいい:1つのインスタンス,Actor を生成しません.単発の視覚効果・音などをその場で処理する用途です.)
AGameplayCueNotify_Loop(AGameplayCueNotify_Actorクラスの派生、ループ演出・継続的な演出効果にしたい場合)
例:GameplayCueNotify_Staticの場合
public:
// AbilitySystem -> GameplayEffect -> GameplayCue_Static
virtual void HandleGameplayCue(AActor* MyTarget,EGameplayCueEvent::Type EventType,const FGameplayCueParameters& Parameters) override;
void USampleCue::HandleGameplayCue(AActor* MyTarget,EGameplayCueEvent::Type EventType,const FGameplayCueParameters& Parameters)
{
// 実行されたとき
if(EventType == EGameplayCueEvent::Executed && MyTarget)
{
// 演出を行う
UGameplayStatics::PlaySoundAtLocation(MyTarget->GetWorld(),Sound,MyTarget->GetActorLocation());
// エフェクトを出す
UGameplayStatics::SpawnEmitterAtLocation(MyTarget->GetWorld(),HitEffect,MyTarget->GetActorLocation());
}
}
2.3 AbitiySystemComponent(UAbilitySystemComponentクラス):ASC
一番大切なもので、 AbilitySystemComponentは、GASの機能の管理を行うコンポーネントです。
GamplayAbilityの付与やGameplayEffectの適用、GameplayTagの管理、ネットワーク複製、クライアント・サーバーの同期、サーバの管理、Replicationの制御,
AActorにおけるGAS関連の統合を行います。
管理棟みたいなものですね。
プレイヤーキャラクターなどに追加していくものです。
最後に
これ以外にもまだまだGASは奥深く、理解が到底追いつかないほどの機能になっているのではと思っています。(特にGameplayCueクラス、GamplayEffectsクラス)
引き続きGASについて研究していきたいと思います。
参考文献
- kazuhironagai77,12章9,10,11節 GameplayAbilities API-テスト,Hatena Blog,2019,https://kazuhiro77unreal.hatenablog.com/entry/2019/02/17/210246, (最終確認日:2025年11月17日)
- kazuhironagai77,12章12節 GameplayTags API –GameplayTagsをアクターに付ける,Hatena Blog,2019,https://kazuhiro77unreal.hatenablog.com/entry/2019/02/24/214818, (最終確認日:2025年11月17日)
- ポジTA,Chapter 11 Gameplay Ability System,Unral Engine R & D ~ポジLab~,Zenn,https://zenn.dev/posita33/books/ue5_posilab_ue_research_and_development/viewer/chap_a2-00-00_gameplay_ability_system ,2025, (最終確認日:2025年11月17日)
査読者
- Epic Developer Assistant for Unreal Engine
- chatgpt
- google gemini
