Added sound effects and music.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user