Accompanying code and example project for Verse Concurrency talk at Unreal Fest 2023.
Attached the UEFN project files in .zip form and the talk slides in .pdf form at bottom of this post.
[This will be updated and added to over time as well.]
concurrency_device.verse
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Game }
using { /Fortnite.com/Playspaces }
###############################################################################
# Concurrency example manager device
concurrency_device := class(creative_device):
@editable
BlockButton : button_device = button_device{}
@editable
SyncButton : button_device = button_device{}
@editable
RaceButton : button_device = button_device{}
@editable
StopButton : button_device = button_device{}
@editable
ResetButton : button_device = button_device{}
@editable
MapPointA : map_indicator_device = map_indicator_device{}
@editable
MapPointB : map_indicator_device = map_indicator_device{}
@editable
MapPointC : map_indicator_device = map_indicator_device{}
@editable
MapPointD : map_indicator_device = map_indicator_device{}
@editable
PropA : creative_prop = creative_prop{}
@editable
PropB : creative_prop = creative_prop{}
@editable
PropC : creative_prop = creative_prop{}
@editable
PropD : creative_prop = creative_prop{}
@editable
ArrivedVFX : vfx_creator_device = vfx_creator_device{}
Forward2m : vector3 = vector3{X:=200.0, Y:=0.0, Z:=0.0}
OffsetF2mU1m : vector3 = vector3{X:=200.0, Y:=0.0, Z:=100.0}
Offset0 : vector3 = vector3{X:=0.0, Y:=0.0, Z:=0.0}
Arrive1m : float = 100.0
###############################################################################
# Runs when the device is started in a running game
OnBegin<override>()<suspends>:void=
Print("Concurrency device started")
MoveLogic()
###############################################################################
# Gets the specified player index as a positional object
PlayerAsPositional(PlayerIdx : int)<transacts><decides>:positional =
GetPlayspace().GetPlayers()[PlayerIdx].GetFortCharacter[]
###############################################################################
GetPlayerXform(PlayerIdx : int):transform =
# Assume zeroeth player
PlayerAsPositional[PlayerIdx].GetTransform() or transform{}
###############################################################################
# Stops any existing `MoveTo()` operating on this prop
(Prop : creative_prop).MoveStop():void =
if: # This will cancel any currently running move command on the same prop
Prop.TeleportTo[Prop.GetTransform()]
###############################################################################
# Teleports to specified transform and keeps any existing scale
(Prop : creative_prop).TeleportToNoScale(Xform : transform):void =
XformSameScale := transform:
Scale := Prop.GetTransform().Scale
Rotation := Xform.Rotation
Translation := Xform.Translation
if: # This will cancel any currently running move command on the same prop
Prop.TeleportTo[XformSameScale]
###############################################################################
# Move to specified offset of positional target adjusting over time as needed.
# [Extension function that can be called on a `creative_prop` within this `concurrency_device` class.]
(Prop : creative_prop).MoveToPositional(Target : positional, Offset : vector3, ArriveRadius : float)<suspends>:void =
var StopOnArrive : logic = false
defer:
if: # If not arrived yet stop moving if MoveToPositional() cancelled
not StopOnArrive?
then:
Prop.MoveStop()
loop:
# Determine desired position
TargetXform := Target.GetTransform()
TargetPos := TargetXform.Translation
MovePos := TargetPos + TargetXform.Rotation.RotateVector(Offset)
PropPos := Prop.GetTransform().Translation
# Set desired rotation to point to destination
Difference := MovePos - PropPos
MoveRotation := MakeRotation(vector3{X:=0.0, Y:=0.0, Z:=1.0}, -ArcTan(Difference.X, Difference.Y))
# If within arrival radius then exit
DistanceRemaining := Difference.Length()
if (DistanceRemaining <= ArriveRadius):
break
# The farther apart the longer the time to get there [could pass in adjustment]
TimeToMove := DistanceRemaining * 0.003
# Would put `race` here directly thouth need to wrap in funtion as bug workaround
set StopOnArrive = Prop.MoveToPositional_race(MovePos, MoveRotation, TimeToMove)
if (StopOnArrive?):
break
#==============================================================================
# [Sub function of MoveToPositional() - not to be called directly]
# Bug workaround: wrap any `race` that isn't at the end of a function in its own function
(Prop : creative_prop).MoveToPositional_race(MovePos : vector3, MoveRotation : rotation, TimeToMove : float)<suspends>:logic =
race:
block:
Prop.MoveTo(MovePos, MoveRotation, TimeToMove)
# Only returns if Sleep() below doesn't win the race first.
return true # Movement stopped
Sleep(0.3) # Update move to location occasionally [could pass in value]
return false
###############################################################################
ArrivedFanfare()<suspends>:void=
if (PlayerAgent := GetPlayspace().GetPlayers()[0]):
ArrivedVFX.SpawnAt(PlayerAgent)
ArrivedVFX.Enable()
###############################################################################
# Specifies which move patter to use on the props
MoveLogic()<suspends>:void=
loop:
race:
block:
ConcurrencyLogic()
ArrivedFanfare()
block:
ResetButton.InteractedWithEvent.Await()
ResetProps()
StopButton.InteractedWithEvent.Await()
###############################################################################
# Moves each prop in serial - one after the other
ConcurrencyLogic()<suspends>:void=
Print("Waiting for move button...")
race:
block:
BlockButton.InteractedWithEvent.Await()
SerialProps()
block:
SyncButton.InteractedWithEvent.Await()
SyncProps()
block:
RaceButton.InteractedWithEvent.Await()
RaceProps()
###############################################################################
# Moves each prop in serial - one after the other
SerialProps()<suspends>:void=
Print("Move props in serial [block]...")
if (PlayerTarget := PlayerAsPositional[0]):
PropA.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
PropB.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
PropC.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
PropD.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
###############################################################################
# Moves props concurrently - simultaneously move all
SyncProps()<suspends>:void=
Print("Move props simultaneously [sync]...")
if (PlayerTarget := PlayerAsPositional[0]):
sync:
PropA.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
PropB.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
PropC.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
PropD.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
###############################################################################
# Moves props concurrently and exit when first arrives
RaceProps()<suspends>:void=
Print("Move props simultaneously and complete when first arrives [race]...")
if (PlayerTarget := PlayerAsPositional[0]):
race:
PropA.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
PropB.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
PropC.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
PropD.MoveToPositional(PlayerTarget, OffsetF2mU1m, Arrive1m)
###############################################################################
# Resets all the props to their original positions.
ResetProps():void=
Print("Resetting props...")
PropA.TeleportToNoScale(MapPointA.GetTransform())
PropB.TeleportToNoScale(MapPointB.GetTransform())
PropC.TeleportToNoScale(MapPointC.GetTransform())
PropD.TeleportToNoScale(MapPointD.GetTransform())
###############################################################################
# Resets all the props to their original positions.
(Prop : creative_prop).Move2(Point1 : map_indicator_device, Point2 : map_indicator_device)<suspends>:map_indicator_device=
PropA.MoveToPositional(Point1, Offset0, Arrive1m)
PropA.MoveToPositional(Point2, Offset0, Arrive1m)
Point2
###############################################################################
AsyncArgsSerial()<suspends>:void=
PropA.Move2(
PropB.Move2(MapPointC, MapPointD),
PropC.Move2(MapPointD, MapPointA))
###############################################################################
AsyncArgsConcurrent()<suspends>:void=
PropA.Move2(
sync:
PropB.Move2(MapPointC, MapPointD),
PropC.Move2(MapPointD, MapPointA)
)
ConcurrencyUnrealFest2023.zip (2.1 MB)
Verse Concurrency - Unreal Fest 2023 October.pdf (4.6 MB)