Tag Archives: Client-side Blazor

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

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

Creating a Game in Blazor – Part 2 – Gravity, and Controls

In this post I started the process of writing a very vague approximation of Jet Set Willy, in an effort to regain my youth.

Here, we’re going to take those very vague foundations, and allow left and right controls, along with a jump function. As a caveat, I’m going to state here that I expect quite large swathes of this code to change significantly before I declare it complete; and, as another caveat, there were 60 rooms in the original Jet Set Willy. I’ll be surprised if this series of posts manages to replicate the bathroom (which is the first room).

From the first post, we have a Game.razor, and all the code for this post will go into there. You can simply view that code here.

I’ve introduced a game loop here, which calls an update and then a draw method. Currently, the game loop is just a crude timer:

    private void TimerElapsed(Object source, System.Timers.ElapsedEventArgs e)
    {
        Update();
        Draw();
    }

This gets registered in the OnInitializedAsync method:

    protected override Task OnInitializedAsync()
    {
        _timer = new Timer();
        _timer.Interval = 16;
        _timer.Elapsed += TimerElapsed;
        _timer.AutoReset = true;
        _timer.Enabled = true;        

        return base.OnInitializedAsync();
    }

The Draw method is, in fact, just a single line:

private void Draw() => this.StateHasChanged();  

The reason for this is that Blazor doesn’t always automatically refresh state – updating bound objects on a timer is one of the times when it doesn’t. The Update method has a little more to it:

    private void Update()
    {
        ApplyGravity();
        _top -= _forceUp;

        if (_top > FLOOR)
        {
            _top = FLOOR;
            _forceUp = 0;
        }

        if (_left <= 0 && _forceRight < 0) _forceRight = 0;

        _left += _forceRight;
    }

Anything in caps is just a constant. The rest of the code basically takes care of the left, right, and jump.

Next, we’ll need to modify the HandleKeyDown method:

    private void HandleKeyDown(KeyboardEventArgs e)
    {
        switch (e.Code)
        {
            case "ArrowLeft": // Left
                if (_forceUp == 0)
                {
                    Walk(-SPEED);
                }
                break;
            case "ArrowUp": // Up
                Jump();
                break;
            case "ArrowRight": // Right
                if (_forceUp == 0)
                {
                    Walk(SPEED);
                }
                break;
            default:
                break;
        }
    }

As you can see, we’ve added Jump and Walk methods. All the logic has moved into those methods:

    private void Walk(int amt) => _forceRight = amt;

    private void Jump()
    {
        if (_forceUp == 0)
        {
            _forceUp += JUMP_FORCE;
        }
    }

Since Walk needs to be immediate, we just set the _forceRight to whatever value it’s passed; similarly with the jump, although we can’t allow a double jump, so we determine if a jump is in progress first.

Finally, we’ve added a HandleKeyUp: this stop walking when the key is released.

    private void HandleKeyUp(KeyboardEventArgs e)
    {
        switch (e.Code)
        {
            case "ArrowLeft": // Left
                Walk(0);
                break;

            case "ArrowRight": // Right
                Walk(0);
                break;

            default:
                break;
        }
    }

If you run this, you should now be able to walk “test” around the screen, and cause him to jump.

What’s Next?

For the next steps, I’m going to add a platform, and try to use some graphics. I’ll also try to refactor a little, as the main form is becoming a little large.

References

https://docs.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-5.0

https://www.pmichaels.net/2019/06/03/creating-a-car-game-in-react-part-1-drawing-and-moving/

https://stackoverflow.com/questions/58920461/how-to-detect-key-press-without-using-an-input-tag-in-blazor

Simple binding in Blazor

A while back, I asked on Dev about moving my blog from WordPress to … well, not WordPress anymore. My main critera was to Markdown instead of whatever the WordPress format is called. The main issue being that you need to replace the code tags.

I resolved to create a tool to do it for me. I’m doing so in Blazor.

The first task was to create a very simple data binding scenario, so I could handle all the view logic in the View Model. I’ve previously written about using view models. This post covers the scenario where you want to bind text boxes and a button.

Let’s see the View Model first:

    public class MainViewModel
    {
        public string WpText { get; set; }
        public string MdText { get; set; }

        public void ConvertText()
        {
            MdText = $"Converted {WpText}";
        }
    }

Okay, so we have two strings, and a method that populates the second string with the first. As per the linked post, the plumbing for the view model is very simple; first, it’s registered in ConfigureServices:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<MainViewModel, MainViewModel>();
        }

Then it’s injected into the Razor view:

@page "/"
@inject ViewModels.MainViewModel MainViewModel

In fact, the next part is much simpler than I thought it would be. To bind a view model property to the view, you just use the syntax bind:

<div class="container">
    <div class="row">
        <div class="form-group col-md-6">
            <label for="WpText">Wordpress Text</label>
            <input type="text" @[email protected] class="form-control" id="WpText" name="WpText"/>
        </div>

        <div class="form-group col-md-6">
            <label for="MdText">Markdown</label>
            <input type="text" @[email protected] class="form-control" id="MdText" name="MdText"/>
        </div>
    </div>
    <div class="row">
        <div class="form-group col-md-12">
            <input type="button" value="Convert" @[email protected](() => MainViewModel.ConvertText()) class="form-control" id="Submit" />
        </div>
    </div>
</div>

Just to point out one thing that tripped me up before I leave this: the event handlers that relate to Blazor must be prefixed with an at symbol (@).

References

https://www.nativoplus.studio/blog/blazor-data-binding-2/