230 lines
7.9 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|
|
|