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); } }
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; } } } }