Files
Flappy-Bird/Program.cs
2025-12-09 08:45:40 +01:00

230 lines
7.9 KiB
C#

using SFML.Graphics;
using SFML.System;
using SFML.Window;
internal class Program
{
private static void Main()
{
// Game constants
uint gameWidth = 900;
uint gameHeight = 504;
float gravity = 800f; // px/sec^2
float jumpForce = -300f;
float scrollSpeedInitial = 200f;// px/sec
float scrollSpeed = scrollSpeedInitial; // px/sec
float scrollDecay = 500f; // px/sec^2
float maxUpAngle = -30f; // tilt up max
float maxDownAngle = 60f; // tilt down max
float maxVelocity = 500f; // velocity that corresponds to max tilt
float backgroundSpeed = 0.75f;
int points = 0;
// Pillars
List<Pillar> pillars = new List<Pillar>();
float spawnInterval = 2.0f; // seconds between pillar spawns
float pillarTimer = spawnInterval; // triggers first pillar immediately
float spacing = 200f; // horizontal distance between pillars
// Gap limits
float minGapHeight = 110f;
float maxGapHeight = 300f;
float minGapY = 100f; // min distance from top
// Game status
bool alive = true;
Random random = new Random();
// Create window
RenderWindow window = new RenderWindow(new VideoMode(gameWidth, gameHeight), "Flappy Bird Clone");
window.Closed += (s, e) => window.Close();
window.SetVerticalSyncEnabled(true);
// Setup view for scaling
View view = new View(new FloatRect(0, 0, gameWidth, gameHeight));
window.SetView(view);
window.Resized += (s, e) =>
{
float windowRatio = (float)e.Width / e.Height;
float gameRatio = (float)gameWidth / gameHeight;
FloatRect viewport;
if (windowRatio > gameRatio)
{
float width = gameRatio / windowRatio;
viewport = new FloatRect((1 - width) / 2f, 0, width, 1);
}
else
{
float height = windowRatio / gameRatio;
viewport = new FloatRect(0, (1 - height) / 2f, 1, height);
}
view.Viewport = viewport;
window.SetView(view);
};
// Load textures
Texture backgroundTexture = new Texture("background.png") { Repeated = true };
Texture birdTexture = new Texture("bird.png");
// Background rectangle
RectangleShape backgroundRect = new RectangleShape(new Vector2f(gameWidth, gameHeight));
backgroundRect.Texture = backgroundTexture;
long backgroundOffset = 0;
// Bird sprite
Sprite bird = new Sprite(birdTexture);
bird.Scale = new Vector2f(3f, 3f); // scale as desired
bird.Position = new Vector2f(100, gameHeight / 2f);
bird.Origin = new SFML.System.Vector2f(
bird.Texture.Size.X / 2f, // horizontal center
bird.Texture.Size.Y / 2f // vertical center
);
float birdVelocity = 0f;
float hitboxWidth = bird.Texture.Size.X * bird.Scale.X * 0.6f; // 60% of sprite width
float hitboxHeight = bird.Texture.Size.Y * bird.Scale.Y * 0.8f; // 80% of sprite height
float hitboxOffsetX = bird.Texture.Size.X * bird.Scale.X * 0.2f; // offset from left
float hitboxOffsetY = bird.Texture.Size.Y * bird.Scale.Y * 0.1f; // offset from top
// Pillar textures
Texture pillarUpTexture = new Texture("pillar-up.png");
Texture pillarDownTexture = new Texture("pillar-down.png");
RectangleShape hitboxRect = new RectangleShape(new Vector2f(hitboxWidth, hitboxHeight))
{
FillColor = new Color(255, 0, 0, 0) // red with transparency
};
// Clock for deltaTime
Clock clock = new Clock();
void Restart()
{
birdVelocity = 0;
pillars.Clear();
bird.Position = new Vector2f(100, gameHeight / 2f);
backgroundOffset = 0;
alive = true;
scrollSpeed = scrollSpeedInitial;
points = 0;
}
while (window.IsOpen)
{
float deltaTime = clock.Restart().AsSeconds();
window.DispatchEvents();
FloatRect birdHitbox = new FloatRect(
bird.Position.X - hitboxWidth / 2f,
bird.Position.Y - hitboxHeight / 2f,
hitboxWidth,
hitboxHeight
);
// Check lose condition
bool collision = false;
foreach (var pillar in pillars)
{
if (birdHitbox.Intersects(pillar.Top.GetGlobalBounds()) ||
birdHitbox.Intersects(pillar.Bottom.GetGlobalBounds()))
{
collision = true;
break;
}
}
hitboxRect.Position = new Vector2f(
bird.Position.X - hitboxWidth / 2f,
bird.Position.Y - hitboxHeight / 2f
);
if (collision && alive)
{
birdVelocity = 0;
alive = false;
}
// Input
if (alive && Keyboard.IsKeyPressed(Keyboard.Key.Space))
{
birdVelocity = jumpForce;
}
if (!alive && Keyboard.IsKeyPressed(Keyboard.Key.Space))
{
Restart();
}
// Update pillars
pillarTimer += deltaTime;
if (pillarTimer > spawnInterval)
{
pillarTimer = 0f;
// Randomize gap size
float gapHeight = minGapHeight + (float)random.NextDouble() * (maxGapHeight - minGapHeight);
float maxGapY = gameHeight - 100f - gapHeight; // adjust max Y for new gap
float gapY = minGapY + (float)random.NextDouble() * (maxGapY - minGapY);
// Add new pillar at the right edge of the screen
pillars.Add(new Pillar(pillarUpTexture, pillarDownTexture, gameWidth, gapY, gapHeight));
}
// Update existing pillars
for (int i = pillars.Count - 1; i >= 0; i--)
{
pillars[i].Update(deltaTime, scrollSpeed);
if (pillars[i].IsOffscreen(gameWidth))
pillars.RemoveAt(i);
}
// Physics
birdVelocity += gravity * deltaTime;
bird.Position += new Vector2f(0, birdVelocity * deltaTime);
// Prevent bird from falling below floor
float birdHeight = bird.Texture.Size.Y * bird.Scale.Y;
if (bird.Position.Y + birdHeight > gameHeight)
{
bird.Position = new Vector2f(bird.Position.X, gameHeight - birdHeight);
birdVelocity = 0;
}
if (!alive)
{
// Gradually reduce speed towards 0
scrollSpeed -= scrollDecay * deltaTime;
if (scrollSpeed < 0) scrollSpeed = 0;
}
// Scroll background
backgroundOffset += (long)(scrollSpeed * deltaTime * backgroundSpeed);
if (backgroundOffset > backgroundTexture.Size.X) backgroundOffset -= backgroundTexture.Size.X;
// Clear
window.Clear();
// Draw tiled scrolling background
backgroundRect.TextureRect = new IntRect((int)backgroundOffset, 0, (int)gameWidth, (int)gameHeight);
window.Draw(backgroundRect);
// Draw pillars
foreach (var pillar in pillars)
pillar.Draw(window);
// Draw bird
float targetRotation = MathF.Max(MathF.Min(birdVelocity / maxVelocity * maxDownAngle, maxDownAngle), maxUpAngle);
bird.Rotation += (targetRotation - bird.Rotation) * 0.1f; // smoothing factor
window.Draw(bird);
window.Draw(hitboxRect);
// Display
window.Display();
}
}
}