diff --git a/Pillar.cs b/Pillar.cs new file mode 100644 index 0000000..e632f70 --- /dev/null +++ b/Pillar.cs @@ -0,0 +1,39 @@ +using SFML.Graphics; + +class Pillar +{ + public Sprite Top; + public Sprite Bottom; + public float X; + + public Pillar(Texture topTex, Texture bottomTex, float startX, float gapY, float gapHeight) + { + X = startX; + + Top = new Sprite(topTex) { Scale = new SFML.System.Vector2f(2f, 2f) }; + Bottom = new Sprite(bottomTex) { Scale = new SFML.System.Vector2f(2f, 2f) }; + + Top.Position = new SFML.System.Vector2f(X, gapY - gapHeight / 2f - topTex.Size.Y * Top.Scale.Y); + Bottom.Position = new SFML.System.Vector2f(X, gapY + gapHeight / 2f); + } + + public void Update(float deltaTime, float speed) + { + X -= speed * deltaTime; + Top.Position = new SFML.System.Vector2f(X, Top.Position.Y); + Bottom.Position = new SFML.System.Vector2f(X, Bottom.Position.Y); + } + + public void Draw(RenderWindow window) + { + window.Draw(Top); + window.Draw(Bottom); + } + + public bool IsOffscreen(float screenWidth) + { + // Rightmost point of the pillar + float pillarRightEdge = X + Top.Texture.Size.X * Top.Scale.X; + return pillarRightEdge < 0; + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..a1db4c8 --- /dev/null +++ b/Program.cs @@ -0,0 +1,211 @@ +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; // px/sec + float scrollSpeed = 150f; // 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; + + // Pillars + List pillars = new List(); + 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.SetFramerateLimit(60); + + // 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, 100) // red with transparency + }; + + // Clock for deltaTime + Clock clock = new Clock(); + + 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; + } + + // 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(); + } + } +} diff --git a/SFML Flappy Bird.csproj b/SFML Flappy Bird.csproj new file mode 100644 index 0000000..d9bafad --- /dev/null +++ b/SFML Flappy Bird.csproj @@ -0,0 +1,30 @@ + + + + WinExe + net8.0 + SFML_Flappy_Bird + enable + enable + + + + + + + + + Always + + + Always + + + Always + + + Always + + + + diff --git a/SFML Flappy Bird.sln b/SFML Flappy Bird.sln new file mode 100644 index 0000000..e9f417f --- /dev/null +++ b/SFML Flappy Bird.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36414.22 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SFML Flappy Bird", "SFML Flappy Bird.csproj", "{5B9EC870-D392-4DA4-81B4-9AAB78D9F5DB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5B9EC870-D392-4DA4-81B4-9AAB78D9F5DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B9EC870-D392-4DA4-81B4-9AAB78D9F5DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B9EC870-D392-4DA4-81B4-9AAB78D9F5DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B9EC870-D392-4DA4-81B4-9AAB78D9F5DB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A0A9F4D4-D2BF-41DF-95F0-7BAC8195E11C} + EndGlobalSection +EndGlobal diff --git a/background.png b/background.png new file mode 100644 index 0000000..1f9af51 Binary files /dev/null and b/background.png differ diff --git a/bird.png b/bird.png new file mode 100644 index 0000000..61d6423 Binary files /dev/null and b/bird.png differ diff --git a/pillar-down.png b/pillar-down.png new file mode 100644 index 0000000..92a5f34 Binary files /dev/null and b/pillar-down.png differ diff --git a/pillar-up.png b/pillar-up.png new file mode 100644 index 0000000..54c5113 Binary files /dev/null and b/pillar-up.png differ