Add project files.
This commit is contained in:
parent
fdb819f7dd
commit
01b3ce4360
26
Hook Climber.csproj
Normal file
26
Hook Climber.csproj
Normal file
@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Hook_Climber</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Raylib-cs" Version="6.0.0" />
|
||||
<PackageReference Include="TiledCS" Version="3.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="main.tmx">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="main.tsx">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
25
Hook Climber.sln
Normal file
25
Hook Climber.sln
Normal file
@ -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
|
463
Program.cs
Normal file
463
Program.cs
Normal file
@ -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<Rectangle> 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<Vector2> headProjection(uint steps, float dt)
|
||||
{
|
||||
var points = new List<Vector2>();
|
||||
|
||||
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<Rectangle> {
|
||||
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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user