Delta and Input detection for player input actions.

I’d say these are fairly critical requirement when creating game modes.

KeyCode, InputType, etc… would be nice, but in reality we just need EventInput > ActionEnum.Primary/Secondary, (left and right click for pc).
ActionPressed, ActionHeld, ActionReleased, ActionClicked.

Delta since last update is a fairly simple one. The fabled TICK event and method.
You can kind of get past this with the Sleep(0.0), but the tick event quite frankly doesn’t exist by default. There’s no telling what sort of problems will crop up when you run a tick update from a Singleton device of some sort with an OnBegin > loop → Sleep(0.0) :Tick event deployer in the long run.

This may not be a viable way to create a customized game mode, and it could cause serious problems down the line.

You can’t do a real “tick”, and for good reason IMO (see my opinion on why below the code and log output), but you can do this…

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

# Simple example of a device that acts as a "service" for your game mechanic(s)
# Verse Protip: If operating within a <suspends> function, be sure to call setters with <transacts> effect when you want anything to persist/commit!
service_device := class(creative_device) {
    var Active : logic = true # start ticking immediately

    var UpdateNowSecs<private> : float = 0.0
    var UpdatePrevSecs<private> : float = 0.0
    var UpdateDeltaSecs<private> : float = 0.0
    GetUpdateDeltaSecs()<transacts> : float = { return UpdateDeltaSecs }

    var WorkQueueCount : int = 0 # just some fake value representing "work to be done"

    var FrameNumber : int = 0
    GetFrameNumber()<transacts> : int = { return FrameNumber }

    OnBegin<override>()<suspends> : void = {
        Print("OnBegin, frame {FrameNumber}")
        # Skipping first frame is usually required if you want to interact with other devices immediately (they might not have been loaded by Fortnite yet)
        Sleep(0.0)
        spawn:
            WorkerThread()

        set UpdatePrevSecs = GetSimulationElapsedTime()
        loop {
            # note that we don't call "Setters" with <transacts> on these, we have <transacts> on the getter instead since you might not even use it
            set UpdateNowSecs = GetSimulationElapsedTime()
            set UpdateDeltaSecs = (UpdateNowSecs - UpdatePrevSecs)
            set UpdatePrevSecs = UpdateNowSecs
            OnUpdate()

            set FrameNumber = FrameNumber + 1
            Sleep(0.0)
        }
    }

    OnEnd<override>()<transacts> : void = {
        set Active = false
    }

    # Loops forever, many times per tick (depends how long DoWork goes for). Essentially a second thread. Will also fire BEFORE the first game tick, so be careful.
    WorkerThread()<suspends> : void = {
        loop {
            if (Active = false) {
                break
            } else {
                DoAllPendingWork()
                Sleep(0.0)
            }
        }
    }

    # Called every game tick (~30 per second), but after render.
    OnUpdate()<suspends> : void = {
        Print("OnUpdate; Frame = {GetFrameNumber()}; Delta = {GetUpdateDeltaSecs()}")
        set WorkQueueCount = 10 # set the fake work value to 10
    }

    HasWork()<transacts> : logic = {
        # just a sample, there is no work this tick
        return (if (WorkQueueCount > 0) { true } else { false } )
        #if (WorkQueueCount > 0) { return true } else { return false }
    }

    DoWork()<transacts> : void = {
        set WorkQueueCount = WorkQueueCount - 1
    }

    DoAllPendingWork()<suspends> : void = {
        loop {
            if (HasWork() = true) {
                # Maybe poll a queue, iterate an array, etc...
                #  ! Be aware of contention, otherwise you'll nerf throughput. Try not to operate on game objects or too much data; e.g. use logic 
                #    flags like dirty/changed and cache values from OnUpdate method where possible.
                Print("DoWork {WorkQueueCount} ...")
                DoWork()
            } else { return }
        }
    }
}

…if you place this device and run as-is, your Print/Log output looks like this:

LogVerse: : OnBegin, frame 0
LogVerse: : OnUpdate; Frame = 0; Delta = 0.000000
LogVerse: : DoWork 10 ...
LogVerse: : DoWork 9 ...
LogVerse: : DoWork 8 ...
LogVerse: : DoWork 7 ...
LogVerse: : DoWork 6 ...
LogVerse: : DoWork 5 ...
LogVerse: : DoWork 4 ...
LogVerse: : DoWork 3 ...
LogVerse: : DoWork 2 ...
LogVerse: : DoWork 1 ...
LogVerse: : OnUpdate; Frame = 1; Delta = 0.033394
LogVerse: : DoWork 10 ...
LogVerse: : DoWork 9 ...
LogVerse: : DoWork 8 ...
LogVerse: : DoWork 7 ...
LogVerse: : DoWork 6 ...
LogVerse: : DoWork 5 ...
LogVerse: : DoWork 4 ...
LogVerse: : DoWork 3 ...
LogVerse: : DoWork 2 ...
LogVerse: : DoWork 1 ...
LogVerse: : OnUpdate; Frame = 2; Delta = 0.033399
LogVerse: : DoWork 10 ...
LogVerse: : DoWork 9 ...
LogVerse: : DoWork 8 ...
LogVerse: : DoWork 7 ...
LogVerse: : DoWork 6 ...
LogVerse: : DoWork 5 ...
LogVerse: : DoWork 4 ...
LogVerse: : DoWork 3 ...
LogVerse: : DoWork 2 ...
LogVerse: : DoWork 1 ...
LogVerse: : OnUpdate; Frame = 3; Delta = 0.033414
LogVerse: : DoWork 10 ...
LogVerse: : DoWork 9 ...
LogVerse: : DoWork 8 ...
LogVerse: : DoWork 7 ...
LogVerse: : DoWork 6 ...
LogVerse: : DoWork 5 ...
LogVerse: : DoWork 4 ...
LogVerse: : DoWork 3 ...
LogVerse: : DoWork 2 ...
LogVerse: : DoWork 1 ...
LogVerse: : OnUpdate; Frame = 4; Delta = 0.033400
LogVerse: : DoWork 10 ...
LogVerse: : DoWork 9 ...
LogVerse: : DoWork 8 ...
LogVerse: : DoWork 7 ...
LogVerse: : DoWork 6 ...
LogVerse: : DoWork 5 ...
LogVerse: : DoWork 4 ...
LogVerse: : DoWork 3 ...
LogVerse: : DoWork 2 ...
LogVerse: : DoWork 1 ...
LogVerse: : OnUpdate; Frame = 5; Delta = 0.033398
LogVerse: : OnUpdate; Frame = 6; Delta = 0.033452
LogVerse: : DoWork 10 ...
LogVerse: : DoWork 9 ...
LogVerse: : DoWork 8 ...
LogVerse: : DoWork 7 ...
LogVerse: : DoWork 6 ...

…do be aware that the delta’s are kinda high (should be at or below 0.33333, being a 30 tick game) because the Print operation is kinda expensive. Also that skipped update between frame 5 and 6 could be an issue depending on what you want to do, though that can be solved… I’m only giving a basic example.

Anyway, you can do a LOT with this.

Having our own events for OnPlayerMovement and OnPlayerAttack and such would be good. Avoids having to use round-about ways (e.g. measuring distances between things, stuck-to-player creative devices…)

A tick function is not necessary. Not only that, but it shifts responsibility of the core game logic to our own verse code - which is not good for performance and “cloud gaming platform” reasons. We should be able to achieve everything with Events, and I think it’s fine that we can’t roll roll our own solution for Events that are lacking. We have so many other platforms that let you do a real Tick method, Core for example. And it’s so much more complicated because of it.; needing to facilitate that.

…that’s just my opinion though. Maybe as a compromise, they could allow us to have one Tick event per game, and just limit how many operations we can do per-tick (to encourage using async stuff). But I have a feeling it’s much easier said than done, given the hosted/cloud-based/GaaS nature of Fortnite.

1 Like

I’m more interested in the OnPlayerInput, OnPlayerMoved, and so on.

Player movement really isn’t too tough to simulate and fire signals, but it seems like the
current system’s Verse iterations are quite taxing in comparison to something at the C++ level.

Player action input on the other hand, we have no real handle for these subsystems from what I can tell. Considering we have a large disconnect between what WE SEE, and what is BEING HANDLED by the game, there’s a bit of a paradoxical situation we face in the large scale of development.

There’s a large amounts of EVENTS for actions when the action begins, but nothing for direct inputs to begin those actions, and nothing visible to completely intercept these events or reconfigure their behavior into something completely customized. You can only simply subscribe to them and expect the outcome to fire once the defined game behavior begins.

On one hand, we have the ability to create complex game modes rapidly if they are within a certain paradigm. On the other hand, anything derivative or experimental will often require large amounts of code and devices to generate the outcome, depending entirely on what you CAN DO and what is out of the possibility scope due to the event and module limitations.

Indeed, Input/Movement/Action events seem essential. Hopefully the lack of such Events was just a matter of priority, since foreign language interfacing (UE <> Verse) is usually pretty expensive in most systems - devs need to make sure it won’t cause any performance issues. For something like Input that often fires many times per tick, they’ve probably tried it out before but had issues with latency so put it on the backlog.

Semi-related, I would really like it if they added the ability for Triggers to be triggered by Other Triggers, so we can more easily do our own collision checks (e.g. custom projectiles). Prop mover has that functionality, but it’s tedious to use and I’ve heard bad things about the performance.

I do have an experiment already working where the player is a 2D character, it’s a top-down perspective; movement is OK because it’s like an Asteroids style player character where the prop has some drag and momentum and such. That’s loosely based on the example code I gave above. But I’ve put it on the backburner for now because it’ll be a lot of work to make AI that fires projectiles at that prop; with hit detection and such. Not to mention the lack of good player input for firing weapons (player cannot hold down Jump for example).

Or maybe there is something else they have in mind. Like, the ability for us to create Weapons from scratch, and to have the ability to attach weapon instances anywhere and make them aim and shoot via Verse… honestly not sure what I would prefer. The former traditional way of manual collision/projectile stuff gives us more flexibility but the latter might be more effective in actual practice.

I will finally say that I think it might be wise to not hold your breath for manual player input though, at least not in the way we might expect - simply because it allows the dev to effectively hide the player’s possibly-hard-earned-skins which is a huge factor of the Fortnite. I would honestly be happy if they just implement alternate input/camera modes, like what Core does. If you’re not familiar, it has all the familiar modes - mouse look being relative or cursor based, movement being forward-with-steering or vector based, turn axis, what determines the player firing angle (mouse position or current rotation)… all that stuff.