Added more sound effects. Added initial weapon muzzle fire logic.

This commit is contained in:
2026-01-02 00:54:09 -05:00
parent c4a98ce03d
commit df45203227
43 changed files with 459 additions and 30 deletions

View File

@@ -1,8 +1,10 @@
using Microsoft.Xna.Framework;
using AlienAttack.MonoGame.Things.Items;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Media;
using System;
using System.Collections.Generic;
namespace AlienAttack.MonoGame.Audio;
@@ -19,6 +21,7 @@ public sealed class AudioManager
private VariantSoundPool _enemyGunPool = default!;
private VariantSoundPool _explosionPool = default!;
private VariantSoundPool _impactPool = default!;
private Dictionary<PickupKind, VariantSoundPool> _pickupPool = [];
// Rate limiters (global caps per category/event)
private RateLimiter _playerGunLimiter = new(0.06f); // max ~16 plays/sec
@@ -39,16 +42,9 @@ public sealed class AudioManager
LoadImpactPool(content);
LoadExplosionPool(content);
//var playerGun = content.Load<SoundEffect>("Audio/Sfx/player_fire");
//var enemyGun = content.Load<SoundEffect>("Audio/Sfx/enemy_fire");
//var explosion = content.Load<SoundEffect>("Audio/Sfx/explosion");
//var impact = content.Load<SoundEffect>("Audio/Sfx/impact");
// Create pools with sane voice caps
//_playerGunPool = new SoundPool(playerGun, maxVoices: 2);
//_enemyGunPool = new SoundPool(enemyGun, maxVoices: 3);
//_explosionPool = new SoundPool(explosion, maxVoices: 6);
//_impactPool = new SoundPool(impact, maxVoices: 4);
LoadShieldPickupPool(content);
LoadAmmoPickupPool(content);
LoadRocketsPickupPool(content);
}
private void LoadPlayerGunPool(ContentManager content)
@@ -111,6 +107,56 @@ public sealed class AudioManager
_explosionPool = new VariantSoundPool(variants, voicesPerVariant: 1, rng: _random);
}
private void LoadShieldPickupPool(ContentManager content)
{
var shieldVariants = new[]
{
content.Load<SoundEffect>("Sfx/Shield/SCIEnrg_Shield Activate_01_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Shield/SCIEnrg_Shield Activate_02_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Shield/SCIEnrg_Shield Activate_03_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Shield/SCIEnrg_Shield Activate_04_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Shield/SCIEnrg_Shield Activate_05_SFRMS_SCIWPNS")
};
_pickupPool.Add(PickupKind.Shield, new VariantSoundPool(shieldVariants, voicesPerVariant: 1, rng: _random));
}
private void LoadAmmoPickupPool(ContentManager content)
{
var variants = new[]
{
content.Load<SoundEffect>("Sfx/Reload/GUNMech_Insert Clip_01_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Reload/GUNMech_Insert Clip_02_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Reload/GUNMech_Insert Clip_03_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Reload/GUNMech_Insert Clip_04_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Reload/GUNMech_Insert Clip_05_SFRMS_SCIWPNS")
};
_pickupPool.Add(PickupKind.Ammo, new VariantSoundPool(variants, voicesPerVariant: 1, rng: _random));
}
private void LoadRocketsPickupPool(ContentManager content)
{
var variants = new[]
{
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_01_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_02_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_03_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_04_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_05_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_06_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_07_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_08_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_09_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_10_SFRMS_SCIWPNS"),
content.Load<SoundEffect>("Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_11_SFRMS_SCIWPNS"),
};
_pickupPool.Add(PickupKind.Rockets, new VariantSoundPool(variants, voicesPerVariant: 1, rng: _random));
}
//GUNMech_Rocket Launcher Reload_01_SFRMS_SCIWPNS
// Call this once per frame
public void Update(GameTime gameTime)
{
@@ -192,6 +238,21 @@ public sealed class AudioManager
}
}
public void PlayPickup(PickupKind kind)
{
if (_pickupPool.TryGetValue(kind, out VariantSoundPool pool) == false)
return;
float baseVol = 0.85f;
float vol = baseVol * RandRange(0.95f, 1.05f);
float pitch = RandRange(-0.04f, 0.04f);
pool.Play(
volume: Clamp01(MasterVolume * SfxVolume * vol),
pitch: pitch
);
}
// -----------------------
// Music
// -----------------------

View File

@@ -223,6 +223,138 @@
/processorParam:Quality=Best
/build:Sfx/Pistol/GUNPis_Pistol Fire_06_SFRMS_SCIWPNS.wav;Sfx/Pistol/GUNPis_Pistol Fire_06_SFRMS_SCIWPNS
#begin Sfx/Reload/GUNMech_Insert Clip_01_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Reload/GUNMech_Insert Clip_01_SFRMS_SCIWPNS.wav;Sfx/Reload/GUNMech_Insert Clip_01_SFRMS_SCIWPNS
#begin Sfx/Reload/GUNMech_Insert Clip_02_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Reload/GUNMech_Insert Clip_02_SFRMS_SCIWPNS.wav;Sfx/Reload/GUNMech_Insert Clip_02_SFRMS_SCIWPNS
#begin Sfx/Reload/GUNMech_Insert Clip_03_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Reload/GUNMech_Insert Clip_03_SFRMS_SCIWPNS.wav;Sfx/Reload/GUNMech_Insert Clip_03_SFRMS_SCIWPNS
#begin Sfx/Reload/GUNMech_Insert Clip_04_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Reload/GUNMech_Insert Clip_04_SFRMS_SCIWPNS.wav;Sfx/Reload/GUNMech_Insert Clip_04_SFRMS_SCIWPNS
#begin Sfx/Reload/GUNMech_Insert Clip_05_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Reload/GUNMech_Insert Clip_05_SFRMS_SCIWPNS.wav;Sfx/Reload/GUNMech_Insert Clip_05_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_01_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_01_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_01_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_02_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_02_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_02_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_03_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_03_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_03_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_04_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_04_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_04_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_05_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_05_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_05_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_06_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_06_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNArtl_Rocket Launcher Fire_06_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_01_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_01_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_01_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_02_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_02_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_02_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_03_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_03_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_03_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_04_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_04_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_04_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_05_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_05_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_05_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_06_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_06_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_06_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_07_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_07_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_07_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_08_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_08_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_08_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_09_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_09_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_09_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_10_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_10_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_10_SFRMS_SCIWPNS
#begin Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_11_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_11_SFRMS_SCIWPNS.wav;Sfx/Rocket Launcher/GUNMech_Rocket Launcher Reload_11_SFRMS_SCIWPNS
#begin Sfx/Shield/SCIEnrg_Shield Activate_01_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor

View File

@@ -6,9 +6,12 @@ namespace AlienAttack.MonoGame.Things.Bullets;
internal class MinigunBulletLarge : Bullet
{
public const int Width = 9;
public const int Height = 27;
public MinigunBulletLarge(float x, float y, float xVel, float yVel, Sprite owner) : base(x, y, xVel, yVel, owner)
{
BoundBox = new(0, 0, 9, 27);
BoundBox = new(0, 0, Width, Height);
Damage = 3;
}

View File

@@ -6,9 +6,12 @@ namespace AlienAttack.MonoGame.Things.Bullets;
internal class MinigunBulletMedium : Bullet
{
public const int Width = 11;
public const int Height = 21;
public MinigunBulletMedium(float x, float y, float xVel, float yVel, Sprite owner) : base(x, y, xVel, yVel, owner)
{
BoundBox = new(0, 0, 11, 21);
BoundBox = new(0, 0, Width, Height);
Damage = 2;
}

View File

@@ -6,9 +6,12 @@ namespace AlienAttack.MonoGame.Things.Bullets;
internal class MinigunBulletSmall : Bullet
{
public const int Width = 11;
public const int Height = 21;
public MinigunBulletSmall(float x, float y, float xVel, float yVel, Sprite owner) : base(x, y, xVel, yVel, owner)
{
BoundBox = new(0, 0, 7, 17);
BoundBox = new(0, 0, Width, Height);
Damage = 1;
}

View File

@@ -14,7 +14,7 @@ internal class RedEnemy : EnemyShip
public RedEnemy(int x, int y) : base(x, y)
{
BoundBox = new Rectangle(0, 0, 64, 64);
YVelocity = 2;
YVelocity = 1.5f;
}
public override void Draw(SpriteDrawArgs args)
@@ -88,12 +88,13 @@ internal class RedEnemy : EnemyShip
CurrentFireThreshold--;
}
if (CurrentFireThreshold == 0 && context.Random.Next(0, 20) == 1)
if (CurrentFireThreshold == 0 && context.Random.Next(0, 50) == 1)
{
float originX = XPosition + (BoundBox.Width / 2) - (7 / 2);
context.SpawnSprite(new MinigunBulletSmall(originX - 9, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this));
context.SpawnSprite(new MinigunBulletSmall(originX + 14, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this));
context.SpawnSprite(new MinigunBulletSmall(originX - 9, YPosition + BoundBox.Height - 12, -1, 2 + YVelocity, this));
context.SpawnSprite(new MinigunBulletSmall(originX + 3, YPosition + BoundBox.Height - 12, 0, 2 + YVelocity, this));
context.SpawnSprite(new MinigunBulletSmall(originX + 14, YPosition + BoundBox.Height - 12, 1, 2 + YVelocity, this));
CurrentFireThreshold = FireThreshold;

View File

@@ -2,6 +2,8 @@
internal class Ammo : Item
{
protected override PickupKind Kind => PickupKind.Ammo;
public Ammo(int x, int y) : base(x, y)
{
TextureName = @$"Sprites\Powerup_Ammo";

View File

@@ -2,6 +2,8 @@
internal class Energy : Item
{
protected override PickupKind Kind => PickupKind.Plasma;
public Energy(int x, int y) : base(x, y)
{
TextureName = @$"Sprites\Powerup_Energy";

View File

@@ -2,6 +2,8 @@
internal class Health : Item
{
protected override PickupKind Kind => PickupKind.Health;
public Health(int x, int y) : base(x, y)
{
TextureName = @$"Sprites\Powerup_Health";

View File

@@ -14,6 +14,8 @@ internal abstract class Item : MoveableSprite
protected string TextureName;
protected abstract PickupKind Kind { get; }
public Item(int x, int y) : base(x, y)
{
YVelocity = .5f;
@@ -61,6 +63,7 @@ internal abstract class Item : MoveableSprite
{
IsDead = true;
ApplyEffect(player);
context.AudioManager.PlayPickup(Kind);
}
}

View File

@@ -0,0 +1,10 @@
namespace AlienAttack.MonoGame.Things.Items;
public enum PickupKind
{
Health,
Shield,
Ammo,
Rockets,
Plasma
}

View File

@@ -2,6 +2,8 @@
internal class Rockets : Item
{
protected override PickupKind Kind => PickupKind.Rockets;
public Rockets(int x, int y) : base(x, y)
{
TextureName = @$"Sprites\Powerup_Rockets";

View File

@@ -2,6 +2,8 @@
internal class Shields : Item
{
protected override PickupKind Kind => PickupKind.Shield;
public Shields(int x, int y) : base(x, y)
{
TextureName = @$"Sprites\Powerup_Shields";

View File

@@ -0,0 +1,9 @@
using Microsoft.Xna.Framework;
namespace AlienAttack.MonoGame.Things.Muzzles;
public readonly struct Muzzle(MuzzleId id, Vector2 localPx)
{
public MuzzleId Id { get; } = id;
public Vector2 LocalPx { get; } = localPx;
}

View File

@@ -0,0 +1,8 @@
namespace AlienAttack.MonoGame.Things.Muzzles;
public enum MuzzleId
{
Center,
Left,
Right
}

View File

@@ -0,0 +1,20 @@
using Microsoft.Xna.Framework;
namespace AlienAttack.MonoGame.Things.Muzzles;
public static class MuzzleMath
{
public static Vector2 GetMuzzleWorld(Sprite owner, Vector2 muzzleLocalPx, float ownerScale = 1f)
{
// Assumes owner.Position is the top-left of the sprite
return owner.Position + muzzleLocalPx * ownerScale;
}
public static Vector2 CenterBulletOn(Vector2 muzzleWorld, int bulletW, int bulletH)
{
return new Vector2(
muzzleWorld.X - bulletW / 2f,
muzzleWorld.Y - bulletH / 2f
);
}
}

View File

@@ -1,15 +1,13 @@
using AlienAttack.MonoGame.Things.Bullets;
using AlienAttack.MonoGame.Things.Enemies;
using AlienAttack.MonoGame.Things.Items;
using AlienAttack.MonoGame.Things.Muzzles;
using AlienAttack.MonoGame.Things.Weapons;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.IO;
using static System.Net.Mime.MediaTypeNames;
namespace AlienAttack.MonoGame.Things;
@@ -52,11 +50,19 @@ internal class Player : MoveableSprite
public int Health { get; protected set; }
public int Shield { get; protected set; }
public IReadOnlyList<Muzzle> Muzzles { get; } =
[
new Muzzle(MuzzleId.Center, new Vector2(31.5f, 1f)),
new Muzzle(MuzzleId.Left, new Vector2(15f, 27f)),
new Muzzle(MuzzleId.Right, new Vector2(47f, 27f)),
];
public Player(int x, int y) : base(x, y)
{
BoundBox = new Rectangle(0, 0, 64, 64);
ActiveWeapons.Add(new Minigun());
//ActiveWeapons.Add(new Minigun());
//ActiveWeapons.Add(new FastMinigun());
ActiveWeapons.Add(new MinigunSpread());
Health = 100;
Shield = 100;
}
@@ -336,10 +342,12 @@ internal class Player : MoveableSprite
{
//SCIEnrg_Shield Activate_01_SFRMS_SCIWPNS
int number = context.Random.Next(1, 6);
//int number = context.Random.Next(1, 6);
SoundEffect soundEffect = context.Content.Load<SoundEffect>(@$"Sfx\Shield\SCIEnrg_Shield Activate_0{number}_SFRMS_SCIWPNS");
soundEffect.Play(0.85f, (float)(context.Random.NextDouble() * 0.1 - 0.05), 0);
//SoundEffect soundEffect = context.Content.Load<SoundEffect>(@$"Sfx\Shield\SCIEnrg_Shield Activate_0{number}_SFRMS_SCIWPNS");
//soundEffect.Play(0.85f, (float)(context.Random.NextDouble() * 0.1 - 0.05), 0);
//context.AudioManager.PlayPickup(PickupKind.Shield);
GiveShield(20);
}

View File

@@ -0,0 +1,12 @@
namespace AlienAttack.MonoGame.Things.Weapons;
public enum Anchor
{
TopLeft,
TopCenter,
TopRight,
Center,
BottomLeft,
BottomCenter,
BottomRight
}

View File

@@ -0,0 +1,43 @@
using AlienAttack.MonoGame.Things.Bullets;
using AlienAttack.MonoGame.Things.Muzzles;
using Microsoft.Xna.Framework;
using System;
namespace AlienAttack.MonoGame.Things.Weapons;
internal record FireBulletContext
{
public Sprite Owner { get; init; }
public SpriteUpdateContext SpriteUpdateContext { get; init; }
public int BulletWidth { get; init; }
public int BulletHeight { get; init; }
public Anchor Anchor { get; init; }
public Shot[] Shots { get; init; }
public Func<float, float, float, float, Sprite, Bullet> Factory { get; init; }
}
public record Shot
{
public float OffsetX { get; init; }
public float OffsetY { get; init; }
public float XVelocity { get; init; }
public float YVelocity { get; init; }
}
internal record FireBulletFromMuzzleContext
{
public Sprite Owner { get; init; }
public SpriteUpdateContext SpriteUpdateContext { get; init; }
public int BulletWidth { get; init; }
public int BulletHeight { get; init; }
public MuzzleShot[] Shots { get; init; }
public Vector2 ExtraOffset { get; init; } = default;
public Func<float, float, float, float, Sprite, Bullet> Factory { get; init; }
}
public record MuzzleShot
{
public MuzzleId Muzzle { get; init; }
public float XVelocity { get; init; }
public float YVelocity { get; init; }
}

View File

@@ -1,9 +1,9 @@
using AlienAttack.MonoGame.Things.Bullets;
using Microsoft.Xna.Framework.Audio;
using AlienAttack.MonoGame.Things.Muzzles;
namespace AlienAttack.MonoGame.Things.Weapons;
public class Minigun : Weapon
internal class Minigun : Weapon
{
public override int FireThreshold => 15;
@@ -31,7 +31,49 @@ public class Minigun : Weapon
}
}
public class FastMinigun : Weapon
internal class MinigunSpread : Weapon
{
public override int FireThreshold => 20;
public override void Fire(Sprite owner, SpriteUpdateContext context)
{
// Calculate bullet spawn positions relative to the player's bounding box
int x1 = (int)owner.XPosition + 14;
int x2 = (int)owner.XPosition + owner.BoundBox.Width - 16;
int x3 = (int)owner.XPosition + owner.BoundBox.Width / 2 - 1;
int y = (int)owner.YPosition + 10;
MinigunBulletLarge bullet1 = new(x1, y, -1, -6, owner);
MinigunBulletLarge bullet2 = new(x2, y, 1, -6, owner);
MinigunBulletLarge bullet3 = new(x3, y-16, 0, -6, owner);
//context.SpawnSprite(bullet1);
//context.SpawnSprite(bullet2);
//context.SpawnSprite(bullet3);
FireBulletFromMuzzleContext fireBulletContext = new()
{
Owner = owner,
SpriteUpdateContext = context,
BulletWidth = MinigunBulletLarge.Width,
BulletHeight = MinigunBulletLarge.Height,
Factory = (x, y, vx, vy2, o) => new MinigunBulletLarge(x, y, vx, vy2, o),
ExtraOffset = new(4f, 0),
Shots =
[
new() { Muzzle = MuzzleId.Left, XVelocity = -1, YVelocity = -6 },
new() { Muzzle = MuzzleId.Right, XVelocity = 1, YVelocity = -6 },
new() { Muzzle = MuzzleId.Center, XVelocity = 0, YVelocity = -6 }
]
};
FireFromMuzzle(fireBulletContext);
context.AudioManager.PlayPlayerFire();
}
}
internal class FastMinigun : Weapon
{
public override int FireThreshold => 10;

View File

@@ -1,6 +1,12 @@
namespace AlienAttack.MonoGame.Things.Weapons;
using AlienAttack.MonoGame.Things.Bullets;
using AlienAttack.MonoGame.Things.Muzzles;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
public abstract class Weapon : IWeapon
namespace AlienAttack.MonoGame.Things.Weapons;
internal abstract class Weapon : IWeapon
{
public abstract int FireThreshold { get; }
public int CurrentFireThreshold { get; private set; }
@@ -23,4 +29,59 @@ public abstract class Weapon : IWeapon
}
public abstract void Fire(Sprite owner, SpriteUpdateContext context);
protected static void FireBullet(FireBulletContext context)
{
var (x, y) = ComputeAnchorPosition(context);
foreach (Shot shot in context.Shots)
{
Bullet bullet = context.Factory(x + shot.OffsetX, y + shot.OffsetY, shot.XVelocity, shot.YVelocity, context.Owner);
context.SpriteUpdateContext.SpawnSprite(bullet);
}
}
private static (float x, float y) ComputeAnchorPosition(FireBulletContext context)
{
Sprite owner = context.Owner;
int bw = context.BulletWidth;
int bh = context.BulletHeight;
Anchor anchor = context.Anchor;
float left = owner.XPosition;
float top = owner.YPosition;
float right = owner.XPosition + owner.BoundBox.Width;
float bottom = owner.YPosition + owner.BoundBox.Height;
return anchor switch
{
Anchor.TopLeft => (left, top - bh),
Anchor.TopCenter => (left + (owner.BoundBox.Width - bw) / 2f, top - bh),
Anchor.TopRight => (right - bw, top - bh),
Anchor.Center => (left + (owner.BoundBox.Width - bw) / 2f, top + (owner.BoundBox.Height - bh) / 2f),
Anchor.BottomLeft => (left, bottom),
Anchor.BottomCenter => (left + (owner.BoundBox.Width - bw) / 2f, bottom),
Anchor.BottomRight => (right - bw, bottom),
_ => (left, top)
};
}
protected static void FireFromMuzzle(FireBulletFromMuzzleContext context)
{
if (context.Owner is not Player player)
return;
foreach (MuzzleShot shot in context.Shots)
{
var m = player.Muzzles.First(x => x.Id == shot.Muzzle).LocalPx;
Vector2 worldMuzzle = MuzzleMath.GetMuzzleWorld(player, m);
Vector2 bulletPos = MuzzleMath.CenterBulletOn(worldMuzzle, context.BulletWidth, context.BulletHeight) + context.ExtraOffset;
Bullet bullet = context.Factory(bulletPos.X, bulletPos.Y, shot.XVelocity, shot.YVelocity, context.Owner);
context.SpriteUpdateContext.SpawnSprite(bullet);
}
}
}