docs.unity3d.com
Search Results for

    Show / Hide Table of Contents

    Implementing the Jetpack Ability

    Now that we have the Jetpack Fuel stat and HUD set up, we need to implement the actual movement logic and the visual effects. We will create two scripts:

    1. JetpackLocomotionAbility: Handles the physics of flying.
    2. JetpackAddon: Handles input, consuming fuel, playing effects, and networking visuals.

    Part 1: The Jetpack Locomotion Ability

    This script implements IMovementAbility to integrate with the CoreMovement system. It overrides gravity when active to allow the player to fly.

    Create a new script named JetpackLocomotionAbility.cs and paste the following code:

    using UnityEngine;
    using Blocks.Gameplay.Core;
    
    public class JetpackLocomotionAbility : MonoBehaviour, IMovementAbility
    {
        [Header("Fuel")]
        [SerializeField] private float fuelCost = 10f;
    
        [Header("Movement Settings")]
        [SerializeField] private float acceleration = 15f;
        [SerializeField] private float deceleration = 25f;
        [SerializeField] private float moveSpeed = 6f;
    
        [Header("Jetpack Settings")]
        [SerializeField] private float flyAcceleration = 20f;
        [SerializeField] private float maxFlySpeed = 10f;
    
        private CoreMovement m_Motor;
        private float m_CurrentSpeed;
        private Vector3 m_LastMoveDirection;
    
        public int Priority => 10;
        public float StaminaCost => 0f;
        public float FuelCost => fuelCost;
        public bool IsJetpacking { get; set; }
    
        public void Initialize(CoreMovement motor)
        {
            m_Motor = motor;
        }
    
        public MovementModifier Process()
        {
            var modifier = new MovementModifier();
            HandleHorizontalMovement(ref modifier);
            HandleVerticalMovement(ref modifier);
            return modifier;
        }
    
        public bool TryActivate() => false;
    
        private void HandleHorizontalMovement(ref MovementModifier modifier)
        {
            float targetSpeed = m_Motor.InputMagnitude * moveSpeed;
            bool hasInput = m_Motor.InputMagnitude > 0.01f;
            float rate = hasInput ? acceleration : deceleration;
    
            m_CurrentSpeed = Mathf.MoveTowards(m_CurrentSpeed, targetSpeed, rate * Time.deltaTime);
    
            if (hasInput)
            {
                Vector3 inputDirection = new Vector3(m_Motor.MoveInput.x, 0.0f, m_Motor.MoveInput.y).normalized;
                m_LastMoveDirection = TransformInputDirection(inputDirection);
            }
    
            modifier.ArealVelocity = m_LastMoveDirection * m_CurrentSpeed;
        }
    
        private void HandleVerticalMovement(ref MovementModifier modifier)
        {
            if (IsJetpacking)
            {
                modifier.OverrideGravity = true;
                float currentVertical = m_Motor.VerticalVelocity;
                float newVertical = Mathf.MoveTowards(currentVertical, maxFlySpeed, flyAcceleration * Time.deltaTime);
                m_Motor.SetVerticalVelocity(newVertical);
            }
        }
    
        private Vector3 TransformInputDirection(Vector3 inputDirection)
        {
            if (m_Motor.directionMode == CoreMovement.MovementDirectionMode.CameraRelative)
            {
                return Quaternion.Euler(0.0f, m_Motor.TargetRotationY, 0.0f) * inputDirection;
            }
    
            return m_Motor.transform.rotation * inputDirection;
        }
    }
    
    

    Part 2: The Jetpack Addon

    The Addon script acts as the controller. It listens for input, tells the JetpackLocomotionAbility when to fly, consumes fuel from CoreStatsHandler, and synchronizes effects over the network.

    Create a new script named JetpackAddon.cs:

    using UnityEngine;
    using Unity.Netcode;
    using Blocks.Gameplay.Core;
    
    public class JetpackAddon : NetworkBehaviour, IPlayerAddon
    {
        [Header("References")]
        [Tooltip("The actual locomotion ability to toggle.")]
        [SerializeField] private JetpackLocomotionAbility jetpackAbility;
        [Tooltip("Visuals object (e.g. wings/flames) to enable while flying.")]
        [SerializeField] private GameObject jetpackVisuals;
    
        [Header("Input Events")]
        [Tooltip("Event raised when the jetpack button is pressed.")]
        [SerializeField] private GameEvent onJetpackPressed;
        [Tooltip("Event raised when the jetpack button is released.")]
        [SerializeField] private GameEvent onJetpackReleased;
    
        [Header("Effects")]
        [Tooltip("Effect spawned when jetpack starts (e.g. burst).")]
        [SerializeField] private GameObject startEffectPrefab;
        [Tooltip("Ribbon/Trail effect spawned when jetpack starts.")]
        [SerializeField] private GameObject ribbonEffectPrefab;
        [Tooltip("Effect spawned when jetpack stops.")]
        [SerializeField] private GameObject stopEffectPrefab;
    
        [Header("Spawn Points")]
        [SerializeField] private Transform thrusterLeft;
        [SerializeField] private Transform thrusterRight;
    
        [Header("Camera Shake")]
        [Tooltip("Camera shake intensity on start.")]
        [SerializeField] private float shakeIntensity = 1f;
        [Tooltip("Camera shake duration on start.")]
        [SerializeField] private float shakeDuration = 0.2f;
    
        private CoreStatsHandler m_StatsHandler;
        private CorePlayerManager m_PlayerManager;
        private bool m_IsJetPacking;
    
        public void Initialize(CorePlayerManager playerManager)
        {
            m_StatsHandler = playerManager.CoreStats;
            m_PlayerManager = playerManager;
            if (jetpackAbility == null)
            {
                jetpackAbility = GetComponent<JetpackLocomotionAbility>();
            }
        }
    
        public void OnPlayerSpawn()
        {
            if (m_PlayerManager != null && m_PlayerManager.IsOwner)
            {
                RegisterEventListeners();
            }
        }
    
        public void OnPlayerDespawn()
        {
            if (m_PlayerManager != null && m_PlayerManager.IsOwner)
            {
                UnregisterEventListeners();
            }
        }
    
        public void OnLifeStateChanged(PlayerLifeState previousState, PlayerLifeState newState)
        {
            if (newState == PlayerLifeState.InitialSpawn || newState == PlayerLifeState.Respawned)
            {
                ResetJetpackState();
            }
        }
    
        private void RegisterEventListeners()
        {
            if (onJetpackPressed != null) onJetpackPressed.RegisterListener(HandlePressed);
            if (onJetpackReleased != null) onJetpackReleased.RegisterListener(HandleReleased);
        }
    
        private void UnregisterEventListeners()
        {
            if (onJetpackPressed != null) onJetpackPressed.UnregisterListener(HandlePressed);
            if (onJetpackReleased != null) onJetpackReleased.UnregisterListener(HandleReleased);
        }
    
        private void ResetJetpackState()
        {
            m_IsJetPacking = false;
            if (jetpackAbility != null)
            {
                jetpackAbility.IsJetpacking = false;
            }
        }
    
        private void HandlePressed()
        {
            if (!m_PlayerManager.IsOwner) return;
            if (jetpackAbility != null)
            {
                jetpackAbility.IsJetpacking = true;
                CoreDirector.RequestCameraShake()
                    .WithVelocity(shakeIntensity)
                    .WithDuration(shakeDuration)
                    .AtPosition(transform.position)
                    .Execute();
            }
    
            SetJetpackStateRpc(true);
        }
    
        private void HandleReleased()
        {
            if (!m_PlayerManager.IsOwner) return;
            if (jetpackAbility != null)
            {
                jetpackAbility.IsJetpacking = false;
            }
    
            SetJetpackStateRpc(false);
        }
    
        private void Update()
        {
            if (m_PlayerManager == null || !m_PlayerManager.IsOwner || !m_IsJetPacking || jetpackAbility == null || m_StatsHandler == null) return;
    
            float fuelNeeded = jetpackAbility.FuelCost * Time.deltaTime;
    
            if (!m_StatsHandler.TryConsumeStat(StatKeys.JetpackFuel, fuelNeeded, OwnerClientId))
            {
                HandleReleased();
            }
        }
    
        [Rpc(SendTo.Everyone)]
        private void SetJetpackStateRpc(bool active)
        {
            m_IsJetPacking = active;
            if (active)
            {
                SpawnEffectAtThrusters(startEffectPrefab);
                SpawnEffectAtThrusters(ribbonEffectPrefab, true);
                if (jetpackVisuals != null) jetpackVisuals.SetActive(true);
            }
            else
            {
                SpawnEffectAtThrusters(stopEffectPrefab);
            }
        }
    
        private void SpawnEffectAtThrusters(GameObject prefab, bool attachToThrusters = false)
        {
            if (prefab == null) return;
    
            Transform[] thrusters = { thrusterLeft, thrusterRight };
    
            foreach (var thruster in thrusters)
            {
                if (thruster != null)
                {
                    var builder = CoreDirector.CreatePrefabEffect(prefab)
                        .WithPosition(thruster.position)
                        .WithRotation(thruster.rotation);
    
                    if (attachToThrusters)
                    {
                        builder.WithParent(thruster)
                            .WithScale(thruster.localScale * 0.01f);
                    }
    
                    builder.Create();
                }
            }
        }
    }
    
    

    Setup Instructions

    1. Add Components: Add both JetpackLocomotionAbility and JetpackAddon to [BB] PlatformerPlayer Prefab using the CoreMovement and CorePlayerManager custom editors to easily add these capabilities, similar to how it was done in Dash Ability. Jetpack Ability handles walking and flight, so make sure to remove Jump and Walk abilities from the player to avoid stacking.
    2. Assign References:
      • In JetpackAddon, assign the Jetpack Ability field to the component.
      • Assign the On Jetpack Pressed and On Jetpack Released GameEvents.
      • Assign the Thruster Transforms and Effect Prefabs.
    3. Here is an example of what the configuration could look like:

    Final Result

    Play the game! When you press the assigned button, your character should fly up, consuming fuel. When fuel runs out, they will fall.

    In This Article
    Back to top
    Copyright © 2026 Unity Technologies — Trademarks and terms of use
    • Legal
    • Privacy Policy
    • Cookie Policy
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)