Through the looking glass
25 October 2019
That was an interesting experience, Serilog isn’t quite what I was expecting but was relatively easy to get going in a rudimentary way. I can see it offers a lot more options than I’m currently using. But, since it only took a few lines also to send the logs to my server, and then minutes to get a graph of some performance characteristics from that, I can say I’m happy for now.
Actually, the most annoying challenge with the logs was handling the XAML code-behind classes - the inability to pass constructor arguments is super irritating. Thankfully Serilog handles this relatively smoothly with a static Log object; the only surprise was that I had to initialise that manually.
Only time will tell if I got the logging right. It’s only when things start to go wrong that logs show their usefulness (or lack thereof). And so far everything is working as expected.
With that technical task behind me, I can finally turn my attention to the viewport.
Viewports
A viewport is such a simple concept; it almost doesn’t seem worth talking about. And yet. It turns out to be somewhat more complicated than first glance would suggest.
The best description I ever heard of a viewport was to think of it like reading a newspaper underneath a large sheet of cardboard with only a small window cut in the board. To see the whole paper, one would have to move the card around all over the place, and you could only see a little of the article at any given moment. A bit like the animation below:
)
(viewport)
Of course, computer monitors aren’t easily moved around to see the entirety of the game world, so instead, we change the frame of reference and move the world around behind the monitor. Like this:
)
(relative viewport)
Viewport Location
That’s all straightforward and standard stuff, but where the challenge occurs is in how to move the world. What triggers the move?
Control by Scrollbars
In most software, the location of the world can be controlled by using scrollbars. I don’t want my game to have scrollbars!
Control by Dragging
Some software also allows the world location to be adjusted during drag operations. Dragging something (e.g. a shape in powerpoint) close to the edge of the screen causes the world to shift slowly in the opposite direction. Depending on the software the drag action might need to be combined with either holding the mouse still or possibly frantically wiggling the mouse. Now, the game is going to have hard walls around the play area and touching them will result in losing. So, having the world movable by touching the edge seems like a bad plan - the first you would know of the edge of the game area would be your ship exploding. Bah.
Centre relative control
In many games - like Diablo - the cursor stays fixed in the middle of the screen, and the world moves around this centre point. In effect, your character never really moves; the world moves around your character. The world is therefore made larger than the playable area, and the walls move in until they reach the character. This is quite a neat solution, but it doesn’t feel right for this game - part of the appeal will be the ship flicking around the screen, and this would, I feel, reduce that impression.
Ratioed Movement
Another option would be to calculate the ratio between the world size and the viewport size, such that when the cursor was 25% across the screen, the navigation was 25% across the world. Like this, any movement of the mouse would move both the ship and the world. Such a relative mechanism could work although I’m not convinced that arriving at the edge at the same moment as colliding with the wall would be ideal. It’s definitely on my shortlist of viable options - let’s call it Option One.
Sticky Cursor
Yet another option would be to have the cursor in the centre and the world move around it until the edge of the world met the side of the viewport. At that moment the cursor would become uncoupled and head towards the edge of the viewport. Moving the mouse the other way would cause the cursor to move back towards the centre of the viewport. Upon reaching the centre, the world would again begin moving. I suspect the transition between free movement and being captured at the centre could be jarring; however, the upside to this is changing the size of the world won’t have the effect of changing the realised mouse sensitivity. So, let’s call this Option Two.
Conceivably options one and two could be combined in some kind of hybrid, but usually, that approach gets the worst of both options.
Viewport Implementation
Ultimately the only way to know for sure if something works is to implement it. My gut tells me that Option Two will be better, so I’m going to go with that first. If it doesn’t work, I guess I’ll try Option One :D
Edge cases
It’s always worth thinking about edge cases. In the case of the viewport, there are two worthy edge cases that I can think of. The first is when the viewport and the game area happen to be precisely the same size. The second is when the viewport is actually larger than the game area (e.g. if someone has a truly massive monitor). In both of these cases, we can treat the cursor as a 1-to-1 movement - hitting the edge of the world will be destructive anyway, so it doesn’t matter if that happens before the side of the viewport is reached.
Direction of movement
It is worth noting that the direction of movement of the cursor is going to matter here - moving from the centre towards an edge might Unsticky the mouse, whereas moving from a side towards the centre might Sticky the mouse.
Dimensionality
The same logic will apply to both the vertical and horizontal axes. So luckily this means I can implement it once and reuse it - provided I stay away from terms like left and right.
Zoom
One other thing I want to implement is the ability to zoom in/out on the world. Effectively zooming is just applying a scaling factor to the world before fitting it to the viewport.
Since these concepts are so closely linked, I’m going to implement them together.
Putting it together
To summarise, the plan is as follows:
Add a game world size setting to the engine. Also, add a border size (this sits inside the world size).
Render the world border.
Multiply the world size by a scaling factor. Control the scaling factor using the keyboard, for example [ and ] keys to increase/decrease the factor.
Implement a one-dimensional Viewport that moves with the cursor. Viewport movement occurs only until the edge of the viewport is at the side of the world, or in the case of small worlds, past the side. If the viewport edge is at or past the side of the world, and the cursor is moving away from the centre of the world, then the cursor moves. If the cursor collides with the side, or leaves the bordered world space, trigger a Lose event. If the viewport edge is at or past the side of the world, and the cursor is moving towards the centre of the world, and the cursor reaches the centre of the viewport, freeze the cursor and begin moving the viewport.
)
(game viewport plan)