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:
@page "/" @using System.Timers @using BlazorGame.GameLogic @inject IEnumerable<GameObject> GameObjects @inject IControls Controls @inject IWorld World @inject IGraphics Graphics <div @onkeydown="HandleKeyDown" @onkeyup="HandleKeyUp" @onkeydown:preventDefault style="background-color: #000000; width: 80vw; height: 80vh; margin: auto" tabindex="0" @ref="mainDiv"> <div style="color: white; top: @(World.Player.Top)px; left: @(World.Player.Left)px; width: @(World.Player.Width)px; height: @(World.Player.Height)px; overflow: hidden; position: relative"> <img src="/images/Willy-Sprite-Sheet.png" style="margin: 0 @(Graphics.PlayerOffset)px; transform: scaleX(@(Graphics.PlayerDirection))" /> </div> @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> } </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:
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).