Gorosuke's Lab https://gorosuke.net Sun, 25 Jan 2026 11:55:38 +0000 ja hourly 1 https://wordpress.org/?v=6.9.4 https://gorosuke.net/wp-content/uploads/2025/05/cropped-KashiwaNoKi-32x32.png Gorosuke's Lab https://gorosuke.net 32 32 くるくる回るパネルを紹介 | UEFN, シーングラフ https://gorosuke.net/uefn-scene-graph-spin-panel-share/ Sun, 25 Jan 2026 11:55:38 +0000 https://gorosuke.net/?p=430

島に導入

https://drive.google.com/drive/folders/1W1UStSx4pvg8GEv54TfArFUkMC_tinoJ?usp=sharing

上記のGoogle Driveのリンクから、SpinPanelと呼ばれるフォルダをダウンロードします。

解凍 > プロジェクトに移動

ダウンロードしたzipファイルを解凍し

それをプロジェクトのフォルダに直接移動します

「Fortnite Projects / {プロジェクト名} / Content」と進んでいき、GameFeatureData.uassetが見える個所に配置で大丈夫です。

配置と設置

ダウンロードしたSpinPanelを移動し終わったら、プロジェクトを起動して配置と設定を行います。

まず、画像を参考に「アクタを配置 > Entities > entity」でエンティティと呼ばれるオブジェクトを配置します。

透明な何もない見た目で配置されますが、正しい挙動なので進めていきます。

デフォルトではentityという名前で配置されるのですが、名前は好きに決めて大丈夫なので、「SpinPanel」などわかりやすく管理しましょう。

アウトライナーで先ほど配置したentityを右クリックすると、このような画面が表示されます。

ここで、「Add Entity」からentityを選びましょう。

すると、このように階層としてentityが設置されます。この階層の構造が非常に重要となります。

そうしましたら、階層で子となるentityを選び、詳細パネルから「+Component」を押します。xxx_componentのようなものが一覧として表示されるので、mesh_componentへ行き

SM_SpinPanelを選びます。もし、ここで表示されていない場合は、プロジェクトを再起動したり、セッション開始のボタンの隣から「Verseをコンパイル」を押して再度確認してみましょう。

もし、メッシュが多すぎて見つからない場合は、上の検索バーから「SM_SpinPanel」と検索して出すでもよいです。

ここまでうまく進めれたら、このように木目のメッシュがビューポート上に配置されるはずです。

そうしたら、再度「+Component」からspin_panel_componentというものも追加します。ここで、パネルの画像の設定を行います。

追加後に詳細パネルから「spin_panel_component」の項目へ行くと、設定画面が表示されます。もし、何も設定せずにセッションを開始しようとした場合、初期化エラーで開始できないと思いますのでご注意を。

「FrontTexture」「BackTexture」には画像アセットを入れることができます。これが、パネルの表裏に表示される画像となります。

「FrameMaterial」はパネルを横から見た際に表示される見た目となるマテリアルを入れます。正直なんでもよいとは思います。

例えば、サンプルとしてのパネルはこのように設定されています。InitialStateとかDurationは無視しても大丈夫な値なので、とりあえず画像を入れていきましょう。

最終的にはこのようになればよいです。

まず、全体を管理する用の親のentityがあり、その階層にパネルが必要な分だけ設定されている感じです。今回は3×3の9個ですが、パネルの数に特に制限はないので、少なかったり、多かったり、正方形以外の形でも問題はないです。

もし、設定抜けが無ければ、ここでセッションを開始して実際に確認できるはずです。

シャッフル

こちらは任意の設定でも大丈夫ですが、親となっているentityに「spin_panel_manager_component」を足してあげて、ShuffleTriggerを入れてあげることで、そのトリガーが起動した際にパネルがランダムで回転するようになります。

まとめて設定

特にFrameMaterialなどはパネル単位でデザインを変えることはないと思いますので、そういうときはアウトライナーでまとめて選択からの、詳細パネルで設定をすると、一括編集を行うことができます。

送信中です

]]>
buttonクラスでカスタムボタンをつくろう! https://gorosuke.net/verse-custom-button-widget/ Sat, 24 Jan 2026 20:27:09 +0000 https://gorosuke.net/?p=423 buttonクラスって?
# Button is a container of a single child widget slot and fires the OnClick event when the button is clicked.
button<native><public> := class<final>(widget):
    # The child widget of the button. Used only during initialization of the widget and not modified by SetSlot.
    Slot<native><public>:button_slot

    # Sets the child widget slot.
    SetWidget<native><public>(InSlot:button_slot):void

    # Subscribable event that fires when the button is clicked.
    OnClick<public>():listenable(widget_message) = external {}

    HighlightEvent<public>():listenable(widget_message) = external {}

    UnhighlightEvent<public>():listenable(widget_message) = external {}

UnrealEngine.digest.verseには「button」と呼ばれるクラスが定義されています。「button_loud」や「button_quiet」と普段なじみのあるデザインではなく、完全に自作のデザインにすることができるボタンです。

きほんの「き」

using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Colors }

# ひとり用なのでマルチ未対応
button_sample_device<public> := class(creative_device):

    ButtonColorBlock:color_block = color_block:
        DefaultDesiredSize := vector2{X := 256.0, Y := 64.0}
        DefaultColor := NamedColors.Gray

    OnBegin<override>()<suspends>:void=
        if:
            FirstPlayer := GetPlayspace().GetPlayers()[0]
        then:
            ShowButtonUI(FirstPlayer)

    ShowButtonUI(InPlayer:player):void=
        if:
            PlayerUI := GetPlayerUI[InPlayer]
        then:
            NewButton := button:
                Slot := button_slot:
                    Widget := ButtonColorBlock

            NewCanvas := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors:
                            Minimum := vector2{X := 0.5, Y := 0.9}
                            Maximum := vector2{X := 0.5, Y := 0.9}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        Widget := NewButton

            NewButton.HighlightEvent().Subscribe(OnHighlighted)
            NewButton.UnhighlightEvent().Subscribe(OnUnhighlighted)
            NewButton.OnClick().Subscribe(OnClicked)
                        
            PlayerUI.AddWidget(NewCanvas, player_ui_slot{InputMode := ui_input_mode.All})

    OnHighlighted(WidgetMessage:widget_message):void=
        ButtonColorBlock.SetColor(NamedColors.White)

    OnUnhighlighted(WidgetMessage:widget_message):void=
        ButtonColorBlock.SetColor(NamedColors.Gray)

    OnClicked(WidgetMessage:widget_message):void=
        Print("ボタンを押したよ!")

まずは実際の使用例を見た方が理解しやすいと思うので、簡単なサンプルを用意しました(わかりやすさ重視でマルチは未対応です!そのままつかわないでね( •̀ ω •́ )✧)

まずはbuttonをつくる!

buttonを作成すると、Slotの中身を要求されます。Slotは「button_slot」型となっています。

# Slot for button widget.
button_slot<native><public> := struct:
    # The widget assigned to this slot.
    Widget<native><public>:widget

    # Horizontal alignment of the widget inside the slot.
    HorizontalAlignment<native><public>:horizontal_alignment = external {}

    # Vertical alignment of the widget inside the slot.
    VerticalAlignment<native><public>:vertical_alignment = external {}

    # Empty distance in pixels that surrounds the widget inside the slot. Assumes 1080p resolution.
    Padding<native><public>:margin = external {}

じゃあ、button_slotってなんぞやって話ですが。基本はWidgetにはデザインとして使用する別のwidgetクラスを入れます。今回の例では「color_block」を使用していますが、overlayとtext_blockを組み合わせて文字付きボタンを作ってみたり、ウィジェットブループリントを入れて凝ったデザインにしてみたり。

NewButton := button:
    Slot := button_slot:
        Widget := {デザイン用のウィジェット}

サンプルからbuttonを作る箇所だけ取り出したのが上記のコードです。よーわからんって方は、とりあえず何個かデザイン違いを作ってみてなれるとか…してみましょう!サンプルコードの「ButtonColorBlock」をtext_blockとかにしてみるのも面白いですよ!

canvasで使用する

buttonはwidgetクラスなので、canvas_slot内で使用することができます。記事の趣旨から外れてしまうので、canvas_slotやwidgetクラスのあれこれは説明しきれませんが、とりあえずbutton_loud系統のやつと同じ感覚で使えます。

NewCanvas := canvas:
    Slots := array:
        canvas_slot:
            Anchors := anchors:
                Minimum := vector2{X := 0.5, Y := 0.9}
                Maximum := vector2{X := 0.5, Y := 0.9}
            Alignment := vector2{X := 0.5, Y := 0.5}
            Widget := NewButton

今回の例ですと画面下部中央に表示するみたいな感じです。

サブスクライブ

buttonクラスの真骨頂である「HighlightEvent」と「UnhighlightEvent」についてです。これらは簡単に説明すると「ボタンをホバーした際」と「ホバーが解除された際」にイベントを発信するlistenableです。

例えばマウスプレイヤーの場合は、マウスをボタンの位置に合わせた場合にHighlightEventが発信され、ボタンの位置から外れたらUnhighlightEventが発信されます。同様に、コントローラープレイヤーの場合は、ボタンを選択するとHighlightEventが発信され、別のボタンなどを選択するとUnhighlightEventが発信されます。

あとは、通常のボタン同様にOnClickも用意されているので、ボタンを押したら○○をするみたいな実装もできます。

OnHighlighted(WidgetMessage:widget_message):void=
    ButtonColorBlock.SetColor(NamedColors.White)

OnUnhighlighted(WidgetMessage:widget_message):void=
    ButtonColorBlock.SetColor(NamedColors.Gray)

OnClicked(WidgetMessage:widget_message):void=
    Print("ボタンを押したよ!")

では、HighlightEventとUnhighlightEventが発信された場合の処理も見てみましょう。これらは基本的に「今選択中であるか」をプレイヤーに伝える目的で使用するため、色を差し替えたり、サイズを調整したり、プレイヤー目線で分かりやすい変化を持たせるのがおすすめです。

まあ、デザイン変更にとらわれず、ホバーのたびにダメージを食らうみたいなのもできますので…ここは皆様の発想力でいい感じの実装をしましょう!

きほんの「ほ」

では、ちょっとした応用みたいなのを紹介します。

Verse FieldでHighlightの状態を渡す用の変数を作ります(今回はlogicにしましたが、intでもっと色んな状態を管理したりだとか?)

で、Verse Fieldを設定するとwidgetクラスとその変数を持ったウィジェットブループリントのクラスが公開されるので

using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/Colors }

# ひとり用なのでマルチ未対応
button_sample_device<public> := class(creative_device):

    BlueprintWidget:WBP_Button = WBP_Button{}

    OnBegin<override>()<suspends>:void=
        if:
            FirstPlayer := GetPlayspace().GetPlayers()[0]
        then:
            ShowButtonUI(FirstPlayer)

    ShowButtonUI(InPlayer:player):void=
        if:
            PlayerUI := GetPlayerUI[InPlayer]
        then:
            NewButton := button:
                Slot := button_slot:
                    Widget := BlueprintWidget

            NewCanvas := canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors:
                            Minimum := vector2{X := 0.5, Y := 0.9}
                            Maximum := vector2{X := 0.5, Y := 0.9}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        Widget := NewButton

            NewButton.HighlightEvent().Subscribe(OnHighlighted)
            NewButton.UnhighlightEvent().Subscribe(OnUnhighlighted)
                        
            PlayerUI.AddWidget(NewCanvas, player_ui_slot{InputMode := ui_input_mode.All})

    OnHighlighted(WidgetMessage:widget_message):void=
        set BlueprintWidget.IsHighlighted = true

    OnUnhighlighted(WidgetMessage:widget_message):void=
        set BlueprintWidget.IsHighlighted = false

それを、button_slotのウィジェットで渡してあげることで、ウィジェットブループリントでデザインしたカスタムVerseボタンを作ることができます。

実際にどう見えるか

今回のサンプルコードをゲーム内で見たらどうなるかです。ちゃんとマウスを向けたら色変わってるよね!

送信中です

]]>
リプレイモードのライティング機能 https://gorosuke.net/replay-mode-lighting-feature/ Sat, 19 Jul 2025 10:27:57 +0000 https://gorosuke.net/?p=372

リプレイモードの機能として「Lighting」というものが追加されました。

まずはクリエイト

Lightingは空間上に光源となるライトを設置するための機能です。流れとしては

  • 新しいライトを設置する
  • 位置を指定する
  • 明かりの強さや色を調整する

となります。そのため、まずは明かりを設置するために「Lighting > 新しいライト > クリエイト」のボタンを押しましょう。

すると、このように新しく設置された光源が持つ明かりの情報が表示されるようになります。

複数個設置

すでにライトが設置されている状態で、再度「新しいライト > クリエイト」を押すと、現在のカメラの位置に別の光源が設置されます。

それぞれのライトが持つ色/明かりの強さの数値は別々に設定できます。各光源は独自の番号が割り振られており、現在どのライトを調整しているかは「選択中のライト」で確認できます。

例えば1番の光源の色を青に変えたい場合は、選択中のライトを「1/3」にし、色相や明度の数値を調整します(この辺の要素はまた後程説明します。

1番の光源の調整が終わり、次に2番の光源の調整をしたい場合は、選択中のライトの「>ボタン」をクリックし、「2/3」の状態で色や強度を設定します。

また、PCであれば光源のアイコンをクリックすることで、「選択中のライト」がその光源の番号に切り替わります。

上限

空間上に配置できるライトの上限は「5個」までとなります。

移動させる

ライトを移動させる方法は複数用意されています。

ライトをカメラの位置に移動

「ライトをカメラの位置に移動」のボタンを押すと、選択中のライトがドローンの位置まで移動します。

パイロットカメラ

ライトを移動させる方法の一つとして「パイロットカメラ」というものがあります。これを「オン」にしている間は、ライトは現在のカメラの位置に移動し続けます。

まず、パイロットカメラをオンにすると、選択中のライトの位置までカメラが移動します。この状態でドローンを動かすと、光源の位置も現在のドローンの位置に追従して移動します。

位置の調整が終わった場合は、パイロットカメラをオフにすることで追従が終わります。すると、再度移動させない限り光源はその場にとどまり続けます。

ライトの種類

「ライトの種類」では光源の形を設定できます。画像では左から「スポット | 短形 | ポイント」の順番で並んでいます。

ライトの種類ごとに独自の調整要素を持っているため紹介します。

共通

明度

明度は明かりの強さを表しています。明かりの影響範囲と強さは別のモノとして扱われるため、範囲が同じであれば明度を変えたとしても明かりの広がりは変わりません。

例として、上記の画像は減衰距離が全く同じで、明度だけ変えたものとなります。片方は地面の色が変わるくらい強い明かりを放っていますが、広さ自体は両方とも同じとなっています。

減衰

減衰は明かりの影響範囲を表しています。値が大きくなれば、ライトが照らす範囲も広くなりますが、ライト自体の強さが変わったというわけではありません。中心となる場所は常に同じ強度の明かりをしています。

また、明かりは常に同じ強度で広がるのではなく、徐々に弱くなりながら広がっていきます。

色相・彩度

ライトの色を表す項目です。色相は実際にどの色を使用するかを決め

彩度は実際の濃さを決めます。

スペキュラのスケール

反射する表面に対する影響度を表す項目です。ここの値を小さく設定すると、明かりによる反射が減っていき、0.0とすると一切反射しなくなります。車のボディや水面など、何かと反射するモノは多いですが、明かりだけ出して反射させたくない場合などはここの数値を調整しましょう。

ボリュメトリック散乱

ライトの光源とは別に、本体から明かりを出すかを表しています。

ポイントライト

ポイントライトは設置した場所から全方向を照らすライトです。

短形

短形ライトは四角い形状のライトで、アイコンを中心に表面と裏面が存在します。ライト自体は表方向に向かって光を放っています。

光源の幅・高さ

短形ライトは四角をしたライトです。その幅と高さを決める項目が「光源の幅」と「光源の高さ」です。

バーンドアの角度・高さ

バーンドアとは、ライトの横側に光を通さない見えない壁を作り出す機能です。

いらすとや様の画像をお借りしますが、イメージとしてはこのような照明ライトを考えてください。

光源は壁のある場所ではきっちりと明暗がはっきりするのですが、壁を超すと再度左右へのライトの広がりが始まります。

スポット

スポットは街灯のように円錐状に広がるライトです。

内部・外部コーン概要

スポットライトは内部コーン角と外部コーン角の二つの数値を持っています。内部コーンは実際のライトの大きさを示しており、外部コーンはその大きさをもとに明かりを広げる役割を持ちます。

内部コーン角

内部コーン角は実際の光源の大きさを表します。例として外部コーンを0.0にした状態で数値を変更すると、明暗がはっきりとした円錐状の明かりとなります。外部コーンが持つ光を広げる特性をなくしているため、実際の明かり部分である内部コーンだけが照らされています。

外部コーン角

外部コーン角は内部コーン角を基準にどれだけ光を広げるかを表しています。0.0とすれば一切広げないということになり、内部コーン角の場所だけ照らされますが、数値を大きくすると外側に向かって小さくなりながら光が広がるようになります。

ライトの視認性

「ライトの視認性」を使用すると、設置したライトを一時的に非表示にすることができます。

  • 全て非表示: 設置したライトをすべて見えない状態にします
  • 全て表示: 設置したライトがすべて見える状態にします。
  • 選択項目を非表示: 「選択中のライト」で選ばれている番号のライトを見えない状態にします。

減衰を可視化

より高度な明かりの調整や、ライト同士の干渉を少しでも気にせず調整したい場合は、「減衰を可視化」を表示にすると光源のサイズを示す線状の範囲が表示されます。

複製・削除

「選択中のライト」で選んだ状態で、複製を押すとまったく同じ状態のライトが空間上に設置されます。また、削除を押すことで現在選択中の番号のライトが空間上から消されます。

送信中です

]]>
Switch 2のフォートナイトでマウス操作を有効にする方法 https://gorosuke.net/fortnite-switch2-mouse-enable/ Fri, 04 Jul 2025 06:41:29 +0000 https://gorosuke.net/?p=342

Nintendo Switch 2では、標準機能として「マウス操作」が実装されています。ジョイコンを本体との接続面を下にして地面を滑らすことで、パソコンのマウスのように操作が行えます。

ホーム画面や対応ソフトではマウスの持ち方をするだけで自動で有効になりますが、フォートナイトでは設定画面から手動で有効にしないとマウス操作が行えません。

FORTNITE

設定を開く

Switch 2対応のコントローラーの「+(プラスボタン)」を押すことで、FORTNITEのメニュー画面を開きます。なお、フォートナイト自体のバージョンの違いで表記が変わることも結構あるので、あくまでv36.10時点でのUIとして参考にしてください。

コントローラーの「Rボタン」を押すことでメニュー内の設定を開くための画面に移動できます。ここで「設定」と書かれたボタンを押してください。

マウス操作有効化

このような画面が表示されたら、再度「Rボタン」を押して「映像」カテゴリから「マウス操作」カテゴリ移動します。

マウス操作カテゴリ内に「マウス操作: なし(オフ)」という設定が用意されているため

例えば通常のPCのように、右手だけマウス持ちしたい場合は「右」と選び、「Xボタン」で適用することで、右のジョイコンだけマウス操作が有効になります。ほかにも「左」「両方」と色々用意されているので、実際にマッチに参加して確認してみましょう!

移動用スティックを入れ替え

デフォルトでは左のジョイコンのスティックを傾けると移動、右のスティックを傾けると視線になるのですが、これを逆にする用の設定も用意されています。

マウス操作を「左」にした場合、操作が「ジョイコン左のマウス移動」と「ジョイコン左のスティック移動」となってしまうため、左手が忙しい感じになってしまいます。そのため、この設定を有効にすることで「視点移動はジョイコン左」「移動はジョイコン右」と役割分担させることができます。

送信中です

]]>
ランキングボードの作り方(ビルボード編) | UEFN, Verse https://gorosuke.net/uefn-ranking-board-billboard/ Wed, 02 Jul 2025 14:30:56 +0000 https://gorosuke.net/?p=331 統計データクリエイター

仕掛けで数値を保存して、その値を基準にランキングする方法についてです。map型を用いた実装は下の方に書いておきます。

Verseよくわかんないやーって方は、完成形のコードがあるのでそれをコピペで使うのもアリです。

message変換

AgentToMessage<localizes>(InAgent:agent):message="{InAgent}"
ValueToMessage<localizes>(InValue:int):message="{InValue}"
EmptyMessage<localizes>:message=" "

まずはビルボードの「名前」と「値」を表示させるために、変換用の関数を作成します。これらはクラス外に配置しても良いです。

また、ビルボードの文字をクリアする用の空のmessageを用意しておきます。よく見ると「” “」の部分は空白が一つだけ入っています。というのも、「””」と何も文字を入れないとテキストが更新されない仕様なので、「透明文字」という意味を込めて半角スペースを入れておきます。

billboard管理

ranking_board_billboard := class<concrete>:
    @editable
    NameBillboard:billboard_device = billboard_device{}

    @editable
    ValueBillboard:billboard_device = billboard_device{}

    SetValue(InAgent:agent, InValue:int):void=
        NameBillboard.SetText(AgentToMessage(InAgent))
        ValueBillboard.SetText(ValueToMessage(InValue))

    SetEmpty():void=
        NameBillboard.SetText(EmptyMessage)
        ValueBillboard.SetText(EmptyMessage)

次に、ビルボードを格納するranking_boardクラスを作成します。今回は「名前」と「現在の値」を表示するため、「NameBillboard」と「ValueBillboard」の二つにしましたが、より情報を増やしたい場合などはeditableの変数を増やしたりしてみましょう。

デバイス作成

ranking_board_device := class(creative_device):
    @editable
    RankingBoards:[]ranking_board_billboard = array{}

    @editable
    ValueStat:stat_creator_device = stat_creator_device{}

    OnBegin<override>()<suspends>:void=
        loop:
            Sleep(1.0)
            UpdateBillboards()

    UpdateBillboards():void=
        AgentValues := GetAgentValues()
        SortedAgentValues := SortBy(AgentValues, SortByValue)
        
        for:
            Index -> Board : RankingBoards
        do:
            if(AgentValue := SortedAgentValues[Index]):
                Board.SetValue(AgentValue(0), AgentValue(1))
            else:
                Board.SetEmpty()

    GetAgentValues():[]tuple(agent, int)=
        for:
            PlayerElement : GetPlayspace().GetPlayers()
            PlayerValue := ValueStat.GetValue[PlayerElement]
        do:
            (PlayerElement, PlayerValue)

    SortByValue(X:tuple(agent, int), Y:tuple(agent, int))<computes><decides>:void=
        X(1) > Y(1)

実際に値の確認とビルボードの更新をする用のデバイスを作成します。

RankingBoards

先ほど作ったビルボードを格納するクラスを配列にし、editableとして公開します。

このようにレベル上にビルボードを表示し

Verseデバイス上では、配列に対応する形で入れていきます。今回は統計データの値を大きいもの順で並び替えているため、インデックスの値が0に近いランキングボードほど値が大きいプレイヤーということになります。

また、今回の仕組みではプレイヤーの人数とビルボードの人数が一致しなくても大丈夫なようになっています。そのため、プレイヤーに対してビルボードが少ない場合は、ビルボードの上限以上のプレイヤーは切り捨てられて表示されます。逆に、プレイヤーの方が少ない場合は、ビルボードは空のテキストが入るため表示されなくなります。

ValueStat

ValueStatには統計データクリエイターを入れるのですが、一つ注意点があります。

「最大値」にチェックを入れてあげないと、プレイヤーのデータを正常にとることができない場合があります。そのため、仮でもよいので最大値を「10000000」などに設定して、Verseデバイスで参照しましょう。

GetAgentValues関数

統計データの値を、tuple(agent, int)の配列にして取得する関数を用意します。

一応、ちょっと特殊な書き方をしていますが、

    GetAgentValues():[]tuple(agent, int)=
        var Result:[]tuple(agent, int) = array{}
        for:
            PlayerElement : GetPlayspace().GetPlayers()
            PlayerValue := ValueStat.GetValue[PlayerElement]
        do:
            set Result += array{(PlayerElement, PlayerValue)}
        return Result

と同じ意味であるとみて大丈夫です(書き方が気持ち悪ければこっちに置き換えるでもヨシ)

仕組み

気になる人向けに原理を説明します。Verse言語はfor, ifやその他機能は「式」として扱われます。そのため、呼び出すと呼び出し元で何らかの結果を返すようになっています。
forであれば、doの中に書かれた最後の値をもとに、配列として返します。そのため

Result := 
    for:
        Value := 1..10
    do:
        Value

という処理があれば、Resultの中には「1, 2, 3, 4, 5, 6, 7, 8, 9, 10」という配列が入ります。

また、関数はreturnを省略できるという特性があります。その場合、関数の戻り値は関数内で最後に実行されたモノの値が渡されます。今回の関数ではforで配列を作るような処理しか入っていないため、それが最後の処理として見られ、戻り値には(PlayerElement, PlayerValue)が入った配列が渡されます。

SortByValue

SortBy関数でソートの基準を指定する用の関数です。

Verse言語には値をソートする用の関数が用意されているのですが、そのソートの基準は「<decides><computes>」の関数をもとに行われます。まあ、詳しくは上記の記事にも書いてあるので、見てみましょう!

UpdateBillboards

ここでは実際にビルボードに反映させる処理が書かれています。

for:
    Index -> Board : RankingBoards
do:
    if(AgentValue := SortedAgentValues[Index]):
        Board.SetValue(AgentValue(0), AgentValue(1))
    else:
        Board.SetEmpty()

の部分ではRankingBoardsを分割し、そのインデックスを取得するような処理を書いています。

SortedAgentValuesには統計データの値を基準にソートされたプレイヤーの配列が入っています。そのため、ビルボードのインデックスに対応させることで、ビルボードの0番目から大きい順でプレイヤーの名前が表示されます。

elseと分岐している部分では、島内のプレイヤーがビルボードの数に満たしていなかった場合の処理となっています。RankingBoards.Length > SortedAgentValues.Lengthであれば、「SortedAgentValues[Index]」の部分で無いものを取り出そうとしてifが失敗するため、elseでキャッチしてSetEmptyで半角スペースをビルボードに反映させます。

OnBegin

最後に、今回は他の仕掛けを使わなかったため、毎秒ごとにビルボードを更新する処理を書きました。より負荷対策を考えたいなどであれば、「○○の条件を満たしたらUpdateBillboardsを呼び出す」などをしてみましょう。

完成形

using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary }

AgentToMessage<localizes>(InAgent:agent):message="{InAgent}"
ValueToMessage<localizes>(InValue:int):message="{InValue}"
EmptyMessage<localizes>:message=" "

ranking_board_billboard := class<concrete>:
    @editable
    NameBillboard:billboard_device = billboard_device{}

    @editable
    ValueBillboard:billboard_device = billboard_device{}

    SetValue(InAgent:agent, InValue:int):void=
        NameBillboard.SetText(AgentToMessage(InAgent))
        ValueBillboard.SetText(ValueToMessage(InValue))

    SetEmpty():void=
        NameBillboard.SetText(EmptyMessage)
        ValueBillboard.SetText(EmptyMessage)

ranking_board_device := class(creative_device):
    @editable
    RankingBoards:[]ranking_board_billboard = array{}

    @editable
    ValueStat:stat_creator_device = stat_creator_device{}

    OnBegin<override>()<suspends>:void=
        loop:
            Sleep(1.0)
            UpdateBillboards()

    UpdateBillboards():void=
        AgentValues := GetAgentValues()
        SortedAgentValues := SortBy(AgentValues, SortByValue)
        
        for:
            Index -> Board : RankingBoards
        do:
            if(AgentValue := SortedAgentValues[Index]):
                Board.SetValue(AgentValue(0), AgentValue(1))
            else:
                Board.SetEmpty()

    GetAgentValues():[]tuple(agent, int)=
        for:
            PlayerElement : GetPlayspace().GetPlayers()
            PlayerValue := ValueStat.GetValue[PlayerElement]
        do:
            (PlayerElement, PlayerValue)

    SortByValue(X:tuple(agent, int), Y:tuple(agent, int))<computes><decides>:void=
        X(1) > Y(1)

コピペ用に全体図も貼っておきます。

map型

using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary }

AgentToMessage<localizes>(InAgent:agent):message="{InAgent}"
ValueToMessage<localizes>(InValue:int):message="{InValue}"
EmptyMessage<localizes>:message=" "

ranking_board_billboard := class<concrete>:
    @editable
    NameBillboard:billboard_device = billboard_device{}

    @editable
    ValueBillboard:billboard_device = billboard_device{}

    SetValue(InAgent:agent, InValue:int):void=
        NameBillboard.SetText(AgentToMessage(InAgent))
        ValueBillboard.SetText(ValueToMessage(InValue))

    SetEmpty():void=
        NameBillboard.SetText(EmptyMessage)
        ValueBillboard.SetText(EmptyMessage)

ranking_board_device := class(creative_device):
    @editable
    RankingBoards:[]ranking_board_billboard = array{}

    var ValuePerAgent:[agent]int = map{}

    OnBegin<override>()<suspends>:void=
        loop:
            Sleep(1.0)
            UpdateBillboards()

    UpdateBillboards():void=
        AgentValues := GetAgentValues()
        SortedAgentValues := SortBy(AgentValues, SortByValue)
        
        for:
            Index -> Board : RankingBoards
        do:
            if(AgentValue := SortedAgentValues[Index]):
                Board.SetValue(AgentValue(0), AgentValue(1))
            else:
                Board.SetEmpty()

    GetAgentValues():[]tuple(agent, int)=
        for:
            Agent -> Value : ValuePerAgent
        do:
            (Agent, Value)

    SortByValue(X:tuple(agent, int), Y:tuple(agent, int))<computes><decides>:void=
        X(1) > Y(1)

大体の個所は同じです。

まず、ValueStatをValuePerAgentという[agent]intのマップに置き換えました。

次にGetAgentValuesの中身をガラッと変えています。mapに対するforでは、キーと値を同時に取り出すことができるため、forの呼び出し元に配列が返る特性を生かしてこのような処理にしました。

最後に

完成形の動画となります。ぜひ、皆様も作ってみてくださいね!

送信中です

]]>
varのアクセス指定子で外部からはreadonlyな変数を | Verse https://gorosuke.net/verse-var-access/ Tue, 01 Jul 2025 02:25:45 +0000 https://gorosuke.net/?p=321 varに対してアクセス指定子をつけてあげることで、クラス内部では値は変更できるが、外部からは定数として扱われる変数を作ることができます。 サンプルコードではsample_timer_dev […]]]> var<private>
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Random }

sample_timer_device := class(creative_device):

    var<private> Time:int = 0

    OnBegin<override>()<suspends>:void=
        loop:
            # 毎秒1ずつTimeをインクリメントする
            Sleep(1.0)
            set Time += 1

sample_device := class(creative_device):

    @editable
    SampleTimerDevice:sample_timer_device = sample_timer_device{}

    OnBegin<override>()<suspends>:void=
        loop:
            Sleep(GetRandomFloat(1.0, 5.0))
            CurrentTime := SampleTimerDevice.Time

            # The assignment's left hand expression type `int` cannot be assigned to(3509)
            set SampleTimerDevice.Time = 100

varに対してアクセス指定子をつけてあげることで、クラス内部では値は変更できるが、外部からは定数として扱われる変数を作ることができます。

サンプルコードではsample_timer_device内では毎秒Time変数に毎秒1ずつ足す処理を書いているのですが、sample_deviceなどの外部からではThe assignment’s… と定数なので値を入れることができませんとエラーが出ます。

デフォルト

varはデフォルトでpublicとして扱われます(当然といえば当然ですが)。

そのため、

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Random }

sample_timer_device := class(creative_device):

    var Time:int = 0

    OnBegin<override>()<suspends>:void=
        loop:
            # 毎秒1ずつTimeをインクリメントする
            Sleep(1.0)
            set Time += 1

sample_device := class(creative_device):

    @editable
    SampleTimerDevice:sample_timer_device = sample_timer_device{}

    OnBegin<override>()<suspends>:void=
        loop:
            Sleep(GetRandomFloat(1.0, 5.0))
            CurrentTime := SampleTimerDevice.Time

            set SampleTimerDevice.Time = 100

とprivate指定子を外してあげると、外部のクラスから値を調整することができます。

継承

using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Random }

sample_timer_device := class(creative_device):

    var<protected> Time:int = 0

    OnBegin<override>()<suspends>:void=
        loop:
            # 毎秒1ずつTimeをインクリメントする
            Sleep(1.0)
            set Time += 1

sample_timer_overridable_device := class(sample_timer_device):

    Override(InTime:int):void=
        set Time = InTime

sample_device := class(creative_device):

    @editable
    SampleTimerDevice:sample_timer_overridable_device = sample_timer_overridable_device{}

    OnBegin<override>()<suspends>:void=
        loop:
            Sleep(GetRandomFloat(1.0, 5.0))
            CurrentTime := SampleTimerDevice.Time

            SampleTimerDevice.Override(100)

通常のアクセス指定子と同様にprotectedでクラスの子にだけ値を変更可能かも分けることができます。

おまけ

    # Used to spawn a `particle_system` at the location of this entity. The `particle_system` will simulate while the `particle_system_component` is in the scene.
    # 
    # Dependencies:
    #   * `transform_component` on the entity positions the `particle_system`.
    particle_system_component<native><public> := class<final_super><epic_internal>(component, enableable):
        # Enables the simulation and rendering of this `particle_system`.
        Enable<override><native>():void

        # Disables the simulation and rendering of this `particle_system`.
        Disable<override><native>():void

        # Succeeds if the component is enabled, fails if it’s disabled.
        IsEnabled<override><native>()<transacts><decides>:void

        @editable
        # Controls if the `particle_system_component` should start enabled.
        var<private> Enabled<native><public>:logic = external {}

        Play<native><public>():void

        Stop<native><public>():void

        @editable
        # Controls if the `particle_system_component` should play the simulation automatically when added to the scene, or when enabled from a disabled state.
        var<private> AutoPlay<native><public>:logic = external {}

公式APIでも実際に使われいる箇所がいくつもあります。これらはvarというキーワードがついているものの、privateであるため値を受け取ることしかできません。

そもそも、Verse初期のクラスではGetXXX関数とSetXXX関数がパラメータ分だけ用意されていたのですが、はやり無駄にダイジェストが長くなってしまうので変数として公開されるようになったのだと思います。

送信中です

]]>
[agent]logicよりも[]agentで条件を管理 | Verse https://gorosuke.net/verse-memo-array-vs-map-logic/ Tue, 01 Jul 2025 01:28:07 +0000 https://gorosuke.net/?p=317
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }

sample_map_device := class(creative_device):

    var CompletedAgents:[agent]logic = map{}

    Complete(InAgent:agent):void=
        if(set CompletedAgents[InAgent] = true){}

    Reset(InAgent:agent):void=
        if(set CompletedAgents[InAgent] = false){}

    IsCompleted(InAgent:agent)<decides><transacts>:void=
        CompletedAgents[InAgent]?

    DoForCompletedAgents(InFunction:agent->void):void=
        for:
            AgentElement -> State : CompletedAgents
            State?
        do:
            InFunction(AgentElement)

sample_array_device := class(creative_device):

    var CompletedAgents:[]agent = array{}

    Complete(InAgent:agent):void=
        if(not CompletedAgents.Find[InAgent]):
            set CompletedAgents += array{InAgent}

    Reset(InAgent:agent):void=
        set CompletedAgents = CompletedAgents.RemoveAllElements(InAgent)

    IsCompleted(InAgent:agent)<decides><transacts>:void=
        CompletedAgents.Find[InAgent]

    DoForCompletedAgents(InFunction:agent->void):void=
        for:
            AgentElement : CompletedAgents
        do:
            InFunction(AgentElement)

まったく同じ結果が返ってくるデバイスを二つ用意しました。これらは「達成したか」のフラグを保存しています。

さて、Verseの知識がある方ならどこが違うかは一目でわかりますね。そう、CompletedAgentsの型が「agentをキーにしたlogicのmap型」と「agentを配列に取る型」と違っています。

多くの方は、mapを用いてlogicのフラグの管理をすると思います。しかし、私はこの管理の仕方が好きではないため、配列を用いた管理を普段使用しています。

理由1

というのも、第一にmapでの管理は全て失敗コンテキスト内で行われます。「set XXX[key] = value」をいちいちif内で書くというのは、自分としては可読性の意味であまり好きではありません。しかも、ifでは書くものの理論上はこのifが失敗することはありません。つまり、形式として必要なifというわけなんです。そのために、ifの中身を書かずに{}ですぐ閉じたりするのですが…あまりスマートではありませんよね。

配列で管理する場合、単純に配列にagentを足してあげるだけで大丈夫です。重複も考えずに足すだけでよければ、Findでのチェックも必要ないかもしれません(ただ、無限にデータが溜まっていくのは良くないので確認しています)

消す際はRemoveAllElementsという失敗関数ではない専用の関数も用意されています。そのため、いちいちifを経由しなくてもagentのフラグをオフにできるのです。

理由2

そもそも、logicというのは「true」と「false」という二つの状態を持つ型です。この二つの状態というのは、見方を変えれば「存在する」か「存在しないか」とも取れるはずです。

つまり、配列内に「存在する場合(Findで見つかる)」場合はlogicのtrueとして扱い、「存在しない場合(Findで見つからない)」はfalseとして扱うことで、ほぼ同じ感覚で管理できるはずです。

理由3

logicの状態を取りだす際も、配列の方がケアレスミスを減らせると思います。

# 配列の場合
CompletedAgents.Find[Agent]

# mapの場合
CompletedAgents[Agent] #このままだとダメ
CompletedAgents[Agent]? # ?を足してあげる

マップの場合、存在そのものが失敗する可能性があるため、中身の状態を確認せずとも「マップに登録されているか」で失敗かどうかが確認されます。そのため、その中身に対して「?」で状態を確認していなくても、エラーも出ず通常通りコードは動くはずです。

この場合、Reset関数でfalseに変更したとしても、map自体には登録されているため、ifは常に成功します。また、Complete関数をまだ呼び出していない場合も、mapには登録されていないためifは失敗します。これを、「mapに登録されているかのif」を「logicの状態に基づくif」として勘違いしてしまう可能性もあるわけです(とはいえ、注意すればよい話ではありますが)

これを配列を使うことで、「.Find」と条件を明示する必要があるため上記のようなミスを減らせると思います。

理由3

DoForCompletedAgents関数を見ていただくとわかる通り、forで回す際も配列の方が良かったりします。

というのも、map型は登録された値をすべて取り出すという作業をしているので、取り出し後に「値がtrueか」という工程も挟む必要があるのです。

それに対し、配列であればfalseのタイミングで配列から消しているため、forで実行されることもありません。

こちらもたった一行の差ですが、一目で処理がわかりやすいと思うので、配列の方が好きだったりします。

最後に

ということで、雑なメモとして自論を語りました。

まあ、当然コードの方法なんて好きに書けばいいと思っているので、この方法が絶対正義とは思いません。ただ、、、一応便利だよっていうのと、こういう観点でコードを書くのも楽しいよって紹介でした

また機会があればこういう記事も書きたいな…ということで。以上、ごろ助でした!

送信中です

]]>
透明/半透明のマテリアルの作り方 | UE5, UEFN https://gorosuke.net/ue-opacity-material/ Mon, 30 Jun 2025 02:06:12 +0000 https://gorosuke.net/?p=310 Blend Mode

コンテンツブラウザでマテリアルアセットを開くと、専用にウィンドウが表示されます。ウィンドウ中央のグリッドをマテリアルグラフと呼びます。
マテリアルグラフの左側の「詳細パネル」から、Blend Mode(ブレンドモード)という項目に移動します。こちらを「Masked」もしくは「Translucent」にしてください。

  • Masked: 透明度が0か1のどちらかになり、半透明という状態は作れません。
  • Translucent: 0から1の間で透明度が決まり、例えばオパシティを0.5とすると中間の半透明の状態になります。

結果ノード

薄い茶色の「結果ノード」の項目から、「オパシティ」もしくは「オパシティマスク」の数値を調整し、透明度を設定します。

ブレンドモードを「Masked」にしている場合は「オパシティマスク」が有効になっており、デフォルトだと「0.3333」を境に表示と非表示が切り替わるはずです。この境界線を調整したい場合は、詳細タブの「マテリアル>詳細設定>Opacity Mask Clip Value(オパシティマスククリップ値)」で行えます。

ブレンドモードを「Translucent」にしている場合は「オパシティ」が有効になっており、1.0を不透明、0.0を透明とし、その間で値を設定すると半透明のマテリアルを作り出せます。ただし、デフォルトだとベースカラー以外の入力が消えているはずです(メタリック等)。もし半透明とメタリックのような質感を両立させたい場合は「透過処理>Lighting Mode(ライティングモード)」を「Surface TranslucencyVolume」にすることで可能です。

ただし、半透明は描画のコストが高いため、多用するとプラットフォームや環境によってはカクつきの原因になるかもしれません。特にゲームとして実装する場合は、「きれいだから」だけで進めてしまうと後々修正が必要になるかも…ご利用は計画的に。

送信中です

]]>
GENERATED_BODYに赤波線でエラーが…対処法 / 備忘録 | UE5, C++ https://gorosuke.net/ue5-cpp-generated-body-error-fix/ Mon, 30 Jun 2025 01:47:24 +0000 https://gorosuke.net/?p=308 解決法

結論から言います!

  1. #include (ヘッダーファイル名).generated.hを消してしまった
  2. #include (ヘッダーファイル名).generated.hより下にincludeを書いてしまった

が主な原因で、

  1. 手書きで#include “MyActor.generated.h”のように付け加える
  2. #include “Kismet/BlueprintFunctionLibrary.h”みたいなのを追加したい場合は、include “MyActor.generated.h”より上に付け加える

で大体の場合は解決するかなーと思います!

Error: #include found after .generated.h file – the .generated.h file should always be the last #include in a header

みたいなエラー原因もログとして一応出ますが。。。初心者の時は焦りますよね笑

消してしまった場合

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
// 本来あるべき#include "MyActor.generated.h"が消えている…!

UCLASS()
class LEARNINGUE5_API AMyActor : public AActor
{
    GENERATED_BODY()

public:    
    // Sets default values for this actor's properties
    AMyActor();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:    
    // Called every frame
    virtual void Tick(float DeltaTime) override;

};

原因はいろいろあるでしょうけど、generated.hを間違って消してしまった場合…とりあえず「#include "(ヘッダーファイル名).generated.h"」を足すことで解決するかもしれません
ここでincludeの(ヘッダーファイル名)に入るのはクラスの名称「例: AMyActor」ではなく、現在のファイル名を書きましょう

これで解決しなかったら…さらに下の解決法も試していきます
# generated.hより下に#includeを足さない

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"

#include "Kismet/BlueprintFunctionLibrary.h // ここはOK
#include "MyActor.generated.h" 
#include "Kismet/BlueprintFunctionLibrary.h // ここはダメ

UCLASS()
class LEARNINGUE5_API AMyActor : public AActor
{
    GENERATED_BODY()

public:    
    // Sets default values for this actor's properties
    AMyActor();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:    
    // Called every frame
    virtual void Tick(float DeltaTime) override;

};

何か足してきたいときは下に下にと増やしていきたい気持ちもあり、generated.hより下に足してしまうなんてこともよくあります。ですが、こちらもエラーの原因となるので、generated.hよりも上にincludeを足しましょう。

ファイル名を変更した

ファイル名を変更した場合は少しややこしい事態になるのですが

  1. 元のファイル名に戻す
  2. #include (現在のファイル名).generated.hにする

のどちらかで解決すると思います。
“プロジェクト名\Intermediate\Build\Win64\UnrealEditor\Inc\LearningUE5\UHT”
に該当のヘッダーがあるのですが、こことのずれで起こるエラーです
ずっと変更後にしたい!という場合は、IntermediateとBinariesフォルダを削除してプロジェクトを開きなおすとビルドが始まるので、(includeの現在のファイル名をちゃんと現在のにしていれば)エラーは解消されるはずです。

送信中です

]]>
“存在しない”を設定できるTOptional型について | UE5, C++ https://gorosuke.net/ue5-cpp-toptional/ Mon, 30 Jun 2025 01:40:08 +0000 https://gorosuke.net/?p=305 前書き

例えば何かを生成する関数があったとして、正常に生成された場合はその値を返せばよいのですが、その生成に失敗した場合に「仮のインスタンス/値」を返すのを避けたい場面に遭遇します。

オプション型というのはそのような「値が無い/参照がない」という状態を作り出すために使えます。

Unreal C++ / TOptional<型名>

UnrealのC++にはTOptionalというクラスが用意されています。

https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/uLangCore/uLang/Common/Misc/TOptional

使い方は非常に簡単で

TOptional<int32> Number;
TOptional<int32> Number = 56

と宣言します。

値を消す / 値を設定

//リセット
Number.Reset(); 

//値を設定
Number.Emplace(20);
Number= 20;
値を設定する場合、もしくは値を消す場合は上記の方法で行えます。
リセット関数を呼び出すと、number変数内は空になります。これは無というイメージなので、そのまま値を使おうとすると無いものを取り出すとしてエラーが発生します。
値を設定する場合は、Emplace関数か=で出来ます。

値を取り出して実際に使う場合は

パターン1

if(Number.IsSet())
{
    Use(Number.GetValue());
}

のようにIsSet関数で中身が存在するかをチェックし、その後GetValue関数で生の値を取り出すみたいな感じです。
ここでIsSet関数を呼び出さずにGetValue()を使ってしまうと、値が設定されてる場合は通常に動作するのですが、入ってない場合はエラーを吐いてクラッシュしてしまいます…。

パターン2

Use(Number.Get(0); //numberが空の場合は0が代わりに入る。

のように、引数付きのGet関数を呼び出す方法も存在します。こちらはnumberの中身が空の場合、代わりに引数に渡したデフォルト値が入るよう取り出します。

関数の戻り値

上記が主な使い方ですが、関数の戻り値としても見ていきます。

TOptional<int32> GetNumber()
{
    if(Conditional)
    {
        return 10;
    }
    else
    {
        return TOptional<int32>();
    }
}

と書く…といいたいのですが、こちらについてはあまり自身は無いです。無い状態を表すもの(nullptrのような)が見つからなかったので、何も設定していないTOptionalを返してる感じです(もし詳しい方がいれば教えていただけると助かります…)

送信中です

]]>