Creating a Game in Blazor - Part 4 - Platform and Collision

October 11, 2021

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:

BlazorGame

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 GameObjects @inject IControls Controls @inject IWorld World @inject IGraphics Graphics

@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:

BlazorGame

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).



Profile picture

A blog about one man's journey through code… and some pictures of the Peak District
Twitter

© Paul Michaels 2024