1. icon for News
  2. icon for TCHOW

News

Wednesday, November 13, 2013

Math: What is the right Aspect Ratio?

In the shower the other day, I thought up an interesting question: what is the right aspect ratio to work in? (With, of course, the immediate follow-up question: what does "right" mean in this context?) In this post, I'll attempt to answer both questions with some simple math.

Setup

I've been doing some pre-production work on a storybook game for iOS devices. The game will likely be composed of full-screen scenes with hand-drawn background plates. I don't want to have require new background plates for every resolution (much less aspect ratio), so the plan is to create one background and fit it to the screen.

So, depending on the screen aspect ratio (in this case: 4:3 on the left, 16:9 on the right), some of the image will be cropped, either off the sides or off the top and bottom.

Minimizing Waste

Since I'm not of a mind to have something drawn that isn't needed, one possible definition of "right" aspect ratio is the one that minimizes wasted area in the background image. So how much area is wasted?

Well, denoting the aspect ratio of the screen s and the aspect ratio of the background image b, and -- without loss of generality -- assuming the screen height is 1, it is easy to draw a picture of the situation:

So, if the background is wider than the screen, the percentage of the background image wasted is (b-s) / b. Similarly, when the background is narrower, the waste is (1/b-s) / (1/b).

As expected, on a 4:3 screen a background image of aspect ratio 4:3 wastes the least.

Of course, on iOS one needs to worry about three aspect ratios: 4:3 [iPad], 3:2 [iPhone <= 4], and 71:40 [iPhone >= 5]. (In case you are wondering, 71:40 is 'nearly 16:9'.):

NOTE: I'm only considering landscape aspect ratios because that's the target for this game.

Assuming one wants to give equal weight to background waste on each device, the proper aspect ratio of the background image should minimize the sum of these three curves.

This minimization happens at the 3:2 aspect ratio. Indeed, my intuition is that with equally-weighted devices the minimum-waste aspect ratio will always be the median aspect ratio.

Maximizing Excitement

In the previous section, I counted areas of the background for each time they were cropped -- that is, background that didn't show at 4:3 and at 3:2 was counted as being twice as bad as background that was only cropped at 3:2. Perhaps, instead of obsessing over these lost pixels, one should instead maximize the percentage of the background that is cropped by no device (i.e. the part of the background every screen sees).

What is this percentage? Consider a background of size bx1 to be shown on screens of aspect s1, ..., sN. The width of the visible-on-all-screens region is w = min(b, s1, ..., sN), because the background will be cropped after its aspect exceeds the smallest screen aspect. The height of the visible region is h = min(1, b / s1, ..., b / sN), because of cropping on the top and bottom of edge of the background when it is scaled to the widest aspect ratio. (More intuition: consider scaling the visible region to [horizontally] fit a screen of size sx1. The scale factor is s / b, so it must be the case that h * s / b <= 1; thus h <= b / s.)

Putting these figures together gives percentage 100 * min(b, s1, ..., sN) * min(1, b / s1, ..., b / sN) / (b * 1). Interestingly, this means that maximizing visible area only depends on the narrowest and widest aspect ratio under consideration:

The unfortunate news is that with aspect ratios of 4:3 and 71:40 in the mix there isn't any background size that doesn't end up with about 25% of its area not visible on all screens. The fortunate news is that the best aspect ratio in terms of waste (3:2) is within this 75%-used plateau.

Going Further

In this article I didn't talk at all about foreground content (which must be scaled to fit within each aspect ratio), and only explored two objective functions. Other interesting objective functions might include one weighted for all browser sizes, or one that is concerned with fairness (forcing all screens to lose about the same area of the background).

If you have other ideas, feel free to leave a comment or send an e-mail.

Wednesday, November 6, 2013

Rainbow Update: More Music

I am pleased to report that the iOS version of Rainbow is proceeding well. In fact, a 1.0 version has already been accepted for app store sale (though -- owning to some gameplay changes -- I will probably not release it).

So, what have I been working on? Well, other than finding a real job, tweaking and adjusting Rainbow to be an even better game. Part of that is doing things like adding incidental music to cutscenes -- and that's what this post is about.

The Music

The most recent two tracks I added to Rainbow were an opening and closing theme for the villain (and player's persona), who I will simply call "D.D." herein.

The opening theme is meant to be sinister but energetic, as befits a villain whose goal is to bring color and happiness into the lives of a drab people:

The closing theme is meant to be similar to the opening theme but more mellow, because D.D. is now tired from the effort of playing through the game:

One of the things that I think works really well in both of these tracks is the drums. The main drums are from Reason's factory sound library, but are bit-crushed via a Scream4's "digital" method:

I also automate the 'Rate' parameter of the Scream4 in one of the tracks to get some interesting variation into the drums. It's certainly a rough sound, but it's one that I quite enjoyed using in an earlier track.

The parallel channel through the reverb and compressor is generally mixed very low, but it does provide some interesting texture. (The LFO is being used to sweep the resolution on the Scream4 in this channel, which adds more rhythm to the texture.)

In addition to the main drums, I use a lone synth kick, without any fancy effects, in parts of the track. This kick is used as an sidechain input on the lower pad, in order to make the pad duck the kick (and thus make the kick seem to hit harder):

I doubt Rainbow will be winning any awards for cutscene music design, but these snippets certainly seem better than letting the menu theme continue to play.

Thursday, October 10, 2013

Rktcr Limited Edition (Video)

With just about 12 hours left in the Indie Game Stand sale for Rktcr, I decided to make the following video to give folks a better look at the Rktcr Limited Physical Edition, one of which has now been claimed by the highest contributor in the aforementioned sale.

So if you think this looks cool, start figuring out how to convince me to part with the other copy.

Friday, October 4, 2013

Rktcr's Limited Edition

From October 7th-10th, Rktcr was on pay-what-you-want sale at Indie Game Stand, with 10% of proceeds supporting the EFF. Paying more than average would have netted you an exclusive level pack and a custom digital wallpaper. But that's not what this post is about.

Paying the most would have gotten you one of the only two limited edition physical copies of the game. And these things are amazing. And that is what this post is about.

Each one is an obsessively-crafted testament to my deep love for Rktcr, and feature (from outside to inside):

A paper "Rktcr"-logo band, made from five interleaved sheets, hand-cut and painstakingly assembled.

A Rktcr sprite print box. Each of the four patterns used on these two boxes and their lids are unique and will never appear elsewhere.

A hand-made velvet/foam cushion to showcase...

...a USB drive filled with Rktcr v3.1 and covered in 23K gold leaf, ...

...with a silver-plated chain tassel.

These two limited edition copies are truly amazing things. They, in as non-creepy a sense as possible, reify my deep love for Rktcr, my desire to see it perfected, and my joy at finishing the game. The whole assemblage has a nice weight in the hand, and the drive and chain feel luxurious as you manipulate them.

Tuesday, October 1, 2013

Making Rktcr Levels: Bursts

I'm trying to spend most of my time as of late getting an iOS beta for Rainbow out, so this Rktcr tutorial will be a brief one.

In this tutorial, I will show you how to make bursts -- those little exploding circles that can give the player's vehicle a shove in the right (or wrong) direction.

I will be assuming familiarity with the basics, but nothing more. (Well, other than a full version of Rktcr.)

Bursts

Bursts are actually just gems with the special name. So, to make one, first create a gem (space, gem, enter), then edit its properties (tab), and set the name to burst.

You can still use the front_collide and back_collide properties, like on gems, to change which wheel(s) will trigger the burst.

And that's it! A short tutorial, to be sure, but the upcoming one about style layers will be quite a bit longer. (And, after that, there's only claims and I'll have taught you all there is to know about Rktcr level editing.)

Tuesday, September 24, 2013

Making Rktcr Levels: Disruptable Platforms and Gems

In this Rktcr editing tutorial, I'll talk about how to create disruptable platforms and gems. These are platforms and gems that only one of the vehicle's wheels collide with. This tutorial assumes knowledge of the editor basics.

If you own the full version of Rktcr (and, really, you should), you can grab this level pack to follow along.

In this tutorial, we'll make a level where the player needs to navigate out of the a starting box by letting the front wheel fall through one segment and the back fall through another. After this, the player will need to pick up the final gem with the appropriate wheel (lest it be knocked into the abyss).

Disruptable Platforms

First, we'll make the two short segments in the bottom of the starting area disruptable by going into property edit mode (select, shift-tab), selecting collide (up/down, then enter), and changing the value to 1/2 for the left/right segment (delete, 1/2, enter).

The polygons will now be highlighted in white/gray, indicating that the front/back wheel (only) will collide with them.

Of course, it never hurts to actually check by playtesting the level (select start portal, then press enter).

Disruptable Gems

Making the gem collide (instead of be picked up by) the front wheel is a similar procedure. Select the gem, drop into edit mode (tab) and set the property front_collide to 1:

The gem will be surrounded with a white band to indicate that the front wheel collides with it.

As before, you can always confirm that things are working as you expect by playtesting.

Conclusion

In today's rather short tutorial, I showed you how to make disruptable platforms by setting the collide property on level geometry, and how to make disruptable gems by setting that front_collide and back_collide properties on gems. It is worth noting that -- though we did not do so in this tutorial -- you can also change the collide property on dynamics.

Disruptable platforms and gems can lead to situations where players need to carefully consider the vehicle's orientation. They can add challenge to maze-like levels and also allow for tricky momentum-conservation strategies (like letting the wheel that does not collide spin around the one that does).

Next Steps

I've covered nearly all of the features of the Rktcr editor. The only tutorial subjects that remain are bursts; style layers; and claims.

Tuesday, September 17, 2013

Making Rktcr Levels: Dynamics and Constraints

In this Rktcr level creation tutorial, I will describe how to add dynamic (i.e. physically-simulated) geometry to levels, and how to add constraints between these dynamic bodies. This tutorial builds on ideas introduced in the basics and geometry tutorials.

And, as before, you'll need the latest full version of Rktcr (v3.1 as of this writing).

In this tutorial, I'll start with some existing static geometry, and make some of it dynamic. Above, you can see the plan I'll be following.

If you'd like to follow along, you can download a zip file containing this level (along with the finished version) here. Place the files in your ".rktcr/e_levels" folder, start Rktcr in edit mode, and load tut-dynamics with f4.

Making Dynamics

In Rktcr, all dynamic bodies are created from static geometry, much in the same way smoothed polygons are created from regular polygons.

Select the static geometry you'd like to make dynamic with right-click, press space to open the creation prompt, type dynamic (or dtab, if you're feeling lazy), and press enter.

You can make a dynamic body static again by selecting it (right click) and pressing x. (This deletes the "dynamic" modifier.)

Dynamics Parameters

Dynamics have two types of edit mode: geometry (tab), and parameter (shift-tab). Geometry edit mode does what you'd expect -- edits the geometry underlying the dynamic. Parameter edit mode allows you to change some physical (and physics-effecting) parameters:

  • source_tag shouldn't be changed.
  • seed is the seed for the (somewhat randomized) convex decomposition algorithm. If you don't like how the dynamic is being split into red and green regions, change this.
  • gravity is the direction of gravity effecting the block (0-7 are fixed, 8 is "down relative to the current view").
  • density is what it sounds like. If the object seems too heavy, use something lower.
  • moment_factor scales the moment of inertia. Increasing/decreasing will make the object spin less/more easily.
  • rel_pos is a starting offset for the object as (x,y,theta). It don't recommend using it.

Warning

When working when dynamics, save often! Sometimes, the convex decomposition produced by Rktcr's code can contain degenerate polygons, which -- in turn -- will make Box2D (which Rktcr uses for simulation) assert() when you attempt to test a level.

There will be a fix for this in the next version of Rktcr. Until then, save before you test. (The -- admittedly cumbersome -- work-around is to change the seed value used on the problematic dynamic until you find one where the editor doesn't exit upon running the level.)

Testing

This is what the level looks like after creating all the required dynamics:

At this point -- be sure to save! -- you can press enter to go into physics test mode. (If you end up in play test mode, it's because a portal side was selected.) In physics test mode time runs inexorably forward, and you can grab and pull on objects using the mouse.

Physics test mode will be useful as we create our various constraints.

Constraints

Plain dynamics bodies are fine for creating stacks and tippy platforms, but for the sort of stunts we have planned for this level, we need some physics constraints. Physics constraints are created in the Rktcr editor by pressing c. This creates a constraint between the current dynamic selected and either the previous two dynamic selected or (if the previous selection wasn't a different dynamic) the world.

Another way to say that is: to constrain two dynamics to each-other, right click on them in turn and then press c; to constrain a dynamic to the world, right click on it twice and press c:

Once a constraint has been created, its endpoints may be moved by pressing g while the constraint is selected:

Pressing tab while a constraint is selected allows you to edit its type:

For the hanging circles in the level, we want the constraint type rope, which prevents the constraint points from getting further apart (but does allow them to get closer).

Adding Ropes

Let's finish up the rope constraints. Keep in mind that x can be used to delete constraints, and make sure to set the constraint type to rope (using edit mode -- tab). Also, I find that using the size-1 grid (1) and holding shift (snap to grid), helps to keep things aligned when moving constraint endpoints.

Note that the red/blue color of the lines connecting a constraint to its adjacent dynamics depends on the order in which they were selected, and isn't important to the actual simulation (so if yours don't match mine, don't worry).

At this point (and after saving), you can drop into test mode with enter and make sure everything moves how you want it to (e.g. try dragging the circles with your mouse to get a feel for the rope constraint).

Other Constraints

The first stunt in the level uses rod constraints. Create these as before, but make sure there is no previously selected dynamic by right-clicking on the c-shaped piece twice before pressing c. If you do this properly, one end of the constraint (the one connected to the world) will show as an x instead of a box. Also, make sure to set their type to rod in edit mode (tab).

I slightly offset the bottom sides of the rod constraints because if I let them start vertical the c-shape would fall before the vehicle entered it. (This is one reason test mode is super-useful when working with dynamics.)

Finally, connect the ramp to the background with a pin constraint, so it will pivot up as the vehicle drives under it and then fall back into place. (Note that pin constraints are actually placed at the average of the two endpoints.)

The constraint's type should be pin by default, but it doesn't hurt to hit tab and check.

Finishing Up

All the constraints are now in place:

Generally, it's good to playtest levels as you go along, but I elided that for the sake of this tutorial. So, the question becomes: is this a winnable level?

Well, the last part is quite tricky (removing the circles might be a good idea), but, yeah, it is possible.

Next Steps

Dynamics are one way to add excitement (and chaos) to a level, but certainly not the only way. In upcoming tutorials, I'll talk about other methods, including working with disruptable platforms and gems; and adding bursts. I'll also go into making your level prettier using style layers; and giving folks a par time using claims.

Sunday, September 15, 2013

Brighter 'Bows

The HTML5 version of Rainbow features vibrantly colored Rainbows; when porting to C++, I traded off a bit of the brightness of the rainbows for softer intersection behavior. This weekend, I set out to brighten the bows in the C++ port, while retaining nice bow intersections.

html5: bright bows

old C++: dull bows

new C++: bright bows

As you can see, the C++ version has been improved to have bows as bright as the HTML5 version.

The Goal

In Rainbow, the player can split the rainbow they control into multiple "fronts" and drive these fronts separately. Each front leaves a rainbow trail.

I would like several conditions to hold:

  1. Trails freshly produced by a front should draw over older trails.
  2. Fronts should always remain visible.
  3. No hard edges should be introduced.

To justify briefly: (1) arises because I want players to see what they are currently controlling; (2) is important because players should be able to see how much rainbow they have left; and (3) just seems like how magical rainbow substance should behave.

The Old Solutions

HTML5

In the HTML5 version of Rainbow, the entire rainbow is redrawn each frame with each vertex's depth set based on the number of ticks from the beginning of the rainbow. This method actually fails all three of my ideal conditions in some cases, e.g. when drawing with an older front.

C++

In the C++ port, the rainbow is accumulated into a framebuffer. This pretty much guarantees that condition (1) holds. Condition (2) is satisfied by always drawing (but not accumulating) the very front of the bow over the top of everything in a different rendering pass.

This leaves condition (3) -- no hard edges. This is solved by drawing the blow slowly instead of all at once. Specifically, every time a front moves, it draws over its last 10 positions with partially-transparent segments. This has the effect of slowly building up the bow color over 10 steps:

Unfortunately, because it draws over each of the old positions with the same opacity (alpha value) -- set, in an ad-hoc way to 1/8th -- the effective opacity of the bow is only about 73%.

The New Solution

Let me start by considering a simpler version where we only draw over the last 4 locations. Labeling the segments that get drawn as 1-4 and the locations traversed a-g (both from oldest to newest), one can sketch this diagram:


  ...               ...
frame 4:         1 2 3 4
frame 3:       1 2 3 4
frame 2:     1 2 3 4
frame 1:   1 2 3 4
          ---------------
location:  a b c d e f g

I.e. location a will be drawn into by segment 1, and then no others; location b gets drawn by 2 then 1, location c by 3 then 2 then 1, and so on.

So what opacity value should we pick for segment 4 if we want locations d and beyond to have opacity αtarget? Well, it depends what the next passes (1-3) will draw over it. Let's say that segments 1-3, when drawn, will cover fraction αnext of a pixel. Then we can select opacity value x for segment 4 by writing down the blending equation:

αtarget = αnext + (1 - αnext)x

αtarget - αnext = (1 - αnext)x

x = (αtarget - αnext)/(1 - αnext)

This passes some basic sanity checks -- if we want 100% coverage after segments (1-3) are drawn (i.e. αtarget = 1), we need to draw segment 4 with 100% opacity (i.e. x = 1). Also, if αnext is greater than αtarget then we get negative values -- which makes sense, as segment 4 can't prevent the subsequently drawn segments from covering the location, so it needs to attempt to preemptively remove coverage.

Implementation

Since I'd like the rainbow to smoothly fade in from a cleared start, I chose a "fade" opacity value, f to vary smoothly from 0% coverage (at the trailing edge of the oldest segment) to 100% (at the leading edge of the newest).

In the pixel shader, other factors that influence opacity -- band color, edge-of-band "antialiasing" texture -- are looked up. Letting their product be called m, the shader sets:

αtarget = f * m

αnext = min(1.0, f + 1/10) * m

These values are used to compute the final opacity value x as outlined above. This results in a variable-opacity front that produces a more vibrant bow:

Next Steps

I was a bit worried about having a divide in the pixel shader, but profiling on iOS shows negligible effect on frame time.

Since all of this math is actually done on 8-bit color values, some inaccuracy (and banding) does result. I could avoid this (and the above-mentioned divide) by doing the computation of x with a look-up-table that takes these errors into account. However, the artifacts are not so severe that this is a high priority.

Some Methods That Didn't Work

Before settling on the present solution, I tried all sorts of other ideas including multi-pass rendering and different blending modes. While none of them were what I wanted, they did produce some interesting pictures.