Overcoming obstacles, or more about collisions, pt 1

I don’t know if shooting game could be that much fun without something to hide behind and catch a breath. I will put some obstacles onto the map. What I imagine them to do is first, block all player movement through the obstacle, block all incoming bullets that hit obstacle, and, in future, block player’s view of the part of arena hidden behind obstacle.

I will start ith easy part first. I need obstacles to be visible on the screen. Invisible walls never really suited me, I’m shy guy after all. First, I extended my Arena class to hold list of obstacles. What is obstacle? Well, basicaly a thingy with list of points that define obstacle shape. It works for now, mybe will have something more in future to prove even more useful. But we like list of points, right?

public class Obstacle
{
    public Vector2d[] Points { get; set; }

    public Obstacle()
    {
        Points = new Vector2d[4]
        {
            new Vector2d(50, 100),
            new Vector2d(100, 75),
            new Vector2d(50, 0),
            new Vector2d(0, 25)
        };
    }
}

Since there are four points in this sample obstacle, you know it is going to be quadrilateral (yay for fancy words!, I’m not native speaker as you probably could’ve guessed ;)). I can tell you even more – it is going to be rectangle at an angle (see what I did here?).

And Arena looks pretty much the same but with a twist;

public class Arena
{
    ...
    public List<Obstacle> Obstacles = new List<Obstacle>() { new Obstacle() };
    ...
}

And this gets returned to the player once he signs into the game.

// RaimHub
public void Register(string name)
{
    name = HttpUtility.HtmlEncode(name);

    var player = arena.RegisterPlayer(name);
    players.Add(Context.ConnectionId, player);

    Clients.Caller.SignedIn(player.Id);
    Clients.Caller.SetupArena(arena);
    Clients.All.Registered(player);
    Clients.Caller.OtherPlayers(players.Values.Where(p => p.Name != name));
}

On a client this arena object is available for graphics “engine” to a) draw arena border, and b) draw all the obstacles. See the code, it is pretty simple:

var drawArena = function (gameObjects) {
    drawingContext.clearRect(0, 0, canvas.width, canvas.height);

    drawArenaBorders();
    drawObstacles();

    ...
};

function drawArenaBorders() {
    if (args.arena() == undefined) return;

    drawRectangle([
        { X: 0, Y: 0 },
        { X: 0, Y: args.arena().ArenaSize.Y },
        { X: args.arena().ArenaSize.X, Y: args.arena().ArenaSize.Y },
        { X: args.arena().ArenaSize.X, Y: 0 }]);
}

function drawObstacles() {
    if (args.arena() == undefined) return;

    var obstacles = args.arena().Obstacles;
    for (var i = 0; i < obstacles.length; i++) {
        drawRectangle(obstacles[i].Points);
    }
}

function drawRectangle(points) {
    if (points.length < 2) return;

    drawingContext.beginPath();

    drawingContext.moveTo(points[0].X + args.viewport().x, -(points[0].Y + args.viewport().y));
    for (var i = 1; i < points.length; i++) {
        drawingContext.lineTo(points[i].X + args.viewport().x, -(points[i].Y + args.viewport().y));
    }

    drawingContext.lineTo(points[0].X + args.viewport().x, -(points[0].Y + args.viewport().y));

    drawingContext.strokeStyle = "rgba(0, 0, 0, 1)";
    drawingContext.stroke();
    drawingContext.closePath();
}

The same function draws borders and obstacles. All are rectangles, just one limits you from escaping and the other blocks you from going in. Well, sort-of. It will in future. I promise.

Advertisements

Death by thousand updates, or events in with SignalR

You know what’s weird? When no one moves, server does nothing. Well, that’s not exactly weird, servers are known for being lazy. But that’s not the point. It starts to mess things up when you think about bigger picture. First – client still updates positions of objects in every frame, but server does this only when inputs change. What else it does is – it moves one object, checks for collisions, moves another, checks for collisions, it goes on until all objects are updated.

Now think about this case: player shoots bullet in some direction and does nothing more. The server gets notified, updates objects and all, notifies all other players and everyone is good, and no one moves any more. Until few seconds pass, then one player decides it is time to move. This action of course causes server notification, server updates objects one at the time. But – few seconds have passed, so increments in bullet position will be significant. Big enough to, say, materialize behind player it would normally hit. Or, in other case, player may move in position when server last recognized bullet’s position and server will find collision with bullet that’s no longer there.

Well, I could certainly mess a little bit with how positions are updated on server, come up with algorithm to update positions a fraction of a second at time until longer range is covered. Sure. But this would bring other problems – what if collision happened two seconds ago? That would seriously make players mad – to die two seconds after you thought you’ve survived, who would like that in any game?

I think it is time to move to more proactive server position. My server side Arena object will be the one telling when updates are going to be happening. But first – it needs to have a way to say – yo!, hub, go tell players we’ve updated their position!

Events you say? Why, yes, events seems to be exactly what I want too. And I did this, removed all client updates from hub code, created event in Arena, decided when updates should be triggered (stayed with the same actions as before, just handled in arena, this will not solve the time difference between updates yet), and fired the event. In hub it is handled and notifies client.

public RaimHub()
{
    arena.ArenaChanged += Arena_ArenaChanged;
}

private void Arena_ArenaChanged(object sender, EventArgs e)
{
    Clients.All.PlayerMoved(arena.GameObjects);
}
public class Arena
{
    ...

    public event EventHandler ArenaChanged;

    public Player RegisterPlayer(string name)
    {
        ...
        OnArenaChanged();
        return player;
    }

    public void UnregisterPlayer(Player player)
    {
        ...
        OnArenaChanged();
    }

    private DateTime _lastUpdateTime = DateTime.Now;
    public void UpdatePositions(DateTime? updateTimestamp)
    {
        ...
        OnArenaChanged();
    }

    internal void ProcessInput(PlayerInput input, Player player)
    {
        ...
        OnArenaChanged();
    }

    private void OnArenaChanged()
    {
        ArenaChanged(this, EventArgs.Empty);
    }
}

Seems alright? Of course not, if this would be that easy I wouldn’t write about it. What started happening is – server was sending hundreds, and then thousands of notifications to clients. Every action on client caused even more notification callbacks. This was crazy!

Fortunately it didn’t take me long to notice what is going on. Notice how I initialize event handler in constructor. This would be perfectly fine if there was only one instance of hub ever, and with hub disappearing game would finish. But that is not the case. SignalR will manage hubs as it likes. What does it mean? Well, for example new hub may be created for each message from clients. Or two. Or fifteen. Why? I am not entirely sure. But that’s what was going on. Lots of hubs being created, each subscribing to event, and then being removed. But not completly – they are still subscribed to event, this not only causes memory leakage, but also my strange behaviour – hundreds and thousands of hubs being in memory of process, each gets notified about arena change, each sends update to player. Nice way to kill a server.

There are two possible solutions here. First – remove event handler subscription before removing object from memory. That could be done in finializer if there was no better way, but there is fortunately – IDisposable, ready to be overriden in each hub implementation.

public RaimHub()
{
    arena.ArenaChanged += Arena_ArenaChanged;
}

protected override void Dispose(bool disposing)
{
    arena.ArenaChanged -= Arena_ArenaChanged;
    base.Dispose(disposing);
}

And that’s what I did. Now it works as I hoped for.

And what is the second solution you ask? Weak event pattern. Where you create event which does not count as reference to an object durign garbage collection. It has one problem though – it may still be called as long as the object is in memory. And object not reachable during GC does not mean object is out of memory – it will only be removed when garbage collection happens, which may be very long time since SignalR assumes hub was removed. So IDisposable is a winner here.

Waiving player goodbye – signing off

Feature that’s missing in Raim is – once player gets into game he or she cannot go out. Well, technically – he/she can, by closing the page or navigating to different page. But other players won’t know since they will not get any info. And figuring it out just because player hasn’t moved for past ten minutes does not work great as user experience goes.

Solution is simple: notify all players that player has signed off, moved to do something less fun than play this awesome game. Once chance to do that is to subscribe to beforeunload event of browser window. Once the event is raised page can potentially ask viewer to stay on the page a while longer (which most of the time makes people more angry than happy; that’s why it can be blocked by browser so don’t count on it working). I don’t want player to stay longer that way – I want to notify other players that there is one less person to play against.

(function () {
    var raim = $.connection.raimHub;

    raim.client.signedOff = gameArena.removePlayer;

    function signOff() {
        console.log("unloading");
        raim.server.signOff(name);
    }

    $.connection.hub.start().done(function () {
        var name = Date.now().toString();
        raim.server.register(name);
        gameArena.setPlayer(name);

        window.addEventListener("beforeunload", signOff);
    });
})();

And passing info that player signed off to players list:

var removePlayer = function (player) {
    var playerIndex = _players.findIndex(function (p) { return p.Name === player; });
    _players.splice(playerIndex, 1);
}

Of course there is server handling missing, from RaimHub.cs:

public void SignOff(string name)
{
    players.Remove(Context.ConnectionId);
    Clients.All.SignedOff(name);
}

And this way if player goes to different page everyone will get notified and player will disappear from game screen.

Sending player movement to all players

Once the player has pressed the key and we got the info, everyone would expect the application to react to it somehow. Fortunately that is pretty simple – I defined callback in my Area code that will be called every time game notices player wanting to change the position. To this callback client side hub will subscribe a method sending communication to server. This in turn will modify player’s status on the server side and send updated position to all other players, letting them see position change.

Arena.js

var keyDown = function (e) {
    var key = 0;
    if (e.which === 87 || e.which === 119 || e.which === 38)
        key |= moveDirections.Up;

    if (e.which === 83 || e.which === 115 || e.which === 40)
        key |= moveDirections.Down;

    if (e.which === 65 || e.which === 97 || e.which === 37)
        key |= moveDirections.Left;

    if (e.which === 68 || e.which === 100 || e.which === 39)
        key |= moveDirections.Right;

    if (key > 0)
        playerMoving({ direction: key });
};

var playerMoved = function (who) {
    var player = players.get(who.Name);
    player.Position = who.Position;
    drawArena();
};

raim.js

var arena = new Arena({
    playerMoving: function (e) {
        raim.server.playerMoving(e.direction);
    }
});

raim.client.registered = arena.addNewPlayer;
raim.client.playerMoved = arena.playerMoved;

RaimHub.cs

public void PlayerMoving(int direction)
{
    var player = players[Context.ConnectionId];
    player.Position.X += 10; // draft implementation
    Clients.All.PlayerMoved(player);
}

It gets very ‘drafty’ at points, but the idea is clear. And it works – server gets notification, sends player position back, using player’s name I get his object and set new position (and name will be later changed to Id of some sort in future, of course). There is one problem though – the new player position is printied, true, but the old one gets displayed as well.

Well, not quite. The old one was just not removed from screen. Canvas needs to be cleared between refreshes.

var drawArena = function () {
    drawingContext.clearRect(0, 0, view.width, view.height);
    for (var i = 0; i < players.count() ; i++) {
        var player = players.get(i);

        drawingContext.strokeStyle = "#F00";
        drawingContext.fillStyle = "#F00";
        drawingContext.arc(player.Position.X, player.Position.Y, player.Size, 0, 2 * Math.PI);
        drawingContext.stroke();
        drawingContext.fill();
    }
};

clearRect is supposed to remove content form the part of the screen it was pointed to (view is initialised in constructor with actual available screen width and height). This implementation however does not work. clearRect is not clearing screen, marking pixels as white, transparent or whatever.

What is missing is – every time we draw on canvas, we do it to create some path. And every time we call fill or stroke that path gets filled with colors defined. I haven’t defined any path though, so it is putting all requests into the same path, never closing it. So even though the clearRect clears the screen, first time stroke and fill functions are called, all historical changes to path are being repainted again and again – since the path was not cleared.

How to solve it? Oh, simple – open new path for every object that is being drawn (or even for every part of an object, once they get more complex).

var drawArena = function () {
    drawingContext.clearRect(0, 0, view.width, view.height);

    for (var i = 0; i < players.count() ; i++) {
        var player = players.get(i);

        drawingContext.beginPath();
        drawingContext.strokeStyle = "#F00";
        drawingContext.fillStyle = "#F00";
        drawingContext.arc(player.Position.X, player.Position.Y, player.Size, 0, 2 * Math.PI);
        drawingContext.stroke();
        drawingContext.fill();
        drawingContext.closePath();
    }
};

Client-server communication with SignalR

With SignalR loaded, it is time to do some client-server communication. First we will need to define hub to which we will be connecting from client, and in it some methods we would like server to perform for us. Something as simple as:

public class RaimHub : Hub
{
    public void Register(string name)
    {
        Clients.All.Registered(name);
    }
}

I don’t think it can be any easier than that. We will call the Register method from javascript, passing name as parameter and in response all connected clients should get newly registered client’s name.

On client side we than need some code as well:

$.connection.raimHub.server.register("jporwol");

With SignalR registered, there is connection object initialized, through which we can get to our hub. Notice convention changed from PascalCase to camelCase – each language gets what is standard for it, and library takes care of making it all work. F5 gets application running and, not much of a surprise, it fails. Honestly I don’t remember the last time I wrote the piece of code that worked from scratch!

Uncaught Error: SignalR: Connection must be started before data can be sent. Call .start() before .send()

Fair enough, I did not initialized hub. Easy to fix. We just call start and we get promise. When the promise is done, we can communicate with server.

$.connection.hub.start().done(function () {
    $.connection.raimHub.server.register("jporwol");
});

Breakpoint set in VS, application warming up and now all is perfect. Call gets to hub, it sends the name back to all clients (including the one that sent the message in the first place). Obviously that did nothing – there is no way to handle this message in client side. So there needs to be a function registered that will know how to handle those.

var raim = $.connection.raimHub;
$.connection.hub.start().done(function () {
    raim.client.registered = function (who) {
        console.log(who);
    };

    raim.server.register("jporwol");
});

It should simply log the name to the console window. Should, but does not. What is wrong here? I was determined to figure this one out without documentation. This wasn’t my first time playing with SignalR after all, even though I have forgotten most of it. After checking letter casings and typing errors I was stuck.
Then it came back to me – callbacks have to be registered before connection is initialized. Not sure why, it does not make immediate sense to me – but that’s how it has to be. Small fix:

var raim = $.connection.raimHub;
raim.client.registered = function (who) {
    console.log(who);
};
$.connection.hub.start().done(function () {
    raim.server.register("jporwol");
});

And I’m back in the saddle! Till next time!

Setting up SignalR with Nancy

Time to connect client to server. After all it is supposed to be interactive game with many clients. SignalR seems to be interesting library that should be able to do what I need.

SignalR is from Microsoft, integrates with ASP.NET easily, but can also be used in other technologies. Cool stuff, I tell you. If you will look at the setup in docs, there are few steps only. First of those – install ASP.NET SignalR. Few minutes (seconds if you’re lucky) and we have it.

Second – in Startup map SignalR.

public void Configuration(IAppBuilder builder)
{
    ...
    builder.MapSignalR();
}

Bootstrapper needs to be created to configure Nancy so it servers javascript files as static content.

public class Bootstrapper : DefaultNancyBootstrapper
{
    protected override void ConfigureConventions(NancyConventions nancyConventions)
    {
        base.ConfigureConventions(nancyConventions);
        nancyConventions.StaticContentsConventions.AddDirectory("Scripts");
    }
}

And a little modification for index.html

<a href="http://../Scripts/jquery-2.2.1.min.js">http://../Scripts/jquery-2.2.1.min.js</a>
<a href="http://../Scripts/jquery.signalR-2.2.0.min.js">http://../Scripts/jquery.signalR-2.2.0.min.js</a>
<a href="http://../signalr/hubs">http://../signalr/hubs</a>

All very simple. We need jQuery (updated to latest version, SignalR by default uses 1.6 at the moment). We need SignalR. Third script calls to server to hubs. There is no script for that in my code – that’s internally handled by SignalR.

I pressed F5 to see if all is OK. And to my surprise i got 404 for the last script. Well, alright, I must’ve made some mistakes along the way. Checked once and twice. Found nothing worth fixing. So it must be something else – SignalR mapping must be broken. For some reason system does not recognize /signalr/hubs path.

Since I’m using Nancy, I guessed that it is probably something with it and SignalR not working very well. Well – the latter is MS product, built using MS stack and it works there from the get go.

Quick search on google and I found link app doing what I wanted – real time communication client to server with SignalR. Without going deep into code (it is simple and short though) I could notice some differences. First – there is no UseNancy registration in Startup, but it is replaced with web.config setup:

<httpHandlers>
  <add verb="*" type="Nancy.Hosting.Aspnet.NancyHttpRequestHandler" path="*" />
</httpHandlers>

OK, interesting. Tried that in my app, not expecting much. To my surprise – app started failing, saying it cannot find Nancy.Hosting.Aspnet.NancyHttpRequestHandler. Looking at the project references I knew there most be something missing. In fact – I found one difference. Default Nancy instalation uses Nancy.Owin assembly reference, however SignalR sample uses Nancy.Hosting.Aspnet. So next thing to try was – remove former, install latter. At this point Startup failed to compile due to missing UseNancy. But since it is configured in configuration file – there is no need for it.

F5 again. And all is perfect. All javascript loaded, signalr/hubs also loads without any errors. Perfect! Next time connecting to server and making sure all is in place.