How to create a football throw with a realistic arc / physics in UEFN

Hi everyone,

I’m working on an American football mechanic in UEFN and I’m trying to make the throw feel realistic.

Right now, using a normal projectile just makes the football fly straight forward, which doesn’t look or feel like an actual football pass. What I’m trying to achieve is:

  • A natural arc when the ball is thrown

  • Different throw strengths (short pass vs long pass)

  • Ideally something that feels more “physics-based” instead of a straight-line projectile

I’m open to solutions using:

  • Verse logic

  • Physics impulses

  • Gravity / velocity over time

  • Or even hybrid setups (Verse + device settings)

If anyone has experience making arcing throws, ball physics, or sports-style projectiles in UEFN, I’d really appreciate any advice, examples, or best practices.

I thought of using a physics object, but not sure.

Thanks in advance!

Hi! have you seen this ? Getting Started with Physics | Fortnite Documentation | Epic Developer Community I think it could help.

Just to test and see if it’s something that could work:

  1. Add physics to the project or in a new one to test just this.
  2. Create a prop to use.
  3. Add trigger.
  4. Add this device to test physics, and modify it to your liking.
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Verse.org/SpatialMath } 
pass_device := class(creative_device):

    @editable
    Ball : creative_prop = creative_prop{}

    @editable
    Trigger : trigger_device = trigger_device{}

    @editable
    Impulse : vector3 = vector3{}


    OnBegin<override>()<suspends>:void=
        (super:)OnBegin()
        Trigger.TriggeredEvent.Subscribe(OnTriggerEvent)


    OnTriggerEvent(MaybeAgent:?agent):void=
        Ball.ApplyLinearImpulse(Impulse)
    

On the creative_prop class in the Fortnite.digest you can find a lot of calls for physics to try different things.

Hey @YourCooked how are you?

I’ve been tinkering all day with UEFn in order to find a way to make something like you want and, sadly, I didn’t come to anything really usefull.

The problem here is that you need to use the { /Verse.org/SpatialMath } API to apply inpulse to custom props with physics, but you can only get the player’s forward vector from { /UnrealEngine.com/Temporary/SpatialMath } API, which is incompatible with the one from Verse.

I managed to reach a point where I’m able to apply impulse to a custom prop, but it won’t work as espected as it will be throw in the same direction always. Let me share the code with you, so you can check it ad maybe find anything usefull!

I’m using two different devices so I don’t mix the two APIs:

Device 1: Attach prop to Player

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

# Added APIs
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/ControlInput }
using { /Fortnite.com/Input/Character }

# Device that allows a player to automatically attach to a beach ball
# and throw it using short or long input triggers
AttachPlayer := class(creative_device):

    # Reference to the beach ball prop in the level
    @editable BeachBall : creative_prop = creative_prop{}

    # Input trigger used for a short pass
    @editable ShortInputTrigger : input_trigger_device = input_trigger_device{}

    # Input trigger used for a long pass
    @editable LongInputTrigger : input_trigger_device = input_trigger_device{}

    # Class responsible for applying throw forces to the beach ball
    @editable ThrowClass : Throw_BeachBall = Throw_BeachBall{}

    # Time (in seconds) between proximity checks
    @editable CheckInterval : float = 0.1

    # Maximum distance at which a player can grab the ball
    @editable ProximityRadius : float = 100.0

    # Agent currently holding the ball (optional)
    var Holder : ?agent = false

    # Whether the ball is currently attached to a player
    var IsAttached : logic =  false



    # Called when the device starts
    OnBegin<override>()<suspends>:void=
        # Get all players currently in the playspace
        Players := GetPlayspace().GetPlayers()

        # Register all players to both input triggers
        for (Player : Players):
            ShortInputTrigger.Register(Player)
            LongInputTrigger.Register(Player)

        # Listen for players joining later
        GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)

        # Start the continuous distance check loop
        spawn {StartDetectionLoop()}

        # Subscribe input events for short and long passes
        ShortInputTrigger.PressedEvent.Subscribe(ShortPass)
        LongInputTrigger.PressedEvent.Subscribe(LongPass)


    # Called whenever a new player joins the playspace
    OnPlayerAdded(Player:player):void=
        # Register the new player to the input triggers
        ShortInputTrigger.Register(Player)
        LongInputTrigger.Register(Player)

    
    # Loop that periodically checks player distance to the ball
    StartDetectionLoop()<suspends>:void =
        loop:
            CheckAllPlayerDistances()
            Sleep(CheckInterval)


    # Checks all players to see if any can grab the ball
    CheckAllPlayerDistances():void =
        Players := GetPlayspace().GetPlayers()
        for (Player : Players):
            # Ignore the current holder
            if(Player <> Holder):
                if(FortChar := Player.GetFortCharacter[]):
                    CanGrab := IsPlayerInRange(FortChar)
                    if(CanGrab = true):
                        # Assign the new holder
                        set Holder = option{Player}
                        set IsAttached = true
                        AttachToPlayer(Player)


    # Checks if a player is within grabbing range of the ball
    IsPlayerInRange(FortChar:fort_character) : logic =
        PlayerPos := FortChar.GetTransform().Translation
        BallPos := BeachBall.GetTransform().Translation
        var IsInRange : logic = false

        # Compare distance between player and ball
        if (Distance(PlayerPos, BallPos) <= ProximityRadius):
            set IsInRange = true
        else:
            set IsInRange = false

        return IsInRange


    # Attaches the ball to a player and disables physics
    AttachToPlayer(Agent:agent):void=
        BeachBall.SetDynamic(false)
        spawn:
            FollowPlayer(Agent)


    # Continuously moves the ball to follow the player
    FollowPlayer(Agent:agent)<suspends>:void=
        if(FortChar := Agent.GetFortCharacter[]):
            loop:
                # Stop following if the ball is no longer attached
                if(IsAttached = false):
                    break

                Sleep(0.0)

                # Match the ball position and rotation to the player
                PlayerPos := FortChar.GetTransform().Translation
                PlayerRot := FortChar.GetTransform().Rotation
                BeachBall.MoveTo(PlayerPos, PlayerRot, 0.1)
                
                
    # Handles short pass input
    ShortPass(Agent:agent):void=
        # Only allow the current holder to throw
        if(IsAttached = true):
            if(Agent = Holder?):
                if(FortChar := Agent.GetFortCharacter[]):
                    spawn{DelayedShortPass(FortChar)}
                    set Holder = false
                    set IsAttached = false

    # Executes the short pass after releasing the ball
    DelayedShortPass(HolderChar:fort_character)<suspends>:void=
        Sleep(0.0)
        BeachBall.SetDynamic(true)
        ThrowClass.ShortPass(BeachBall)

    # Handles long pass input
    LongPass(Agent:agent):void=
        # Only allow the current holder to throw
        if(IsAttached = true):
            if(Agent = Holder?):
                if(FortChar := Agent.GetFortCharacter[]):
                    spawn{DelayedLongPass(FortChar)}
                    set Holder = false
                    set IsAttached = false

    # Executes the long pass after releasing the ball
    DelayedLongPass(HolderChar:fort_character)<suspends>:void=
        Sleep(0.0)
        BeachBall.SetDynamic(true)
        ThrowClass.LongPass(BeachBall)

With this values on editor:

Device 2: Throw the ball

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

# Added APIs
using { /Verse.org/SpatialMath }
using { /Fortnite.com/Characters }

# Device responsible for applying impulse forces to a beach ball
# Used by other devices to perform short or long throws
Throw_BeachBall := class(creative_device):

    # Impulse applied for a short pass
    @editable ShortImpulse : vector3 = vector3{}

    # Impulse applied for a long pass
    @editable LongImpulse : vector3 = vector3{}

    # Applies a short impulse to the given prop
    ShortPass(Prop:creative_prop):void=
        # Debug message to confirm the short pass was triggered
        Print("Short Pass")

        # Apply a linear impulse to the prop using the short pass force
        Prop.ApplyLinearImpulse(ShortImpulse)

    # Applies a long impulse to the given prop
    LongPass(Prop:creative_prop):void=
        # Debug message to confirm the long pass was triggered
        Print("Long Pass")

        # Apply a linear impulse to the prop using the long pass force
        Prop.ApplyLinearImpulse(LongImpulse)

With this values on editor:

As @Abrizzer said, I recommend you to check this documentation to understand how physics work.

Hope this helps you to figure out how to make it work! And sorry for not being more usefull! Let me know if you don’t understand anything!

Thank you very much, I wish i could mark yours as a solution as well, I really apricate the help.

1 Like

Thank you so much. the code definitely gave me a base.

1 Like

Hi Juan, Excellent code!

I think you should be able to handle that ambiguity (or use built in functions to convert from XYZ ↔ LUF) with some methods from this doc.

In the context of forward vectors something like:

        loop:
            # Get the player rotation. 
            PlayerRotation := PlayerCharacter.GetViewRotation()

            # Get local forward vector of the player. 
            LocalForward := PlayerRotation.GetLocalForward()

            PlayerCharacter.SetLinearVelocity(ConvertToLUF(LocalForward * 10.0)
            Sleep(0.01)

    

ConvertToLUF(InputTransform : (/UnrealEngine.com/Temporary/SpatialMath:)vector3) : (/Verse.org/SpatialMath:)vector3 =
    LUFTransform := FromVector3(InputTransform)
    return LUFTransform
1 Like