UIボタンの作り方について教えてください!
初心者向けにわかりやすく解説してほしいですね!
こんなお悩みを解決します。
本記事の内容この記事を書いている僕は、クリエイター兼ブロガーです。実際に現在進行形でUEFNを学んでいます。
今回は、UIボタンの作り方について解説します。
なお、当記事の内容は、DragonRage様の動画を参考にさせていただきました。
(DragonRage様、ありがとうございます。)
※以下のコードの解説文は、プログラミング初心者の主が調べながら記述したものです。
コードの解釈に誤りがありましたら恐縮ですm(_ _)m
参考程度にどうぞ。
それでは、さっそくやっていきましょう。
UIボタンの作り方
今回は上記の動画のようなボタンUIを作成していきます。
▼ まず、UEFNを開き、「Verse Explorer」をクリックします。
▼ 青枠部分を右クリックし、「Add new Verse file to project」を選択します。
▼ デバイスネームに『menumanagerdevice』と入力し、「作成」をクリックします。
▼ Verse Explorerの青枠部分「menumanagerdevice.verse」をダブルクリックしましょう。
▼ コードを入力します。
完全なスクリプトです。using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }
using { /Verse.org/Verse }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
# Locks the choice of button styles to the current three: {button_loud = 1, button_quiet = 2, button_regular = 3}
buttonchoice_int := type{_X : int where 1 <= _X, 3 >= _X}
menumanagerdevice := class(creative_device):
# The trigger that will open up this ui menu
@editable TriggerToShowUI : trigger_device = trigger_device{}
# An array of button ui, configurable via the editor
@editable var ButtonUI : []buttonui = array{}
var MaybeMyUIPerPlayer : [player]?canvas = map{}
StringToMessage<localizes>(String : string):message = "{String}"
OnBegin<override>()<suspends>:void=
InitButtonFunctions()
TriggerToShowUI.TriggeredEvent.Subscribe(OpenUI)
OpenUI(Agent : ?agent):void=
if(ValidAgent := Agent?, Player := player[ValidAgent], PlayerUI := GetPlayerUI[Player]):
if(MyUI := MaybeMyUIPerPlayer[Player]?):
PlayerUI.RemoveWidget(MyUI)
if(set MaybeMyUIPerPlayer[Player] = false) {}
else:
NewUI := CreateMyUI(ValidAgent)
PlayerUI.AddWidget(NewUI, player_ui_slot{InputMode := ui_input_mode.All})
if(set MaybeMyUIPerPlayer[Player] = option{NewUI}) {}
CreateMyUI(Agent : agent) : canvas =
for(CurrButtonUI : ButtonUI):
case(CurrButtonUI.ButtonStyle):
1 =>
ThisButtonUI := button_loud{DefaultText := StringToMessage(CurrButtonUI.ButtonText)}
ThisButtonUI.OnClick().Subscribe(HandleSelectedUIButton)
if(set CurrButtonUI.ButtonWidgetPerPlayer[Agent] = ThisButtonUI){}
2 =>
ThisButtonUI := button_quiet{DefaultText := StringToMessage(CurrButtonUI.ButtonText)}
ThisButtonUI.OnClick().Subscribe(HandleSelectedUIButton)
if(set CurrButtonUI.ButtonWidgetPerPlayer[Agent] = ThisButtonUI){}
3 =>
ThisButtonUI := button_regular{DefaultText := StringToMessage(CurrButtonUI.ButtonText)}
ThisButtonUI.OnClick().Subscribe(HandleSelectedUIButton)
if(set CurrButtonUI.ButtonWidgetPerPlayer[Agent] = ThisButtonUI){}
_ =>
Print("~ERROR: ButtonStyle integer over/under-flow")
CanvasArray := ReturnCanvasSlots(Agent)
MyInteractableButtons : canvas = canvas:
Slots := CanvasArray
return MyInteractableButtons
ReturnCanvasSlots(Agent : agent):[]canvas_slot=
var CanvasSlotArray : []canvas_slot = array{}
for(ThisButtonUI : ButtonUI):
if(ThisWidget := ThisButtonUI.ButtonWidgetPerPlayer[Agent]):
Agent.LoadButtonState(ThisButtonUI)
ThisSlot := canvas_slot:
Offsets := margin{Top := ThisButtonUI.ButtonPosition.Y, Left := ThisButtonUI.ButtonPosition.X, Bottom := ThisButtonUI.ButtonSize.Y, Right := ThisButtonUI.ButtonSize.X}
ZOrder := 0
SizeToContent := ThisButtonUI.SizeToContent
Widget := ThisWidget
set CanvasSlotArray += array{ThisSlot}
return CanvasSlotArray
DeleteUI(Message : widget_message) : void=
if(PlayerUI := GetPlayerUI[Message.Player], MyUI := MaybeMyUIPerPlayer[Message.Player]?):
PlayerUI.RemoveWidget(MyUI)
if(set MaybeMyUIPerPlayer[Message.Player] = false) {}
HandleSelectedUIButton(Message : widget_message):void=
if(PlayerUI := GetPlayerUI[Message.Player], MyUI := MaybeMyUIPerPlayer[Message.Player]?):
Player := Message.Player
ThisWidget := Message.Source
StaleButtonUI := Player.FindButtonUI(ThisWidget)
if(ThisButtonUI := StaleButtonUI?):
if(ThisButtonUI.RemovesUIOnClick?):
DeleteUI(Message)
ThisButtonUI.ActivateTrigger(Player)
else:
Print("~ERROR: No buttonui found!")
(Agent : agent).FindButtonUI(ThisWidget : widget):?buttonui=
for(ThisButtonUI : ButtonUI):
if(StaleWidget := ThisButtonUI.ButtonWidgetPerPlayer[Agent]):
if(ThisWidget = StaleWidget):
return option{ThisButtonUI}
return false
(ThisButtonUI : buttonui).ActivateTrigger(Player : player):void=
ThisSignalDestination := ThisButtonUI.ButtonSignal_Send
if(ThisSignalDestination.SendInstigator?):
ThisSignalDestination.TriggerToActivate.Trigger(Player)
ThisSignalDestination.TriggerToActivate.Trigger()
# Loads all button states that have 'SaveButtonState' turned on
(Agent : agent).LoadButtonState(ThisButtonUI : buttonui):void=
if(ThisButtonUI.SaveButtonState?):
if(ThisWidget := ThisButtonUI.ButtonWidgetPerPlayer[Agent]):
if(ThisEnabledState := ThisButtonUI.EnabledStateMap[Agent]):
ThisWidget.SetEnabled(ThisEnabledState)
if(ThisVisibleState := ThisButtonUI.VisibleStateMap[Agent]):
case(ThisVisibleState):
true => ThisWidget.SetVisibility(widget_visibility.Visible)
false => ThisWidget.SetVisibility(widget_visibility.Collapsed)
_ => Print("~ERROR: Failed to fetch 'ThisVisibleState")
else:
Print("~ERROR: Failed to fetch widget!")
# Initializes the 'functions' tab for each button ui
InitButtonFunctions():void=
for(ThisButtonUI : ButtonUI):
ThisButtonUI.ButtonSignal_Recieve.EnableButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.EnableWidget)
ThisButtonUI.ButtonSignal_Recieve.DisableButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.DisableWidget)
ThisButtonUI.ButtonSignal_Recieve.ShowButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.ShowWidget)
ThisButtonUI.ButtonSignal_Recieve.HideButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.HideWidget)
buttonui := class<concrete>():
@editable var ButtonText : string = ""
@editable var ButtonPosition : vector2 = vector2{}
@editable var ButtonSize : vector2 = vector2{X := 475.0, Y:= 80.0}
# 'ButtonStyle' key: {button_loud = 1, button_quiet = 2, button_regular = 3}
@editable var ButtonStyle : buttonchoice_int = 3
@editable var RemovesUIOnClick : logic = true
# Saves the state the button is in even after exiting. Example: Hiding a button, exiting the ui, and then opening the ui will still have that button hidden
@editable SaveButtonState : logic = false
@editable ButtonSignal_Recieve : ButtonUI_UserOptions_Functions = ButtonUI_UserOptions_Functions{}
@editable ButtonSignal_Send : ButtonUI_UserOptions_Events = ButtonUI_UserOptions_Events{}
var SizeToContent : logic = false
var ButtonWidgetPerPlayer : [agent]widget = map{}
var EnabledStateMap : [agent]logic = map{}
var VisibleStateMap : [agent]logic = map{}
EnableWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetEnabled(true)
if(SaveButtonState?):
ValidAgent.SaveEnabledState(true)
# Fades the button ui
DisableWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetEnabled(false)
if(SaveButtonState?):
ValidAgent.SaveEnabledState(false)
# Saves the enabled state the button is currently in
(Agent : agent).SaveEnabledState(State : logic):void=
if(CurrentState := EnabledStateMap[Agent]):
if(set EnabledStateMap[Agent] = State){}
else:
set EnabledStateMap = ConcatenateMaps(EnabledStateMap, map{Agent => State})
ShowWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetVisibility(widget_visibility.Visible)
if(SaveButtonState?):
ValidAgent.SaveVisibleState(true)
# Completely removes the button ui
HideWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetVisibility(widget_visibility.Collapsed)
if(SaveButtonState?):
ValidAgent.SaveVisibleState(false)
# Saves the visible state the button is currently in
(Agent : agent).SaveVisibleState(State : logic):void=
if(CurrentState := VisibleStateMap[Agent]):
if(set VisibleStateMap[Agent] = State){}
else:
set VisibleStateMap = ConcatenateMaps(VisibleStateMap, map{Agent => State})
ButtonUI_UserOptions_Functions := class<concrete>():
# The trigger that will enable this ui button, making it 'un-fade' and clickable
@editable EnableButtonOnTrigger : trigger_device = trigger_device{}
# The trigger that will disable this ui button, making it 'fade' and unclickable
@editable DisableButtonOnTrigger : trigger_device = trigger_device{}
# The trigger that will show this ui button, making it visible and clickable
@editable ShowButtonOnTrigger : trigger_device = trigger_device{}
# The trigger that will hide this ui button, making it not visible at all and therefore unclickable
@editable HideButtonOnTrigger : trigger_device = trigger_device{}
ButtonUI_UserOptions_Events := class<concrete>():
# The trigger/destination to activate
@editable TriggerToActivate : trigger_device = trigger_device{}
# Do we send the instigator of the UI button?
@editable SendInstigator : logic = true
▼ コードを入力したら、画面上部の「Verse」→「Verseコードをビルド」をクリックします。
▼ 「コンテンツドロワー」→「○○コンテンツ」→「Creative Devices」を選択します。
▼ 青枠のVerseクラスを世界に設置します。
▼ こんな感じ。
▼ 再び「コンテンツドロワー」を開き、「All」→「トリガー」で、トリガーを設置しましょう。
▼ トリガーを設置したら、デバイス(青枠の機械)をクリックし、画面右下「Menumanagerdevice」の「TriggerToShowUI」の「なし」を『トリガー』に変更します。
※赤枠部分が表示されていない方は、再度Verseコードをビルドしてください。
▼ 「ButtonUI」の『+』ボタンをクリックし、「インデックス」と書かれた部分の左にある「▶」をクリックします。
▼ ButtonTextに、表示させたい文字を入力しましょう。
▼ そしたら次は、再度「コンテンツドロワー」を開き、「○○コンテンツ」をクリック。
コンテンツ内ならどこでもいいので(例:4アイテムの上あたりとかどこでもOK)右クリックしましょう。
そしたら画面に表示されてるバーが表示されるので『ユーザーインターフェース』→『ウィジェットブループリント』をクリックします。
▼ 「User Widget」を選択します。
▼ 名前を付けましょう。なんでもOKです。
(例として「test」という名前を付けました。)
▼ 名前を入力したら、ダブルクリックします。
▼ 以下のような画面が表示されたら、『Canvas Panel』を矢印部分に配置します。
▼ 続いて、「UEFN Button Loud」「UEFN Button Quiet」「UEFN Button Regular」を設置しましょう。
▼ 設置したい位置を決めたら、画面右の「詳細」に表示されている『位置X,Y』『サイズX,Y』を先ほどのデバイスの設定画面『ButtonPosition』『ButtonSize』にコピペします。
コピペする前に必ずコンパイルしておきましょう。
▼ 「コンテンツドロワー」→「All」をクリックしたら、『トリガー』&『アイテムグランター』を設置しましょう。
▼ アイテムグランターの武器は何でもOKです。
▼ アイテムグランターのユーザーオプションの「アイテムを付与する」を、『トリガー2』『On Triggered』に変更します。
▼ 再びデバイスの設定に戻り、ButtonSignal_Recieveの「HideButtoOnTrigger(非表示)」を『トリガー2』に。
ButtonSignal_Sendの「TriggerToActivate(関連するトリガーのアクティブ化)」を『トリガー2』に変更します。
下記の画像と同じ設定であれば問題ないです。
▼ そしたら、ButtonUIの『+』ボタンを押し、「インデックス1」を作りましょう。
インデックス1はもう一つのボタンの担うものです。
▼ もう片方のボタンを選択し、先ほどと同じように『位置X,Y』『サイズX,Y』をデバイスのインデックス1「ButtonPosition」「ButtoSize」にコピペします。
「RemovesUIOnClick」に『✓』を入れましょう。
※ButtonStyle「1が黄色」「2が透明」「3が青色」
▼ 3つ目のトリガーを設置しましょう。
▼ デバイスの設定に戻り、ButtonSignal_Recieveの「DisableButtonOnTrigger(無効化)」を『トリガー3』。
ButtonSignal_Sendの「TriggerToActivate(関連するトリガーのアクティブ化)」をトリガー3に設定します。
以下の画像の設定と同じであれば問題ないです。
このDisableButtonOnTriggerを設定しない限りボタンUIは無効にならないため、フリーズ等のバグが生じます。
▼ 『セッションを開始』をクリックして試してみましょう。
▼ 以下のようにボタンUIが表示されたら無事完了です。
アレンジするとこんなのも作れます。お疲れさまでした🍵
UIボタンの作り方│Verseコードを徹底解説
完全なスクリプトです。using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }
using { /Verse.org/Verse }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
# Locks the choice of button styles to the current three: {button_loud = 1, button_quiet = 2, button_regular = 3}
buttonchoice_int := type{_X : int where 1 <= _X, 3 >= _X}
menumanagerdevice := class(creative_device):
# The trigger that will open up this ui menu
@editable TriggerToShowUI : trigger_device = trigger_device{}
# An array of button ui, configurable via the editor
@editable var ButtonUI : []buttonui = array{}
var MaybeMyUIPerPlayer : [player]?canvas = map{}
StringToMessage<localizes>(String : string):message = "{String}"
OnBegin<override>()<suspends>:void=
InitButtonFunctions()
TriggerToShowUI.TriggeredEvent.Subscribe(OpenUI)
OpenUI(Agent : ?agent):void=
if(ValidAgent := Agent?, Player := player[ValidAgent], PlayerUI := GetPlayerUI[Player]):
if(MyUI := MaybeMyUIPerPlayer[Player]?):
PlayerUI.RemoveWidget(MyUI)
if(set MaybeMyUIPerPlayer[Player] = false) {}
else:
NewUI := CreateMyUI(ValidAgent)
PlayerUI.AddWidget(NewUI, player_ui_slot{InputMode := ui_input_mode.All})
if(set MaybeMyUIPerPlayer[Player] = option{NewUI}) {}
CreateMyUI(Agent : agent) : canvas =
for(CurrButtonUI : ButtonUI):
case(CurrButtonUI.ButtonStyle):
1 =>
ThisButtonUI := button_loud{DefaultText := StringToMessage(CurrButtonUI.ButtonText)}
ThisButtonUI.OnClick().Subscribe(HandleSelectedUIButton)
if(set CurrButtonUI.ButtonWidgetPerPlayer[Agent] = ThisButtonUI){}
2 =>
ThisButtonUI := button_quiet{DefaultText := StringToMessage(CurrButtonUI.ButtonText)}
ThisButtonUI.OnClick().Subscribe(HandleSelectedUIButton)
if(set CurrButtonUI.ButtonWidgetPerPlayer[Agent] = ThisButtonUI){}
3 =>
ThisButtonUI := button_regular{DefaultText := StringToMessage(CurrButtonUI.ButtonText)}
ThisButtonUI.OnClick().Subscribe(HandleSelectedUIButton)
if(set CurrButtonUI.ButtonWidgetPerPlayer[Agent] = ThisButtonUI){}
_ =>
Print("~ERROR: ButtonStyle integer over/under-flow")
CanvasArray := ReturnCanvasSlots(Agent)
MyInteractableButtons : canvas = canvas:
Slots := CanvasArray
return MyInteractableButtons
ReturnCanvasSlots(Agent : agent):[]canvas_slot=
var CanvasSlotArray : []canvas_slot = array{}
for(ThisButtonUI : ButtonUI):
if(ThisWidget := ThisButtonUI.ButtonWidgetPerPlayer[Agent]):
Agent.LoadButtonState(ThisButtonUI)
ThisSlot := canvas_slot:
Offsets := margin{Top := ThisButtonUI.ButtonPosition.Y, Left := ThisButtonUI.ButtonPosition.X, Bottom := ThisButtonUI.ButtonSize.Y, Right := ThisButtonUI.ButtonSize.X}
ZOrder := 0
SizeToContent := ThisButtonUI.SizeToContent
Widget := ThisWidget
set CanvasSlotArray += array{ThisSlot}
return CanvasSlotArray
DeleteUI(Message : widget_message) : void=
if(PlayerUI := GetPlayerUI[Message.Player], MyUI := MaybeMyUIPerPlayer[Message.Player]?):
PlayerUI.RemoveWidget(MyUI)
if(set MaybeMyUIPerPlayer[Message.Player] = false) {}
HandleSelectedUIButton(Message : widget_message):void=
if(PlayerUI := GetPlayerUI[Message.Player], MyUI := MaybeMyUIPerPlayer[Message.Player]?):
Player := Message.Player
ThisWidget := Message.Source
StaleButtonUI := Player.FindButtonUI(ThisWidget)
if(ThisButtonUI := StaleButtonUI?):
if(ThisButtonUI.RemovesUIOnClick?):
DeleteUI(Message)
ThisButtonUI.ActivateTrigger(Player)
else:
Print("~ERROR: No buttonui found!")
(Agent : agent).FindButtonUI(ThisWidget : widget):?buttonui=
for(ThisButtonUI : ButtonUI):
if(StaleWidget := ThisButtonUI.ButtonWidgetPerPlayer[Agent]):
if(ThisWidget = StaleWidget):
return option{ThisButtonUI}
return false
(ThisButtonUI : buttonui).ActivateTrigger(Player : player):void=
ThisSignalDestination := ThisButtonUI.ButtonSignal_Send
if(ThisSignalDestination.SendInstigator?):
ThisSignalDestination.TriggerToActivate.Trigger(Player)
ThisSignalDestination.TriggerToActivate.Trigger()
# Loads all button states that have 'SaveButtonState' turned on
(Agent : agent).LoadButtonState(ThisButtonUI : buttonui):void=
if(ThisButtonUI.SaveButtonState?):
if(ThisWidget := ThisButtonUI.ButtonWidgetPerPlayer[Agent]):
if(ThisEnabledState := ThisButtonUI.EnabledStateMap[Agent]):
ThisWidget.SetEnabled(ThisEnabledState)
if(ThisVisibleState := ThisButtonUI.VisibleStateMap[Agent]):
case(ThisVisibleState):
true => ThisWidget.SetVisibility(widget_visibility.Visible)
false => ThisWidget.SetVisibility(widget_visibility.Collapsed)
_ => Print("~ERROR: Failed to fetch 'ThisVisibleState")
else:
Print("~ERROR: Failed to fetch widget!")
# Initializes the 'functions' tab for each button ui
InitButtonFunctions():void=
for(ThisButtonUI : ButtonUI):
ThisButtonUI.ButtonSignal_Recieve.EnableButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.EnableWidget)
ThisButtonUI.ButtonSignal_Recieve.DisableButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.DisableWidget)
ThisButtonUI.ButtonSignal_Recieve.ShowButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.ShowWidget)
ThisButtonUI.ButtonSignal_Recieve.HideButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.HideWidget)
buttonui := class<concrete>():
@editable var ButtonText : string = ""
@editable var ButtonPosition : vector2 = vector2{}
@editable var ButtonSize : vector2 = vector2{X := 475.0, Y:= 80.0}
# 'ButtonStyle' key: {button_loud = 1, button_quiet = 2, button_regular = 3}
@editable var ButtonStyle : buttonchoice_int = 3
@editable var RemovesUIOnClick : logic = true
# Saves the state the button is in even after exiting. Example: Hiding a button, exiting the ui, and then opening the ui will still have that button hidden
@editable SaveButtonState : logic = false
@editable ButtonSignal_Recieve : ButtonUI_UserOptions_Functions = ButtonUI_UserOptions_Functions{}
@editable ButtonSignal_Send : ButtonUI_UserOptions_Events = ButtonUI_UserOptions_Events{}
var SizeToContent : logic = false
var ButtonWidgetPerPlayer : [agent]widget = map{}
var EnabledStateMap : [agent]logic = map{}
var VisibleStateMap : [agent]logic = map{}
EnableWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetEnabled(true)
if(SaveButtonState?):
ValidAgent.SaveEnabledState(true)
# Fades the button ui
DisableWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetEnabled(false)
if(SaveButtonState?):
ValidAgent.SaveEnabledState(false)
# Saves the enabled state the button is currently in
(Agent : agent).SaveEnabledState(State : logic):void=
if(CurrentState := EnabledStateMap[Agent]):
if(set EnabledStateMap[Agent] = State){}
else:
set EnabledStateMap = ConcatenateMaps(EnabledStateMap, map{Agent => State})
ShowWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetVisibility(widget_visibility.Visible)
if(SaveButtonState?):
ValidAgent.SaveVisibleState(true)
# Completely removes the button ui
HideWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetVisibility(widget_visibility.Collapsed)
if(SaveButtonState?):
ValidAgent.SaveVisibleState(false)
# Saves the visible state the button is currently in
(Agent : agent).SaveVisibleState(State : logic):void=
if(CurrentState := VisibleStateMap[Agent]):
if(set VisibleStateMap[Agent] = State){}
else:
set VisibleStateMap = ConcatenateMaps(VisibleStateMap, map{Agent => State})
ButtonUI_UserOptions_Functions := class<concrete>():
# The trigger that will enable this ui button, making it 'un-fade' and clickable
@editable EnableButtonOnTrigger : trigger_device = trigger_device{}
# The trigger that will disable this ui button, making it 'fade' and unclickable
@editable DisableButtonOnTrigger : trigger_device = trigger_device{}
# The trigger that will show this ui button, making it visible and clickable
@editable ShowButtonOnTrigger : trigger_device = trigger_device{}
# The trigger that will hide this ui button, making it not visible at all and therefore unclickable
@editable HideButtonOnTrigger : trigger_device = trigger_device{}
ButtonUI_UserOptions_Events := class<concrete>():
# The trigger/destination to activate
@editable TriggerToActivate : trigger_device = trigger_device{}
# Do we send the instigator of the UI button?
@editable SendInstigator : logic = true
1つずつ解説していきます。
クラスの定義
▼ このスクリプトは、Fortniteのゲーム内のUIを管理するためのものです。
以下、コードの各部分の解説です。
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }
using { /Verse.org/Verse }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
# ボタンスタイルの選択肢を現在の3種類に固定します。: {button_loud = 1, button_quiet = 2, button_regular = 3}
buttonchoice_int := type{_X : int where 1 <= _X, 3 >= _X}
menumanagerdevice := class(creative_device):
# UIメニューを開くトリガー
@editable
TriggerToShowUI : trigger_device = trigger_device{}
# エディターで設定可能なボタンUIの配列
@editable
var ButtonUI : []buttonui = array{}
var MaybeMyUIPerPlayer : [player]?canvas = map{}
StringToMessage<localizes>(String : string):message = "{String}"
buttonchoice_int~の詳細
whereキーワードの詳細
button_loudクラスの詳細
Type Macroの詳細
buttonchoice_int~の論理
型の定義で{}を使うことについて
_X : intという記述が必要な理由
menumanagerdevice := class(creative_device):の詳細
TriggerToShowUI: trigger_device = trigger_device{}の詳細
var ButtonUI : []buttonui = array{}の詳細
var MaybeMyUIPerPlayer : [player]?canvas = map{}の詳細
Mapの詳細
Comparable(比較可能)の詳細
サブタイプの詳細
canvasに?をつける理由
@editable var ButtonUI : []buttonui = array{}のbuttonuiの詳細
@editable var ButtonUI : []buttonui = array{}│配列を使用する理由
var ButtonUI&var MaybeMyUIPerPlayerに『var』が付けられている理由
StringToMessage<localizes>(String : string):message = "{String}"の詳細
(String: string)の詳細
StringToMessage(String : string):message = "{String}"がmessage 型である理由
var ButtonWidgetPerPlayer : [agent]widget = map{}の詳細
var ButtonWidgetPerPlayer : [agent]widget = map{}
var EnabledStateMap ~VisibleStateMap : [agent]logic = map{}の詳細
var EnabledStateMap : [agent]logic = map{}
var VisibleStateMap : [agent]logic = map{}
EnableWidget(Agent : ?agent):void=の詳細
EnableWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetEnabled(true)
if(SaveButtonState?):
ValidAgent.SaveEnabledState(true)
if(SaveButtonState?):に?が付く理由
DisableWidget(Agent : ?agent):void=の詳細
DisableWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetEnabled(false)
if(SaveButtonState?):
ValidAgent.SaveEnabledState(false)
条件式とクエリ式の違い
クエリ式を採用した世界線とそうでない場合の世界線
よくある質問
UIボタンの作り方についてよくある質問をまとめてみました。
(Agent : agent).SaveEnabledState(State : logic):void=:Agentが先に記述されている理由は、ボタンの有効状態はエージェントに対して実行されているから?
条件式とクエリ式について
制御フローとは
まとめ
今回はボタンの作成方法について解説しました。
最後に、完全なスクリプトを再掲しておきます↓
完全なスクリプトusing { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /Verse.org/Simulation }
using { /Verse.org/Verse }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
# Locks the choice of button styles to the current three: {button_loud = 1, button_quiet = 2, button_regular = 3}
buttonchoice_int := type{_X : int where 1 <= _X, 3 >= _X}
menumanagerdevice := class(creative_device):
# The trigger that will open up this ui menu
@editable TriggerToShowUI : trigger_device = trigger_device{}
# An array of button ui, configurable via the editor
@editable var ButtonUI : []buttonui = array{}
var MaybeMyUIPerPlayer : [player]?canvas = map{}
StringToMessage<localizes>(String : string):message = "{String}"
OnBegin<override>()<suspends>:void=
InitButtonFunctions()
TriggerToShowUI.TriggeredEvent.Subscribe(OpenUI)
OpenUI(Agent : ?agent):void=
if(ValidAgent := Agent?, Player := player[ValidAgent], PlayerUI := GetPlayerUI[Player]):
if(MyUI := MaybeMyUIPerPlayer[Player]?):
PlayerUI.RemoveWidget(MyUI)
if(set MaybeMyUIPerPlayer[Player] = false) {}
else:
NewUI := CreateMyUI(ValidAgent)
PlayerUI.AddWidget(NewUI, player_ui_slot{InputMode := ui_input_mode.All})
if(set MaybeMyUIPerPlayer[Player] = option{NewUI}) {}
CreateMyUI(Agent : agent) : canvas =
for(CurrButtonUI : ButtonUI):
case(CurrButtonUI.ButtonStyle):
1 =>
ThisButtonUI := button_loud{DefaultText := StringToMessage(CurrButtonUI.ButtonText)}
ThisButtonUI.OnClick().Subscribe(HandleSelectedUIButton)
if(set CurrButtonUI.ButtonWidgetPerPlayer[Agent] = ThisButtonUI){}
2 =>
ThisButtonUI := button_quiet{DefaultText := StringToMessage(CurrButtonUI.ButtonText)}
ThisButtonUI.OnClick().Subscribe(HandleSelectedUIButton)
if(set CurrButtonUI.ButtonWidgetPerPlayer[Agent] = ThisButtonUI){}
3 =>
ThisButtonUI := button_regular{DefaultText := StringToMessage(CurrButtonUI.ButtonText)}
ThisButtonUI.OnClick().Subscribe(HandleSelectedUIButton)
if(set CurrButtonUI.ButtonWidgetPerPlayer[Agent] = ThisButtonUI){}
_ =>
Print("~ERROR: ButtonStyle integer over/under-flow")
CanvasArray := ReturnCanvasSlots(Agent)
MyInteractableButtons : canvas = canvas:
Slots := CanvasArray
return MyInteractableButtons
ReturnCanvasSlots(Agent : agent):[]canvas_slot=
var CanvasSlotArray : []canvas_slot = array{}
for(ThisButtonUI : ButtonUI):
if(ThisWidget := ThisButtonUI.ButtonWidgetPerPlayer[Agent]):
Agent.LoadButtonState(ThisButtonUI)
ThisSlot := canvas_slot:
Offsets := margin{Top := ThisButtonUI.ButtonPosition.Y, Left := ThisButtonUI.ButtonPosition.X, Bottom := ThisButtonUI.ButtonSize.Y, Right := ThisButtonUI.ButtonSize.X}
ZOrder := 0
SizeToContent := ThisButtonUI.SizeToContent
Widget := ThisWidget
set CanvasSlotArray += array{ThisSlot}
return CanvasSlotArray
DeleteUI(Message : widget_message) : void=
if(PlayerUI := GetPlayerUI[Message.Player], MyUI := MaybeMyUIPerPlayer[Message.Player]?):
PlayerUI.RemoveWidget(MyUI)
if(set MaybeMyUIPerPlayer[Message.Player] = false) {}
HandleSelectedUIButton(Message : widget_message):void=
if(PlayerUI := GetPlayerUI[Message.Player], MyUI := MaybeMyUIPerPlayer[Message.Player]?):
Player := Message.Player
ThisWidget := Message.Source
StaleButtonUI := Player.FindButtonUI(ThisWidget)
if(ThisButtonUI := StaleButtonUI?):
if(ThisButtonUI.RemovesUIOnClick?):
DeleteUI(Message)
ThisButtonUI.ActivateTrigger(Player)
else:
Print("~ERROR: No buttonui found!")
(Agent : agent).FindButtonUI(ThisWidget : widget):?buttonui=
for(ThisButtonUI : ButtonUI):
if(StaleWidget := ThisButtonUI.ButtonWidgetPerPlayer[Agent]):
if(ThisWidget = StaleWidget):
return option{ThisButtonUI}
return false
(ThisButtonUI : buttonui).ActivateTrigger(Player : player):void=
ThisSignalDestination := ThisButtonUI.ButtonSignal_Send
if(ThisSignalDestination.SendInstigator?):
ThisSignalDestination.TriggerToActivate.Trigger(Player)
ThisSignalDestination.TriggerToActivate.Trigger()
# Loads all button states that have 'SaveButtonState' turned on
(Agent : agent).LoadButtonState(ThisButtonUI : buttonui):void=
if(ThisButtonUI.SaveButtonState?):
if(ThisWidget := ThisButtonUI.ButtonWidgetPerPlayer[Agent]):
if(ThisEnabledState := ThisButtonUI.EnabledStateMap[Agent]):
ThisWidget.SetEnabled(ThisEnabledState)
if(ThisVisibleState := ThisButtonUI.VisibleStateMap[Agent]):
case(ThisVisibleState):
true => ThisWidget.SetVisibility(widget_visibility.Visible)
false => ThisWidget.SetVisibility(widget_visibility.Collapsed)
_ => Print("~ERROR: Failed to fetch 'ThisVisibleState")
else:
Print("~ERROR: Failed to fetch widget!")
# Initializes the 'functions' tab for each button ui
InitButtonFunctions():void=
for(ThisButtonUI : ButtonUI):
ThisButtonUI.ButtonSignal_Recieve.EnableButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.EnableWidget)
ThisButtonUI.ButtonSignal_Recieve.DisableButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.DisableWidget)
ThisButtonUI.ButtonSignal_Recieve.ShowButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.ShowWidget)
ThisButtonUI.ButtonSignal_Recieve.HideButtonOnTrigger.TriggeredEvent.Subscribe(ThisButtonUI.HideWidget)
buttonui := class<concrete>():
@editable var ButtonText : string = ""
@editable var ButtonPosition : vector2 = vector2{}
@editable var ButtonSize : vector2 = vector2{X := 475.0, Y:= 80.0}
# 'ButtonStyle' key: {button_loud = 1, button_quiet = 2, button_regular = 3}
@editable var ButtonStyle : buttonchoice_int = 3
@editable var RemovesUIOnClick : logic = true
# Saves the state the button is in even after exiting. Example: Hiding a button, exiting the ui, and then opening the ui will still have that button hidden
@editable SaveButtonState : logic = false
@editable ButtonSignal_Recieve : ButtonUI_UserOptions_Functions = ButtonUI_UserOptions_Functions{}
@editable ButtonSignal_Send : ButtonUI_UserOptions_Events = ButtonUI_UserOptions_Events{}
var SizeToContent : logic = false
var ButtonWidgetPerPlayer : [agent]widget = map{}
var EnabledStateMap : [agent]logic = map{}
var VisibleStateMap : [agent]logic = map{}
EnableWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetEnabled(true)
if(SaveButtonState?):
ValidAgent.SaveEnabledState(true)
# Fades the button ui
DisableWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetEnabled(false)
if(SaveButtonState?):
ValidAgent.SaveEnabledState(false)
# Saves the enabled state the button is currently in
(Agent : agent).SaveEnabledState(State : logic):void=
if(CurrentState := EnabledStateMap[Agent]):
if(set EnabledStateMap[Agent] = State){}
else:
set EnabledStateMap = ConcatenateMaps(EnabledStateMap, map{Agent => State})
ShowWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetVisibility(widget_visibility.Visible)
if(SaveButtonState?):
ValidAgent.SaveVisibleState(true)
# Completely removes the button ui
HideWidget(Agent : ?agent):void=
if(ValidAgent := Agent?, ThisButtonWidget := ButtonWidgetPerPlayer[ValidAgent]):
ThisButtonWidget.SetVisibility(widget_visibility.Collapsed)
if(SaveButtonState?):
ValidAgent.SaveVisibleState(false)
# Saves the visible state the button is currently in
(Agent : agent).SaveVisibleState(State : logic):void=
if(CurrentState := VisibleStateMap[Agent]):
if(set VisibleStateMap[Agent] = State){}
else:
set VisibleStateMap = ConcatenateMaps(VisibleStateMap, map{Agent => State})
ButtonUI_UserOptions_Functions := class<concrete>():
# The trigger that will enable this ui button, making it 'un-fade' and clickable
@editable EnableButtonOnTrigger : trigger_device = trigger_device{}
# The trigger that will disable this ui button, making it 'fade' and unclickable
@editable DisableButtonOnTrigger : trigger_device = trigger_device{}
# The trigger that will show this ui button, making it visible and clickable
@editable ShowButtonOnTrigger : trigger_device = trigger_device{}
# The trigger that will hide this ui button, making it not visible at all and therefore unclickable
@editable HideButtonOnTrigger : trigger_device = trigger_device{}
ButtonUI_UserOptions_Events := class<concrete>():
# The trigger/destination to activate
@editable TriggerToActivate : trigger_device = trigger_device{}
# Do we send the instigator of the UI button?
@editable SendInstigator : logic = true
お疲れさまでした🍵
コツコツがんばっていきましょ。
僕のTwitterアカウントです。-
【UEFN】UIボタンの作り方(2ページ目)│Verse解説の続き
続きを見る
-
【UEFN】UIボタンの作り方(3ページ目)│Verse解説の続き
続きを見る
-
【UEFN】UIボタンの作り方(4ページ目)│Verse解説の続き
続きを見る
-
【UEFN】UIボタンの作り方(5ページ目)│Verse解説の続き
続きを見る