Projektdateien hinzufügen.
This commit is contained in:
14
HillLike.csproj
Normal file
14
HillLike.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ppy.osu.Framework" Version="2026.318.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
3
HillLike.slnx
Normal file
3
HillLike.slnx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="HillLike.csproj" />
|
||||||
|
</Solution>
|
||||||
90
HillLikeGame.cs
Normal file
90
HillLikeGame.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace HillLike;
|
||||||
|
|
||||||
|
public class HillLikeGame : Game
|
||||||
|
{
|
||||||
|
private World _world = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void Load(GameHost host)
|
||||||
|
{
|
||||||
|
Add(new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children =
|
||||||
|
[
|
||||||
|
_world = new World(),
|
||||||
|
new Hud(_world)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
_world.Step((float)(Clock.ElapsedFrameTime / 1000.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
_world.HandleKeyDown(e);
|
||||||
|
return base.OnKeyDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnKeyUp(KeyUpEvent e)
|
||||||
|
{
|
||||||
|
_world.HandleKeyUp(e);
|
||||||
|
base.OnKeyUp(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Hud : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Box _speedBar;
|
||||||
|
private readonly Box _speedBarBg;
|
||||||
|
private readonly World _world;
|
||||||
|
|
||||||
|
public Hud(World world)
|
||||||
|
{
|
||||||
|
this._world = world;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren =
|
||||||
|
[
|
||||||
|
_speedBarBg = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(20, 42),
|
||||||
|
Size = new Vector2(260, 10),
|
||||||
|
Colour = new Color4(30, 30, 30, 220)
|
||||||
|
},
|
||||||
|
_speedBar = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = new Vector2(20, 42),
|
||||||
|
Size = new Vector2(0, 10),
|
||||||
|
Colour = new Color4(120, 180, 255, 255)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
var speed = _world.Car.Velocity.X;
|
||||||
|
_speedBar.Width = 260 * Math.Clamp(Math.Abs(speed) / 25f, 0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Program.cs
Normal file
13
Program.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
|
namespace HillLike;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
using GameHost host = Host.GetSuitableDesktopHost("hill-like");
|
||||||
|
host.Run(new HillLikeGame());
|
||||||
|
}
|
||||||
|
}
|
||||||
269
SimpleCar.cs
Normal file
269
SimpleCar.cs
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace HillLike;
|
||||||
|
|
||||||
|
public class SimpleCar
|
||||||
|
{
|
||||||
|
public const float WheelRadius = 0.45f;
|
||||||
|
|
||||||
|
private const float Gravity = -10f;
|
||||||
|
private const float EngineForce = 34f;
|
||||||
|
private const float BrakeForce = 34f;
|
||||||
|
private const float RollingFriction = 2f;
|
||||||
|
private const float AirDrag = 0.1f;
|
||||||
|
|
||||||
|
private const float SuspensionRest = 0f;
|
||||||
|
private const float SuspensionK = 140f;
|
||||||
|
private const float SuspensionD = 18f;
|
||||||
|
|
||||||
|
private const float AngularDamp = 2f;
|
||||||
|
private const float ChassisInertia = 2.4f;
|
||||||
|
private const float SuspensionTorqueInfluence = 0.35f;
|
||||||
|
private const float GroundAlignStrength = 8f;
|
||||||
|
private const float GroundAlignDamping = 2f;
|
||||||
|
private const float WheelAngularAccel = 60f;
|
||||||
|
private const float WheelAngularDamping = 1.2f;
|
||||||
|
private const float WheelGroundGrip = 14f;
|
||||||
|
private const float AirControlFromWheelSpin = 0.12f;
|
||||||
|
|
||||||
|
private readonly Vector2 _wheelBackLocal = new(-0.85f, -0.35f);
|
||||||
|
private readonly Vector2 _wheelFrontLocal = new(0.85f, -0.35f);
|
||||||
|
private bool _brake;
|
||||||
|
|
||||||
|
private bool _throttle;
|
||||||
|
private float _wheelSpin;
|
||||||
|
public float AngularVelocity;
|
||||||
|
public Vector2 Position;
|
||||||
|
|
||||||
|
public float Rotation;
|
||||||
|
public Vector2 Velocity;
|
||||||
|
|
||||||
|
public Vector2 WheelBackPos { get; private set; }
|
||||||
|
public Vector2 WheelFrontPos { get; private set; }
|
||||||
|
|
||||||
|
public float RotationDegrees => Rotation * 180f / (float)Math.PI;
|
||||||
|
|
||||||
|
public void Reset(Vector2 startPos)
|
||||||
|
{
|
||||||
|
Position = startPos;
|
||||||
|
Velocity = Vector2.Zero;
|
||||||
|
Rotation = 0;
|
||||||
|
AngularVelocity = 0;
|
||||||
|
_wheelSpin = 0;
|
||||||
|
_throttle = _brake = false;
|
||||||
|
UpdateWheelWorldPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case Key.D or Key.Right:
|
||||||
|
_throttle = true;
|
||||||
|
break;
|
||||||
|
case Key.A or Key.Left:
|
||||||
|
_brake = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleKeyUp(KeyUpEvent e)
|
||||||
|
{
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case Key.D or Key.Right:
|
||||||
|
_throttle = false;
|
||||||
|
break;
|
||||||
|
case Key.A or Key.Left:
|
||||||
|
_brake = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Step(float dt)
|
||||||
|
{
|
||||||
|
var accel = new Vector2(0, Gravity);
|
||||||
|
var torque = 0f;
|
||||||
|
|
||||||
|
accel += -AirDrag * Velocity;
|
||||||
|
|
||||||
|
UpdateWheelWorldPositions();
|
||||||
|
|
||||||
|
var backGrounded = SolveWheelSuspension(dt, WheelBackPos, out var backForce, out var backTangent,
|
||||||
|
out _);
|
||||||
|
var frontGrounded = SolveWheelSuspension(dt, WheelFrontPos, out var frontForce, out var frontTangent,
|
||||||
|
out _);
|
||||||
|
|
||||||
|
accel += backForce + frontForce;
|
||||||
|
if (backGrounded)
|
||||||
|
torque += TorqueFromForce(WheelBackPos, backForce) * SuspensionTorqueInfluence;
|
||||||
|
|
||||||
|
if (frontGrounded)
|
||||||
|
torque += TorqueFromForce(WheelFrontPos, frontForce) * SuspensionTorqueInfluence;
|
||||||
|
|
||||||
|
var grounded = backGrounded || frontGrounded;
|
||||||
|
|
||||||
|
var driveInput = 0f;
|
||||||
|
|
||||||
|
if (_throttle) driveInput += 1f;
|
||||||
|
if (_brake) driveInput -= 1f;
|
||||||
|
|
||||||
|
_wheelSpin += driveInput * WheelAngularAccel * dt;
|
||||||
|
|
||||||
|
//if (grounded)
|
||||||
|
//{
|
||||||
|
// var driveForce = backTangent * EngineForce;
|
||||||
|
// accel += driveForce;
|
||||||
|
// torque += TorqueFromForce(WheelBackPos, driveForce);
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (grounded)
|
||||||
|
accel += new Vector2(-RollingFriction * Velocity.X, 0);
|
||||||
|
|
||||||
|
if (grounded)
|
||||||
|
{
|
||||||
|
var targetNormal = Vector2.Zero;
|
||||||
|
var groundedCount = 0;
|
||||||
|
|
||||||
|
if (backGrounded)
|
||||||
|
{
|
||||||
|
targetNormal += new Vector2(-backTangent.Y, backTangent.X);
|
||||||
|
groundedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frontGrounded)
|
||||||
|
{
|
||||||
|
targetNormal += new Vector2(-frontTangent.Y, frontTangent.X);
|
||||||
|
groundedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groundedCount > 0)
|
||||||
|
{
|
||||||
|
targetNormal /= groundedCount;
|
||||||
|
targetNormal = targetNormal.LengthSquared > 0 ? targetNormal.Normalized() : Vector2.UnitY;
|
||||||
|
|
||||||
|
var bodyUpDot = Vector2.Dot(BodyUp(), targetNormal);
|
||||||
|
if (bodyUpDot > 0f)
|
||||||
|
{
|
||||||
|
var angleError = SignedAngle(BodyUp(), targetNormal);
|
||||||
|
torque += angleError * GroundAlignStrength - AngularVelocity * GroundAlignDamping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var groundedTangentSpeed = 0f;
|
||||||
|
if (backGrounded) groundedTangentSpeed += Vector2.Dot(Velocity, backTangent);
|
||||||
|
if (frontGrounded) groundedTangentSpeed += Vector2.Dot(Velocity, frontTangent);
|
||||||
|
groundedTangentSpeed /= groundedCount;
|
||||||
|
|
||||||
|
var targetSpin = groundedTangentSpeed / WheelRadius;
|
||||||
|
var gripLerp = 1f - MathF.Exp(-WheelGroundGrip * dt);
|
||||||
|
_wheelSpin = _wheelSpin + (targetSpin - _wheelSpin) * gripLerp;
|
||||||
|
}
|
||||||
|
else if (driveInput != 0f)
|
||||||
|
{
|
||||||
|
torque += driveInput * (2.0f + MathF.Abs(_wheelSpin) * AirControlFromWheelSpin);
|
||||||
|
}
|
||||||
|
|
||||||
|
_wheelSpin *= (float)Math.Exp(-WheelAngularDamping * dt);
|
||||||
|
|
||||||
|
AngularVelocity += (torque / ChassisInertia) * dt;
|
||||||
|
|
||||||
|
AngularVelocity *= (float)Math.Exp(-AngularDamp * dt);
|
||||||
|
|
||||||
|
Velocity += accel * dt;
|
||||||
|
Position += Velocity * dt;
|
||||||
|
Rotation += AngularVelocity * dt;
|
||||||
|
|
||||||
|
if (Rotation > MathF.PI) Rotation -= 2 * MathF.PI;
|
||||||
|
if (Rotation < -MathF.PI) Rotation += 2 * MathF.PI;
|
||||||
|
|
||||||
|
UpdateWheelWorldPositions();
|
||||||
|
|
||||||
|
if (!grounded)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyBrake(bool grounded, Vector2 wheelPos, Vector2 tangent, ref Vector2 accel, ref float torque)
|
||||||
|
{
|
||||||
|
if (!grounded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var tangentSpeed = Vector2.Dot(Velocity, tangent);
|
||||||
|
if (MathF.Abs(tangentSpeed) < 0.02f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var brakeForce = -Math.Sign(tangentSpeed) * BrakeForce * tangent;
|
||||||
|
accel += brakeForce;
|
||||||
|
torque += TorqueFromForce(wheelPos, brakeForce);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float TorqueFromForce(Vector2 applicationPoint, Vector2 force)
|
||||||
|
{
|
||||||
|
var r = applicationPoint - Position;
|
||||||
|
return r.X * force.Y - r.Y * force.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float SignedAngle(Vector2 from, Vector2 to)
|
||||||
|
{
|
||||||
|
var cross = from.X * to.Y - from.Y * to.X;
|
||||||
|
var dot = Vector2.Dot(from, to);
|
||||||
|
return MathF.Atan2(cross, dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateWheelWorldPositions()
|
||||||
|
{
|
||||||
|
WheelBackPos = Position + Rotate(_wheelBackLocal, Rotation);
|
||||||
|
WheelFrontPos = Position + Rotate(_wheelFrontLocal, Rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SolveWheelSuspension(
|
||||||
|
float dt,
|
||||||
|
Vector2 wheelCenter,
|
||||||
|
out Vector2 suspensionForce,
|
||||||
|
out Vector2 tangent,
|
||||||
|
out float compression01)
|
||||||
|
{
|
||||||
|
var groundY = Terrain.HeightAt(wheelCenter.X);
|
||||||
|
var normal = Terrain.NormalAt(wheelCenter.X);
|
||||||
|
tangent = new Vector2(normal.Y, -normal.X);
|
||||||
|
|
||||||
|
var wheelBottomY = wheelCenter.Y - WheelRadius;
|
||||||
|
var penetration = groundY - wheelBottomY;
|
||||||
|
|
||||||
|
var compression = penetration - SuspensionRest;
|
||||||
|
|
||||||
|
if (penetration <= 0)
|
||||||
|
{
|
||||||
|
suspensionForce = Vector2.Zero;
|
||||||
|
compression01 = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var spring = SuspensionK * compression;
|
||||||
|
var vAlongN = Vector2.Dot(Velocity, normal);
|
||||||
|
var damper = -SuspensionD * vAlongN;
|
||||||
|
|
||||||
|
var forceMag = spring + damper;
|
||||||
|
|
||||||
|
forceMag = Math.Clamp(forceMag, 0, 250);
|
||||||
|
|
||||||
|
suspensionForce = normal * forceMag;
|
||||||
|
|
||||||
|
compression01 = Math.Clamp(penetration / (WheelRadius * 2f), 0, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 BodyUp()
|
||||||
|
{
|
||||||
|
return Rotate(Vector2.UnitY, Rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector2 Rotate(Vector2 v, float radians)
|
||||||
|
{
|
||||||
|
var c = MathF.Cos(radians);
|
||||||
|
var s = MathF.Sin(radians);
|
||||||
|
return new Vector2(v.X * c - v.Y * s, v.X * s + v.Y * c);
|
||||||
|
}
|
||||||
|
}
|
||||||
286
World.cs
Normal file
286
World.cs
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
using Triangle = osu.Framework.Graphics.Shapes.Triangle;
|
||||||
|
|
||||||
|
namespace HillLike;
|
||||||
|
|
||||||
|
public class World : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Camera _camera = new();
|
||||||
|
private readonly CarDrawable _carDrawable;
|
||||||
|
private readonly TerrainDrawable _terrainDrawable;
|
||||||
|
|
||||||
|
private readonly Container _worldLayer;
|
||||||
|
public readonly SimpleCar Car = new();
|
||||||
|
|
||||||
|
public World()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren =
|
||||||
|
[
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = new Color4(150, 210, 255, 255)
|
||||||
|
},
|
||||||
|
_worldLayer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children =
|
||||||
|
[
|
||||||
|
_terrainDrawable = new TerrainDrawable(),
|
||||||
|
_carDrawable = new CarDrawable(Car)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed override Axes RelativeSizeAxes
|
||||||
|
{
|
||||||
|
get => base.RelativeSizeAxes;
|
||||||
|
set => base.RelativeSizeAxes = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
Car.Reset(new Vector2(0, Terrain.HeightAt(0) + 3f));
|
||||||
|
_camera.Reset(Car.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Step(float dt)
|
||||||
|
{
|
||||||
|
dt = Math.Clamp(dt, 0, 1 / 20f);
|
||||||
|
Car.Step(dt);
|
||||||
|
_camera.Follow(dt, Car.Position);
|
||||||
|
|
||||||
|
var pixelsPerUnit = _camera.PixelsPerUnit;
|
||||||
|
var viewCenter = DrawSize / 2;
|
||||||
|
|
||||||
|
_terrainDrawable.SetCamera(_camera, viewCenter);
|
||||||
|
_carDrawable.SetCamera(_camera, viewCenter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
Car.HandleKeyDown(e);
|
||||||
|
if (e.Key is Key.R)
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleKeyUp(KeyUpEvent e)
|
||||||
|
{
|
||||||
|
Car.HandleKeyUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class Camera
|
||||||
|
{
|
||||||
|
public readonly float PixelsPerUnit = 60f;
|
||||||
|
public Vector2 Position;
|
||||||
|
|
||||||
|
public void Reset(Vector2 pos)
|
||||||
|
{
|
||||||
|
Position = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Follow(float dt, Vector2 target)
|
||||||
|
{
|
||||||
|
var desired = target + new Vector2(5f, 2f);
|
||||||
|
var smooth = 1f - MathF.Exp(-dt * 4.5f);
|
||||||
|
Position = Vector2.Lerp(Position, desired, smooth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class WorldDrawable : CompositeDrawable
|
||||||
|
{
|
||||||
|
protected Camera? Camera;
|
||||||
|
protected Vector2 ViewCenter;
|
||||||
|
|
||||||
|
public void SetCamera(Camera cam, Vector2 viewCenter)
|
||||||
|
{
|
||||||
|
Camera = cam;
|
||||||
|
ViewCenter = viewCenter;
|
||||||
|
UpdateFromWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Vector2 ToScreen(Vector2 worldPos)
|
||||||
|
{
|
||||||
|
var rel = worldPos - Camera.Position;
|
||||||
|
return new Vector2(
|
||||||
|
ViewCenter.X + rel.X * Camera.PixelsPerUnit,
|
||||||
|
ViewCenter.Y - rel.Y * Camera.PixelsPerUnit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected float ToScreen(float worldLen)
|
||||||
|
{
|
||||||
|
return worldLen * Camera.PixelsPerUnit;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void UpdateFromWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TerrainDrawable : WorldDrawable
|
||||||
|
{
|
||||||
|
private readonly Container _segments = new() { RelativeSizeAxes = Axes.Both };
|
||||||
|
public TerrainDrawable()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
AddInternal(_segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateFromWorld()
|
||||||
|
{
|
||||||
|
if (Camera is null) return;
|
||||||
|
|
||||||
|
_segments.Clear();
|
||||||
|
|
||||||
|
var halfWidthWorld = DrawWidth / Camera.PixelsPerUnit;
|
||||||
|
var left = Camera.Position.X - halfWidthWorld - 2f;
|
||||||
|
var right = Camera.Position.X + halfWidthWorld + 2f;
|
||||||
|
|
||||||
|
var step = 0.6f;
|
||||||
|
var count = (int)Math.Ceiling((right - left) / step);
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i += 1)
|
||||||
|
{
|
||||||
|
var x0 = left + i * step;
|
||||||
|
var x1 = x0 + step;
|
||||||
|
|
||||||
|
var y0 = Terrain.HeightAt(x0);
|
||||||
|
var y1 = Terrain.HeightAt(x1);
|
||||||
|
var y = MathF.Min(y0, y1);
|
||||||
|
|
||||||
|
var depth = 200f;
|
||||||
|
var p = ToScreen(new Vector2(x0, y));
|
||||||
|
var w = ToScreen(step);
|
||||||
|
var h = ToScreen(depth);
|
||||||
|
|
||||||
|
//_segments.Add(new Box
|
||||||
|
//{
|
||||||
|
// Anchor = Anchor.TopLeft,
|
||||||
|
// Origin = Anchor.TopLeft,
|
||||||
|
// Position = p,
|
||||||
|
// Size = new Vector2(w + 1, h),
|
||||||
|
// Colour = new Color4(80, 200, 120, 255)
|
||||||
|
//});
|
||||||
|
|
||||||
|
_segments.Add(new Triangle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Position = p,
|
||||||
|
Size = new Vector2(w + 1, -h),
|
||||||
|
Shear = new Vector2(x1 - x0, y1 - y0),
|
||||||
|
Colour = new Color4(80, 200, 120, 255)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i += 1)
|
||||||
|
{
|
||||||
|
var x0 = left + i * step;
|
||||||
|
var x1 = x0 + step;
|
||||||
|
|
||||||
|
var y0 = Terrain.HeightAt(x0);
|
||||||
|
var y1 = Terrain.HeightAt(x1);
|
||||||
|
var y = (y0 + y1) / 2f;
|
||||||
|
|
||||||
|
var p = ToScreen(new Vector2(x0, y));
|
||||||
|
_segments.Add(new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.TopLeft,
|
||||||
|
Position = p,
|
||||||
|
Size = new Vector2(ToScreen(step) + 1, 3),
|
||||||
|
Colour = new Color4(20, 120, 60, 255)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CarDrawable : WorldDrawable
|
||||||
|
{
|
||||||
|
private readonly Box _body;
|
||||||
|
private readonly SimpleCar _car;
|
||||||
|
private readonly Circle _wheelBack;
|
||||||
|
private readonly Circle _wheelFront;
|
||||||
|
|
||||||
|
public CarDrawable(SimpleCar car)
|
||||||
|
{
|
||||||
|
_car = car;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
_wheelBack = new Circle { Colour = new Color4(25, 25, 25, 255) },
|
||||||
|
_wheelFront = new Circle { Colour = new Color4(25, 25, 25, 255) },
|
||||||
|
_body = new Box { Colour = new Color4(240, 70, 70, 255) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateFromWorld()
|
||||||
|
{
|
||||||
|
if (Camera is null) return;
|
||||||
|
|
||||||
|
var wheelR = SimpleCar.WheelRadius;
|
||||||
|
var bodyW = 2.2f;
|
||||||
|
var bodyH = 0.7f;
|
||||||
|
|
||||||
|
var wb = ToScreen(_car.WheelBackPos);
|
||||||
|
var wf = ToScreen(_car.WheelFrontPos);
|
||||||
|
|
||||||
|
var rPx = ToScreen(wheelR);
|
||||||
|
_wheelBack.Size = new Vector2(rPx * 2);
|
||||||
|
_wheelBack.Position = wb - new Vector2(rPx);
|
||||||
|
_wheelFront.Size = new Vector2(rPx * 2);
|
||||||
|
_wheelFront.Position = wf - new Vector2(rPx);
|
||||||
|
|
||||||
|
var bp = ToScreen(_car.Position);
|
||||||
|
_body.Size = new Vector2(ToScreen(bodyW), ToScreen(bodyH));
|
||||||
|
_body.Origin = Anchor.Centre;
|
||||||
|
_body.Anchor = Anchor.TopLeft;
|
||||||
|
_body.Position = bp;
|
||||||
|
_body.Rotation = -_car.RotationDegrees;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Terrain
|
||||||
|
{
|
||||||
|
public static float HeightAt(float x)
|
||||||
|
{
|
||||||
|
const float baseLine = 0f;
|
||||||
|
|
||||||
|
var h =
|
||||||
|
0.9f * MathF.Sin(x * 0.55f) +
|
||||||
|
0.6f * MathF.Sin(x * 0.18f + 1.3f) +
|
||||||
|
0.25f * MathF.Sin(x * 1.15f);
|
||||||
|
|
||||||
|
var drift = 0.015f * x;
|
||||||
|
|
||||||
|
return baseLine + h + drift;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float SlopeAt(float x)
|
||||||
|
{
|
||||||
|
const float eps = 0.02f;
|
||||||
|
return (HeightAt(x + eps) - HeightAt(x - eps)) / (2 * eps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 NormalAt(float x)
|
||||||
|
{
|
||||||
|
var slope = SlopeAt(x);
|
||||||
|
var n = new Vector2(-slope, 1f);
|
||||||
|
return n.Normalized();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user