I’ve recently been creating a JetSet Willy clone in Blazor. I use the term clone loosely - I’m actually not creating a clone or anything like it - I’m simply replicating some aspects of the game (moving, jumping, platforms, collision, etc.).
This is the fourth post in the series that started here. In this post, I’m going to add a platform - so far we’ve been walking on the air.
As with previous posts, the code for this can be found here on GitHub.
In order to do this, we’ll need to do a bit of refactoring, and to introduce a crude collision concept. Let’s start with the refactoring and platform display:
We’re introducing the concept of a base GameObject, from which, Platform, Player, and NPC inherit. We’re dispensibg of IPlayer, and instead, adapting World to manage the elements in the world:
public class World : IWorld
{
private readonly IEnumerable<GameObject> \_gameObjects;
public World(IEnumerable<GameObject> gameObjects)
{
\_gameObjects = gameObjects;
}
public Player Player
{
get => (Player)\_gameObjects.First(a => a.GameObjectType == GameObjectType.Player);
}
public IEnumerable<GameObject> Platforms
{
get => \_gameObjects.Where(a => a.
GameObjectType == GameObjectType.Platform);
}
public void ApplyPhysics()
{
foreach (var gameObject in \_gameObjects)
{
gameObject.Update();
}
}
}
We’re not dealing with NPCs in this post, but we are dealing with platforms. The new Platform class currently looks like this:
public class Platform : GameObject
{
public Platform(int width, int height, int left, int top)
{
Width = width;
Height = height;
Left = left;
Top = top;
GameObjectType = GameObjectType.Platform;
}
public override void Update()
{
//throw new System.NotImplementedException();
}
}
We’ll no doubt come back to this, but essentially, all we’re doing is maintaining a collection of GameObjects with a specific type.
For now, we’ll inject the properties of the World in through Program.cs:
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddSingleton<IWorld, World>(srv =>
{
var platform = new Platform(
50, 10, 0, WorldSettings.FLOOR);
var player = new Player(17, 21, new[] { platform })
{
GameObjectType = GameObjectType.Player,
};
var world = new World(new GameObject[] { player, platform });
return world;
});
builder.Services.AddSingleton<IControls, Controls>();
builder.Services.AddSingleton<IGraphics, Graphics>();
await builder.Build().RunAsync();
}
}
As you can see, we build the Platform and Player and then build the world. Finally, we adapt Game.razor to display the platforms:
[code lang=“html”]
@page ”/”
@using System.Timers
@using BlazorGame.GameLogic
@inject IEnumerable
@foreach (var platform in World.Platforms)
{
<div style="position: relative; top:@(platform.Top)px; left:@(platform.Left)px; width:@(platform.Width)px; height:@(platform.Height)px; border: 1px solid #FFFFFF; background-color: #FFFFFF"></div>
}
Finally, we need to ensure that our player lands on the platform (and doesn't simply drop through). For now, we'll pass the **World** into **Player** and perform the logic there:
``` csharp
public override void Update()
{
\_forceUp -= WorldSettings.GRAVITY;
Top -= \_forceUp;
Console.WriteLine($"Top: {Top}, Left: {Left}, Width: {Width}");
var platform = \_gameObjects.FirstOrDefault(a =>
a.Top <= Top &&
a.Left <= Left + Width &&
a.Left + a.Width >= Left + Width &&
a.GameObjectType == GameObjectType.Platform);
if (platform != null)
{
Top = platform.Top;
\_forceUp = 0;
}
if (Left <= 0 && \_forceRight < 0)
\_forceRight = 0;
else if (\_forceRight != 0)
\_direction = \_forceRight;
Left += \_forceRight;
}
It’s not quite finished yet, but we now have a platform that we can walk on, and fall off:
What’s Next?
In the next post, I’ll try to introduce an NPC (not quite sure where I can get the graphics from yet, so it might just be a rectangle or something).