Added sound effects and music.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
AlientAttack.MonoGame/Content/Music/Untitled.mp3
Normal file
BIN
AlientAttack.MonoGame/Content/Music/Untitled.mp3
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -4,6 +4,6 @@ namespace AlienAttack.MonoGame.GameLoops;
|
||||
|
||||
internal interface IGameLoop
|
||||
{
|
||||
void Draw();
|
||||
void Draw(GameTime gameTime);
|
||||
void Update(GameTime gameTime);
|
||||
}
|
||||
199
AlientAttack.MonoGame/Hud/Hud.cs
Normal file
199
AlientAttack.MonoGame/Hud/Hud.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user