[UEFN]第7章.白血球アイテムをオブジェクト指向でプログラムしよう
執筆者さんコメント
この記事は 7nap がモンドリアン様クリエイティブ作品「Save the CEO:2911-3111-5636」を作成する過程で、いろいろな情報を参照し見よう見まねで実践した内容を記載しています。必ずしも*正攻法ではない可能性がありますのでご了承ください。(*正しくない、執筆後に変更になっているなど)
シリーズ記事
第1章 第2章 第3章 第4章 第5章 第6章 第7章
前書き
「Save the CEO!!」は下記のVerseファイル構成になっています。
・Boss_Main.verse
一番のメインスクリプトおよびボスのライフサイクル
・Boss_Grind.verse
白血球アイテム制御のメインスクリプト
・ha_spawn.verse
白血球アイテム個々の被インスタンス化クラス
今回のnoteはBoss_Grind.verseとha_spawn.verseについて記載しています。
このアイテム(白血球)をオブジェクト指向化したい
このアイテムは白血球なのでボスをやっつけるためのプレイヤーの味方をしてくれるアイテムです。
赤色アイテム:スピードアップ
黄色アイテム:強い武器が手に入る
青色アイテム:体力が回復する
これらがボスの体力が一定以下になると現れて下記のようにフィールド上をくるくるまわります。
白血球はこんな構成で作られています。
コレクティブルオブジェクト:白血球本体
オーディオプレイヤーデバイス:白血球が飛んでいることを示すピコンピコン音
ポインティングライト:白血球を照らす赤黄青のライト
白血球は、赤黄青のそれぞれのアイテム特性にあわせて3つ分のプログラムを書いていくわけです。
しかし、3つのプログラムは、それぞれのアイテム特性(スピードアップ、武器付与、体力回復)の部分以外は同じプログラムになるはずです。
その同じ部分を3回コピペして作るのではなく、同じ部分は共通化して簡略化しよう、というのが今回のミソです。
オブジェクト指向化せずに作ると、、
第6章の通りに作ると下記のようにそれぞれ3つ分のプログラムを作成することになります。
わかりやすいといえばわかりやすいのですが、共通プログラム部分を修正したくなった場合同じ修正を3か所分行う必要があり、面倒かつ直し忘れなどバグが誘発される可能性が高くなります。
また、もし4つ目を作りたくなった場合どれか一つから完全コピーして作り出す必要があり、これまた面倒で行数が増えてくると頭がおかしくなりそうになります。。
詳細なVerseコードは第6章をご確認ください。
オブジェクト指向とは?
オブジェクト指向とは何か、、何かの文献を引用してもっともらしく記載したいところですが、私の知識が追いつかないのでやめておきます。。
私はオブジェクト指向について学び始めたばかりで、理解しきれていないのですが今回の目的だけで考えるならば、「白血球アイテムの設計図を作り、必要な分だけ実体化させて使用するもの」と表現します。
(ものすごくざっくりです。)
何かプログラムを修正する際にも設計図のほうだけをいじれば、メイン側のプログラムには何も変更を行う必要がないわけです。また設計図を何度も使いまわすことができるのでメイン側の処理でプログラム全体をコピペして使いまわす必要がないのです。
オブジェクト指向化する前は、、
白血球のオブジェクト構成はこんな感じです。
これらをスピードアップ、武器付与、体力回復のアイテムごとにそれぞれ設置しており、Verse側の editable設定もオブジェクト指向化前は下記にようにそれぞれ3回分指定しています。
@editable
Ha1 : collectible_object_device = collectible_object_device{}
@editable
Ha2 : collectible_object_device = collectible_object_device{}
@editable
Ha3 : collectible_object_device = collectible_object_device{}
@editable
Ha1_light : customizable_light_device = customizable_light_device{}
@editable
Ha2_light : customizable_light_device = customizable_light_device{}
@editable
Ha3_light : customizable_light_device = customizable_light_device{}
@editable
Ha1_Audio : audio_player_device = audio_player_device{}
@editable
Ha2_Audio : audio_player_device = audio_player_device{}
@editable
Ha3_Audio : audio_player_device = audio_player_device{}
このコードは下記画像の赤枠の部分ですね。
editable宣言を3アイテム分3回宣言、さらには個々のプログラム部分も各白血球ごとに3回分関数を記載することになります。
(詳しくは第6章)
オブジェクト指向化すると、、
下記黄色い枠が設計図のほうです。
白血球アイテムは3つありますが、基本的な働きはすべて一緒ですので共通部分は青い四角の「共通オブジェクト」と「共通プログラム」(共通クラス)に記載します。
そして、各アイテムの特殊な働きについては共通クラスから継承して継承クラスに記載します。「継承」とはインスタンス化(実体化)させたときに共通プログラム部分を持続させたまま特殊部分を付け加えることができるものです。
こうすることで、共通部分に修正を加える際、今まで3か所同じ修正を加えなければならなかったのですが、1か所だけ修正を加えればすべてのインスタンス化したものに修正されるわけです。
プログラムを作ります
※最後にプログラム全体を貼り付けますので、細かく説明していきます。
まずは設計図側です。
クラス名はこのようにします。
ha_spawner := class<concrete>:
copy
クラスの宣言
見慣れない <concrete> という指定子がでてきました。
<concrete>をつけない場合、
ha_spawner := class():
copy
のようなクラスの宣言をするわけですが、今回設計図側で白血球オブジェクトのeditableを宣言させたいのですが、<concrete>がついていないと creative_device 側でオブジェクトの紐づけをすることができません。
下記画像が creative_device で各オブジェクトとの紐づけを行っているものですが、<concrete>をつけないでインスタンス化すると下記が表示されず紐づけることができません。
【参考】
ちなみに紐づけたいオブジェクトはさきほども表示した下記一連のオブジェクトです。(さらに各アイテムの特殊デバイスなど)
editable及び定数宣言
オブジェクト指向化前は存在している分何度もeditableを記載していましたが、今作ろうとしているものは、設計図ですのでそれぞれ一個だけ宣言しています。
この設計図をインスタンス化(実体化)させることでx1、x2、x3とcreative_device 側で定義できるオブジェクトが増えていくわけですね。
#白血球自体
@editable
Ha : collectible_object_device = collectible_object_device{}
#白血球が存在することを示すピコンピコン音
@editable
Ha_Audio : audio_player_device = audio_player_device{}
#白血球を照らすライト
@editable
Ha_light : customizable_light_device = customizable_light_device{}
#アイテムを取得したことを示すHUD
@editable
Ha_HUD : hud_message_device = hud_message_device{}
#プレイヤーがアイテムを取得した時の音
@editable
get_Audio : audio_player_device = audio_player_device{}
var Ha_flag : logic = false
#白血球のリスポーン間隔
Ha_interval : float = 10.0
#String型を Message型に変換するための関数
StringToMessage<localizes>(value:string)<computes> : message = "{value}"
結論から言ってしまえば、下記のように「SpeedUP」「HPUP」「GetWeapon」とそれぞれインスタンス化しており、共通オブジェクトおよび特殊オブジェクトが定義できるようになっています。(後述)
白血球アイテムの共通機能
白血球アイテム(スピードアップ、武器付与、体力回復)の共通関数(メソッド)部分を作っていきます。下記画像の赤枠部分です。
Init():void=
#白血球アイテムを見えなくする
Ha.Hide()
#白血球アイテムを照らすライトを消す
Ha_light.TurnOff()
#白血球アイテムを取得した際のアクション
Ha.CollectedEvent.Subscribe(Ha_Interacted)
#アイテムを取得すると呼び出される
Ha_Interacted(Agent:agent) : void =
spawn:
Ha_spawn(Agent)
return
#さらに spawn で呼び出される
Ha_spawn(Agent:agent)<suspends>:void=
#アイテムを取得したら呼び出す
Ha_HUD_show(Agent, "PowerUP!!")
return
#アイテム取得したことを示すHUDメッセージ表示
Ha_HUD_show(Agent : agent, Message : string):void=
# stringをmessageに変換して格納
Message_Ha : message = StringToMessage(Message)
#HUDデバイスへメッセージ登録
Ha_HUD.SetText(Message_Ha)
#HUD表示時間を 2.0秒に
Ha_HUD.SetDisplayTime(2.0)
#HUDを Agent の画面に表示
Ha_HUD.Show(Agent)
copy
上記は設計図の共通部分ですね。
Init():void=
はゲーム開始時にメインスクリプト側から呼び出します。(後述)
この共通部分に対して特殊部分を上書きしていきます。(下記)
これがオブジェクト指向の「継承」になります。
白血球アイテムの特殊機能
白血球アイテム(スピードアップ、武器付与、体力回復)の特殊関数(メソッド)部分を作っていきます。下記画像の赤枠部分です。
speedup := class<concrete>(ha_spawner):
@editable
SpeedUp : movement_modulator_device = movement_modulator_device{}
#スピードアップ
Ha_spawn<override>(Agent:agent)<suspends>:void=
set Ha_flag = false
Ha_light.TurnOff()
spawn:
Speed(Agent)
Ha_HUD_show(Agent, "SPEED UP!!")
SpeedUp.Activate(Agent)
Sleep(Ha_interval)
Ha.Respawn(Agent)
set Ha_flag = true
Ha_light.TurnOn()
return
Speed(Agent:agent)<suspends>:void=
get_Audio.Register(Agent)
get_Audio.Play()
get_Audio.Stop()
get_Audio.Unregister(Agent)
return
hpup := class<concrete>(ha_spawner):
@editable
Muteki_powerup : visual_effect_powerup_device = visual_effect_powerup_device{}
var Star_Count : int = 0
Init<override>():void=
Ha.Hide()
Ha_light.TurnOff()
Ha.CollectedEvent.Subscribe(Ha_Interacted)
Muteki_powerup.Despawn()
#体力回復
Ha_spawn<override>(Agent:agent)<suspends>:void=
set Ha_flag = false
Ha_light.TurnOff()
Ha_HUD_show(Agent, "HEALING HP!!")
get_Audio.Register(Agent)
get_Audio.Play()
if (FortniteCharacter := Agent.GetFortCharacter[]):
spawn:
Star(FortniteCharacter, Agent)
Sleep(Ha_interval)
Ha.Respawn(Agent)
Muteki_powerup.Spawn()
set Ha_flag = true
Ha_light.TurnOn()
return
Star(Fort:fort_character, Agent:agent)<suspends>:void=
Fort.SetShield(200.0)
loop:
Fort.Heal(30.0)
Sleep(1.0)
set Star_Count += 1
if(Star_Count = 10):
get_Audio.Unregister(Agent)
set Star_Count = 0
break
return
getweapon := class<concrete>(ha_spawner):
@editable
itemgranter : item_granter_device = item_granter_device{}
#つよ武器ゲット
Ha_spawn<override>(Agent:agent)<suspends>:void=
set Ha_flag = false
Ha_light.TurnOff()
get_Audio.Register(Agent)
get_Audio.Play()
get_Audio.Unregister(Agent)
Ha_HUD_show(Agent, "GETTING STRONG WEAPON!!")
itemgranter.CycleToNextItem(Agent)
#itemgranter.GrantItem(Agent)
Sleep(Ha_interval)
Ha.Respawn(Agent)
set Ha_flag = true
Ha_light.TurnOn()
return
アイテムごとの効果部分についてはプログラムから読み解いていただくとして、要所を説明したいと思います。
speedup := class<concrete>(ha_spawner):
----
hpup := class<concrete>(ha_spawner):
----
getweapon := class<concrete>(ha_spawner):
それぞれの特殊クラス宣言について、
共通クラス(ha_spawner)と同じように<concrete>を付与して宣言します。
なぜここにも<concrete>をつけるかというとそれぞれの特殊効果(ムーブメントモジュレータ、ビジュアルエフェクト、アイテムグランター)の editable設定をさらに追加したいためです。
行の最後に(ha_spawner)を指定しています。
これは共通のeditableやプログラムを格納している ha_spawner の内容をそのまま継承した状態で特殊クラスを作成したいためです。
特殊クラスが共通クラスを継承した状態で、メインスクリプト側で特殊クラスをインスタンス化すれば特殊クラスのみならず共通クラスの内容も一緒にインスタンス化できるという便利なものです。
【参考】
・特殊クラス側で追加している editable設定について
@editable
SpeedUp : movement_modulator_device = movement_modulator_device{}
---------------------
@editable
Muteki_powerup : visual_effect_powerup_device = visual_effect_powerup_device{}
---------------------
@editable
itemgranter : item_granter_device = item_granter_device{}
共通クラス側で既に宣言されている editable に対してさらに追加される形になります。(継承しているため)
creative_device の表示としては下記のようになります。
黄色枠が特殊クラスで宣言された editable、青枠が共通クラスで宣言された editableになります。
追加されているのがわかりますね。
・特殊クラスの特殊関数(メソッド)
上記で説明したように共通クラスで宣言した変数やメソッドは「継承」により特殊クラスに引き継ぐことができますが、もし継承した変数やメソッドを都合よく書き換えたい場合、<override>を指定することにより上書きすることができます。
例えば hpup 特殊クラスは下記のようにすでに共通クラスにて宣言済みである Init 関数について <override> で上書きしています。
Init<override>():void=
Ha.Hide()
Ha_light.TurnOff()
Ha.CollectedEvent.Subscribe(Ha_Interacted)
Muteki_powerup.Despawn()
元々の共通クラスの Init の内容は下記の通りでしたので、
Muteki_powerup.Despawn()
を追加して上書きしてインスタンス化できるようにしています。
Init():void=
Ha.Hide()
Ha_light.TurnOff()
Ha.CollectedEvent.Subscribe(Ha_Interacted)
ちなみに、
Muteki_powerup は白血球オブジェクトを取得するとキャラクターの体の色が変わるようにするビジュアルエフェクトデバイスです。
ゲーム開始時は非表示にしておきたいので、このように Despawn を追加しています。
他にも、
Ha_spawn<override>(Agent:agent)<suspends>:void=
についても同様に <override> していますので、コードを確認してみてください。
【参考】
メインスクリプトからの呼び出し
設計図は出来上がりました。
これらはまだ設計図ですので実体はありません。メインスクリプト側でインスタンス化をして実体化させます。
editableでインスタンス化
@editable
SpeedUP : speedup = speedup{}
@editable
HPUP : hpup = hpup{}
@editable
GetWeapon : getweapon = getweapon{}
スピードアップの特殊クラスのインスタンス化の部分だけで説明します。
speedup 型の speedup{} クラスを SpeedUP という名称でインスタンス化しています。
これをやることで メインスクリプトの creative_device にて下記のように設計図が実体化されて、設計図内で指定している editable の編集ができるようになるのですね。
初期化処理
上記で設計図が実体化されましたのであとは実体化された側への指示を出すだけです。下記はゲーム開始時にそれぞれのインスタンスに対して初期化処理実行を指示しています。
OnBegin<override>()<suspends>:void=
SpeedUP.Init()
HPUP.Init()
GetWeapon.Init()
SpeedUP.Init()
実体化されたインスタンス名の後ろに(ドット)、その後にインスタンス内の関数名を指定することで関数実行ができます。
インスタンス内のデバイスに指示を与える
#ボスの体力が一定になったら白血球が現れる
if (boss_main.First_time = false):
#Ha_flag が true であれば白血球が出現
set SpeedUP.Ha_flag = true
set HPUP.Ha_flag = true
set GetWeapon.Ha_flag = true
#白血球を出現させる
SpeedUP.Ha.Show()
HPUP.Ha.Show()
GetWeapon.Ha.Show()
#白血球を照らすライトを照らす
SpeedUP.Ha_light.TurnOn()
HPUP.Ha_light.TurnOn()
GetWeapon.Ha_light.TurnOn()
set SpeedUP.Ha_flag = true
設計図側クラス(ha_spawn)では false に設定されているのでインスタンス化した直後は false なわけですが、この宣言により true に変更しています。
こちらも Init のときと同様にインスタンス名の後ろに(ドット)、その後にインスタンス内の変数名を指定すること set することができます。
SpeedUP.Ha.Show()
こちらは Ha(コレクティブオブジェクトデバイス)に対して Show (表示)の指示を出しています。インスタンス化していない場合(オブジェクト指向化していない)場合は、Ha.Show() のみでよかったのですが、インスタンス化されているデバイスですので、Init と同様にインスタンス名の後ろに(ドット)、その後に Ha.Show() を指定することでインスタンス化されたデバイスに対して指示を出すことができます。
SpeedUP.Ha_light.TurnOn()
こちらも同じです。
インスタンス名の後ろに(ドット)、その後に Ha_light.TurnOn() を指定することでインスタンス化されたデバイスに対して指示を出すことができます。
あとがき
私の知識の中だけでオブジェクト指向化を説明してみましたが、オブジェクト指向とはこんなものではなくもっとすごいはずなのです。
また私がレベルアップしたらアウトプットしていきたいと思います。
間違いとか、こうしたほうが効率的とかあればお教えください。
メインスクリプト全体(Boss_Grind.verse)
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Vehicles }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Playspaces }
using { /Verse.org/Random }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Curves }
using { /Verse.org/Simulation/Tags }
using { /UnrealEngine.com/Temporary/SpatialMath }
# See https://dev.epicgames.com/documentation/en-us/uefn/create-your-own-device-in-verse for how to create a verse device.
# A Verse-authored creative device that can be placed in a level
Boss_Grind := class(creative_device):
#メインヴァースデバイス
@editable
boss_main : Boss_Main = Boss_Main{}
#ボス戦用ホバー
@editable
Hover_BP : creative_prop = creative_prop{}
#@editable
#Ha_Spawners : []ha_spawner = array{}
@editable
Ha_BP : creative_prop = creative_prop{}
@editable
SpeedUP : speedup = speedup{}
@editable
HPUP : hpup = hpup{}
@editable
GetWeapon : getweapon = getweapon{}
StringToMessage<localizes>(value:string)<computes> : message = "{value}"
OnBegin<override>()<suspends>:void=
SpeedUP.Init()
HPUP.Init()
GetWeapon.Init()
#白血球をまわす
spawn:
Move_Ha()
#白血球の回転
Move_Ha()<suspends>:void=
var Ha_Yaw : float = 0.0
var Ha_Pitch : float = 0.0
var Ha_Roll : float = 0.0
var Ha_ue : logic = true
loop:
if (boss_main.Boss_alive_Check = false):
break
targetRotation_Ha := MakeRotationFromYawPitchRollDegrees(Ha_Yaw, Ha_Pitch, Ha_Roll)
HaLocation := Ha_BP.GetTransform().Translation
#白血球が下ラインに来たらピコン音を鳴らす
if (Ha_ue = false):
Ha_BP.MoveTo(HaLocation + vector3{Z:=200.0}, targetRotation_Ha, 1.0)
if (SpeedUP.Ha_flag = true):
SpeedUP.Ha_Audio.Play()
if (HPUP.Ha_flag = true):
HPUP.Ha_Audio.Play()
if (GetWeapon.Ha_flag = true):
GetWeapon.Ha_Audio.Play()
set Ha_ue = true
else:
Ha_BP.MoveTo(HaLocation - vector3{Z:=200.0}, targetRotation_Ha, 1.0)
set Ha_ue = false
#白血球を少し回す
set Ha_Yaw += 10.0
#ボスの体力が一定になったら白血球が現れる
if (boss_main.First_time = false):
set SpeedUP.Ha_flag = true
set HPUP.Ha_flag = true
set GetWeapon.Ha_flag = true
SpeedUP.Ha.Show()
HPUP.Ha.Show()
GetWeapon.Ha.Show()
SpeedUP.Ha_light.TurnOn()
HPUP.Ha_light.TurnOn()
GetWeapon.Ha_light.TurnOn()
#ボスを倒した後の処理
#Star_Audio.Stop()
#Speed_Audio.Stop()
SpeedUP.Ha.Hide()
HPUP.Ha.Hide()
GetWeapon.Ha.Hide()
設計図側スクリプト(ha_spawn.verse)
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Characters }
ha_spawner := class<concrete>:
#@editable
#BossGrind : Boss_Grind = Boss_Grind{}
#@editable
#boss_main : Boss_Main = Boss_Main{}
@editable
Ha : collectible_object_device = collectible_object_device{}
@editable
Ha_Audio : audio_player_device = audio_player_device{}
@editable
Ha_light : customizable_light_device = customizable_light_device{}
@editable
Ha_HUD : hud_message_device = hud_message_device{}
@editable
get_Audio : audio_player_device = audio_player_device{}
var Ha_flag : logic = false
#白血球のリスポーン間隔
Ha_interval : float = 10.0
StringToMessage<localizes>(value:string)<computes> : message = "{value}"
Init():void=
Ha.Hide()
Ha_light.TurnOff()
#collectable_deviceのインタラクト用
Ha.CollectedEvent.Subscribe(Ha_Interacted)
Ha_Interacted(Agent:agent) : void =
spawn:
Ha_spawn(Agent)
return
Ha_spawn(Agent:agent)<suspends>:void=
Ha_HUD_show(Agent, "PowerUP!!")
return
Ha_HUD_show(Agent : agent, Message : string):void=
Message_Ha : message = StringToMessage(Message)
#Print ("{Total_Score}")
Ha_HUD.SetText(Message_Ha)
Ha_HUD.SetDisplayTime(2.0)
Ha_HUD.Show(Agent)
speedup := class<concrete>(ha_spawner):
@editable
SpeedUp : movement_modulator_device = movement_modulator_device{}
#スピードアップ
Ha_spawn<override>(Agent:agent)<suspends>:void=
set Ha_flag = false
Ha_light.TurnOff()
spawn:
Speed(Agent)
Ha_HUD_show(Agent, "SPEED UP!!")
SpeedUp.Activate(Agent)
Sleep(Ha_interval)
Ha.Respawn(Agent)
set Ha_flag = true
Ha_light.TurnOn()
return
Speed(Agent:agent)<suspends>:void=
get_Audio.Register(Agent)
get_Audio.Play()
get_Audio.Stop()
get_Audio.Unregister(Agent)
return
hpup := class<concrete>(ha_spawner):
@editable
Muteki_powerup : visual_effect_powerup_device = visual_effect_powerup_device{}
var Star_Count : int = 0
Init<override>():void=
Ha.Hide()
Ha_light.TurnOff()
Ha.CollectedEvent.Subscribe(Ha_Interacted)
Muteki_powerup.Despawn()
#体力回復
Ha_spawn<override>(Agent:agent)<suspends>:void=
set Ha_flag = false
Ha_light.TurnOff()
Ha_HUD_show(Agent, "HEALING HP!!")
get_Audio.Register(Agent)
get_Audio.Play()
if (FortniteCharacter := Agent.GetFortCharacter[]):
spawn:
Star(FortniteCharacter, Agent)
Sleep(Ha_interval)
Ha.Respawn(Agent)
Muteki_powerup.Spawn()
set Ha_flag = true
Ha_light.TurnOn()
return
Star(Fort:fort_character, Agent:agent)<suspends>:void=
Fort.SetShield(200.0)
loop:
Fort.Heal(30.0)
Sleep(1.0)
set Star_Count += 1
if(Star_Count = 10):
get_Audio.Unregister(Agent)
set Star_Count = 0
break
return
getweapon := class<concrete>(ha_spawner):
@editable
itemgranter : item_granter_device = item_granter_device{}
#つよ武器ゲット
Ha_spawn<override>(Agent:agent)<suspends>:void=
set Ha_flag = false
Ha_light.TurnOff()
get_Audio.Register(Agent)
get_Audio.Play()
get_Audio.Unregister(Agent)
Ha_HUD_show(Agent, "GETTING STRONG WEAPON!!")
itemgranter.CycleToNextItem(Agent)
#itemgranter.GrantItem(Agent)
Sleep(Ha_interval)
Ha.Respawn(Agent)
set Ha_flag = true
Ha_light.TurnOn()
return
一緒にゲーム、創りませんか?
【大募集】 モンドリアンは #PLATEAU を活用した #UEFN コンテンツをリリースするために日々、研究中! お気軽にお問合せ&DMを!
この記事が気に入ったらサポートをしてみませんか?