Added sound effects and music.

This commit is contained in:
2025-12-22 21:55:46 -05:00
parent 42608a16e3
commit 874c263910
36 changed files with 566 additions and 76 deletions

View File

@@ -1,8 +1,10 @@
using AlienAttack.MonoGame.GameLoops;
using AlienAttack.MonoGame.View;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System;
using System.Runtime.InteropServices;
@@ -44,6 +46,28 @@ public class AlienAttackGame : Game
base.Initialize();
MaximizeWindow();
PlayBackgroundMusic();
}
private void PlayBackgroundMusic()
{
Song song = Content.Load<Song>($@"Music\Untitled");
// Set whether the song should repeat when finished
MediaPlayer.IsRepeating = true;
// Adjust the volume (0.0f to 1.0f)
MediaPlayer.Volume = 0.5f;
// Check if the media player is already playing, if so, stop it
if (MediaPlayer.State == MediaState.Playing)
{
MediaPlayer.Stop();
}
// Start playing the background music
MediaPlayer.Play(song);
}
private void ConfigureWindow()
@@ -86,7 +110,7 @@ public class AlienAttackGame : Game
GraphicsDevice.Clear(Color.Black);
// TODO: Add your drawing code here
_gameLoop.Draw();
_gameLoop.Draw(gameTime);
base.Draw(gameTime);
}

View File

@@ -37,6 +37,150 @@
/processorParam:TextureFormat=Color
/build:Background/PixelBackgroundSeamlessVertically.png
#begin Music/Untitled.mp3
/importer:Mp3Importer
/processor:SongProcessor
/processorParam:Quality=Best
/build:Music/Untitled.mp3
#begin Sfx/Explosions/EXPLDsgn_Explosion Impact_01_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Explosions/EXPLDsgn_Explosion Impact_01_SFRMS_SCIWPNS.wav;Sfx/Explosions/EXPLDsgn_Explosion Impact_01_SFRMS_SCIWPNS
#begin Sfx/Explosions/EXPLDsgn_Explosion Impact_02_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Explosions/EXPLDsgn_Explosion Impact_02_SFRMS_SCIWPNS.wav;Sfx/Explosions/EXPLDsgn_Explosion Impact_02_SFRMS_SCIWPNS
#begin Sfx/Explosions/EXPLDsgn_Explosion Impact_03_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Explosions/EXPLDsgn_Explosion Impact_03_SFRMS_SCIWPNS.wav;Sfx/Explosions/EXPLDsgn_Explosion Impact_03_SFRMS_SCIWPNS
#begin Sfx/Explosions/EXPLDsgn_Explosion Impact_04_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Explosions/EXPLDsgn_Explosion Impact_04_SFRMS_SCIWPNS.wav;Sfx/Explosions/EXPLDsgn_Explosion Impact_04_SFRMS_SCIWPNS
#begin Sfx/Explosions/EXPLDsgn_Explosion Impact_05_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Explosions/EXPLDsgn_Explosion Impact_05_SFRMS_SCIWPNS.wav;Sfx/Explosions/EXPLDsgn_Explosion Impact_05_SFRMS_SCIWPNS
#begin Sfx/Explosions/EXPLDsgn_Explosion Impact_06_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Explosions/EXPLDsgn_Explosion Impact_06_SFRMS_SCIWPNS.wav;Sfx/Explosions/EXPLDsgn_Explosion Impact_06_SFRMS_SCIWPNS
#begin Sfx/GUNAuto_Assault Rifle A Fire_01_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNAuto_Assault Rifle A Fire_01_SFRMS_SCIWPNS.wav
#begin Sfx/GUNAuto_Assault Rifle A Fire_02_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNAuto_Assault Rifle A Fire_02_SFRMS_SCIWPNS.wav
#begin Sfx/GUNAuto_Assault Rifle A Fire_03_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNAuto_Assault Rifle A Fire_03_SFRMS_SCIWPNS.wav
#begin Sfx/GUNAuto_Assault Rifle A Fire_04_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNAuto_Assault Rifle A Fire_04_SFRMS_SCIWPNS.wav
#begin Sfx/GUNAuto_Assault Rifle A Fire_05_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNAuto_Assault Rifle A Fire_05_SFRMS_SCIWPNS.wav
#begin Sfx/GUNAuto_Assault Rifle A Fire_06_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNAuto_Assault Rifle A Fire_06_SFRMS_SCIWPNS.wav
#begin Sfx/GUNPis_Pistol Fire_01_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNPis_Pistol Fire_01_SFRMS_SCIWPNS.wav
#begin Sfx/GUNPis_Pistol Fire_02_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNPis_Pistol Fire_02_SFRMS_SCIWPNS.wav
#begin Sfx/GUNPis_Pistol Fire_03_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNPis_Pistol Fire_03_SFRMS_SCIWPNS.wav
#begin Sfx/GUNPis_Pistol Fire_04_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNPis_Pistol Fire_04_SFRMS_SCIWPNS.wav
#begin Sfx/GUNPis_Pistol Fire_05_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNPis_Pistol Fire_05_SFRMS_SCIWPNS.wav
#begin Sfx/GUNPis_Pistol Fire_06_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/GUNPis_Pistol Fire_06_SFRMS_SCIWPNS.wav
#begin Sfx/Shield/SCIEnrg_Shield Activate_01_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Shield/SCIEnrg_Shield Activate_01_SFRMS_SCIWPNS.wav;Sfx/Shield/SCIEnrg_Shield Activate_01_SFRMS_SCIWPNS
#begin Sfx/Shield/SCIEnrg_Shield Activate_02_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Shield/SCIEnrg_Shield Activate_02_SFRMS_SCIWPNS.wav;Sfx/Shield/SCIEnrg_Shield Activate_02_SFRMS_SCIWPNS
#begin Sfx/Shield/SCIEnrg_Shield Activate_03_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Shield/SCIEnrg_Shield Activate_03_SFRMS_SCIWPNS.wav;Sfx/Shield/SCIEnrg_Shield Activate_03_SFRMS_SCIWPNS
#begin Sfx/Shield/SCIEnrg_Shield Activate_04_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Shield/SCIEnrg_Shield Activate_04_SFRMS_SCIWPNS.wav;Sfx/Shield/SCIEnrg_Shield Activate_04_SFRMS_SCIWPNS
#begin Sfx/Shield/SCIEnrg_Shield Activate_05_SFRMS_SCIWPNS.wav
/importer:WavImporter
/processor:SoundEffectProcessor
/processorParam:Quality=Best
/build:Sfx/Shield/SCIEnrg_Shield Activate_05_SFRMS_SCIWPNS.wav;Sfx/Shield/SCIEnrg_Shield Activate_05_SFRMS_SCIWPNS
#begin Sprites/Asteroid 01_png_processed.png
/importer:TextureImporter
/processor:TextureProcessor

Binary file not shown.

View File

@@ -1,6 +1,7 @@
using AlienAttack.MonoGame.Things;
using AlienAttack.MonoGame.Things.Enemies;
using AlienAttack.MonoGame.Things.Stars;
using AlienAttack.MonoGame.View;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -15,7 +16,6 @@ internal class GameLoop : GameLoopBase
private readonly Random _random = new();
private int _spawnNewEnemyThreshold = 100;
private float _backgroundYOffset = 0;
private Starfield _starfield;
private Texture2D _pixel;
@@ -56,84 +56,87 @@ internal class GameLoop : GameLoopBase
_starfield.Initialize(ViewTransform.ScreenWidth, ViewTransform.ScreenHeight);
}
public override void OnDraw()
public override void OnDraw(GameTime gameTime)
{
_starfield.Draw(SpriteBatch, _pixel);
//DrawBackground();
DrawHud();
DrawHud(gameTime);
DrawSprites();
}
private void DrawBackground()
private void DrawHud(GameTime gameTime)
{
Texture2D backgroundTexture = Content.Load<Texture2D>($@"Background\PixelBackgroundSeamlessVertically");
int blockCountY = (int)Math.Ceiling((decimal)ViewTransform.ScreenHeight / (decimal)backgroundTexture.Height);
int blockCountX = (int)Math.Ceiling((decimal)ViewTransform.ScreenWidth / (decimal)backgroundTexture.Width);
for (int y = 0; y < blockCountY; y++)
{
float startY = (y * backgroundTexture.Height) + _backgroundYOffset;
_backgroundYOffset = _backgroundYOffset + 0.25f;
if (_backgroundYOffset > ViewTransform.ScreenHeight)
{
_backgroundYOffset = 0;
}
for (int x = 0; x < blockCountX; x++)
{
int width = backgroundTexture.Width;
if (x * backgroundTexture.Width + backgroundTexture.Width > ViewTransform.ScreenWidth)
{
width = ViewTransform.ScreenWidth - (x * backgroundTexture.Width);
}
Vector2 position = new(x * backgroundTexture.Width, startY);
SpriteBatch.Draw(backgroundTexture, position, new Rectangle(new Point(x, y), new Point(width, backgroundTexture.Height)), Color.White);
}
}
double t = gameTime.TotalGameTime.TotalSeconds;
DrawSideBars(SpriteBatch, ViewTransform.ScreenWidth, ViewTransform.ScreenHeight, _player.Health, _player.Shield, t);
}
private void DrawHud()
{
DrawSideBars(SpriteBatch, ViewTransform.ScreenWidth, ViewTransform.ScreenHeight, _player.Health, _player.Shield);
}
private void DrawSideBars(SpriteBatch sb, int screenW, int screenH, int health, int shield)
private void DrawSideBars(SpriteBatch sb, int screenW, int screenH, int health, int shield, double totalSeconds)
{
health = Math.Clamp(health, 0, 100);
shield = Math.Clamp(shield, 0, 100);
// Tweak these to taste to match “Raptor-ish” proportions
int marginTop = 8;
int marginBottom = 8;
int barWidth = 10; // thin like the screenshot
int gap = 4; // small gap between segments
int marginTop = 10;
int marginBottom = 10;
int segments = 100;
int gap = 4;
int barWidth = 10; // thickness of the colored segments
int borderPad = 2; // thickness around the bar (border area)
int borderW = barWidth + borderPad * 2;
int barHeight = screenH - marginTop - marginBottom;
// Compute segment height so 100 segments + gaps fit in the available height
// If your resolution is small, this could become 0; clamp to 1.
int segHeight = Math.Max(1, (barHeight - gap * (segments - 1)) / segments);
// Recompute barHeight actually used by segments (so we can bottom-align perfectly)
int usedHeight = segments * segHeight + (segments - 1) * gap;
// Position bars along left/right edges
int leftX = 6; // a few pixels from edge
int rightX = screenW - 6 - barWidth;
int barTopY = marginTop;
int barBottomY = marginTop + usedHeight;
int bottomY = marginTop + usedHeight; // bottom of the bar area
// Put left / right bars near screen edges
int leftBorderX = 8;
int rightBorderX = screenW - 8 - borderW;
// Colors
Color healthOn = new Color(255, 140, 0); // orange-ish
Color shieldOn = new Color(60, 140, 255); // blue-ish
Color off = new Color(30, 30, 30); // dark "empty" segment
// Colors (tweak as desired)
Color healthOn = new Color(255, 140, 0); // orange
Color shieldOn = new Color(60, 140, 255); // blue
DrawSegmentedBar(sb, leftX, bottomY, barWidth, segHeight, gap, health, healthOn, off, segments);
DrawSegmentedBar(sb, rightX, bottomY, barWidth, segHeight, gap, shield, shieldOn, off, segments);
Color off = new Color(28, 28, 28); // empty segment base
// Draw metallic frames (behind segments)
//DrawMetallicFrame(sb, new Rectangle(leftBorderX, barTopY - borderPad, borderW, usedHeight + borderPad * 2));
//DrawMetallicFrame(sb, new Rectangle(rightBorderX, barTopY - borderPad, borderW, usedHeight + borderPad * 2));
// Segment drawing X starts inside the border
int leftSegX = leftBorderX + borderPad;
int rightSegX = rightBorderX + borderPad;
// Low health pulse settings
bool lowHealth = health <= 20;
float pulse = lowHealth
? (0.85f + 0.15f * (float)Math.Sin(totalSeconds * 6.0)) // subtle wobble
: 1f;
DrawSegmentedBar(
sb, leftSegX, barBottomY, barWidth, segHeight, gap,
value0To100: health,
onColor: healthOn,
offColor: off,
segments: segments,
// pulse only on filled health segments
pulseMultiplier: pulse,
pulseFilledOnly: true
);
DrawSegmentedBar(
sb, rightSegX, barBottomY, barWidth, segHeight, gap,
value0To100: shield,
onColor: shieldOn,
offColor: off,
segments: segments,
// no pulse for shield by default
pulseMultiplier: 1f,
pulseFilledOnly: true
);
}
private void DrawSegmentedBar(
@@ -146,8 +149,11 @@ internal class GameLoop : GameLoopBase
int value0To100,
Color onColor,
Color offColor,
int segments = 100)
int segments,
float pulseMultiplier,
bool pulseFilledOnly)
{
// Gradient: bottom darker, top brighter
float minBrightness = 0.55f;
float maxBrightness = 1.00f;
@@ -155,21 +161,76 @@ internal class GameLoop : GameLoopBase
{
int y = bottomY - (i + 1) * segHeight - i * gap;
// i = 0 bottom, i = segments-1 top
// i=0 bottom, i=segments-1 top
float t = i / (float)(segments - 1);
float brightness = MathHelper.Lerp(minBrightness, maxBrightness, t);
Color baseColor = (i < value0To100) ? onColor : offColor;
bool filled = i < value0To100;
Color baseColor = filled ? onColor : offColor;
// Apply brightness scaling
Color finalColor = new Color(
(byte)(baseColor.R * brightness),
(byte)(baseColor.G * brightness),
(byte)(baseColor.B * brightness),
baseColor.A
);
// Apply pulse (usually only on the filled parts)
if (pulseMultiplier != 1f && (!pulseFilledOnly || filled))
{
brightness *= pulseMultiplier;
brightness = Math.Min(brightness, 1.2f); // allow a tiny “pop”, still safe
}
Color finalColor = ScaleRgb(baseColor, brightness);
sb.Draw(_pixel, new Rectangle(x, y, barWidth, segHeight), finalColor);
// Optional: little “shine” line on filled segments (adds a nice DOS UI feel)
// Draw a 1px highlight on the left edge of lit segments.
if (filled && barWidth >= 3)
{
var shine = ScaleRgb(Color.White, 0.10f * brightness);
sb.Draw(_pixel, new Rectangle(x, y, 1, segHeight), shine);
}
}
}
private static Color ScaleRgb(Color c, float mul)
{
mul = Math.Max(0f, mul);
byte r = (byte)Math.Clamp((int)(c.R * mul), 0, 255);
byte g = (byte)Math.Clamp((int)(c.G * mul), 0, 255);
byte b = (byte)Math.Clamp((int)(c.B * mul), 0, 255);
return new Color(r, g, b, c.A);
}
private void DrawMetallicFrame(SpriteBatch sb, Rectangle outer)
{
// “Metal” palette
Color outerBorder = new Color(10, 10, 10);
Color fill = new Color(45, 45, 45);
Color highlight = new Color(90, 90, 90); // top/left
Color shadow = new Color(20, 20, 20); // bottom/right
// Outer border
sb.Draw(_pixel, outer, outerBorder);
// Inner fill
var inner = new Rectangle(outer.X + 1, outer.Y + 1, outer.Width - 2, outer.Height - 2);
if (inner.Width > 0 && inner.Height > 0)
sb.Draw(_pixel, inner, fill);
// Bevel lines (1px)
// Top highlight
sb.Draw(_pixel, new Rectangle(inner.X, inner.Y, inner.Width, 1), highlight);
// Left highlight
sb.Draw(_pixel, new Rectangle(inner.X, inner.Y, 1, inner.Height), highlight);
// Bottom shadow
sb.Draw(_pixel, new Rectangle(inner.X, inner.Bottom - 1, inner.Width, 1), shadow);
// Right shadow
sb.Draw(_pixel, new Rectangle(inner.Right - 1, inner.Y, 1, inner.Height), shadow);
// Optional: an even tighter inner “lip” for extra metal feel
var lip = new Rectangle(inner.X + 1, inner.Y + 1, inner.Width - 2, inner.Height - 2);
if (lip.Width > 0 && lip.Height > 0)
{
Color lipDark = new Color(35, 35, 35);
sb.Draw(_pixel, lip, lipDark);
}
}
@@ -199,6 +260,7 @@ internal class GameLoop : GameLoopBase
{
Random = _random,
GameTime = gameTime,
Content = Content,
SpawnSprite = _spritesToAdd.Add
};
@@ -219,7 +281,9 @@ internal class GameLoop : GameLoopBase
SpriteCollisionContext context = new(Game)
{
Sprite = otherSprite,
SpawnSprite = _spritesToAdd.Add
SpawnSprite = _spritesToAdd.Add,
Random = _random,
Content = Content
};
// TODO: If perfomed once; do not perform again
@@ -282,4 +346,11 @@ internal class GameLoop : GameLoopBase
}
}
}
}
public record DrawContext
{
public GameTime GameTime;
public SpriteBatch SpriteBatch;
public ViewTransform ViewTransoform;
}

View File

@@ -12,11 +12,11 @@ internal abstract class GameLoopBase(AlienAttackGame game) : IGameLoop
protected readonly SpriteBatch SpriteBatch = game.SpriteBatch;
protected readonly ViewTransform ViewTransform = game.ViewTransform;
public void Draw()
public void Draw(GameTime gameTime)
{
SpriteBatch.Begin(transformMatrix: ViewTransform.ViewMatrix);
OnDraw();
OnDraw(gameTime);
SpriteBatch.End();
}
@@ -26,6 +26,6 @@ internal abstract class GameLoopBase(AlienAttackGame game) : IGameLoop
OnUpdate(gameTime);
}
public abstract void OnDraw();
public abstract void OnDraw(GameTime gameTime);
public abstract void OnUpdate(GameTime gameTime);
}

View File

@@ -4,6 +4,6 @@ namespace AlienAttack.MonoGame.GameLoops;
internal interface IGameLoop
{
void Draw();
void Draw(GameTime gameTime);
void Update(GameTime gameTime);
}

View File

@@ -0,0 +1,199 @@
using AlienAttack.MonoGame.GameLoops;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace AlienAttack.MonoGame.Hud;
public record DrawHudContext : DrawContext
{
public int Health;
public int Shield;
}
internal class Hud
{
private Texture2D _pixel;
public Hud(Game game)
{
_pixel = new Texture2D(game.GraphicsDevice, 1, 1);
_pixel.SetData(new[] { Color.White });
}
public void Draw(DrawHudContext context)
{
DrawSideBars(context);
}
private void DrawSideBars(DrawHudContext context)
{
SpriteBatch sb = context.SpriteBatch;
int screenW = context.ViewTransoform.ScreenWidth;
int screenH = context.ViewTransoform.ScreenHeight;
double totalSeconds = context.GameTime.TotalGameTime.TotalSeconds;
int health = Math.Clamp(context.Health, 0, 100);
int shield = Math.Clamp(context.Shield, 0, 100);
int marginTop = 10;
int marginBottom = 10;
int segments = 100;
int gap = 4;
int barWidth = 10; // thickness of the colored segments
int borderPad = 2; // thickness around the bar (border area)
int borderW = barWidth + borderPad * 2;
int barHeight = screenH - marginTop - marginBottom;
int segHeight = Math.Max(1, (barHeight - gap * (segments - 1)) / segments);
int usedHeight = segments * segHeight + (segments - 1) * gap;
int barTopY = marginTop;
int barBottomY = marginTop + usedHeight;
// Put left / right bars near screen edges
int leftBorderX = 8;
int rightBorderX = screenW - 8 - borderW;
// Colors (tweak as desired)
Color healthOn = new Color(255, 140, 0); // orange
Color shieldOn = new Color(60, 140, 255); // blue
Color off = new Color(28, 28, 28); // empty segment base
// Draw metallic frames (behind segments)
//DrawMetallicFrame(sb, new Rectangle(leftBorderX, barTopY - borderPad, borderW, usedHeight + borderPad * 2));
//DrawMetallicFrame(sb, new Rectangle(rightBorderX, barTopY - borderPad, borderW, usedHeight + borderPad * 2));
// Segment drawing X starts inside the border
int leftSegX = leftBorderX + borderPad;
int rightSegX = rightBorderX + borderPad;
// Low health pulse settings
bool lowHealth = health <= 20;
float pulse = lowHealth
? (0.85f + 0.15f * (float)Math.Sin(totalSeconds * 6.0)) // subtle wobble
: 1f;
DrawSegmentedBar(
sb, leftSegX, barBottomY, barWidth, segHeight, gap,
value0To100: health,
onColor: healthOn,
offColor: off,
segments: segments,
// pulse only on filled health segments
pulseMultiplier: pulse,
pulseFilledOnly: true
);
DrawSegmentedBar(
sb, rightSegX, barBottomY, barWidth, segHeight, gap,
value0To100: shield,
onColor: shieldOn,
offColor: off,
segments: segments,
// no pulse for shield by default
pulseMultiplier: 1f,
pulseFilledOnly: true
);
}
private void DrawSegmentedBar(
SpriteBatch sb,
int x,
int bottomY,
int barWidth,
int segHeight,
int gap,
int value0To100,
Color onColor,
Color offColor,
int segments,
float pulseMultiplier,
bool pulseFilledOnly)
{
// Gradient: bottom darker, top brighter
float minBrightness = 0.55f;
float maxBrightness = 1.00f;
for (int i = 0; i < segments; i++)
{
int y = bottomY - (i + 1) * segHeight - i * gap;
// i=0 bottom, i=segments-1 top
float t = i / (float)(segments - 1);
float brightness = MathHelper.Lerp(minBrightness, maxBrightness, t);
bool filled = i < value0To100;
Color baseColor = filled ? onColor : offColor;
// Apply pulse (usually only on the filled parts)
if (pulseMultiplier != 1f && (!pulseFilledOnly || filled))
{
brightness *= pulseMultiplier;
brightness = Math.Min(brightness, 1.2f); // allow a tiny “pop”, still safe
}
Color finalColor = ScaleRgb(baseColor, brightness);
sb.Draw(_pixel, new Rectangle(x, y, barWidth, segHeight), finalColor);
// Optional: little “shine” line on filled segments (adds a nice DOS UI feel)
// Draw a 1px highlight on the left edge of lit segments.
if (filled && barWidth >= 3)
{
var shine = ScaleRgb(Color.White, 0.10f * brightness);
sb.Draw(_pixel, new Rectangle(x, y, 1, segHeight), shine);
}
}
}
private static Color ScaleRgb(Color c, float mul)
{
mul = Math.Max(0f, mul);
byte r = (byte)Math.Clamp((int)(c.R * mul), 0, 255);
byte g = (byte)Math.Clamp((int)(c.G * mul), 0, 255);
byte b = (byte)Math.Clamp((int)(c.B * mul), 0, 255);
return new Color(r, g, b, c.A);
}
private void DrawMetallicFrame(SpriteBatch sb, Rectangle outer)
{
// “Metal” palette
Color outerBorder = new Color(10, 10, 10);
Color fill = new Color(45, 45, 45);
Color highlight = new Color(90, 90, 90); // top/left
Color shadow = new Color(20, 20, 20); // bottom/right
// Outer border
sb.Draw(_pixel, outer, outerBorder);
// Inner fill
var inner = new Rectangle(outer.X + 1, outer.Y + 1, outer.Width - 2, outer.Height - 2);
if (inner.Width > 0 && inner.Height > 0)
sb.Draw(_pixel, inner, fill);
// Bevel lines (1px)
// Top highlight
sb.Draw(_pixel, new Rectangle(inner.X, inner.Y, inner.Width, 1), highlight);
// Left highlight
sb.Draw(_pixel, new Rectangle(inner.X, inner.Y, 1, inner.Height), highlight);
// Bottom shadow
sb.Draw(_pixel, new Rectangle(inner.X, inner.Bottom - 1, inner.Width, 1), shadow);
// Right shadow
sb.Draw(_pixel, new Rectangle(inner.Right - 1, inner.Y, 1, inner.Height), shadow);
// Optional: an even tighter inner “lip” for extra metal feel
var lip = new Rectangle(inner.X + 1, inner.Y + 1, inner.Width - 2, inner.Height - 2);
if (lip.Width > 0 && lip.Height > 0)
{
Color lipDark = new Color(35, 35, 35);
sb.Draw(_pixel, lip, lipDark);
}
}
}

View File

@@ -2,10 +2,12 @@
using AlienAttack.MonoGame.Things.Items;
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 static System.Runtime.InteropServices.JavaScript.JSType;
namespace AlienAttack.MonoGame.Things.Enemies;
@@ -43,7 +45,7 @@ internal class GreenEnemy : EnemyShip
if (Health <= 0)
{
IsDead = true;
context.SpawnSprite(new Explosion((int)XPosition, (int)YPosition, XVelocity, YVelocity));
SpawnExplosion(context);
switch (context.Random.Next(0, 5))
{
@@ -94,6 +96,16 @@ internal class GreenEnemy : EnemyShip
base.Update(context);
}
private void SpawnExplosion(SpriteUpdateContext context)
{
context.SpawnSprite(new Explosion((int)XPosition, (int)YPosition, XVelocity, YVelocity));
int number = context.Random.Next(1, 7);
SoundEffect soundEffect = context.Content.Load<SoundEffect>(@$"Sfx\Explosions\EXPLDsgn_Explosion Impact_0{number}_SFRMS_SCIWPNS");
soundEffect.Play(0.95f, (float)(context.Random.NextDouble() * 0.1 - 0.05), 0);
}
private void CheckFire(SpriteUpdateContext context)
{
//foreach (IWeapon weapon in ActiveWeapons)

View File

@@ -1,5 +1,6 @@
using AlienAttack.MonoGame.Things.Bullets;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
namespace AlienAttack.MonoGame.Things.Enemies;
@@ -53,7 +54,7 @@ internal class RedEnemy : EnemyShip
if (Health <= 0)
{
IsDead = true;
context.SpawnSprite(new Explosion((int)XPosition, (int)YPosition, XVelocity, YVelocity));
SpawnExplosion(context);
return;
}
@@ -68,6 +69,16 @@ internal class RedEnemy : EnemyShip
base.Update(context);
}
private void SpawnExplosion(SpriteUpdateContext context)
{
context.SpawnSprite(new Explosion((int)XPosition, (int)YPosition, XVelocity, YVelocity));
int number = context.Random.Next(1, 7);
SoundEffect soundEffect = context.Content.Load<SoundEffect>(@$"Sfx\Explosions\EXPLDsgn_Explosion Impact_0{number}_SFRMS_SCIWPNS");
soundEffect.Play(0.95f, (float)(context.Random.NextDouble() * 0.1 - 0.05), 0);
}
private void TryFire(SpriteUpdateContext context)
{
if (CurrentFireThreshold > 0)

View File

@@ -1,5 +1,6 @@
using AlienAttack.MonoGame.Things.Bullets;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
namespace AlienAttack.MonoGame.Things.Enemies;
@@ -45,7 +46,7 @@ internal class TealEnemy : EnemyShip
if (Health <= 0)
{
IsDead = true;
context.SpawnSprite(new Explosion((int)XPosition, (int)YPosition, XVelocity, YVelocity));
SpawnExplosion(context);
return;
}
@@ -61,6 +62,16 @@ internal class TealEnemy : EnemyShip
base.Update(context);
}
private void SpawnExplosion(SpriteUpdateContext context)
{
context.SpawnSprite(new Explosion((int)XPosition, (int)YPosition, XVelocity, YVelocity));
int number = context.Random.Next(1, 7);
SoundEffect soundEffect = context.Content.Load<SoundEffect>(@$"Sfx\Explosions\EXPLDsgn_Explosion Impact_0{number}_SFRMS_SCIWPNS");
soundEffect.Play(0.95f, (float)(context.Random.NextDouble() * 0.1 - 0.05), 0);
}
private void TryFire(SpriteUpdateContext context)
{
if (CurrentFireThreshold > 0)

View File

@@ -3,6 +3,7 @@ using AlienAttack.MonoGame.Things.Enemies;
using AlienAttack.MonoGame.Things.Items;
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;
@@ -331,6 +332,13 @@ internal class Player : MoveableSprite
if (context.Sprite is Shields)
{
//SCIEnrg_Shield Activate_01_SFRMS_SCIWPNS
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);
GiveShield(20);
}

View File

@@ -1,5 +1,6 @@
using AlienAttack.MonoGame.View;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using System;
namespace AlienAttack.MonoGame.Things;
@@ -10,6 +11,7 @@ public class SpriteUpdateContext(AlienAttackGame game)
public required Action<Sprite> SpawnSprite { get; init; }
public required Random Random { get; init; }
public required GameTime GameTime { get; init; }
public required ContentManager Content { get; init; }
}
public class SpriteCollisionContext(AlienAttackGame game)
@@ -17,4 +19,6 @@ public class SpriteCollisionContext(AlienAttackGame game)
public ViewTransform ViewTransform => game.ViewTransform;
public required Sprite Sprite { get; init; }
public required Action<Sprite> SpawnSprite { get; init; }
public required Random Random { get; init; }
public required ContentManager Content { get; init; }
}

View File

@@ -1,4 +1,5 @@
using AlienAttack.MonoGame.Things.Bullets;
using Microsoft.Xna.Framework.Audio;
namespace AlienAttack.MonoGame.Things.Weapons;
@@ -20,6 +21,11 @@ public class Minigun : Weapon
// Queue the bullets for spawning
context.SpawnSprite(bullet1);
context.SpawnSprite(bullet2);
int number = context.Random.Next(1, 7);
SoundEffect soundEffect = context.Content.Load<SoundEffect>(@$"Sfx\GUNAuto_Assault Rifle A Fire_0{number}_SFRMS_SCIWPNS");
soundEffect.Play(0.25f, (float)(context.Random.NextDouble() * 0.1 - 0.05), 0);
}
}