302 lines
7.9 KiB
C#
302 lines
7.9 KiB
C#
using UnityEngine;
|
|
using Fusion;
|
|
|
|
namespace TPSBR
|
|
{
|
|
[DefaultExecutionOrder(6000)]
|
|
public sealed class Jetpack : ContextBehaviour
|
|
{
|
|
// CONSTANTS
|
|
|
|
private const int BIT_IS_ACTIVE = 0;
|
|
private const int BIT_FULL_THRUST = 1;
|
|
private const int BIT_HAS_STARTED = 2;
|
|
|
|
// PUBLIC MEMBERS
|
|
|
|
public bool IsActive { get { return _state.IsBitSet(BIT_IS_ACTIVE); } set { byte state = _state; state.SetBit(BIT_IS_ACTIVE, value); _state = state; } }
|
|
public bool FullThrust { get { return _state.IsBitSet(BIT_FULL_THRUST); } set { byte state = _state; state.SetBit(BIT_FULL_THRUST, value); _state = state; } }
|
|
public bool HasStarted { get { return _state.IsBitSet(BIT_HAS_STARTED); } set { byte state = _state; state.SetBit(BIT_HAS_STARTED, value); _state = state; } }
|
|
|
|
public bool IsRunning => IsActive == true && _fuel > 0f;
|
|
public float Fuel => _fuel;
|
|
public float MaxFuel => _maxFuel;
|
|
|
|
// PRIVATE MEMBERS
|
|
|
|
[SerializeField]
|
|
private float _initialFuel = 100f;
|
|
[SerializeField]
|
|
private float _maxFuel = 100f;
|
|
[SerializeField]
|
|
private float _idleThrustConsumption = 7f;
|
|
[SerializeField]
|
|
private float _fullThrustConsumption = 15f;
|
|
[SerializeField]
|
|
private GameObject _jetpackObject;
|
|
[SerializeField]
|
|
private Animation _jetpackAnimation;
|
|
[SerializeField]
|
|
private Transform _visualsRoot;
|
|
[SerializeField]
|
|
private Vector3 _centerOfGravityOffset = new Vector3(0f, 1.5f, 0f);
|
|
[SerializeField]
|
|
private ShakeSetup _cameraPositionShake;
|
|
[SerializeField]
|
|
private ShakeSetup _cameraRotationShake;
|
|
[SerializeField]
|
|
private float _fullThrustShakeMultiplier = 2f;
|
|
|
|
[Header("Propellers")]
|
|
[SerializeField]
|
|
private Transform _rightMount;
|
|
[SerializeField]
|
|
private Transform _leftMount;
|
|
[SerializeField]
|
|
private Transform _rightPropeller;
|
|
[SerializeField]
|
|
private Transform _leftPropeller;
|
|
[SerializeField]
|
|
private float _mountForwardAngle = 30f;
|
|
[SerializeField]
|
|
private float _mountBackwardAngle = -15;
|
|
[SerializeField]
|
|
private float _mountChangeSpeed = 8f;
|
|
[SerializeField]
|
|
private float _idlePropellerSpeed = 720f;
|
|
[SerializeField]
|
|
private float _fullPropellerSpeed = 3500f;
|
|
[SerializeField]
|
|
private float _propellerSpeedChange = 8f;
|
|
|
|
[Header("Audio")]
|
|
[SerializeField]
|
|
private AudioSource _engineSound;
|
|
[SerializeField]
|
|
private float _engineSoundChangeSpeed = 8f;
|
|
[SerializeField]
|
|
private float _fullThrustPitch = 1.5f;
|
|
[SerializeField]
|
|
private float _idleThrustVolume = 0.6f;
|
|
[SerializeField]
|
|
private float _fullThrustVolume = 1f;
|
|
|
|
[Networked, HideInInspector]
|
|
private byte _state { get; set; }
|
|
|
|
[Networked]
|
|
private float _fuel { get; set; }
|
|
|
|
private Agent _agent;
|
|
private byte _localState;
|
|
|
|
private float _mountAngle;
|
|
private float _propellerSpeed;
|
|
|
|
private float _defaultPitch;
|
|
|
|
private float _positionShakeMagnitude;
|
|
private float _rotationShakeMagnitude;
|
|
|
|
private Vector3 _tiltAngles;
|
|
|
|
// PUBLIC METHODS
|
|
|
|
public void OnSpawned(Agent agent)
|
|
{
|
|
_agent = agent;
|
|
_localState = default;
|
|
|
|
_jetpackObject.SetActive(false);
|
|
|
|
AddFuel(_initialFuel);
|
|
|
|
UpdateLocalState();
|
|
}
|
|
|
|
public void OnFixedUpdate()
|
|
{
|
|
if (IsActive == false)
|
|
return;
|
|
|
|
if (_agent.Health.IsAlive == false)
|
|
{
|
|
Deactivate();
|
|
return;
|
|
}
|
|
|
|
bool isGrounded = _agent.Character.CharacterController.Data.IsGrounded;
|
|
if (HasStarted == false)
|
|
{
|
|
HasStarted = isGrounded == false;
|
|
}
|
|
else if (isGrounded == true)
|
|
{
|
|
Deactivate();
|
|
return; // Deactivate after touch down
|
|
}
|
|
|
|
HasStarted |= _agent.Character.CharacterController.Data.IsGrounded == false;
|
|
|
|
UpdateLocalState();
|
|
|
|
// Ensure no weapon can be held
|
|
_agent.Weapons.DisarmCurrentWeapon();
|
|
|
|
float consumption = 0;
|
|
_fuel -= consumption * Runner.DeltaTime;
|
|
// // float consumption = FullThrust == true ? _fullThrustConsumption : _idleThrustConsumption;
|
|
// _fuel -= consumption * Runner.DeltaTime;
|
|
//
|
|
// if (_fuel <= 0f)
|
|
// {
|
|
// _fuel = 0f;
|
|
// //Deactivate();
|
|
// }
|
|
}
|
|
|
|
public void OnDespawned()
|
|
{
|
|
Deactivate();
|
|
}
|
|
|
|
public bool AddFuel(float fuel)
|
|
{
|
|
if (_fuel >= _maxFuel)
|
|
return false;
|
|
|
|
_fuel = Mathf.Min(_fuel + fuel, _maxFuel);
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool Activate()
|
|
{
|
|
if (IsActive == true)
|
|
return false;
|
|
|
|
if (_fuel <= 0)
|
|
return false;
|
|
|
|
IsActive = true;
|
|
|
|
UpdateLocalState();
|
|
|
|
return true;
|
|
}
|
|
|
|
public void Deactivate()
|
|
{
|
|
if (IsActive == false)
|
|
return;
|
|
|
|
IsActive = false;
|
|
FullThrust = false;
|
|
HasStarted = false;
|
|
|
|
UpdateLocalState();
|
|
}
|
|
|
|
public override void Render()
|
|
{
|
|
UpdateLocalState();
|
|
|
|
float moveDirectionY = _agent.HasInputAuthority == true ? _agent.AgentInput.AccumulatedInput.MoveDirection.y : default;
|
|
|
|
float targetMountAngle = MathUtility.Map(-1, 1, _mountBackwardAngle, _mountForwardAngle, moveDirectionY);
|
|
_mountAngle = Mathf.Lerp(_mountAngle, targetMountAngle, Time.deltaTime * _mountChangeSpeed);
|
|
|
|
_rightMount.localRotation = Quaternion.Euler(new Vector3(_mountAngle, 0f, 0f));
|
|
_leftMount.localRotation = _rightMount.localRotation;
|
|
|
|
float targetPropellerSpeed = _fuel > 0f ? (FullThrust == true ? _fullPropellerSpeed : _idlePropellerSpeed) : 0f;
|
|
_propellerSpeed = Mathf.Lerp(_propellerSpeed, targetPropellerSpeed, Time.deltaTime * _propellerSpeedChange);
|
|
|
|
_rightPropeller.Rotate(Vector3.up, _propellerSpeed * Time.deltaTime, Space.Self);
|
|
_leftPropeller.Rotate(Vector3.up, _propellerSpeed * Time.deltaTime, Space.Self);
|
|
|
|
float targetVolume = IsRunning == true ? (FullThrust == true ? _fullThrustVolume : _idleThrustVolume) : 0f;
|
|
float targetPitch = IsRunning == true && FullThrust == true ? _fullThrustPitch : _defaultPitch;
|
|
|
|
_engineSound.volume = Mathf.Lerp(_engineSound.volume, targetVolume, Time.deltaTime * _engineSoundChangeSpeed);
|
|
_engineSound.pitch = Mathf.Lerp(_engineSound.pitch, targetPitch, Time.deltaTime * _engineSoundChangeSpeed);
|
|
|
|
_visualsRoot.localPosition = Vector3.zero;
|
|
_visualsRoot.localRotation = Quaternion.identity;
|
|
|
|
var localVelocity = IsRunning == true ? Quaternion.Inverse(transform.rotation) * _agent.Character.CharacterController.Data.RealVelocity : Vector3.zero;
|
|
|
|
_tiltAngles.x = Mathf.Lerp(_tiltAngles.x, -localVelocity.x, Time.deltaTime * 8f);
|
|
_tiltAngles.z = Mathf.Lerp(_tiltAngles.z, localVelocity.z * 2f, Time.deltaTime * 8f);
|
|
|
|
_visualsRoot.RotateAround(transform.position + _centerOfGravityOffset, transform.forward, _tiltAngles.x);
|
|
_visualsRoot.RotateAround(transform.position + _centerOfGravityOffset, transform.right, _tiltAngles.z);
|
|
|
|
_cameraPositionShake.Magnitude = FullThrust == true ? _positionShakeMagnitude * _fullThrustShakeMultiplier : _positionShakeMagnitude;
|
|
_cameraRotationShake.Magnitude = FullThrust == true ? _rotationShakeMagnitude * _fullThrustShakeMultiplier : _rotationShakeMagnitude;
|
|
}
|
|
|
|
// MONOBEHAVIOUR
|
|
|
|
private void Awake()
|
|
{
|
|
_positionShakeMagnitude = _cameraPositionShake.Magnitude;
|
|
_rotationShakeMagnitude = _cameraRotationShake.Magnitude;
|
|
|
|
_defaultPitch = _engineSound.pitch;
|
|
}
|
|
|
|
// PRIVATE METHODS
|
|
|
|
private void Activate_Internal()
|
|
{
|
|
_jetpackAnimation.PlayForward();
|
|
_engineSound.Play();
|
|
|
|
if (HasInputAuthority == true)
|
|
{
|
|
var shake = Context.Camera.ShakeEffect;
|
|
|
|
shake.Play(_cameraPositionShake);
|
|
shake.Play(_cameraRotationShake);
|
|
}
|
|
}
|
|
|
|
private void Deactivate_Internal()
|
|
{
|
|
_jetpackAnimation.Play(_jetpackAnimation.clip.name, -3f);
|
|
_engineSound.Stop();
|
|
|
|
if (HasInputAuthority == true)
|
|
{
|
|
var shake = Context.Camera.ShakeEffect;
|
|
|
|
shake.Stop(_cameraPositionShake);
|
|
shake.Stop(_cameraRotationShake);
|
|
}
|
|
}
|
|
|
|
// NETWORK CALLBACKS
|
|
|
|
private void UpdateLocalState()
|
|
{
|
|
bool stateIsActive = _state.IsBitSet(BIT_IS_ACTIVE);
|
|
bool localStateIsActive = _localState.IsBitSet(BIT_IS_ACTIVE);
|
|
|
|
_localState = _state;
|
|
|
|
if (stateIsActive != localStateIsActive)
|
|
{
|
|
if (stateIsActive == true)
|
|
{
|
|
Activate_Internal();
|
|
}
|
|
else
|
|
{
|
|
Deactivate_Internal();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|