From 01b3ce436021ea238aab7b4d9876996f560126ca Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 9 Jun 2024 14:38:32 +0300 Subject: [PATCH] Add project files. --- Hook Climber.csproj | 26 +++ Hook Climber.sln | 25 +++ Program.cs | 463 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 514 insertions(+) create mode 100644 Hook Climber.csproj create mode 100644 Hook Climber.sln create mode 100644 Program.cs diff --git a/Hook Climber.csproj b/Hook Climber.csproj new file mode 100644 index 0000000..3467498 --- /dev/null +++ b/Hook Climber.csproj @@ -0,0 +1,26 @@ + + + + Exe + net6.0 + Hook_Climber + enable + enable + true + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/Hook Climber.sln b/Hook Climber.sln new file mode 100644 index 0000000..185dee4 --- /dev/null +++ b/Hook Climber.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34221.43 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hook Climber", "Hook Climber.csproj", "{47ACEAFD-7819-4DAB-BAA2-7B98C7132276}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {47ACEAFD-7819-4DAB-BAA2-7B98C7132276}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47ACEAFD-7819-4DAB-BAA2-7B98C7132276}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47ACEAFD-7819-4DAB-BAA2-7B98C7132276}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47ACEAFD-7819-4DAB-BAA2-7B98C7132276}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B80AB0B0-39BA-44FF-A87E-F21DF586CC1C} + EndGlobalSection +EndGlobal diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..f0c9dec --- /dev/null +++ b/Program.cs @@ -0,0 +1,463 @@ +using static Raylib_cs.Raylib; +using Raylib_cs; +using static Raylib_cs.Raymath; +using System.Numerics; +using TiledCS; + +namespace Hook_Climber; + +static class Utils +{ + public static Rectangle centeredRect(Vector2 center, Vector2 size) + { + return new Rectangle(center - size / 2, size); + } +} + +class Game { + public static float playerMaxSpeed = 650.0f; + public static float playerWalkForce = 4500.0f; + public static float playerFriction = 0.999f; + public static float playerGravity = 2000.0f; + + public static float hookDistance = 75f; + public static float hookPower = 300f; + public static float hookGravity = 600.0f; + public static float hookOffset = 50f; + public static float hookRopeStrength = 30f; + + + public static Vector2 hookSize = new Vector2(10, 10); + public static Vector2 playerSize = new Vector2(50, 50); + + public Player player; + public List platforms; + + public static Rectangle GetSweptBroadphaseBox(Rectangle b, Vector2 v) + { + Rectangle broadphasebox = b; + + broadphasebox.X = v.X > 0 ? b.X : b.X + v.X; + broadphasebox.Y = v.Y > 0 ? b.Y : b.Y + v.Y; + broadphasebox.Width = v.X > 0 ? v.X + b.Width : b.Width - v.X; + broadphasebox.Height = v.Y > 0 ? v.Y + b.Height : b.Height - v.Y; + + return broadphasebox; + } + + + public IEnumerable<(Rectangle, float, Vector2)> IterCollisions(Rectangle collider, Vector2 velocity, float dt) + { + foreach (var platform in platforms) + { + Vector2 normal; + float collisiontime = CheckCollision( + collider, + velocity * dt, + platform, + out normal + ); + if (normal.X == 0 && normal.Y == 0) continue; + + yield return (platform, collisiontime * dt, normal); + } + } + + public static float CheckCollision(Rectangle b1, Vector2 v1, Rectangle b2, out Vector2 normal) + { + normal.X = 0; + normal.Y = 0; + if (!CheckCollisionRecs(GetSweptBroadphaseBox(b1, v1), b2)) + { + return 1; + } + + float entryX, exitX; + if (v1.X > 0) + { + entryX = b2.X - (b1.X + b1.Width); + exitX = (b2.X + b2.Width) - b1.X; + } + else + { + entryX = (b2.X + b2.Width) - b1.X; + exitX = b2.X - (b1.X + b1.Width); + } + + float entryY, exitY; + if (v1.Y > 0) + { + entryY = b2.Y - (b1.Y + b1.Height); + exitY = (b2.Y + b2.Height) - b1.Y; + } + else + { + entryY = (b2.Y + b2.Height) - b1.Y; + exitY = b2.Y - (b1.Y + b1.Height); + } + + float entryXTime, exitXTime; + if (v1.X == 0) + { + entryXTime = float.MinValue; + exitXTime = float.MaxValue; + } + else + { + entryXTime = entryX / v1.X; + exitXTime = exitX / v1.X; + } + + float entryYTime, exitYTime; + if (v1.Y == 0) + { + entryYTime = float.MinValue; + exitYTime = float.MaxValue; + } + else + { + entryYTime = entryY / v1.Y; + exitYTime = exitY / v1.Y; + } + + float entryTime = Math.Max(entryXTime, entryYTime); + float exitTime = Math.Min(exitXTime, exitYTime); + + if (entryTime > exitTime || entryXTime < 0 && entryYTime < 0 || entryXTime > 1.0f || entryYTime > 1.0f) + { + return 1; + } + + if (entryXTime > entryYTime) + { + normal.X = entryX < 0.0 ? 1 : -1; + } + else + { + normal.Y = entryY < 0.0 ? 1 : -1; + } + + return entryTime; + } +} + +class Hook +{ + public Game game; + + public Vector2 offset; + public Vector2 dir; + + public Vector2 primedAt; + public bool shown; + public bool primed; + + public bool headReleased; + public Vector2 headPosition; + public Vector2 headVelocity; + public bool headGrounded; + public float reelDir = 0; + public float idealRopeLength = 200f; + + public GamepadAxis moveX; + public GamepadAxis moveY; + public GamepadButton use; + public GamepadButton prime; + + public void resetHead() + { + headGrounded = false; + headReleased = false; + } + + public List headProjection(uint steps, float dt) + { + var points = new List(); + + var position = headPosition; + var velocity = headVelocity; + for (int i = 0; i < steps; i++) + { + if (updatePhysics(ref position, ref velocity, dt)) break; + points.Add(position); + } + + return points; + } + + public bool updatePhysics(ref Vector2 position, ref Vector2 velocity, float dt) + { + velocity.Y += Game.hookGravity * dt; + bool collisionOccured = checkAndResolveCollisions(Utils.centeredRect(position, Game.hookSize), ref velocity, dt); + position += velocity * dt; + + return collisionOccured; + } + + public bool checkAndResolveCollisions(Rectangle collider, ref Vector2 velocity, float dt) + { + bool collision = false; + + foreach (var (_, collisiontime, normal) in game.IterCollisions(collider, velocity, dt)) + { + velocity.X *= 0; + velocity.Y *= 0; + + //velocity.X = velocity.X * collisiontime / dt; + //velocity.Y = velocity.Y * collisiontime / dt; + collision = true; + } + + return collision; + } + +}; + +class Player +{ + public Vector2 position = new Vector2(0, 0); + public Vector2 velocity = new Vector2(0, 0); + + public Rectangle collisionRect() + { + return new Rectangle(position - Game.playerSize / 2, Game.playerSize.X, Game.playerSize.Y); + } +}; + +internal class Program +{ + public static void Main() + { + InitWindow(2000, 1200, "Hook Climber"); + SetWindowState(ConfigFlags.ResizableWindow); + + var map = new TiledMap("./main.tmx"); + var tilesets = map.GetTiledTilesets("./"); + + var game = new Game(); + game.player = new Player(); + + var leftHook = new Hook + { + game = game, + offset = new Vector2(-Game.hookOffset, 0), + moveX = GamepadAxis.LeftX, + moveY = GamepadAxis.LeftY, + use = GamepadButton.LeftTrigger2, + prime = GamepadButton.LeftTrigger1, + idealRopeLength = 200 + }; + + var rightHook = new Hook + { + game = game, + offset = new Vector2(Game.hookOffset, 0), + moveX = GamepadAxis.RightX, + moveY = GamepadAxis.RightY, + use = GamepadButton.RightTrigger2, + prime = GamepadButton.RightTrigger1, + idealRopeLength = 200 + }; + + var hooks = new Hook[]{ leftHook, rightHook }; + + var gamepadId = 0; + + game.platforms = new List { + new Rectangle(-450, game.player.position.Y + Game.playerSize.Y/2, 1000, 20), + new Rectangle(0, -200, 100, 10), + new Rectangle(-100, -400, 100, 10), + new Rectangle(100, -600, 100, 10), + new Rectangle(-200, -800, 100, 10), + new Rectangle(-300, -1000, 100, 10), + }; + + var player = game.player; + + var camera = new Camera2D(); + camera.Target = player.position; + + while (!WindowShouldClose()) + { + var dt = GetFrameTime(); + var windowWidth = GetScreenWidth(); + var windowHeight = GetScreenHeight(); + + camera.Offset = new Vector2(windowWidth / 2, windowHeight / 2); + camera.Target = Vector2.Lerp(camera.Target, player.position, 20 * dt); + camera.Zoom = 1; + + BeginDrawing(); + ClearBackground(Color.White); + BeginMode2D(camera); + + var dx = 0f; + if (IsKeyDown(KeyboardKey.D)) + { + dx += 1; + } + if (IsKeyDown(KeyboardKey.A)) + { + dx -= 1; + } + + if (IsGamepadAvailable(gamepadId)) + { + foreach (var hook in hooks) + { + if (IsGamepadButtonPressed(gamepadId, hook.prime)) + { + hook.primedAt = hook.dir; + } + if (IsGamepadButtonDown(gamepadId, hook.prime)) + { + var trajectory = hook.primedAt - hook.dir; + + hook.headPosition = player.position + hook.primedAt * Game.hookDistance + hook.offset; + hook.headVelocity = trajectory * Game.hookPower + player.velocity; + } + + if (IsGamepadButtonReleased(gamepadId, hook.prime)) + { + if (hook.headReleased && (hook.primedAt - hook.dir).Length() < 0.01) + { + hook.resetHead(); + } + else if (hook.primed && hook.shown) + { + hook.headReleased = true; + } + } + + hook.shown = IsGamepadButtonDown(gamepadId, hook.use); + hook.primed = IsGamepadButtonDown(gamepadId, hook.prime); + + if (!hook.headReleased) + { + hook.dir.X = GetGamepadAxisMovement(gamepadId, hook.moveX); + hook.dir.Y = GetGamepadAxisMovement(gamepadId, hook.moveY); + } else + { + hook.dir = new Vector2(); + + if (IsGamepadButtonDown(gamepadId, hook.use)) + { + hook.reelDir = GetGamepadAxisMovement(gamepadId, hook.moveY); + } else + { + hook.reelDir = 0; + } + } + } + + dx += GetGamepadAxisMovement(gamepadId, GamepadAxis.LeftX); + } + + dx = Clamp(dx, -1, 1); + + if (leftHook.shown) + { + dx = 0; + } + + player.velocity.X += dx * Game.playerWalkForce * dt; + player.velocity.Y += Game.playerGravity * dt; + player.velocity = Vector2ClampValue(player.velocity, 0, Game.playerMaxSpeed); + player.velocity *= (float)Math.Pow(1 - Game.playerFriction, dt); + + foreach (var (_, collisiontime, normal) in game.IterCollisions(player.collisionRect(), player.velocity, dt)) + { + float remainingtime = (1.0f - collisiontime); + float dotprod = (player.velocity.X * normal.Y + player.velocity.Y * normal.X) * remainingtime; + // player.velocity.X = dotprod * normal.Y; + player.velocity.Y = dotprod * normal.X; + } + + player.position += player.velocity * dt; + + DrawCircle(0, 0, 5, Color.Red); + DrawRectangleRec(player.collisionRect(), Color.Black); + + foreach (var hook in hooks) + { + + var handPosition = player.position + hook.dir * Game.hookDistance + hook.offset; + + if (hook.headReleased) + { + + if (!hook.headGrounded) + { + hook.headGrounded = hook.updatePhysics(ref hook.headPosition, ref hook.headVelocity, dt); + + if (hook.headGrounded) + { + hook.idealRopeLength = (handPosition - hook.headPosition).Length(); + } + } + + if (hook.headGrounded) + { + hook.idealRopeLength += hook.reelDir * dt * 150; + hook.idealRopeLength = Math.Max(hook.idealRopeLength, 0f); + + var handToHead = hook.headPosition - handPosition; + player.velocity += (handToHead.Length() - hook.idealRopeLength) * Vector2Normalize(handToHead) * Game.hookRopeStrength * dt; + } + + DrawRectangleRec(Utils.centeredRect(hook.headPosition, Game.hookSize), Color.DarkBlue); + DrawLineV(handPosition, hook.headPosition, Color.Blue); + } + + if (!hook.shown && !hook.headReleased) continue; + + if (hook.primed) + { + var primePosition = player.position + hook.primedAt * Game.hookDistance + hook.offset; + DrawRectangleRec(Utils.centeredRect(primePosition, Game.hookSize), Color.DarkGray); + DrawLineV(handPosition, primePosition, Color.Gray); + + var projection = hook.headProjection(100, 1f / 60f); + if (projection.Count > 0) + { + var projectionArray = projection.ToArray(); + unsafe + { + fixed (Vector2* pointerToFirst = &projectionArray[0]) + { + DrawLineStrip(pointerToFirst, projectionArray.Length, Color.Blue); + } + } + } + } + + DrawRectangleRec(Utils.centeredRect(handPosition, Game.hookSize), Color.Blue); + } + + foreach (var platform in game.platforms) + { + DrawRectangleRec(platform, Color.Gray); + } + + EndMode2D(); + + { // Speed bar + var speedWidth = 300.0f; + DrawText("Speed", 10, 10, 20, Color.Black); + DrawRectangleRec(new Rectangle(75, 10, speedWidth, 20), Color.Gray); + DrawRectangleRec(new Rectangle(75, 10, speedWidth * (player.velocity.Length() / Game.playerMaxSpeed), 20), Color.Red); + DrawText($"{player.velocity.X:N3}", 400, 10, 20, Color.Black); + DrawText($"{player.velocity.Y:N3}", 500, 10, 20, Color.Black); + } + + { // Rope lengths + DrawText($"Left ideal rope length: {leftHook.idealRopeLength}", 10, 30, 20, Color.Black); + DrawText($"Right ideal rope length: {rightHook.idealRopeLength}", 10, 50, 20, Color.Black); + } + + EndDrawing(); + } + + CloseWindow(); + } +} \ No newline at end of file