UEProg by milkc0de

    Search

    Home

    UEFN Verse: ガード撃破位置にVFXを出す

    Guardヘッドショットで「headshot」を1秒表示する(3デバイスのみ)

    UEFN / Verseでプレイヤー名を表示する方法

    UEFN Verseサンプル - Trigger起動でBearのPropがプレイヤーについてくる

    ガードの行動範囲を「プレイヤー中心」に縛る(Leash)ガードAI

    通常編集とシンプル編集で 窓開けリセットの最短時間を測定

    UEFN: 建物内が暗くなる場合の対処(Cast Shadow一括OFF) by milkc0de

    UEFN / VerseのみでPINコード・パスコード入力パッドを実装する

    UEFN / Verseのみで作ったPINコード入力パッドを初心者向けに分解して解説する

    UEProg by milkc0de

    Home

    UEFN / Verseでプレイヤー名を表示する方法

    UEFN Verseサンプル - Trigger起動でBearのPropがプレイヤーについてくる

    UEFN Verse: ガード撃破位置にVFXを出す

    Guardヘッドショットで「headshot」を1秒表示する(3デバイスのみ)

    ガードの行動範囲を「プレイヤー中心」に縛る(Leash)ガードAI

    通常編集とシンプル編集で 窓開けリセットの最短時間を測定

    UEFN: 建物内が暗くなる場合の対処(Cast Shadow一括OFF) by milkc0de

    UEFN / VerseのみでPINコード・パスコード入力パッドを実装する

    UEFN / Verseのみで作ったPINコード入力パッドを初心者向けに分解して解説する

    milkc0de

    XTwitch
    UEProg by milkc0de
    /
    UEFN / VerseのみでPINコード・パスコード入力パッドを実装する
    UEFN / VerseのみでPINコード・パスコード入力パッドを実装する

    UEFN / VerseのみでPINコード・パスコード入力パッドを実装する

    UEFNでPINコード / パスコード / Secret Code Pad のような仕掛けを作りたいことがあります。

    例えば

    • 金庫のロック
    • 秘密の扉
    • 謎解き
    • セキュリティ端末

    などです。

    今回は VerseのみでUIを生成してPIN入力パッドを実装する方法を紹介します。

    デバイスUIではなく Verseで完全にUIを構築する方式です。

    さらに

    • マルチプレイヤー対応
    • 同時操作対応
    • 複数のPINコード対応
    • 成功トリガー分岐

    まで実装しています。

    🧩 完成イメージ

    PIN入力UIをVerseで生成します。

    • 数字ボタン
    • delete
    • enter
    • close

    すべてVerseで配置しています。

    image

    ⚙️ このデバイスの特徴

    このPinPadは以下の仕様になっています。

    ✔ VerseのみでUI生成

    UEFNのWidget Blueprintなどは使っていません。

    ✔ プレイヤーごとUI生成

    UIは

    player_ui.AddWidget

    で 起動したプレイヤーだけに表示されます。

    ✔ 同時操作対応

    複数プレイヤーが同時に操作しても

    状態が混ざらないように playerごとの状態管理をしています。

    [player]widget
    [player]string
    [player]player_ui

    のMapで状態を分離しています。

    ✔ 複数PINコード対応

    複数のPINを設定できます。

    PinCodes
    SuccessTriggers

    を 同じインデックスで対応させます。

    例

    PinCodes[0] → SuccessTriggers[0]
    PinCodes[1] → SuccessTriggers[1]

    🧱 デバイス構成

    このVerseデバイスには以下の接続があります。

    OpenTrigger

    UIを開くトリガーです。

    例えば

    • ボタン
    • Interaction Device
    • Trigger

    などから接続します。

    プレイヤー → Trigger → PinPad

    FailedTrigger

    PINが間違っていた場合に発火します。

    例えば

    • 警報
    • ダメージ
    • 再入力

    などに使えます。

    SuccessTriggers

    PINコードごとの成功トリガーです。

    例えば

    1234 → 金庫が開く
    4321 → 隠し部屋
    0000 → 爆発

    のような分岐が可能です。

    🧩 デバイス設定

    Verseデバイスを配置すると以下の設定が表示されます。

    OpenTrigger

    UIを開くトリガー

    FailedTrigger

    PINが間違ったときのトリガー

    PinCodes

    入力可能なPINコード

    1234
    9999
    0420

    など複数設定できます。

    SuccessTriggers

    PIN成功時のトリガー

    PinCodesと同じインデックスで対応します。

    TitleText

    UIのタイトル

    PIN PAD

    など

    MaxDigits

    最大入力桁数

    ☆ マルチプレイヤー対応のポイント

    UEFNのUIは

    状態管理をミスるとプレイヤー同士でUIが干渉します。

    例えば

    • Aの入力がBに表示される
    • UIが消える
    • PINが共有される

    などです。

    このデバイスでは以下のように プレイヤーごとに状態を保持しています。

    var PlayerRoots : [player]widget = map{}
    var PlayerInputTexts : [player]text_block = map{}
    var PlayerInputs : [player]string = map{}
    var PlayerUIs : [player]player_ui = map{}

    これにより

    A → 1234
    B → 9999

    でも完全に独立して処理されます。

    💻 Verseコード

    以下がPinPadのVerse実装です。

    🚀 まとめ

    Verseだけでも

    • UI生成
    • 入力処理
    • マルチプレイヤー対応

    を行うことで

    かなり本格的なゲームUIを作ることができます。

    UEFNでは

    UI + Verse + Trigger

    を組み合わせることで

    ゲーム的な仕掛けの幅がかなり広がります。

    ぜひ色々応用してみてください。

    image
    using { /Fortnite.com/Devices }
    using { /Fortnite.com/UI }
    using { /UnrealEngine.com/Temporary/UI }
    using { /UnrealEngine.com/Temporary/SpatialMath }
    using { /Verse.org/Colors }
    using { /Verse.org/Simulation }
    
    pinpad_device := class(creative_device):
    
        @editable
        OpenTrigger : trigger_device = trigger_device{}
    
        @editable
        FailedTrigger : trigger_device = trigger_device{}
    
        @editable
        PinCodes : []string = array{}
    
        @editable
        SuccessTriggers : []trigger_device = array{}
    
        @editable
        TitleText : string = "PIN PAD"
    
        @editable
        MaxDigits : int = 8
    
        var PlayerRoots : [player]widget = map{}
        var PlayerInputTexts : [player]text_block = map{}
        var PlayerInputs : [player]string = map{}
        var PlayerUIs : [player]player_ui = map{}
    
        ToMessage<localizes>(S:string) : message = "{S}"
    
        OnBegin<override>()<suspends>:void =
            OpenTrigger.TriggeredEvent.Subscribe(OnOpenTriggered)
    
        OnOpenTriggered(MaybeAgent:?agent):void =
            if (A := MaybeAgent?):
                if (P := player[A]):
                    OpenForPlayer(P)
    
        OpenForPlayer(P:player):void =
            CloseForPlayer(P)
    
            if (UI := GetPlayerUI[P]):
                if (set PlayerUIs[P] = UI):
                    Root := BuildRootWidget(P)
    
                    UI.AddWidget(
                        Root,
                        player_ui_slot{
                            ZOrder := 100,
                            InputMode := ui_input_mode.All
                        }
                    )
    
                    if (set PlayerRoots[P] = Root):
                    if (set PlayerInputs[P] = ""):
                    UpdateDisplay(P)
    
        CloseForPlayer(P:player):void =
            if (UI := PlayerUIs[P]):
                if (Root := PlayerRoots[P]):
                    UI.RemoveWidget(Root)
    
        UpdateDisplay(P:player):void =
            if (TB := PlayerInputTexts[P]):
                if (Current := PlayerInputs[P]):
                    if (Current.Length > 0):
                        TB.SetText(ToMessage(Current))
                    else:
                        TB.SetText(ToMessage(""))
    
        AppendDigit(P:player, Digit:string):void =
            if (Current := PlayerInputs[P]):
                if (Current.Length >= MaxDigits):
                    return
    
                NewValue : string = "{Current}{Digit}"
                if (set PlayerInputs[P] = NewValue):
                UpdateDisplay(P)
    
        DeleteLastDigit(P:player):void =
            if (Current := PlayerInputs[P]):
                if (Current.Length <= 0):
                    return
    
                NewValue : string = Substring(Current, 0, Current.Length - 1)
                if (set PlayerInputs[P] = NewValue):
                UpdateDisplay(P)
    
        Substring(S:string, Start:int, L:int):string =
            if ((Start >= 0) and (L >= 0) and (Start < S.Length)):
                var End : int = Start + L
                if (End > S.Length):
                    set End = S.Length
    
                var Result : string = ""
                var I : int = Start
                loop:
                    if (I >= End):
                        break
                    if (C := S[I]):
                        set Result = "{Result}{C}"
                    set I = I + 1
                return Result
            return ""
    
        SubmitPin(P:player):void =
            if (Current := PlayerInputs[P]):
                for (Index -> ExpectedPin : PinCodes):
                    if (Current = ExpectedPin):
                        if (Agent := agent[P]):
                            if (SuccessTrigger := SuccessTriggers[Index]):
                                SuccessTrigger.Trigger(Agent)
                            else:
                                FailedTrigger.Trigger(Agent)
                        CloseForPlayer(P)
                        return
    
                if (Agent := agent[P]):
                    FailedTrigger.Trigger(Agent)
                CloseForPlayer(P)
    
        BuildRootWidget(P:player):canvas =
            Dimmer : color_block = color_block{
                DefaultColor := color{R := 0.0, G := 0.0, B := 0.0},
                DefaultOpacity := 0.60,
                DefaultDesiredSize := vector2{X := 1920.0, Y := 1080.0}
            }
    
            Panel : color_block = color_block{
                DefaultOpacity := 0.98,
                DefaultDesiredSize := vector2{X := 560.0, Y := 800.0},
                DefaultColor := color{R := 0.0, G := 0.0, B := 1.0}
            }
    
            Title : text_block = text_block{
                DefaultText := ToMessage(TitleText),
                DefaultTextColor := color{R := 1.0, G := 1.0, B := 1.0},
                DefaultTextSize := 36.0,
                DefaultJustification := text_justification.Center
            }
    
            DisplayBG : color_block = color_block{
                DefaultColor := color{R := 0.0, G := 0.0, B := 0.0},
                DefaultOpacity := 1.0,
                DefaultDesiredSize := vector2{X := 420.0, Y := 74.0}
            }
    
            DisplayText : text_block = text_block{
                DefaultText := ToMessage(""),
                DefaultTextColor := color{R := 1.0, G := 1.0, B := 1.0},
                DefaultTextSize := 34.0,
                DefaultJustification := text_justification.Center
            }
            if (set PlayerInputTexts[P] = DisplayText):
    
            ButtonWidth : float = 120.0
            ButtonHeight : float = 90.0
            SmallButtonWidth : float = 140.0
            SmallButtonHeight : float = 90.0
    
            Btn1 : button_loud = button_loud{ DefaultText := ToMessage(" 1 ") }
            Btn2 : button_loud = button_loud{ DefaultText := ToMessage(" 2 ") }
            Btn3 : button_loud = button_loud{ DefaultText := ToMessage(" 3 ") }
            Btn4 : button_loud = button_loud{ DefaultText := ToMessage(" 4 ") }
            Btn5 : button_loud = button_loud{ DefaultText := ToMessage(" 5 ") }
            Btn6 : button_loud = button_loud{ DefaultText := ToMessage(" 6 ") }
            Btn7 : button_loud = button_loud{ DefaultText := ToMessage(" 7 ") }
            Btn8 : button_loud = button_loud{ DefaultText := ToMessage(" 8 ") }
            Btn9 : button_loud = button_loud{ DefaultText := ToMessage(" 9 ") }
            Btn0 : button_loud = button_loud{ DefaultText := ToMessage(" 0 ") }
    
            BtnDelete : button_regular = button_regular{ DefaultText := ToMessage("DELETE") }
            BtnEnter : button_loud = button_loud{ DefaultText := ToMessage("ENTER") }
            BtnClose : button_quiet = button_quiet{ DefaultText := ToMessage(" X ") }
    
            Btn1.OnClick().Subscribe(OnDigit1)
            Btn2.OnClick().Subscribe(OnDigit2)
            Btn3.OnClick().Subscribe(OnDigit3)
            Btn4.OnClick().Subscribe(OnDigit4)
            Btn5.OnClick().Subscribe(OnDigit5)
            Btn6.OnClick().Subscribe(OnDigit6)
            Btn7.OnClick().Subscribe(OnDigit7)
            Btn8.OnClick().Subscribe(OnDigit8)
            Btn9.OnClick().Subscribe(OnDigit9)
            Btn0.OnClick().Subscribe(OnDigit0)
    
            BtnDelete.OnClick().Subscribe(OnDeletePressed)
            BtnEnter.OnClick().Subscribe(OnEnterPressed)
            BtnClose.OnClick().Subscribe(OnClosePressed)
    
            Root : canvas = canvas:
                Slots := array:
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.0, Y := 0.0}, Maximum := vector2{X := 0.0, Y := 0.0}}
                        Offsets := margin{Left := 0.0, Top := 0.0, Right := 1920.0, Bottom := 1080.0}
                        Alignment := vector2{X := 0.0, Y := 0.0}
                        SizeToContent := false
                        Widget := Dimmer
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 0.0, Top := 0.0, Right := 560.0, Bottom := 800.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := false
                        Widget := Panel
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 0.0, Top := -320.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Title
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 212.0, Top := -325.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := BtnClose
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 0.0, Top := -230.0, Right := 420.0, Bottom := 74.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := false
                        Widget := DisplayBG
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 0.0, Top := -230.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := DisplayText
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := -130.0, Top := -110.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn1
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 0.0, Top := -110.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn2
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 130.0, Top := -110.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn3
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := -130.0, Top := 0.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn4
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 0.0, Top := 0.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn5
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 130.0, Top := 0.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn6
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := -130.0, Top := 110.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn7
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 0.0, Top := 110.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn8
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 130.0, Top := 110.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn9
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X :=  0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 0.0, Top := 220.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := Btn0
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := -130.0, Top := 330.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := BtnDelete
    
                    canvas_slot:
                        Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.5}, Maximum := vector2{X := 0.5, Y := 0.5}}
                        Offsets := margin{Left := 130.0, Top := 330.0, Right := 0.0, Bottom := 0.0}
                        Alignment := vector2{X := 0.5, Y := 0.5}
                        SizeToContent := true
                        Widget := BtnEnter
    
            Root
    
        OnDigit1(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                AppendDigit(P, "1")
    
        OnDigit2(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                AppendDigit(P, "2")
    
        OnDigit3(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                AppendDigit(P, "3")
    
        OnDigit4(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                AppendDigit(P, "4")
    
        OnDigit5(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                AppendDigit(P, "5")
    
        OnDigit6(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                AppendDigit(P, "6")
    
        OnDigit7(Msg:widget_message):void =
            if (    P := player[Msg.Player]):
                AppendDigit(P, "7")
    
        OnDigit8(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                AppendDigit(P, "8")
    
        OnDigit9(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                AppendDigit(P, "9")
    
        OnDigit0(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                AppendDigit(P, "0")
    
        OnDeletePressed(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                DeleteLastDigit(P)
    
        OnEnterPressed(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                SubmitPin(P)
    
        OnClosePressed(Msg:widget_message):void =
            if (P := player[Msg.Player]):
                CloseForPlayer(P)