Through The Looking Glass


It’s been a busy few months since last log. I worked on various aspects of the game design, notes and documents which I will share in the future. But for now let’s focus on tangible progress I made on the representation of the world outside the submarine.


Cyclorama

It’s the term used in film to designate the projection of a 360 degrees background image onto a cylinder. I needed a similar system for the exterior views (periscope and conning tower). 

The enormous cyclorama depicting "The Battle of Atlanta".

Two tricks for the illusion to work:
. A fake perspective, since I don’t work in 3D.
. A scrolling that seamlessly comes back around to its starting point.

Last time I went over clouds parallax and sky perspective. I had to build a similar system for the ocean. But first I wanted to implement the wrap around scrolling, which would bring me close to a functional exterior view. 

Picture the cyclorama as a strip of photos stitched together, even though the reality is more complex because of lens distortion: it stretches the image around the center. But this level of fidelity requires a 3D projection, which makes little sense in the context of a 2D game like Atlantic ‘41. What’s worse, stretching and translating 1-bit patterns may trigger visual artifacts and flickering. On the Playdate you must always keep these issues in mind. A simple plane of 2D sky and sea to scroll on screen will be just as effective, and easier to control.

In the background, I’ll have to maintain a polar coordinates system, to place the ships on the cyclorama based on their location relative to the submarine. But first I had to decide how many screens will constitute one full orbit. 


History Lesson

For this, I had to decide on the field of view, which is the angle that the camera can see. 


Comparison of various field of view (FOV) values.

Research can help for that; one thing about submarines is that they were almost never submerged (They’re called boats for a reason). U-boats had to remain surfaced at least six hours a day to recharge their batteries, and travelled almost always surfaced, as the electric motors gave a measly seven knots top speed. Diesel engines allowed for greater speed, but they were limited to surface usage, since combustion needs oxygen.

Six hours is a considerable amount of time to spend at the mercy of ships ten times your size, with canons powerful enough to blow you out to smithereens. And that’s without counting aircrafts, by far the most lethal threat. For this reason, a watch crew was always on duty, looking for danger and sounding the alarm for an emergency dive. Everybody onboard had to take watch duty, including the captain. 


U-118 right after being torpedoed by an aircraft.

A shift required four men, each one surveying a 90 degrees arc. They watched with the naked eye and used magnifying goggles to check far distance objects. If you wonder who needs to know that nerd stuff, it is all relevant to the game. That choice of four men was dictated by the human field of vision: 220 degrees, of which about half of that is binocular, which means seen by both eyes at the same time (essential for depth perception). 90 degrees is comfortable enough for one person to watch, without relying on sketchy peripheral vision. 


The human field of vision

With that in mind, a 90 degrees FOV is a sensible approach. To cover the full 360 degrees, my cyclorama will be four screens wide, that is 1600 pixels. I decided to leave aside the pesky question of magnification. Atlantic ‘41 is a turn based strategy game (or rather hybrid but we’ll get to that in the future); The player won’t need to zoom on distant objects. Exterior views will allow the player to identify and mark targets. They won’t need the precision required for targeting calculations. That aspect will be dealt with under the hood.

Periscope is a similar case. They had x1.5 and x6 magnifications, but in my context, I’ll keep things simple. In the end, both views will share the same 90 degrees FOV. That doesn’t mean they will look identical tough. Aside from field of view, there was a more dramatic visual distinction: the height above the surface. I’ll focus on the periscope view for now and explain the differences between both views in the next log.

A watch crew on duty. Each man is covering his quarter of the sky.


It's All an Illusion

With the FOV determined, all that’s left is a way to render an infinite scrolling. It’s much simpler than I first thought. If you think about it, all you need is to happily scroll along the cyclorama and reset the camera to the starting position without the player noticing it. The trick is for both the start and end positions to look identical. If they are, the camera can jump between them without ever breaking the illusion of a smooth motion. 

A schematic of the cyclorama, with pixel coordinates and matching heading.

Now one way of doing that is to add one virtual screen to the cyclorama; a copycat of the first one. This screen serves as an overlap between beginning and end, a buffer of sorts. The camera moves along the cyclorama up until it reaches past the edge of the first four screens, then every pixel beyond that is cloned from the first screen. When that buffer screen is fully revealed, the camera can safely cut back to the first screen. Since they’re identical, it’s like nothing happened, and you can go for a new turn. Have a look at the illustration below. It’s much easier to grasp the idea with a visual aid.


See how screen 1 and 5 overlap, to allow for the camera to cut unnoticed between the two.

With that goal in mind, I needed the code to ensure that screen 1 and 5 would look the same, down to the pixel. Let’s break down the components drawn:

Ocean and sky
They’re both fixed rectangles filled with a pattern. This is to avoid flickering. The illusion of motion comes the translation of clouds, waves, and ships. Sea and sky are immobile. I can ignore them.

Waves
As you’ll see, waves are triggered randomly. They have a short life cycle, with their position tracked relative to the camera. They don’t have persistent coordinates in the world, and consequently don’t factor into the problem.

Clouds and ships
The solution is trivial. For screen 1 and 5 to be the same, I have to make sure that any persistent object is cloned every four screens. It’s that easy.

Clouds are drawn one by one, from an array that keeps track of their location in the world. I scan through the array one cloud at a time. For each cloud, I draw every cloud inside the camera’s vision. If the cloud is not on screen I calculate if one of the two clones on either side, 1600 pixels away, must be drawn. In effect, I manage an array of clouds tiling every 1600 pixels.

Every persistent object like the clouds must tile every 1600 pixels.

Ships aren’t implemented yet, but they’ll follow the same principle, like any persistent object. All that’s left to do is to teleport clouds and ships (and the camera) that would leave the cyclorama on either side, straight to the opposite end, 1600 pixels away. And with that, I now have a seamless 360 degrees free camera.


The top number represents the position of the camera in pixels. It jumps from 0 to 1600 without any noticeable cut.


The Woes of Animation

Then came the dreadful time of animating the ocean. It’s the same rules of perspective and overlap governing the clouds. Water is a different beast though, due to its active nature.

I tried the simplest approach first: a white rectangle and a few “keep alive” pixels, like I did for my first title screen mock-up. Several issues arised:

/ That quiet look works for a lake but not for open sea. 

/ It lacked depth. 

/ Much of the effect relied on reflection, which means mirroring and distorting in real time everything above the horizon. And that sounds like the best way to overload the processor. 


My first attempt at water.

Moving on.

In doubt, go for brute force. I found a looping clip of realistic CG waves and converted it into a high contrast video, with highlights reduced to pure white on a black canvas. I trimmed frames down to a 10 fps animation. Then I screened the result onto a dithered rectangle. That 12 seconds clip amounted to 120 frames covering the entire ocean visible on screen (400 x 100 pixels). Lots of problems with that one. 


From left to right: the original frame - the black and white high contrast - the 1-bit at Playdate resolution.

First, that’s an enormous quantity of data for the little Playdate, and without the console to test on, I don’t know if it could handle it. 

Second, the result was passable at best. Stylistically, I found it too realistic, compared to the clouds. I think you can tell when something has been hand drawn, as opposed to scanned, or even rotoscoped.

Third, you would be surprised how obvious the repetition of one screen tile is. I would have had to cycle between two different tiles at minimum, doubling the worrying amount of frames. 

Finally, this method was limited to one unique perspective, which would come as a problem when switching between periscope and conning tower.

Meh.

Well, scratch that one too.

There’s always a moment, after you have exhausted all the easy avenues, when reality kicks in. I didn’t like the idea of spending days drawing animated waves, but short of a sudden spark of genius, I couldn’t come with a better alternative. Introducing Aseprite, a software for which I have conflicting feelings, but remains my preferred poison for animation.


The iconic scene from "Fantasia".

I knew convincing fluids can be drawn, as Disney animators have demonstrated time and again in the old classics. But I have to work with a limited resolution, two colors, and a conservative amount of frames. That’s a lot of constraints to put on something already hard to do in the first place. Plus I must face the fact that I’m a mediocre animator at best. Thankfully, I’m willing to make up with work what I lack in talent. In itself this explains the long time since I updated this log.


Evolution of the animation of a wave.

Even with perseverance and good references, it took me a while to find the right recipe. I decided against any simulated values with dithering, for graphic, legible shapes. My first animated waves looked too big, or they had too much lateral motion, like these water tubes the surfers love. Then I tried simpler triangular shapes, mimicking the foam caps. Better in a way but still lacking character.

First version with all the waves animated.

Another challenge was to remain consistent in shape and motion, while retaining variety. With enough time I found something I could live with. For the far distance, I made a thin sprite that covers the whole screen, and simulates the shimmering of water close to the horizon line. Then it was a matter of stacking four levels of sprites (each one increasing in size with proximity to the camera), and finding the right scale progression and vertical position for a convincing perspective effect. 

But I was only halfway there. I programmed a subtle downward motion as the waves break up, plus slight parallax lateral translation. I discovered that animation was only a part of the effect. It came down to fine tuning all the parameters (triggering frequency, initial position, secondary motion…). In hindsight, I should have expected it.


Version with more perspective and camera sway.


Final Touches

I finalized the look with overlaid sprites to give the ‘goggles’ effect. Nothing fancy there. The only challenge was finding the right dithering to blur the edge. A sharp edge looked cleaner, but I don’t mind the artefacts from intermixed patterns. It could pass for grime on the edge of the eye piece. Not sure if I’ll keep the targeting sights, but this can be decided later. 

Thinking of ways to make the view more believable and alive, I tried giving a light sway to the camera. It’s the gentle rock that you feel at sea. Ducking the camera would involve taxing rotation computing so that’s out of the question, but a simple sine applied to the camera’s vertical position could do. I discovered that the small resolution comes as a significant obstacle against any subtle motion. Too little sway and the smooth sine doesn’t register. The motion feels choppy, almost like a glitch. A large amplitude allows for a nice ease in and out, but the motion feels exaggerated and distracting. 

With hours of playing with curve and frequency, I found somewhat of a sweet spot. That’s the best I can do and I’m not sure it’s good enough. I’ll leave it there for now. I need to judge it in context before sending it to the chopping block. 


Final version...for now.

I may have to add even more waves animation cycles in the final game, if the ocean feels too repetitive. I have simple conditions in place to prevent identical waves to trigger in sync and in tight proximity that deserve improvement. It “works” for me right now, but I’ve lost an objective view on it. Better to let this rest for a while and revisit later, in particular when I’ll have the console to test on. I hope to kill two birds in one stone and optimize for the platform while ironing out the last kinks. 

With that done, I began to imagine ways to differentiate the conning tower view from the periscope view, while making them immersive in their own specific ways. But I’ll end it there for now as this entry is getting long. 

Thankfully I’m close to having the conning tower view done though, which means that you can expect the next log to come quicker. 

More next time.




Comments

Log in with itch.io to leave a comment.

When you say “clone the cloud every 4th screen” I thought of drawing it with the x position using % modulo, which should let you draw only one at the correct location depending on the scroll position?

(+1)

Yes you’re right. I phrased that poorly. Ultimately I only ever draw any cloud once per loop, if it should be visible at the camera position . By cloning I guess I tried to make people imagine an infinite cloud table where any cloud  “virtually” repeats every 4 screens, if that makes sense. You summed up the idea  much more accurately. 

I love reading these devlogs and hearing about how you go about solving your game design problems!

The first wave animation looked perfectly fine to me. The ones that followed could still work if the waves didn’t look so triangular like shark fins. 

Yep. You’re totally right. This is a typical case of going too fancy and over doing. I wanted to give more perspective and really push the style, but now I realize that the new waves are just too distracting. 
It’s a bit painful to realize you went down the wrong path but it’s one of these things that are just hard to predict before actually doing them.

But I’m glad that you’re giving this feedback because it comforts the doubts I had. I would always encourage you to do so. It can only benefit to the game. It’s part of the process and I’ve learned things going through this. The good thing is that I keep a complete version of the source every day so it’s easy to revert.

There’s still things I can take from the new version that will benefit the old one, so it’s not all lost. I can even tell you that I can also improve on the clouds, so I plan to go back to this too.

It seems like not much progress for so much effort but these views are what the player will see a good 50% of the time, so it’s worth doing right.

Next devlog will be about having the courage to back pedal and hopefully the benefits of what I learned with a final (for real this time) persicope view! :)

Thank you very much for sharing your honest opinion. 

I love this game so much... Dumb (maybe?) question. Inside the SUB when the crew is submerge are we going to see some fishes?

(3 edits)

It’s not a dumb question. Wildlife in the game is something I thought about. You’re not going to see fishes underwater because I want to keep the game fairly realistic, and U-boats, once submerged, are blind. There’s no windows to see outside the submarine. 

However it doesn’t mean that animals won’t be present at all. For instance I want to have sea birds near coasts. You will also get to encounter other marine life in very rare occasions and under special circumstances, but that’s as much as I’m going to say because I don’t want to spoil the surprise for future players. 

(+1)

Please be a Kraken, please be a kraken.

ah ah! You’ll see…