using MalbersAnimations.Scriptables; using UnityEngine; using UnityEngine.Serialization; namespace MalbersAnimations.Controller { [HelpURL("https://malbersanimations.gitbook.io/animal-controller/main-components/manimal-controller/states/fly")] public class Fly : State { public override string StateName => "Fly"; public enum FlyInput { Toggle, Press, None} [Header("Fly Parameters")] [Range(0, 90),Tooltip("Bank amount used when turning")] public float Bank = 30; [Range(0, 90), Tooltip("Pitch Limit to Rotate the Rotator Up and Down")] [FormerlySerializedAs("Ylimit")] public float PitchLimit = 80; [Tooltip("Bank amount used when turning while straffing")] public float BankStrafe = 0; [Tooltip("Limit to go Up and Down while straffing")] [FormerlySerializedAs("YlimitStrafe")] public float PitchLimitStrafe = 0; [Space] [Tooltip("Max Y Height the Animal Can Fly. If this value is Zero this value will be ignored")] public float MaxFlyHeight = 0; [Tooltip("When Entering the Fly State... The animal will keep the Velocity from the last State if this value is greater than zero")] [FormerlySerializedAs("InertiaTime")] public FloatReference InertiaLerp = new FloatReference(1); [Tooltip("The animal will move forward while flying, without the need to push the W Key, or Move forward Input")] public BoolReference AlwaysForward = new BoolReference(false); private bool LastAlwaysForward; [Header("TakeOff")] [Tooltip("Impulse to push the animal Upwards for a time to help him take off.\nIf set to zero this logic will be ignored")] public FloatReference Impulse = new FloatReference(); [Tooltip("Time the Impulse will be applied")] public FloatReference ImpulseTime = new FloatReference(0.5f); [Tooltip("Curve to apply to the Impulse Logic")] public AnimationCurve ImpulseCurve = new AnimationCurve(new Keyframe(0, 1), new Keyframe(1, 0)); private float elapsedImpulseTime; [Header("Landing")] [Tooltip("When the Animal is close to the Ground it will automatically Land")] public BoolReference canLand = new BoolReference( true); [Tooltip("Layers to Land on")] public LayerMask LandOn = (1); [Tooltip("Ray Length multiplier to check for ground and automatically land (increases or decreases the MainPivot Lenght for the Fall Ray")] public FloatReference LandMultiplier = new FloatReference(1f); [Space,Tooltip("Avoids a surface to land when Flying. E.g. if the animal does not have a swim state, set this to void landing/entering the water")] public bool AvoidSurface = false; [Tooltip("RayCast distance to find the Surface to avoid"), Hide("AvoidSurface", true, false)] public float SurfaceDistance = 0.5f; [Tooltip("Which layers to search to avoid that surface. Triggers are not inlcuded"), Hide("AvoidSurface", true, false)] public LayerMask SurfaceLayer = 16; [Header("Gliding")] [Tooltip("Vertical Speed on the Animator set to Flap the wings")] public float FlapSpeed = 1; [Tooltip("Vertical Speed on the Animator set as Glide")] public float GlideSpeed = 2; [Space] public BoolReference GlideOnly = new BoolReference(false); [Tooltip("When the Forward Input is Released,this will be the movement speed the gliding will have")] public FloatReference GlideOnlyIdleS = new FloatReference(0.5f); public FloatReference GlideOnlyIdleV = new FloatReference(1); [Header("Auto Glide")] [Tooltip("It will do Auto gliding while flying")] public BoolReference AutoGlide = new BoolReference(true); [MinMaxRange(0, 10)] public RangedFloat GlideChance = new RangedFloat(0.8f, 4); [MinMaxRange(0, 10)] public RangedFloat FlapChange = new RangedFloat(0.5f, 4); [Tooltip("Variation to make Random Flap and Glide Animation")] public float Variation = 0.3f; protected bool isGliding = false; protected float FlyStyleTime = 1; protected float AutoGlide_CurrentTime = 1; [Header("Down Acceleration")] public FloatReference GravityDrag = new FloatReference(0); public FloatReference DownAcceleration = new FloatReference(0.5f); private float acceleration = 0; protected Vector3 verticalInertia; [Header("Bone Blocking Landing"),Tooltip("Somethimes the Head blocks the Landing Ray.. this will solve the landing by raycasting a ray from the Bone that is blocking the Logic")] /// If the Animal is a larger one sometimes public bool BoneBlockingLanding = false; [Hide("BoneBlockingLanding", true),Tooltip("Name of the blocker bone")] public string BoneName = "Head"; [Hide("BoneBlockingLanding", true),Tooltip("Local Offset from the Blocker Bone")] public Vector3 BoneOffsetPos = Vector3.zero; [Hide("BoneBlockingLanding", true),Tooltip("Distance of the Landing Ray from the blocking Bone")] public float BlockLandDist = 0.4f; private Transform BlockingBone; public override void InitializeState() { AutoGlide_CurrentTime = Time.time; FlyStyleTime = GlideChance.RandomValue; SearchForContactBone(); } /// When using Contact bone Find it on the Animal that is using it void SearchForContactBone() { BlockingBone = null; if (BoneBlockingLanding) BlockingBone = animal.transform.FindGrandChild(BoneName); } MSpeed LastSpeedModifier; MSpeed NewSpeedModifier; public override void Activate() { LastSpeedModifier = animal.CurrentSpeedModifier; //Store the current speed modifier base.Activate(); LastAlwaysForward = animal.AlwaysForward; animal.AlwaysForward = AlwaysForward; InputValue = true; //Make sure the Input is set to True when the flying is not being activated by an input player NewSpeedModifier = animal.CurrentSpeedModifier; //Store the current speed modifier } public override void EnterTagAnimation() { if (CurrentAnimTag == EnterTagHash) //Meaning its on Take Off Animation { animal.CurrentSpeedModifier = LastSpeedModifier; //BUG THAT DOES NOT USE THE VerticalSpeed } } public override void EnterCoreAnimation() { animal.CurrentSpeedModifier = NewSpeedModifier; //Restore the Fly Speed Modifier verticalInertia = Vector3.Project(animal.DeltaPos, animal.UpVector); //Find the Up Inertia to keep it while entering the Core Anim animal.PitchDirection = animal.Forward; acceleration = 0; animal.InertiaPositionSpeed = animal.HorizontalVelocity * animal.DeltaTime; if (animal.LastState.ID.ID <=1) animal.InertiaPositionSpeed = Vector3.zero; //*TIny Hack... Remove the Intertia if the last state was IDle or Locomotion. if (GlideOnly.Value) { animal.currentSpeedModifier.Vertical = GlideSpeed; animal.UseSprintState = false; } else { animal.currentSpeedModifier.Vertical = FlapSpeed; isGliding = true; } } // public override bool TryActivate() => InputValue && CanBeActivated; public override Vector3 Speed_Direction() { if (GlideOnly) { MovementAxisMult.y = 0; var Mult = animal.MovementAxis.z < 0.1 ? GlideOnlyIdleS.Value : 1f; animal.currentSpeedModifier.Vertical = animal.MovementAxis.z < 0.1 ? GlideOnlyIdleV.Value : GlideSpeed; animal.MovementAxis.z = 1; return animal.Forward * Mult; } if (animal.FreeMovement) return animal.PitchDirection; return animal.Forward; } public override void OnStateMove(float deltatime) { if (InCoreAnimation) //While is flying { var limit = PitchLimit; var bank = Bank; if (animal.Strafe) { limit = PitchLimitStrafe; bank = BankStrafe; } if (AutoGlide && !GlideOnly.Value) AutoGliding(); //Limit Fly Max Height if (MaxFlyHeight > 0 && animal.transform.position.y > MaxFlyHeight) { limit = 0; var Pos = animal.transform.position; Pos.y = MaxFlyHeight; animal.transform.position = Pos; } GravityPush(deltatime); //Add artificial gravity to the Fly animal.FreeMovementRotator(limit, bank); if (TryAvoidSurface()) return; if (InertiaLerp.Value > 0) animal.AddInertia(ref verticalInertia, InertiaLerp); } //Takeoff Impulse Logic if (Impulse > 0 && ImpulseTime > 0) { if (elapsedImpulseTime <= ImpulseTime) { var takeOffImp = Impulse * ImpulseCurve.Evaluate(elapsedImpulseTime / ImpulseTime); animal.AdditivePosition += animal.UpVector * takeOffImp * deltatime; elapsedImpulseTime += deltatime; Debug.Log(takeOffImp); } } } public override void OnModeStart(Mode mode) { if (!mode.AllowMovement) verticalInertia = Vector3.zero; //Remove the vertical inertia } private bool TryAvoidSurface() { if (AvoidSurface) { var surfacePos = transform.position + animal.AdditivePosition; var Dist = SurfaceDistance * ScaleFactor; if (Physics.Raycast(surfacePos, Gravity, out RaycastHit hit, Dist, SurfaceLayer)) { Color findWater = Color.cyan; if (animal.MovementAxis.y < 0) animal.MovementAxis.y = 0; if (hit.distance < Dist * 0.75f) { animal.AdditivePosition += Gravity * -(Dist * 0.75f - hit.distance); } if (debug) Debug.DrawRay(surfacePos, Gravity * Dist, findWater); return true; } } return false; } public override void TryExitState(float DeltaTime) { if (!InputValue) AllowExit(); if (canLand.Value) { var Point = BlockingBone ? BlockingBone.TransformPoint(BoneOffsetPos) : animal.Main_Pivot_Point; var Dist = (BlockingBone ? BlockLandDist : LandMultiplier.Value) * animal.ScaleFactor; if (Physics.Raycast(Point, Gravity, out RaycastHit landHit, Dist, LandOn, IgnoreTrigger)) { Debugging($"[AllowExit] Can Land on <{landHit.collider.name}> [Using Blocking Bone]"); FlyAllowExit(); return; } if (Physics.Raycast(animal.Main_Pivot_Point, Gravity, out RaycastHit landHitMain, LandMultiplier.Value * animal.ScaleFactor, LandOn, IgnoreTrigger)) { Debugging($"[AllowExit] Can Land on <{landHitMain.collider.name}> "); FlyAllowExit(); return; } } } private void FlyAllowExit() { animal.FreeMovement = false; //Disable the Free Movement animal.UseGravity = true; AllowExit(); } void GravityPush(float deltaTime) { if (animal.Strafe) return; //Do not push Down when if (animal.MovementAxisRaw.y < 0f) { acceleration += DownAcceleration * Mathf.Abs(animal.MovementAxis.y) * deltaTime; } else { //Deacelerate slowly all the acceleration you earned.. acceleration = Mathf.MoveTowards(acceleration, 0, deltaTime * DownAcceleration); } //USE INERTIA SPEED INSTEAD OF TARGET POSITION if (acceleration != 0) animal.AdditivePosition += animal.InertiaPositionSpeed.normalized * acceleration * deltaTime; if (GravityDrag > 0) { animal.AdditivePosition += Gravity * (GravityDrag * animal.ScaleFactor) * deltaTime; } } void AutoGliding() { if (MTools.ElapsedTime(FlyStyleTime, AutoGlide_CurrentTime)) { AutoGlide_CurrentTime = Time.time; isGliding ^= true; FlyStyleTime = isGliding ? GlideChance.RandomValue : FlapChange.RandomValue; var newGlideSpeed = Random.Range(GlideSpeed - Variation, GlideSpeed); var newFlapSpeed = Random.Range(FlapSpeed, FlapSpeed + Variation); animal.currentSpeedModifier.Vertical = (isGliding && !animal.Strafe) ? newGlideSpeed : newFlapSpeed; } } public override void ResetStateValues() { verticalInertia = Vector3.zero; acceleration = 0; isGliding = false; InputValue = false; elapsedImpulseTime = 0; LastSpeedModifier = new MSpeed(); } public override void RestoreAnimalOnExit() { animal.FreeMovement = false; animal.AlwaysForward = LastAlwaysForward; // animal.Speed_Lock(false); animal.InputSource?.SetInput(Input, false); //Hack to reset the toggle when it exit on Grounded animal.LockUpDownMovement = false; } public override void AllowStateExit() { base.InputValue = false; //release the base Input value base.ExitInputValue = false; //release the base Input value } public override bool InputValue //lets override to Allow exit when the Input Changes { get => base.InputValue; set { base.InputValue = value; // Debug.Log($"{this.name} value = " + value); if (InCoreAnimation && IsActiveState && !value && CanExit) //When the Fly Input is false then allow exit { AllowExit(); } } } #if UNITY_EDITOR void Reset() { ID = MTools.GetInstance("Fly"); Input = "Fly"; EnterTag.Value = "TakeOff"; General = new AnimalModifier() { RootMotion = true, Grounded = false, Sprint = true, OrientToGround = false, CustomRotation = false, IgnoreLowerStates = true, Gravity = false, modify = (modifier)(-1), AdditivePosition = true, AdditiveRotation = true, FreeMovement = true, }; } public override void StateGizmos(MAnimal animal) { if (canLand && animal.debugGizmos) { var Gravity = animal.Gravity; Gizmos.color = Color.yellow; var width = 2f; var PointDown = Gravity.normalized * (LandMultiplier) * animal.transform.lossyScale.y; MTools.DrawLine(animal.Main_Pivot_Point, animal.Main_Pivot_Point + PointDown, width); if (BlockingBone) { var HitPoint = BlockingBone.TransformPoint(BoneOffsetPos); MTools.DrawLine(HitPoint, HitPoint + Gravity * BlockLandDist * animal.transform.lossyScale.y, width); } if (AvoidSurface && !Application.isPlaying) { Gizmos.color = Color.cyan; var Dist = SurfaceDistance * animal.ScaleFactor; Gizmos.DrawRay(animal.Center, Gravity.normalized * Dist); } } } #endif } }