예제 #1
0
        private void FireExplosives()
        {
            // Set up working data
            EnemyBlueprint blueprint = this.blueprint.Res;
            Ship           ship      = this.GameObj.GetComponent <Ship>();
            Vector2        pos       = this.GameObj.Transform.Pos.Xy;

            // Push other objects away
            GameplayHelper.Shockwave(
                pos,
                blueprint.ExplosionRadius,
                blueprint.ExplosionForce,
                blueprint.ExplosionMaxVelocity,
                obj => obj.GameObj != this.GameObj);

            // Damage other objects
            GameplayHelper.ExplosionDamage(
                pos,
                blueprint.ExplosionRadius,
                blueprint.ExplosionDamage,
                body => body.GameObj.GetComponent <Ship>() != null && body.GameObj != ship.GameObj);

            // Die instantly
            ship.Die();

            // Spawn explosion effects
            if (blueprint.ExplosionEffects != null)
            {
                Transform transform = this.GameObj.Transform;
                for (int i = 0; i < blueprint.ExplosionEffects.Length; i++)
                {
                    GameObject effectObj = blueprint.ExplosionEffects[i].Res.Instantiate(transform.Pos);
                    this.Scene.AddObject(effectObj);
                }
            }

            // Play explosion sound
            if (blueprint.ExplosionSound != null)
            {
                SoundInstance inst = DualityApp.Sound.PlaySound3D(blueprint.ExplosionSound, new Vector3(pos));
                inst.Pitch = MathF.Rnd.NextFloat(0.8f, 1.25f);
            }
        }
예제 #2
0
        void ICmpUpdatable.OnUpdate()
        {
            EnemyBlueprint blueprint = this.blueprint.Res;
            Transform      transform = this.GameObj.Transform;
            RigidBody      body      = this.GameObj.GetComponent <RigidBody>();
            Ship           ship      = this.GameObj.GetComponent <Ship>();

            // Calculate distress caused by going in a different direction than desired
            float moveDistress = 0.0f;

            if (body.LinearVelocity.Length > 1.0f)
            {
                Vector2 actualVelocityDir      = body.LinearVelocity.Normalized;
                Vector2 desiredVelocityDir     = ship.TargetThrust;
                float   desiredDirectionFactor = Vector2.Dot(actualVelocityDir, desiredVelocityDir);
                moveDistress = MathF.Clamp(1.0f - desiredDirectionFactor, 0.0f, 1.0f) * MathF.Clamp(body.LinearVelocity.Length - 0.5f, 0.0f, 1.0f);
            }

            // Do AI state handling stuff
            float moveTowardsEnemyRatio = 0.0f;

            switch (this.state)
            {
            case MindState.Asleep:
            {
                // Wake up, if there is a player near
                float      nearestDist;
                GameObject nearestObj = this.GetNearestPlayerObj(out nearestDist);
                if (nearestObj != null && nearestDist <= WakeupDist && this.HasLineOfSight(nearestObj, true))
                {
                    this.Awake();
                }

                // Don't move actively
                ship.TargetThrust     = Vector2.Zero;
                ship.TargetAngle      = MathF.Rnd.NextFloat(-MathF.RadAngle30, MathF.RadAngle30);
                ship.TargetAngleRatio = 0.0f;
                break;
            }

            case MindState.FallingAsleep:
            {
                if (this.eyeOpenValue <= 0.0001f)
                {
                    this.state = MindState.Asleep;
                }
                break;
            }

            case MindState.Awaking:
            {
                if (this.eyeOpenValue >= 0.9999f)
                {
                    this.state = MindState.Idle;
                }
                break;
            }

            case MindState.Idle:
            {
                // Follow, if there is a player near
                float      nearestDist;
                GameObject nearestObj = this.GetNearestPlayerObj(out nearestDist);
                if (nearestObj != null && this.HasLineOfSight(nearestObj, false))
                {
                    if (this.behavior.HasFlag(BehaviorFlags.Chase))
                    {
                        Transform nearestObjTransform = nearestObj.Transform;
                        Vector2   targetDiff          = nearestObjTransform.Pos.Xy - transform.Pos.Xy;
                        ship.TargetThrust     = targetDiff / MathF.Max(targetDiff.Length, 25.0f);
                        moveTowardsEnemyRatio = ship.TargetThrust.Length;
                    }
                    else
                    {
                        ship.TargetThrust = Vector2.Zero;
                    }
                    ship.TargetAngle     += 0.001f * Time.TimeMult;
                    ship.TargetAngleRatio = 0.1f;

                    this.idleTimer = MathF.Rnd.NextFloat(0.0f, SleepTime * 0.25f);

                    if (nearestDist <= SpikeAttackMoveDist)
                    {
                        moveDistress = 0.0f;
                        if (!this.spikesActive)
                        {
                            this.ActivateSpikes();
                        }
                    }
                    else if (ship.TargetThrust.Length > 0.1f)
                    {
                        if (this.spikesActive)
                        {
                            this.DeactivateSpikes();
                        }
                    }
                }
                // Try to stay in place otherwise
                else
                {
                    ship.TargetThrust     = -body.LinearVelocity / MathF.Max(body.LinearVelocity.Length, ship.Blueprint.Res.MaxSpeed);
                    ship.TargetAngleRatio = 0.1f;
                    this.idleTimer       += Time.MillisecondsPerFrame * Time.TimeMult;

                    if (this.spikesActive)
                    {
                        this.DeactivateSpikes();
                    }
                }

                // Blink occasionally
                this.blinkTimer -= Time.MillisecondsPerFrame * Time.TimeMult;
                if (this.blinkTimer <= 0.0f)
                {
                    this.RandomizeBlinkTimer();
                    this.BlinkEye();
                }

                // Go to sleep if nothing happens.
                if (this.idleTimer > SleepTime)
                {
                    this.Sleep();
                }
                break;
            }
            }

            // Udpate the eyes state and visual appearance
            {
                float actualTarget = MathF.Clamp(this.eyeOpenTarget - moveDistress * 0.35f, 0.0f, 1.0f);
                float eyeDiff      = MathF.Abs(actualTarget - this.eyeOpenValue);
                float eyeChange    = MathF.Sign(actualTarget - this.eyeOpenValue) * MathF.Min(this.eyeSpeed, eyeDiff);

                this.eyeOpenValue = MathF.Clamp(this.eyeOpenValue + eyeChange * Time.TimeMult, 0.0f, 1.0f);
                if (this.eyeBlinking && this.eyeOpenValue <= this.eyeOpenTarget + 0.0001f)
                {
                    this.eyeOpenTarget = 1.0f;
                }

                if (this.eye != null)
                {
                    this.eye.AnimTime = this.eyeOpenValue;
                }
            }

            // Update the spikes state and visual appearance
            for (int i = 0; i < this.spikeState.Length; i++)
            {
                float actualTarget = MathF.Clamp(this.spikeState[i].OpenTarget - moveDistress, 0.0f, 1.0f);
                if (actualTarget > this.spikeState[i].OpenValue)
                {
                    Vector2 spikeDir;
                    switch (i)
                    {
                    default:
                    case 0: spikeDir = new Vector2(1, -1); break;

                    case 1: spikeDir = new Vector2(1, 1); break;

                    case 2: spikeDir = new Vector2(-1, 1); break;

                    case 3: spikeDir = new Vector2(-1, -1); break;
                    }

                    Vector2 spikeBeginWorld = transform.GetWorldPoint(spikeDir * 4);
                    Vector2 spikeEndWorld   = transform.GetWorldPoint(spikeDir * 11);
                    bool    hitAnything     = false;
                    this.Scene.Physics.RayCast(spikeBeginWorld, spikeEndWorld, data =>
                    {
                        if (data.Shape.IsSensor)
                        {
                            return(-1);
                        }
                        if (data.Body == body)
                        {
                            return(-1);
                        }

                        Ship otherShip = data.GameObj.GetComponent <Ship>();
                        if (otherShip != null && otherShip.Owner != null)
                        {
                            return(-1);
                        }

                        hitAnything = true;
                        return(0);
                    });

                    if (hitAnything)
                    {
                        actualTarget = 0.0f;
                    }
                }

                float spikeMoveDir = MathF.Sign(actualTarget - this.spikeState[i].OpenValue);
                this.spikeState[i].OpenValue = MathF.Clamp(this.spikeState[i].OpenValue + spikeMoveDir * this.spikeState[i].Speed * Time.TimeMult, 0.0f, 1.0f);
                if (this.spikeState[i].Blinking && this.spikeState[i].OpenValue <= this.spikeState[i].OpenTarget + 0.0001f)
                {
                    this.spikeState[i].OpenTarget = 1.0f;
                    this.spikeState[i].Speed      = Time.SecondsPerFrame / MathF.Rnd.NextFloat(0.25f, 1.0f);
                }

                // If we're extending a spike where the sensor has already registered a contact, explode
                if (this.spikeState[i].OpenValue > 0.75f && this.spikeState[i].ContactCount > 0)
                {
                    this.FireExplosives();
                }
            }
            if (this.spikes != null)
            {
                for (int i = 0; i < this.spikes.Length; i++)
                {
                    if (this.spikes[i] == null)
                    {
                        continue;
                    }
                    Rect spikeRect = this.spikes[i].Rect;

                    spikeRect.Y = MathF.Lerp(3.5f, -4.5f, this.spikeState[i].OpenValue);

                    this.spikes[i].Rect = spikeRect;
                }
            }

            // Make a sound while moving
            if (blueprint.MoveSound != null)
            {
                // Determine the target volume
                float targetVolume = MathF.Clamp(moveTowardsEnemyRatio, 0.0f, 1.0f);

                // Clean up disposed loop
                if (this.moveSoundLoop != null && this.moveSoundLoop.Disposed)
                {
                    this.moveSoundLoop = null;
                }

                // Start the loop when requested
                if (targetVolume > 0.0f && this.moveSoundLoop == null)
                {
                    this.moveSoundLoop        = DualityApp.Sound.PlaySound3D(blueprint.MoveSound, this.GameObj, true);
                    this.moveSoundLoop.Looped = true;
                }

                // Configure existing loop and dispose it when no longer needed
                if (this.moveSoundLoop != null)
                {
                    this.moveSoundLoop.Volume += (targetVolume - this.moveSoundLoop.Volume) * 0.05f * Time.TimeMult;
                    if (this.moveSoundLoop.Volume <= 0.05f)
                    {
                        this.moveSoundLoop.FadeOut(0.1f);
                        this.moveSoundLoop = null;
                    }
                }
            }

            // Make a danger sound while moving with spikes out
            if (blueprint.AttackSound != null)
            {
                // Determine the target volume
                float targetVolume = this.spikesActive ? MathF.Clamp(moveTowardsEnemyRatio, 0.25f, 1.0f) : 0.0f;

                // Clean up disposed loop
                if (this.dangerSoundLoop != null && this.dangerSoundLoop.Disposed)
                {
                    this.dangerSoundLoop = null;
                }

                // Start the loop when requested
                if (targetVolume > 0.0f && this.dangerSoundLoop == null)
                {
                    this.dangerSoundLoop        = DualityApp.Sound.PlaySound3D(blueprint.AttackSound, this.GameObj, true);
                    this.dangerSoundLoop.Looped = true;
                }

                // Configure existing loop and dispose it when no longer needed
                if (this.dangerSoundLoop != null)
                {
                    this.dangerSoundLoop.Volume += (targetVolume - this.dangerSoundLoop.Volume) * 0.1f * Time.TimeMult;
                    if (this.dangerSoundLoop.Volume <= 0.05f)
                    {
                        this.dangerSoundLoop.FadeOut(0.1f);
                        this.dangerSoundLoop = null;
                    }
                }
            }
        }