285 lines
9.2 KiB
C#
285 lines
9.2 KiB
C#
using AlienAttack.MonoGame.Things;
|
|
using AlienAttack.MonoGame.Things.Enemies;
|
|
using AlienAttack.MonoGame.Things.Stars;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace AlienAttack.MonoGame.GameLoops;
|
|
|
|
internal class GameLoop : GameLoopBase
|
|
{
|
|
private readonly List<Sprite> _spritesToAdd = [];
|
|
private readonly Random _random = new();
|
|
|
|
private int _spawnNewEnemyThreshold = 100;
|
|
private float _backgroundYOffset = 0;
|
|
|
|
private Starfield _starfield;
|
|
private Texture2D _pixel;
|
|
private Player _player;
|
|
|
|
protected readonly List<Sprite> Sprites = [];
|
|
|
|
public GameLoop(AlienAttackGame game) : base(game)
|
|
{
|
|
InitializeStarField(game);
|
|
|
|
_player = new Player(game.ViewTransform.ScreenWidth / 2 - 32, game.ViewTransform.ScreenHeight - 64);
|
|
|
|
Sprites.Add(_player);
|
|
}
|
|
|
|
private void InitializeStarField(AlienAttackGame game)
|
|
{
|
|
_starfield = new Starfield(seed: 42);
|
|
|
|
//// Far layer: many tiny, slow, faint
|
|
//_starfield.AddLayer(count: 450, speedPxPerSec: 20f, minSize: 1f, maxSize: 1.5f, minAlpha: 0.15f, maxAlpha: 0.35f);
|
|
|
|
//// Mid layer
|
|
//_starfield.AddLayer(count: 220, speedPxPerSec: 45f, minSize: 1.5f, maxSize: 2.2f, minAlpha: 0.25f, maxAlpha: 0.60f);
|
|
|
|
//// Near layer: fewer, bigger, faster, brighter
|
|
//_starfield.AddLayer(count: 90, speedPxPerSec: 85f, minSize: 2.0f, maxSize: 3.2f, minAlpha: 0.40f, maxAlpha: 0.90f);
|
|
|
|
_starfield.AddLayer(count: 450, velocityPxPerSec: new Vector2(+4f, 20f), minSize: 1f, maxSize: 1.5f, minAlpha: 0.15f, maxAlpha: 0.35f);
|
|
_starfield.AddLayer(count: 220, velocityPxPerSec: new Vector2(-10f, 45f), minSize: 1.5f, maxSize: 2.2f, minAlpha: 0.25f, maxAlpha: 0.60f);
|
|
_starfield.AddLayer(count: 90, velocityPxPerSec: new Vector2(+20f, 85f), minSize: 2.0f, maxSize: 3.2f, minAlpha: 0.40f, maxAlpha: 0.90f);
|
|
|
|
|
|
_pixel = new Texture2D(game.GraphicsDevice, 1, 1);
|
|
_pixel.SetData(new[] { Color.White });
|
|
|
|
_starfield.Initialize(ViewTransform.ScreenWidth, ViewTransform.ScreenHeight);
|
|
}
|
|
|
|
public override void OnDraw()
|
|
{
|
|
_starfield.Draw(SpriteBatch, _pixel);
|
|
//DrawBackground();
|
|
DrawHud();
|
|
DrawSprites();
|
|
}
|
|
|
|
private void DrawBackground()
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
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 segments = 100;
|
|
|
|
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 bottomY = marginTop + usedHeight; // bottom of the bar area
|
|
|
|
// 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
|
|
|
|
DrawSegmentedBar(sb, leftX, bottomY, barWidth, segHeight, gap, health, healthOn, off, segments);
|
|
DrawSegmentedBar(sb, rightX, bottomY, barWidth, segHeight, gap, shield, shieldOn, off, segments);
|
|
}
|
|
|
|
private void DrawSegmentedBar(
|
|
SpriteBatch sb,
|
|
int x,
|
|
int bottomY,
|
|
int barWidth,
|
|
int segHeight,
|
|
int gap,
|
|
int value0To100,
|
|
Color onColor,
|
|
Color offColor,
|
|
int segments = 100)
|
|
{
|
|
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);
|
|
|
|
Color baseColor = (i < value0To100) ? onColor : offColor;
|
|
|
|
// Apply brightness scaling
|
|
Color finalColor = new Color(
|
|
(byte)(baseColor.R * brightness),
|
|
(byte)(baseColor.G * brightness),
|
|
(byte)(baseColor.B * brightness),
|
|
baseColor.A
|
|
);
|
|
|
|
sb.Draw(_pixel, new Rectangle(x, y, barWidth, segHeight), finalColor);
|
|
}
|
|
}
|
|
|
|
private void DrawSprites()
|
|
{
|
|
SpriteDrawArgs args = new(Game);
|
|
|
|
foreach (Sprite sprite in Sprites)
|
|
{
|
|
sprite.Draw(args);
|
|
}
|
|
}
|
|
|
|
public override void OnUpdate(GameTime gameTime)
|
|
{
|
|
_starfield.Update(gameTime);
|
|
UpdateSprites(gameTime);
|
|
CheckSpriteCollisions();
|
|
ClearDeadSprites();
|
|
AddEnemySprites();
|
|
AddNewSprites();
|
|
}
|
|
|
|
private void UpdateSprites(GameTime gameTime)
|
|
{
|
|
SpriteUpdateContext args = new(Game)
|
|
{
|
|
Random = _random,
|
|
GameTime = gameTime,
|
|
SpawnSprite = _spritesToAdd.Add
|
|
};
|
|
|
|
foreach (Sprite sprite in Sprites)
|
|
{
|
|
sprite.Update(args);
|
|
}
|
|
}
|
|
|
|
private void CheckSpriteCollisions()
|
|
{
|
|
var collisionSprites = Sprites.Where(x => x.CanCollide).ToList();
|
|
|
|
foreach (var sprite in collisionSprites)
|
|
{
|
|
foreach (var otherSprite in collisionSprites.Where(x => x != sprite))
|
|
{
|
|
SpriteCollisionContext context = new(Game)
|
|
{
|
|
Sprite = otherSprite,
|
|
SpawnSprite = _spritesToAdd.Add
|
|
};
|
|
|
|
// TODO: If perfomed once; do not perform again
|
|
if (sprite.Intersects(otherSprite))
|
|
{
|
|
sprite.OnCollision(context);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ClearDeadSprites()
|
|
{
|
|
var deadSprites = Sprites.Where(x => x.IsDead).ToList();
|
|
|
|
foreach (var sprite in deadSprites)
|
|
{
|
|
Sprites.Remove(sprite);
|
|
}
|
|
}
|
|
|
|
private void AddNewSprites()
|
|
{
|
|
foreach (Sprite sprite in _spritesToAdd)
|
|
{
|
|
Sprites.Add(sprite);
|
|
}
|
|
|
|
_spritesToAdd.Clear();
|
|
}
|
|
|
|
private void AddEnemySprites()
|
|
{
|
|
if (_spawnNewEnemyThreshold > 0)
|
|
{
|
|
_spawnNewEnemyThreshold--;
|
|
}
|
|
|
|
if (_spawnNewEnemyThreshold == 0)
|
|
{
|
|
int randomNumber = _random.Next(0, 100);
|
|
|
|
if (randomNumber == 0)
|
|
{
|
|
GreenEnemy enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64);
|
|
Sprites.Add(enemy);
|
|
_spawnNewEnemyThreshold = 100 + _random.Next(0, 100);
|
|
}
|
|
else if (randomNumber == 1)
|
|
{
|
|
RedEnemy enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64);
|
|
Sprites.Add(enemy);
|
|
_spawnNewEnemyThreshold = 100 + _random.Next(0, 100);
|
|
}
|
|
else if (randomNumber == 2)
|
|
{
|
|
TealEnemy enemy = new(_random.Next(0, ViewTransform.ScreenWidth - 64), -64);
|
|
Sprites.Add(enemy);
|
|
_spawnNewEnemyThreshold = 100 + _random.Next(0, 100);
|
|
}
|
|
}
|
|
}
|
|
} |