Space Capitalism Torch Aiming Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using DG.Tweening; | |
using Fungus; | |
using Micosmo.SensorToolkit; | |
using RootMotion.FinalIK; | |
using UnityEngine; | |
using VLB; | |
namespace HumbleGrove.SpaceCapitalism | |
{ | |
[RequireComponent(typeof(Animator), typeof(AimController))] | |
public class AimActor : SpaceCapitalismMonoBehaviour | |
{ | |
#region Enums and Structs | |
public enum AimActorState | |
{ | |
Uninitialized, // before the aim actor is initialized | |
Idle, // when the prominent player's torch is not aimed | |
IdleAiming, // when the non prominent player is providing ambient fill light | |
AimingAfterIdleAim, // when the newly prominent player's flashlight is briefly off after transitioning from IdleAiming | |
Aiming, // when the prominent player is aiming but not focusing on any selectables | |
Focusing // when the prominent player is aiming at a selectable | |
} | |
[Serializable] | |
public struct TorchProperties | |
{ | |
[Range(1, 180)] | |
public float ColliderAngles; | |
[Min(0f)] | |
public float InnerSpotAngle; | |
[Min(0f)] | |
public float OuterSpotAngle; // vlb spot angle is the same as this | |
[Min(0f)] | |
public float SpotlightIntensity; | |
[Min(0f)] | |
public float VlbIntensity; | |
public bool VlbNoiseEnabled; | |
[Range(0, 1)] | |
public float ParticlesAlpha; | |
[Min(0f)] | |
public float ParticlesDensity; | |
[MinMaxRange(0.0f, 1.0f)] | |
public MinMaxRangeFloat ParticlesDistanceRange; | |
public static readonly TorchProperties IdleDefaults = new() | |
{ | |
ColliderAngles = 20f, | |
InnerSpotAngle = 21.80208f, | |
OuterSpotAngle = 30f, | |
SpotlightIntensity = 3f, | |
VlbIntensity = 0.03f, | |
VlbNoiseEnabled = true, | |
ParticlesAlpha = 0.1f, | |
ParticlesDensity = 2, | |
ParticlesDistanceRange = new MinMaxRangeFloat(0f, 0.3f) | |
}; | |
public static readonly TorchProperties IdleAimingDefaults = new() | |
{ | |
ColliderAngles = 20f, | |
InnerSpotAngle = 120.5997f, | |
OuterSpotAngle = 128.7976f, | |
SpotlightIntensity = 25f, | |
VlbIntensity = 0.03f, | |
VlbNoiseEnabled = false, | |
ParticlesAlpha = 0.1f, | |
ParticlesDensity = 15, | |
ParticlesDistanceRange = new MinMaxRangeFloat(0.05f, 0.7f) | |
}; | |
public static readonly TorchProperties AimingDefaults = new() | |
{ | |
ColliderAngles = 20f, | |
InnerSpotAngle = 21.80208f, | |
OuterSpotAngle = 30f, | |
SpotlightIntensity = 25f, | |
VlbIntensity = 0.5f, | |
VlbNoiseEnabled = true, | |
ParticlesAlpha = 0.2f, | |
ParticlesDensity = 2, | |
ParticlesDistanceRange = new MinMaxRangeFloat(0f, 0.3f) | |
}; | |
public static readonly TorchProperties FocusDefaults = new() | |
{ | |
ColliderAngles = 7f, | |
InnerSpotAngle = 0f, | |
OuterSpotAngle = 8.197921f, | |
SpotlightIntensity = 175f, | |
VlbIntensity = 0.7f, | |
VlbNoiseEnabled = true, | |
ParticlesAlpha = 0.2f, | |
ParticlesDensity = 2, | |
ParticlesDistanceRange = new MinMaxRangeFloat(0f, 0.3f) | |
}; | |
} | |
[Serializable] | |
public struct TorchSpeedSettings | |
{ | |
[Min(0f)] | |
public float ColliderSpeed; | |
[Min(0f)] | |
public float SpotlightSpotAngleSpeed; | |
[Min(0f)] | |
public float VlbSpotAngleSpeed; | |
[Min(0f)] | |
public float SpotlightIntensitySpeed; | |
[Min(0f)] | |
public float VlbIntensitySpeed; | |
public static readonly TorchSpeedSettings IdleDefaults = new() | |
{ | |
ColliderSpeed = 35f, | |
SpotlightSpotAngleSpeed = 45f, | |
VlbSpotAngleSpeed = 35f, | |
SpotlightIntensitySpeed = 25f, | |
VlbIntensitySpeed = 1f | |
}; | |
public static readonly TorchSpeedSettings IdleAimingDefaults = new() | |
{ | |
ColliderSpeed = 70f, | |
SpotlightSpotAngleSpeed = 90f, | |
VlbSpotAngleSpeed = 70f | |
}; | |
public static readonly TorchSpeedSettings AimingDefaults = new() | |
{ | |
ColliderSpeed = 35f, | |
SpotlightSpotAngleSpeed = 45f, | |
VlbSpotAngleSpeed = 35f | |
}; | |
public static readonly TorchSpeedSettings FocusDefaults = new() | |
{ | |
ColliderSpeed = 20f, | |
SpotlightSpotAngleSpeed = 20f, | |
VlbSpotAngleSpeed = 25f | |
}; | |
public static readonly TorchSpeedSettings FocusSuccessDefaults = new() | |
{ | |
ColliderSpeed = 45f, | |
SpotlightSpotAngleSpeed = 45f, | |
VlbSpotAngleSpeed = 45f | |
}; | |
} | |
[Serializable] | |
public struct AnimationCurveEase | |
{ | |
public Ease Ease; | |
public bool UseAnimationCurve; | |
public AnimationCurve Curve; | |
public static readonly AnimationCurveEase InOutSine = new() | |
{ | |
Ease = Ease.InOutSine, | |
Curve = AnimationCurve.EaseInOut(0,0,1,1) | |
}; | |
public static readonly AnimationCurveEase InOutElastic = new() | |
{ | |
Ease = Ease.InOutElastic, | |
Curve = AnimationCurve.EaseInOut(0,0,1,1) | |
}; | |
} | |
[Serializable] | |
public struct AimControllerProperties | |
{ | |
[Min(0f)] | |
[Tooltip("Speed of turning towards the target using Vector3.RotateTowards.")] | |
public float MaxRadiansDelta; | |
[Min(0f)] | |
[Tooltip("Speed of moving towards the target using Vector3.RotateTowards.")] | |
public float MaxMagnitudeDelta; | |
[Min(0f)] | |
[Tooltip("Speed of slerping towards the target.")] | |
public float SlerpSpeed; | |
public static readonly AimControllerProperties IdleAiming = new() | |
{ | |
MaxRadiansDelta = 0.3f, | |
MaxMagnitudeDelta = 0.3f, | |
SlerpSpeed = 0.5f | |
}; | |
public static readonly AimControllerProperties Aiming = new() | |
{ | |
MaxRadiansDelta = 3f, | |
MaxMagnitudeDelta = 3f, | |
SlerpSpeed = 3f | |
}; | |
} | |
#endregion | |
#region Variables and Properties | |
[SerializeField] private Animator anim; | |
[SerializeField] private AimController aim; | |
[SerializeField] private TriggerSensor torchSensor; | |
[SerializeField] private FOVCollider torchCollider; | |
[SerializeField] private Light torchSpotlight; | |
[SerializeField] private VolumetricLightBeam torchVlb; | |
[SerializeField] private EffectFlicker torchFlicker; | |
[SerializeField] private VolumetricDustParticles torchParticles; | |
[Space] | |
[SerializeField] private string hitboxTag; | |
[SerializeField] private Transform idleTarget; | |
public float FocusStartDelay = 0.3f; | |
public float FocusCooldownDuration = 1f; | |
public float AimAfterIdleAimDelay = 0.5f; | |
[Header("State Settings")] | |
public TorchProperties IdleProperties = TorchProperties.IdleDefaults; | |
public TorchSpeedSettings IdleSpeedSettings = TorchSpeedSettings.IdleDefaults; | |
public AnimationCurveEase IdleEase = AnimationCurveEase.InOutSine; | |
[Space] | |
public TorchProperties IdleAimingProperties = TorchProperties.IdleAimingDefaults; | |
public TorchSpeedSettings IdleAimingSpeedSettings = TorchSpeedSettings.IdleAimingDefaults; | |
public AnimationCurveEase IdleAimingEase = AnimationCurveEase.InOutSine; | |
[Space] | |
public TorchProperties AimingProperties= TorchProperties.AimingDefaults; | |
public TorchSpeedSettings AimingSpeedSettings = TorchSpeedSettings.AimingDefaults; | |
public AnimationCurveEase AimingEase = AnimationCurveEase.InOutSine; | |
[Space] | |
public TorchProperties FocusProperties = TorchProperties.FocusDefaults; | |
public TorchSpeedSettings FocusSpeedSettings = TorchSpeedSettings.FocusDefaults; | |
public AnimationCurveEase FocusEase = AnimationCurveEase.InOutSine; | |
[Space] | |
public TorchSpeedSettings FocusSuccessSpeedSettings = TorchSpeedSettings.FocusSuccessDefaults; | |
public AnimationCurveEase FocusSuccessEase = AnimationCurveEase.InOutElastic; | |
[Header("Aim Settings")] | |
public AimControllerProperties NonProminentAimProperties = AimControllerProperties.IdleAiming; | |
public AimControllerProperties ProminentAimProperties = AimControllerProperties.Aiming; | |
[Header("Debug Settings")] | |
[SerializeField] private bool verbose; | |
private AimTargetController targetController; | |
private Coroutine torchRoutine; | |
private StoppableCoroutine torchNestedRoutine; | |
private Tween colliderAnglesTween, outerSpotAngleTween, innerSpotAngleTween, vlbSpotAngleTween, spotlightIntensityTween, vlbIntensityTween; | |
private EventDispatcher eventDispatcher; | |
private GameObject toBeFocusAim, toBeFocusCooldown; | |
private AimActorState aimState; | |
private bool initialized, isFocusing, isFocusCoolingDown; | |
public Animator Anim => anim; | |
public AimController Aim => aim; | |
public TriggerSensor TorchSensor => torchSensor; | |
public Light TorchSpotlight => torchSpotlight; | |
public VolumetricLightBeam TorchVlb => torchVlb; | |
public string HitboxTag => hitboxTag; | |
public Transform IdleTarget => idleTarget; | |
public bool TorchAimed { get; private set; } | |
public AimActorState AimState | |
{ | |
get => aimState; | |
private set | |
{ | |
if (aimState == value) return; | |
#if UNITY_EDITOR | |
if (verbose) Debug.Log(name + " changing state from " + aimState + " to " + value); | |
#endif | |
aimState = value; | |
} | |
} | |
#endregion | |
public void Init(AimTargetController controller) | |
{ | |
if (initialized || !controller) return; | |
targetController = controller; | |
// force some settings | |
torchVlb.trackChangesDuringPlaytime = true; | |
torchVlb.intensityFromLight = false; | |
torchVlb.spotAngleFromLight = false; | |
torchFlicker.returnToStartValuesAfterFlicker = true; | |
torchFlicker.restoreBaseIntensity = false; | |
initialized = true; | |
} | |
#region MonoBehaviour Methods | |
private void OnEnable() | |
{ | |
eventDispatcher = FungusManager.Instance.EventDispatcher; | |
} | |
private void OnDisable() | |
{ | |
eventDispatcher = null; | |
} | |
private void OnValidate() | |
{ | |
if (anim == null) | |
anim = GetComponent<Animator>(); | |
if (aim == null) | |
aim = GetComponent<AimController>(); | |
if (torchSensor == null) | |
torchSensor = GetComponentInChildren<TriggerSensor>(); | |
if (torchCollider == null) | |
torchCollider = GetComponentInChildren<FOVCollider>(); | |
if (torchSpotlight == null) | |
torchSpotlight = GetComponentInChildren<Light>(); | |
if (torchVlb == null) | |
torchVlb = GetComponentInChildren<VolumetricLightBeam>(); | |
if (torchFlicker == null) | |
torchFlicker = GetComponentInChildren<EffectFlicker>(); | |
if (torchParticles == null) | |
torchParticles = GetComponentInChildren<VolumetricDustParticles>(); | |
} | |
private void Update() | |
{ | |
if (!initialized) return; | |
SetAimControllerValues(); | |
// check if we can now focus on objects that were previously rejected | |
if (toBeFocusAim && TorchAimed) | |
FocusTorch(toBeFocusAim, null); | |
else if (toBeFocusCooldown && !isFocusCoolingDown) | |
FocusTorch(toBeFocusCooldown, null); | |
} | |
#endregion | |
#region Public Methods | |
/// <summary> | |
/// Used when the torch is not being aimed by the actor in control. | |
/// </summary> | |
/// <param name="instant">Instantly apply values instead of tweening.</param> | |
public void IdleTorch(bool instant = false) | |
{ | |
if (!initialized || AimState == AimActorState.Idle) return; | |
MaybeKillTorchRoutine(); | |
TorchAimed = false; | |
AimState = AimActorState.Idle; | |
if (instant) | |
SetTorchValuesInstantly(IdleProperties); | |
else | |
torchRoutine = StartCoroutine(TorchRoutine(IdleProperties, IdleSpeedSettings, IdleEase, false, true)); | |
} | |
/// <summary> | |
/// Used for providing ambient fill light over the scene while the actor is not in control. | |
/// </summary> | |
/// <param name="instant">Instantly apply values instead of tweening.</param> | |
public void IdleAimTorch(bool instant = false) | |
{ | |
if (!initialized || AimState == AimActorState.IdleAiming) return; | |
MaybeKillTorchRoutine(); | |
torchFlicker.enabled = false; | |
TorchAimed = false; | |
AimState = AimActorState.IdleAiming; | |
if (instant) | |
SetTorchValuesInstantly(IdleAimingProperties); | |
else | |
torchRoutine = StartCoroutine(IdleAimRoutine()); | |
} | |
/// <summary> | |
/// Used for aiming after idle aiming. | |
/// </summary> | |
public void AimTorchAfterIdleAim() | |
{ | |
if (!initialized) return; | |
MaybeKillTorchRoutine(); | |
TorchAimed = false; | |
AimState = AimActorState.AimingAfterIdleAim; | |
torchRoutine = StartCoroutine(AimTorchAfterIdleRoutine()); | |
} | |
/// <summary> | |
/// Used for aiming after idle. | |
/// </summary> | |
/// <param name="instant">Instantly apply values instead of tweening.</param> | |
public void AimTorchAfterIdle(bool instant = false) | |
{ | |
if (!initialized || AimState is AimActorState.Aiming or AimActorState.AimingAfterIdleAim) return; | |
MaybeKillTorchRoutine(); | |
TorchAimed = false; | |
AimState = AimActorState.Aiming; | |
if (instant) | |
SetTorchValuesInstantly(AimingProperties); | |
else | |
torchRoutine = StartCoroutine(AimTorchRoutine()); | |
} | |
/// <summary> | |
/// Used for aiming after focusing. | |
/// </summary> | |
/// <param name="go">GameObject that lost focus.</param> | |
/// <param name="sensor">Not used.</param> | |
public void AimTorchAfterFocusing(GameObject go, Sensor sensor) | |
{ | |
if (!initialized || AimState == AimActorState.Aiming) return; | |
// reset toBeFocus variables | |
if (go == toBeFocusAim) | |
toBeFocusAim = null; | |
if (go == toBeFocusCooldown) | |
toBeFocusCooldown = null; | |
if (AimState == AimActorState.AimingAfterIdleAim) return; | |
// left focusing state before it was successful. apply a cooldown on selecting objects. but only if not in lock on mode so we dont delay selection. | |
if (AimState == AimActorState.Focusing && isFocusing && !targetController.LockOnToggled) | |
StartCoroutine(FocusCooldownRoutine()); | |
MaybeKillTorchRoutine(); | |
TorchAimed = false; | |
AimState = AimActorState.Aiming; | |
torchRoutine = StartCoroutine(AimTorchRoutine()); | |
} | |
/// <summary> | |
/// Used to focus on an interactable GameObject. | |
/// Called by the TriggerSensor upon detecting an selectable. | |
/// </summary> | |
/// <param name="go">GameObject to begin focusing on.</param> | |
/// <param name="sensor">Not used.</param> | |
public void FocusTorch(GameObject go, Sensor sensor) | |
{ | |
if (!initialized || AimState == AimActorState.Focusing || !GameStateManager.HasControl) return; | |
// require the torch to be finished aiming before we can start focusing. | |
// if we aren't aimed yet store what we passed in and try again with it later. | |
if (!TorchAimed) | |
{ | |
toBeFocusAim = go; | |
return; | |
} | |
// check if the torch is cooling down after an unsuccessful focus attempt. | |
// if we aren't cooled down yet store what we passed in and try again with it later. | |
if (isFocusCoolingDown) | |
{ | |
toBeFocusCooldown = go; | |
return; | |
} | |
// get and check if the selectable is interactable | |
var selectable = go.GetComponent<SceneSelectable>(); | |
if (!selectable || !selectable.Interactable) return; | |
//raise SelectableDetected fungus event | |
eventDispatcher.Raise(new SelectableDetected.DetectEvent(selectable)); | |
// check that what we've started to focus on has its conditionals fulfilled | |
if (!selectable.ConditionalsFulfilled) return; | |
MaybeKillTorchRoutine(); | |
torchFlicker.enabled = false; // dont allow flickering of the torch while focusing | |
AimState = AimActorState.Focusing; | |
torchRoutine = StartCoroutine(FocusTorchRoutine(selectable)); | |
} | |
#endregion | |
#region Private Coroutines | |
/// <summary> | |
/// Set/Tween the 8 different values that determine the torches look. | |
/// </summary> | |
private IEnumerator TorchRoutine(TorchProperties properties, TorchSpeedSettings speedSettings, AnimationCurveEase ease, bool waitForLight = false, bool intensitySpeedBased = false) | |
{ | |
// use booleans to signal completion instead of yielding to WaitForCompletion | |
// because if a tween is already finished before its reached it will throw a warning. | |
bool colliderComplete = false; | |
bool innerSpotComplete = false; | |
bool outerSpotComplete = false; | |
bool vlbSpotComplete = false; | |
// set vlb's noise mode | |
torchVlb.noiseMode = properties.VlbNoiseEnabled ? NoiseMode.LocalSpace : NoiseMode.Disabled; | |
// tween the fov collider | |
bool runColliderTween = !Mathf.Approximately(torchCollider.FOVAngle, properties.ColliderAngles); | |
if (runColliderTween) | |
{ | |
float angleValue = torchCollider.FOVAngle; | |
colliderAnglesTween = DOTween.To(x => angleValue = x, torchCollider.FOVAngle, properties.ColliderAngles, speedSettings.ColliderSpeed) | |
.SetSpeedBased() | |
.OnUpdate(() => | |
{ | |
var value = Mathf.Clamp(angleValue, 1, 180); | |
torchCollider.FOVAngle = value; | |
torchCollider.ElevationAngle = value; | |
torchCollider.CreateCollider(); | |
}) | |
.OnComplete(() => colliderComplete = true); | |
if (ease.UseAnimationCurve) | |
colliderAnglesTween.SetEase(ease.Curve); | |
else | |
colliderAnglesTween.SetEase(ease.Ease); | |
} | |
else | |
{ | |
torchCollider.FOVAngle = properties.ColliderAngles; | |
torchCollider.ElevationAngle = properties.ColliderAngles; | |
torchCollider.CreateCollider(); | |
} | |
// tween the spotlight's inner spot angle | |
var innerSpotAngleDuration = 0f; | |
innerSpotAngleTween = DOTween.To(x => torchSpotlight.innerSpotAngle = x, torchSpotlight.innerSpotAngle, properties.InnerSpotAngle, speedSettings.SpotlightSpotAngleSpeed) | |
.SetSpeedBased() | |
.OnStart(() => innerSpotAngleDuration = innerSpotAngleTween.Duration()) | |
.OnComplete(() => innerSpotComplete = true); | |
if (ease.UseAnimationCurve) | |
innerSpotAngleTween.SetEase(ease.Curve); | |
else | |
innerSpotAngleTween.SetEase(ease.Ease); | |
// tween the spotlight's outer spot angle | |
outerSpotAngleTween = DOTween.To(x => torchSpotlight.spotAngle = x, torchSpotlight.spotAngle, properties.OuterSpotAngle, speedSettings.SpotlightSpotAngleSpeed) | |
.SetSpeedBased() | |
.OnComplete(() => outerSpotComplete = true); | |
if (ease.UseAnimationCurve) | |
outerSpotAngleTween.SetEase(ease.Curve); | |
else | |
outerSpotAngleTween.SetEase(ease.Ease); | |
// tween the vlb's spot angle | |
vlbSpotAngleTween = DOTween.To(x => torchVlb.spotAngle = x, torchVlb.spotAngle, properties.OuterSpotAngle, speedSettings.VlbSpotAngleSpeed) | |
.SetSpeedBased() | |
.OnComplete(() => vlbSpotComplete = true); | |
if (ease.UseAnimationCurve) | |
vlbSpotAngleTween.SetEase(ease.Curve); | |
else | |
vlbSpotAngleTween.SetEase(ease.Ease); | |
innerSpotAngleTween.Play(); | |
outerSpotAngleTween.Play(); | |
vlbSpotAngleTween.Play(); | |
yield return null; | |
if (!intensitySpeedBased) | |
{ | |
// tween the spotlight's intensity | |
spotlightIntensityTween = torchSpotlight.DOIntensity(properties.SpotlightIntensity, innerSpotAngleDuration); | |
if (ease.UseAnimationCurve) | |
spotlightIntensityTween.SetEase(ease.Curve); | |
else | |
spotlightIntensityTween.SetEase(ease.Ease); | |
// tween the vlb's intensity | |
vlbIntensityTween = DOTween.To(x => torchVlb.intensityGlobal = x, torchVlb.intensityGlobal, properties.VlbIntensity, innerSpotAngleDuration); | |
if (ease.UseAnimationCurve) | |
vlbIntensityTween.SetEase(ease.Curve); | |
else | |
vlbIntensityTween.SetEase(ease.Ease); | |
} | |
else | |
{ | |
// tween the spotlight's intensity | |
spotlightIntensityTween = torchSpotlight.DOIntensity(properties.SpotlightIntensity, speedSettings.SpotlightIntensitySpeed) | |
.SetSpeedBased(); | |
if (ease.UseAnimationCurve) | |
spotlightIntensityTween.SetEase(ease.Curve); | |
else | |
spotlightIntensityTween.SetEase(ease.Ease); | |
// tween the vlb's intensity | |
vlbIntensityTween = DOTween.To(x => torchVlb.intensityGlobal = x, torchVlb.intensityGlobal, properties.VlbIntensity, speedSettings.VlbIntensitySpeed) | |
.SetSpeedBased(); | |
if (ease.UseAnimationCurve) | |
vlbIntensityTween.SetEase(ease.Curve); | |
else | |
vlbIntensityTween.SetEase(ease.Ease); | |
} | |
// set particles values | |
torchParticles.alpha = properties.ParticlesAlpha; | |
torchParticles.density = properties.ParticlesDensity; | |
torchParticles.spawnDistanceRange = properties.ParticlesDistanceRange; | |
if (runColliderTween) | |
yield return new WaitUntil(() => colliderComplete); | |
if (waitForLight) | |
yield return new WaitUntil(() => innerSpotComplete && outerSpotComplete && vlbSpotComplete); | |
} | |
private IEnumerator IdleAimRoutine() | |
{ | |
torchParticles.enabled = false; // disable to restart simulation once values are adjusted | |
torchNestedRoutine = StartStoppableCoroutine(TorchRoutine(IdleAimingProperties, IdleAimingSpeedSettings, IdleAimingEase, true)); | |
yield return torchNestedRoutine.WaitFor(); | |
torchNestedRoutine = null; | |
torchParticles.enabled = true; | |
} | |
private IEnumerator AimTorchRoutine() | |
{ | |
torchNestedRoutine = StartStoppableCoroutine(TorchRoutine(AimingProperties, AimingSpeedSettings, AimingEase)); | |
yield return torchNestedRoutine.WaitFor(); | |
torchNestedRoutine = null; | |
TorchAimed = true; | |
} | |
/// <summary> | |
/// Briefly kill the torch's light to swap values from idle mode to aim mode. | |
/// </summary> | |
private IEnumerator AimTorchAfterIdleRoutine() | |
{ | |
torchSpotlight.enabled = false; | |
torchVlb.enabled = false; | |
torchParticles.enabled = false; // disable the component since we WANT the simulation to restart. | |
SetTorchValuesInstantly(AimingProperties); | |
yield return new WaitForSeconds(AimAfterIdleAimDelay); | |
torchSpotlight.enabled = true; | |
torchVlb.enabled = true; | |
torchParticles.enabled = true; | |
TorchAimed = true; | |
AimState = AimActorState.Aiming; | |
} | |
private IEnumerator FocusTorchRoutine(SceneSelectable selectable) | |
{ | |
yield return new WaitForSeconds(FocusStartDelay); | |
isFocusing = true; | |
torchParticles.SetParticlesVisibility(false); | |
torchNestedRoutine = StartStoppableCoroutine(TorchRoutine(FocusProperties, FocusSpeedSettings, FocusEase)); | |
yield return torchNestedRoutine.WaitFor(); | |
torchNestedRoutine = null; | |
isFocusing = false; | |
FungusPrioritySignals.DoIncreasePriorityDepth(); // revoke control from player | |
// lock into position to ensure close to same lighting on objects for all players, if we opted in. | |
if (selectable.LockOnWhenFocused) | |
aim.target = selectable.GetFocusPoint(); // set AimController's target itself instead of the aim target position so we get smoothing between the two targets. | |
innerSpotAngleTween?.Kill(); | |
outerSpotAngleTween?.Kill(); | |
vlbSpotAngleTween?.Kill(); | |
spotlightIntensityTween?.Kill(); | |
vlbIntensityTween?.Kill(); | |
torchNestedRoutine = StartStoppableCoroutine(TorchRoutine(AimingProperties, FocusSuccessSpeedSettings, FocusSuccessEase)); | |
torchParticles.SetParticlesVisibility(true); | |
yield return torchNestedRoutine.WaitFor(); | |
torchNestedRoutine = null; | |
//raise SelectableFocused fungus event | |
eventDispatcher.Raise(new SelectableFocused.FocusEvent(selectable)); | |
TorchAimed = true; | |
AimState = AimActorState.Aiming; | |
} | |
private IEnumerator FocusCooldownRoutine() | |
{ | |
isFocusCoolingDown = true; | |
yield return new WaitForSeconds(FocusCooldownDuration); | |
isFocusCoolingDown = false; | |
} | |
#endregion | |
#region Private Methods | |
private void SetTorchValuesInstantly(TorchProperties properties) | |
{ | |
torchCollider.FOVAngle = properties.ColliderAngles; | |
torchCollider.ElevationAngle = properties.ColliderAngles; | |
torchCollider.CreateCollider(); | |
torchSpotlight.spotAngle = properties.OuterSpotAngle; | |
torchSpotlight.innerSpotAngle = properties.InnerSpotAngle; | |
torchSpotlight.intensity = properties.SpotlightIntensity; | |
torchVlb.spotAngle = properties.OuterSpotAngle; | |
torchVlb.intensityGlobal = properties.VlbIntensity; | |
torchVlb.noiseMode = properties.VlbNoiseEnabled ? NoiseMode.LocalSpace : NoiseMode.Disabled; | |
torchParticles.alpha = properties.ParticlesAlpha; | |
torchParticles.density = properties.ParticlesDensity; | |
torchParticles.spawnDistanceRange = properties.ParticlesDistanceRange; | |
} | |
/// <summary> | |
/// Set speed values on the Actor's Aim Target Controller. | |
/// </summary> | |
private void SetAimControllerValues() | |
{ | |
if (targetController.FollowAimTargetOnIdle) | |
{ | |
if (targetController.ProminentActor == this) | |
{ | |
aim.maxRadiansDelta = ProminentAimProperties.MaxRadiansDelta; | |
aim.maxMagnitudeDelta = ProminentAimProperties.MaxMagnitudeDelta; | |
aim.slerpSpeed = ProminentAimProperties.SlerpSpeed; | |
} | |
else | |
{ | |
aim.maxRadiansDelta = NonProminentAimProperties.MaxRadiansDelta; | |
aim.maxMagnitudeDelta = NonProminentAimProperties.MaxMagnitudeDelta; | |
aim.slerpSpeed = NonProminentAimProperties.SlerpSpeed; | |
} | |
} | |
else | |
{ | |
aim.maxRadiansDelta = ProminentAimProperties.MaxRadiansDelta; | |
aim.maxMagnitudeDelta = ProminentAimProperties.MaxMagnitudeDelta; | |
aim.slerpSpeed = ProminentAimProperties.SlerpSpeed; | |
} | |
} | |
private void MaybeKillTorchRoutine() | |
{ | |
if (torchRoutine != null) | |
StopCoroutine(torchRoutine); | |
torchNestedRoutine?.Stop(); | |
torchNestedRoutine = null; | |
colliderAnglesTween?.Kill(); | |
innerSpotAngleTween?.Kill(); | |
outerSpotAngleTween?.Kill(); | |
vlbSpotAngleTween?.Kill(); | |
spotlightIntensityTween?.Kill(); | |
vlbIntensityTween?.Kill(); | |
torchParticles.SetParticlesVisibility(true); | |
isFocusing = false; | |
torchSpotlight.enabled = true; | |
torchVlb.enabled = true; | |
torchParticles.enabled = true; | |
torchFlicker.enabled = true; | |
toBeFocusAim = null; | |
toBeFocusCooldown = null; | |
} | |
#endregion | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Fungus; | |
using UnityEngine; | |
using UnityEngine.UI; | |
#if UNITY_EDITOR | |
using UnityEditor; | |
#endif | |
namespace HumbleGrove.SpaceCapitalism | |
{ | |
public class AimTargetController : MonoBehaviour | |
{ | |
[SerializeField] private AimActor[] actors; | |
[SerializeField] private Transform aimTarget; | |
[SerializeField] private new Camera camera; | |
[Header("General Settings")] | |
public bool ControlBothActors = true; | |
[SerializeField] private bool holdButtonToAim = true; | |
[SerializeField] private bool followAimTargetOnIdle; | |
[Header("Mouse Settings")] | |
[Min(0f)] | |
[SerializeField] private float raycastLength = 10000; | |
[SerializeField] private LayerMask hitboxLayer = 7; | |
[Min(0f)] | |
[SerializeField] private float breakLockOnDistance = 200f; | |
[Header("Controller Settings")] | |
[Min(0f)] | |
[SerializeField] private float defaultSplineHeight = 10f; | |
[Min(0f)] | |
[SerializeField] private float switchActorsBuffer = 0.1f; | |
[Min(0f)] | |
[SerializeField] private float splineXSpeed = 1; | |
[Min(0f)] | |
[SerializeField] private float splinePrecisionXSpeed = 0.5f; | |
[Min(0f)] | |
[SerializeField] private float splineYSpeed = 15; | |
[Min(0f)] | |
[SerializeField] private float splinePrecisionYSpeed = 7.5f; | |
[Min(0f)] | |
[SerializeField] private float breakLockOnLength = 0.8f; | |
[Header("Optional")] | |
[SerializeField] private TrackedDollyController trackedDollyController; | |
// tutorial stuff | |
[HideInInspector] public bool CanAim = true; | |
[HideInInspector] public bool ForceLockOnModeOnly; | |
private bool initalized, torchToggled, precisionModeToggled, lockOnToggled, usingControllerLastFrame; | |
private AimActor prominentActor; | |
private AimTargetSplineController aimTargetSplineController; | |
private List<SceneSelectable> selectables = new(); | |
private int lockedOnIndex; | |
private Vector2 mousePositionBeforeLockOn; | |
#if UNITY_EDITOR | |
private bool debugPreventInput; | |
#endif | |
private static readonly int HasTargetID = Animator.StringToHash("HasTarget"); | |
public AimActor ProminentActor => prominentActor; | |
public bool LockOnToggled => lockOnToggled; | |
public bool PrecisionModeToggled => precisionModeToggled; | |
public bool FollowAimTargetOnIdle => followAimTargetOnIdle; | |
#region MonoBehaviour Methods | |
private void Start() | |
{ | |
if (initalized) return; | |
// check that we have the values we need and abort if we dont | |
if (actors.Length != 2) | |
{ | |
Debug.LogError("AimTargetController requires 2 actors, aborting initialization. If you wish to control only one actor set ControlBothActors to False"); | |
return; | |
} | |
aimTargetSplineController = aimTarget.GetComponent<AimTargetSplineController>(); | |
if (aimTargetSplineController == null) | |
{ | |
Debug.LogError("AimTargetController's AimTarget doesn't have a AimTargetSplineController, aborting initialization."); | |
return; | |
} | |
aimTargetSplineController.enabled = false; | |
if (!camera) | |
{ | |
Debug.LogError("AimTargetController's doesn't have a Camera assigned, aborting initialization."); | |
return; | |
} | |
// initialize the actors and set the proper state | |
for (var i = 0; i < actors.Length; i++) | |
{ | |
var actor = actors[i]; | |
actor.Init(this); | |
if (i == 0) | |
{ | |
prominentActor = actor; | |
prominentActor.Aim.target = aimTarget; | |
prominentActor.Aim.weight = 0; | |
prominentActor.Anim.SetBool(HasTargetID, false); | |
prominentActor.IdleTorch(true); | |
} | |
else | |
{ | |
actor.Aim.target = followAimTargetOnIdle ? aimTarget : actor.IdleTarget; | |
actor.Aim.weight = 1; | |
actor.Anim.SetBool(HasTargetID, true); | |
actor.IdleAimTorch(true); | |
} | |
} | |
AddSensorListeners(); | |
// get all the selectables in the scene and subscribe to changes to them | |
EventManager.AddListener(EventManager.REFRESH_SELECTABLES, UpdateSelectablesPool); | |
UpdateSelectablesPool(); | |
// configure stuff that is changed via player options and subscribe to any changes to them | |
EventManager.AddListener(EventManager.OPTION_CHANGED, UpdateOptions); | |
UpdateOptions(); | |
FungusPrioritySignals.OnFungusPriorityEnd += OnPriorityEnd; | |
initalized = true; | |
} | |
private void OnDestroy() | |
{ | |
EventManager.RemoveListener(EventManager.REFRESH_SELECTABLES, UpdateSelectablesPool); | |
EventManager.RemoveListener(EventManager.OPTION_CHANGED, UpdateOptions); | |
FungusPrioritySignals.OnFungusPriorityEnd -= OnPriorityEnd; | |
} | |
private void Update() | |
{ | |
#if UNITY_EDITOR | |
// toggle locking the torch into place for debugging | |
if (Input.GetKeyDown(KeyCode.L)) | |
debugPreventInput = !debugPreventInput; | |
if (debugPreventInput) return; | |
#endif | |
if (!initalized || !GameStateManager.HasControl || !CanAim || GameStateManager.IsPaused) return; | |
bool isDefaultMap = InputManager.Instance.GetCurrentRuleset() == "Default"; // hack cause aim torch is being triggered outside of the default map (shrug) | |
// toggle torch if the torch activation mode is toggle | |
if (!holdButtonToAim) | |
{ | |
if (InputManager.Player.GetButtonDown(RewiredConsts.Action.AimTorch) && isDefaultMap) | |
torchToggled = !torchToggled; | |
} | |
var torchInput = GetTorchInput() && isDefaultMap; | |
// only allow camera movement when torch isn't aimed | |
if (trackedDollyController) | |
trackedDollyController.enabled = !torchInput; | |
if (DataManager.GetOptionBoolean(Values.USING_CONTROLLER)) | |
{ | |
// enter controller mode if needed | |
if (!usingControllerLastFrame) | |
{ | |
lockOnToggled = false; | |
precisionModeToggled = false; | |
torchToggled = false; | |
// reset the position of the aim target on the spline | |
aimTargetSplineController.enabled = true; | |
aimTargetSplineController.RelativePosition = ControlBothActors ? prominentActor == actors[0] ? 0.25f : 0.75f : 0.5f; | |
aimTargetSplineController.Height = defaultSplineHeight; | |
prominentActor.Aim.target = aimTarget; | |
IdleProminentActorTorch(); | |
usingControllerLastFrame = true; | |
} | |
// extract stick values | |
var x = InputManager.Player.GetAxis(RewiredConsts.Action.TorchHorizontal); | |
var y = InputManager.Player.GetAxis(RewiredConsts.Action.TorchVertical); | |
if (DataManager.GetOptionBoolean(Values.INVERT_AIM_Y)) // maybe invert y axis | |
y *= -1; | |
// let locking on take priority over manually aiming | |
#region Lock On Mode | |
if (lockOnToggled) | |
{ | |
if (new Vector2(x, y).magnitude >= breakLockOnLength || !AreAnySelectableInteractable()) // maybe break lock state | |
{ | |
aimTargetSplineController.enabled = true; | |
lockOnToggled = false; | |
} | |
else | |
{ | |
UpdateLockOnTarget(); | |
return; | |
} | |
} | |
else if (!lockOnToggled && (InputManager.Player.GetButtonDown(RewiredConsts.Action.TorchSelectNext) || InputManager.Player.GetButtonDown(RewiredConsts.Action.TorchSelectPrevious))) // maybe enter lock on mode | |
{ | |
if (AreAnySelectableInteractable()) | |
{ | |
aimTargetSplineController.enabled = false; | |
lockOnToggled = true; | |
lockedOnIndex = GetIndexOfFirstInteractableSelectable(); | |
if (trackedDollyController) | |
trackedDollyController.enabled = false; | |
if (ControlBothActors) | |
{ | |
var maybeNewActor = selectables[lockedOnIndex].LockOnActor; | |
if (prominentActor != maybeNewActor) | |
ChangeProminentActor(maybeNewActor); | |
} | |
AimProminentActorTorch(); | |
aimTarget.transform.position = selectables[lockedOnIndex].GetFocusPoint().position; | |
return; | |
} | |
else | |
{ | |
//here is place for an sfx warning the player that nothing was interactable | |
return; | |
} | |
} | |
#endregion | |
if (ControlBothActors) | |
{ | |
// change actors if the aiming spline's relative position has passed its threshold to do so. | |
if (aimTargetSplineController.RelativePosition < 0.5f - switchActorsBuffer && prominentActor != actors[0]) | |
ChangeProminentActor(actors[0]); | |
else if (aimTargetSplineController.RelativePosition >= 0.5f + switchActorsBuffer && prominentActor != actors[1]) | |
ChangeProminentActor(actors[1]); | |
} | |
if (torchInput && !ForceLockOnModeOnly) // check that the proper input is being made for the current torch activation mode | |
{ | |
AimProminentActorTorch(); // aim the torch, there is no layer check here like the raycasting below, we are just always aiming when we use controller. | |
if (prominentActor.TorchAimed) // during the moment when prominent actor changes and the torch isn't aimed don't allow spline movement | |
{ | |
if (InputManager.Player.GetButtonDown(RewiredConsts.Action.PrecisionAim)) // maybe toggle precision aim | |
precisionModeToggled = !precisionModeToggled; | |
// adjust aim target relative position along spline | |
var xSpeed = precisionModeToggled ? splinePrecisionXSpeed : splineXSpeed; | |
if (x != 0) | |
aimTargetSplineController.RelativePosition = Mathf.Clamp01(aimTargetSplineController.RelativePosition + x * xSpeed * Time.deltaTime); | |
// adjust aim target height along spline | |
var ySpeed = precisionModeToggled ? splinePrecisionYSpeed : splineYSpeed; | |
if (y != 0) | |
aimTargetSplineController.Height = Mathf.Clamp(aimTargetSplineController.Height + y * ySpeed * Time.deltaTime, 0f, aimTargetSplineController.MaxHeight); | |
} | |
} | |
else // torch input isn't being made | |
{ | |
IdleProminentActorTorch(); | |
} | |
} | |
else | |
{ | |
// exit controller mode if needed | |
if (usingControllerLastFrame) | |
{ | |
aimTargetSplineController.enabled = false; | |
usingControllerLastFrame = false; | |
} | |
// let locking on take priority over manually aiming | |
#region Lock On Mode | |
if (lockOnToggled) | |
{ | |
if (Vector2.Distance(mousePositionBeforeLockOn, Input.mousePosition) > breakLockOnDistance || InputManager.Player.GetButtonDown(RewiredConsts.Action.AimTorch) || !AreAnySelectableInteractable()) // maybe break lock on state | |
{ | |
lockOnToggled = false; | |
} | |
else | |
{ | |
UpdateLockOnTarget(); | |
return; | |
} | |
} | |
else if (!lockOnToggled && (InputManager.Player.GetButtonDown(RewiredConsts.Action.TorchSelectNext) || InputManager.Player.GetButtonDown(RewiredConsts.Action.TorchSelectPrevious))) // maybe enter lock on mode | |
{ | |
if (AreAnySelectableInteractable()) | |
{ | |
lockOnToggled = true; | |
lockedOnIndex = GetIndexOfFirstInteractableSelectable(); | |
mousePositionBeforeLockOn = Input.mousePosition; | |
if (trackedDollyController) | |
trackedDollyController.enabled = false; | |
if (ControlBothActors) | |
{ | |
var maybeNewActor = selectables[lockedOnIndex].LockOnActor; | |
if (prominentActor != maybeNewActor) | |
ChangeProminentActor(maybeNewActor); | |
} | |
AimProminentActorTorch(); | |
aimTarget.transform.position = selectables[lockedOnIndex].GetFocusPoint().position; | |
return; | |
} | |
else | |
{ | |
//here is place for an sfx warning the player that nothing was interactable | |
return; | |
} | |
} | |
#endregion | |
if (torchInput && !ForceLockOnModeOnly) // check that the proper input is being made for the current torch activation mode | |
{ | |
Ray mouseRay = camera.ScreenPointToRay(Input.mousePosition); | |
if (Physics.Raycast(mouseRay, out var hit, raycastLength, hitboxLayer)) // check if we're hitting the torch hitbox layer | |
{ | |
if (ControlBothActors) | |
{ | |
var detectedActor = String.IsNullOrWhiteSpace(hit.transform.tag) ? actors[0] : actors.FirstOrDefault(actor => hit.transform.CompareTag(actor.HitboxTag)); | |
if (detectedActor && prominentActor != detectedActor) | |
{ | |
ChangeProminentActor(detectedActor); | |
} | |
else | |
{ | |
aimTarget.transform.position = hit.point; | |
AimProminentActorTorch(); | |
} | |
} | |
else // there is only one actor to control | |
{ | |
aimTarget.transform.position = hit.point; | |
AimProminentActorTorch(); | |
} | |
} | |
else // did not hit torch hitbox layer | |
{ | |
IdleProminentActorTorch(); | |
} | |
} | |
else // torch input isn't being made | |
{ | |
IdleProminentActorTorch(); | |
} | |
} | |
} | |
#endregion | |
#region Private Methods | |
/// <summary> | |
/// Is the proper button held/toggled for aiming the torch. | |
/// </summary> | |
public bool GetTorchInput() | |
{ | |
return (holdButtonToAim && InputManager.Player.GetButton(RewiredConsts.Action.AimTorch)) || (!holdButtonToAim && torchToggled); | |
} | |
/// <summary> | |
/// Assign listeners for the the prominent actor's torch sensor for when an object has bee focused/unfocused. | |
/// </summary> | |
private void AddSensorListeners() | |
{ | |
prominentActor.TorchSensor.OnDetected.AddListener(prominentActor.FocusTorch); | |
prominentActor.TorchSensor.OnLostDetection.AddListener(prominentActor.AimTorchAfterFocusing); | |
} | |
/// <summary> | |
/// Remove listeners for the the prominent actor's torch sensor. | |
/// </summary> | |
private void RemoveSensorListeners() | |
{ | |
prominentActor.TorchSensor.OnDetected.RemoveListener(prominentActor.FocusTorch); | |
prominentActor.TorchSensor.OnLostDetection.RemoveListener(prominentActor.AimTorchAfterFocusing); | |
} | |
private void IdleProminentActorTorch() | |
{ | |
if (prominentActor.AimState is AimActor.AimActorState.Idle) return; | |
prominentActor.Aim.weight = 0; | |
prominentActor.Anim.SetBool(HasTargetID, false); | |
prominentActor.IdleTorch(); | |
} | |
private void AimProminentActorTorch() | |
{ | |
if (prominentActor.AimState is AimActor.AimActorState.Aiming or AimActor.AimActorState.Focusing) return; | |
prominentActor.Aim.target = aimTarget; // force the aim target back after locking on to an interactable when focused | |
prominentActor.Aim.weight = 1; | |
prominentActor.Anim.SetBool(HasTargetID, true); | |
prominentActor.AimTorchAfterIdle(); | |
} | |
private void UpdateLockOnTarget() | |
{ | |
// one last sanity check even tho we do this before the method is possibly called because of the loop below. | |
if (!AreAnySelectableInteractable()) return; | |
bool valueChanged = false; | |
if (InputManager.Player.GetButtonDown(RewiredConsts.Action.TorchSelectPrevious)) | |
{ | |
do // decrease locked on index until we reach a selectable that is interactable | |
{ | |
lockedOnIndex--; | |
if (lockedOnIndex <= -1) // wrap value | |
lockedOnIndex = selectables.Count - 1; | |
} | |
while (!selectables[lockedOnIndex].Interactable); | |
valueChanged = true; | |
} | |
else if (InputManager.Player.GetButtonDown(RewiredConsts.Action.TorchSelectNext)) | |
{ | |
do // increase locked on index until we reach a selectable that is interactable | |
{ | |
lockedOnIndex++; | |
if (lockedOnIndex > selectables.Count - 1) // wrap value | |
lockedOnIndex = 0; | |
} | |
while (!selectables[lockedOnIndex].Interactable); | |
valueChanged = true; | |
} | |
if (valueChanged) | |
{ | |
if (ControlBothActors) | |
{ | |
// check if the new interactable has a different actor than the one that is currently prominent | |
var maybeNewActor = selectables[lockedOnIndex].LockOnActor; | |
if (prominentActor != maybeNewActor) | |
ChangeProminentActor(maybeNewActor); | |
} | |
// set aim target position to the selectable's focus point | |
aimTarget.transform.position = selectables[lockedOnIndex].GetFocusPoint().position; | |
} | |
} | |
/// <summary> | |
/// Clear and get all currently active selectable GameObjects (that have been properly setup). | |
/// Called when the 'refresh-selectables' event is called. | |
/// </summary> | |
private void UpdateSelectablesPool() | |
{ | |
selectables.Clear(); | |
foreach (var maybeSelectable in GameObject.FindGameObjectsWithTag("Selectable")) | |
{ | |
var selectable = maybeSelectable.GetComponent<SceneSelectable>(); | |
if (selectable == null) continue; | |
if (selectable.LockOnActor != null) | |
{ | |
// was properly configured, add to pool as is | |
selectables.Add(selectable); | |
} | |
else | |
{ | |
// wasn't configured. populate lock on actor with the first actor | |
#if UNITY_EDITOR | |
Debug.LogWarning($"{selectable.name} didn't have its LockOnActor specified, assigning to {actors[0].name}"); | |
#endif | |
selectable.LockOnActor = actors[0]; | |
selectables.Add(selectable); | |
} | |
} | |
} | |
/// <summary> | |
/// Checks if any selectables in the current pool are interactable. | |
/// </summary> | |
private bool AreAnySelectableInteractable() | |
{ | |
return selectables.Any(selectable => selectable.Interactable); | |
} | |
/// <summary> | |
/// Returns the index of the first selectable in the current pool that is interactable | |
/// </summary> | |
private int GetIndexOfFirstInteractableSelectable() | |
{ | |
for (var i = 0; i < selectables.Count; i++) | |
{ | |
var selectable = selectables[i]; | |
if (selectable.Interactable) | |
return i; | |
} | |
return 0; | |
} | |
private void OnPriorityEnd() | |
{ | |
prominentActor.Aim.target = aimTarget; // set the aim target back after locking on to an interactable when focused | |
} | |
private void UpdateOptions() | |
{ | |
holdButtonToAim = !DataManager.GetOptionBoolean(Values.TOGGLE_AIM); | |
torchToggled = false; | |
} | |
#endregion | |
#region Public Methods | |
/// <summary> | |
/// Change the actor that is aiming at the AimTarget. | |
/// </summary> | |
/// <param name="newActor">The actor to change to, must be initialized with this AimTargetController.</param> | |
public void ChangeProminentActor(AimActor newActor) | |
{ | |
if (!initalized) return; | |
if (!actors.Contains(newActor)) | |
{ | |
Debug.LogWarning("Attempted to set the prominent actor to an actor not initialized with this AimTargetController, aborting."); | |
return; | |
} | |
RemoveSensorListeners(); | |
prominentActor.Aim.target = followAimTargetOnIdle ? aimTarget : prominentActor.IdleTarget; // either follow along with the aim target while idle or idle at a predefined position | |
prominentActor.Aim.weight = 1; | |
prominentActor.IdleAimTorch(); | |
prominentActor.Anim.SetBool(HasTargetID, true); | |
newActor.Aim.target = aimTarget; | |
newActor.AimTorchAfterIdleAim(); | |
prominentActor = newActor; | |
AddSensorListeners(); | |
} | |
// used from the tutorial only | |
public void ResetTorchToggle() | |
{ | |
torchToggled = false; | |
} | |
#endregion | |
#region Editor Only Methods | |
#if UNITY_EDITOR | |
public void CopyValuesFromMainActor() | |
{ | |
if (actors.Length <= 1) | |
{ | |
Debug.LogWarning("Unable to copy values from main actor, 2 or more actors are required."); | |
return; | |
} | |
Undo.IncrementCurrentGroup(); | |
Undo.SetCurrentGroupName("Sync Values From Main Actor"); | |
var mainActor = actors[0]; | |
for (var i = 0; i < actors.Length; i++) | |
{ | |
var actor = actors[i]; | |
if (i != 0) | |
{ | |
Undo.RecordObject(actor.TorchSpotlight, "Sync Spotlight"); | |
actor.TorchSpotlight.intensity = mainActor.TorchSpotlight.intensity; | |
actor.TorchSpotlight.spotAngle = mainActor.TorchSpotlight.spotAngle; | |
actor.TorchSpotlight.innerSpotAngle = mainActor.TorchSpotlight.innerSpotAngle; | |
actor.TorchSpotlight.range = mainActor.TorchSpotlight.range; | |
Undo.RecordObject(actor.TorchVlb, "Sync Volumetric Light Beam"); | |
actor.TorchVlb.intensityGlobal = mainActor.TorchVlb.intensityGlobal; | |
actor.TorchVlb.spotAngle = mainActor.TorchVlb.spotAngle; | |
actor.TorchVlb.noiseMode = mainActor.TorchVlb.noiseMode; | |
Undo.RecordObject(actor, "Sync Aim Actor"); | |
actor.FocusStartDelay = mainActor.FocusStartDelay; | |
actor.AimAfterIdleAimDelay = mainActor.AimAfterIdleAimDelay; | |
actor.FocusCooldownDuration = mainActor.FocusCooldownDuration; | |
actor.IdleProperties = mainActor.IdleProperties; | |
actor.IdleSpeedSettings = mainActor.IdleSpeedSettings; | |
actor.IdleEase = mainActor.IdleEase; | |
actor.IdleAimingProperties = mainActor.IdleAimingProperties; | |
actor.IdleAimingSpeedSettings = mainActor.IdleAimingSpeedSettings; | |
actor.IdleAimingEase = mainActor.IdleAimingEase; | |
actor.AimingProperties = mainActor.AimingProperties; | |
actor.AimingSpeedSettings = mainActor.AimingSpeedSettings; | |
actor.AimingEase = mainActor.AimingEase; | |
actor.FocusProperties = mainActor.FocusProperties; | |
actor.FocusSpeedSettings = mainActor.FocusSpeedSettings; | |
actor.FocusEase = mainActor.FocusEase; | |
actor.FocusSuccessSpeedSettings = mainActor.FocusSuccessSpeedSettings; | |
actor.FocusSuccessEase = mainActor.FocusSuccessEase; | |
actor.NonProminentAimProperties = mainActor.NonProminentAimProperties; | |
actor.ProminentAimProperties = mainActor.ProminentAimProperties; | |
} | |
} | |
Debug.Log($"Copied values from main actor ({mainActor.name}) to {actors.Length - 1} actor(s)."); | |
} | |
#endif | |
#endregion | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Linq; | |
using UnityEngine; | |
namespace HumbleGrove.SpaceCapitalism | |
{ | |
[RequireComponent(typeof(Collider))] | |
public class SceneSelectable : MonoBehaviour | |
{ | |
[Tooltip("Can this selectable be focused on.")] | |
public bool Interactable = true; | |
[Tooltip("Should the refresh-selectables event be called upon enabling/disabling.")] | |
public bool AutomaticallyRefreshSelectables = true; | |
[Header("Focus Settings")] | |
[Tooltip("Should the actor lock its aim upon focusing on this selectable. Useful for providing consistent lighting on an object.")] | |
[SerializeField] private bool lockOnWhenFocused = true; | |
[Tooltip("Alternative transform to aim at upon focusing on this selectable. If null, the transform of the selectable will be used.")] | |
[SerializeField] private Transform focusLockOverride; | |
[Header("Lock On Settings")] | |
[Tooltip("Actor that will aim at this selectable in lock on mode. If null, the first actor in the AimTargetController will be used.")] | |
public AimActor LockOnActor; | |
private ConditionalBase[] conditionals; | |
public bool LockOnWhenFocused => lockOnWhenFocused; | |
public bool ConditionalsFulfilled { get; private set; } | |
private void Awake() | |
{ | |
// force tag used by the trigger sensor if it's not setup already | |
if (!CompareTag("Selectable")) | |
{ | |
#if UNITY_EDITOR | |
Debug.LogWarning($"{name} didn't have its Tag set to Selectable. assigning now.", gameObject); | |
#endif | |
tag = "Selectable"; | |
} | |
conditionals = GetComponentsInChildren<ConditionalBase>(); | |
} | |
public void OnEnable() | |
{ | |
EventManager.AddListener(EventManager.VARIABLE_CHANGED, UpdateConditionalCheck); | |
EventManager.AddListener(EventManager.GLOBAL_VARIABLE_CHANGED, UpdateConditionalCheck); | |
UpdateConditionalCheck(); | |
if (AutomaticallyRefreshSelectables) | |
EventManager.TriggerEvent(EventManager.REFRESH_SELECTABLES); | |
} | |
public void OnDisable() | |
{ | |
EventManager.RemoveListener(EventManager.VARIABLE_CHANGED, UpdateConditionalCheck); | |
EventManager.RemoveListener(EventManager.GLOBAL_VARIABLE_CHANGED, UpdateConditionalCheck); | |
if (AutomaticallyRefreshSelectables) | |
EventManager.TriggerEvent(EventManager.REFRESH_SELECTABLES); | |
} | |
/// <summary> | |
/// Get the transform that the torch should rest on upon completing focusing. | |
/// </summary> | |
public Transform GetFocusPoint() | |
{ | |
return focusLockOverride ? focusLockOverride : transform; | |
} | |
/// <summary> | |
/// Check that all child conditionals evaluate to true and cache the result. | |
/// </summary> | |
private void UpdateConditionalCheck() | |
{ | |
if (conditionals == null || conditionals.Length == 0) | |
{ | |
ConditionalsFulfilled = true; | |
return; | |
} | |
ConditionalsFulfilled = conditionals.All(conditional => conditional.Evaluate()); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment