Game Development Community

dev|Pro Game Development Curriculum

Force Display Ratio Using Letterboxes For TGEA

by Ben McIntosh · 07/29/2009 (4:25 pm) · 4 comments

We are making a 3D side-scroller (e.g. Shadow Complex) and have been looking for a good way to force a widescreen render area to ensure a consistent gameplay experience. I wanted it be be flexible enough to handle dynamic resolutions where you are free to change the size of the canvas in windowed mode. I also wanted the option of having a pattern on the letterboxes instead of just plain black.

First, we only need to add 2 lines of code to the engine source in order to provide a callback to tell the letterbox graphics (or any gui control for that matter) to update themselves. I originally posted this resource which provides a callback to the canvas control when the window is resized. You can easily use that to accomplish what we are trying to do, but the following is a little more elegant allowing each letterbox bitmap to control itself.

In engine/source/gui/core/guiControl.cpp
...
 void GuiControl::parentResized(const RectI &oldParentRect, const RectI &newParentRect)
 {
     ...
     // Resizing Re factor [9/18/2006]
     // Only resize if our minExtent is satisfied with it.
     Point2I minExtent = getMinExtent();
     if( newExtent.x >= minExtent.x && newExtent.y >= minExtent.y )
+    {
         resize(newPosition, newExtent);
+        if(isMethod("onParentResize"))
+            Con::executef(this, "onParentResize", Con::getIntArg(newParentRect.extent.x), Con::getIntArg(newParentRect.extent.y));
+    }     
 }
 ...
In a graphics editor, create a small tile (like 64 by 64) and either make it black or have some pattern. I am using the Garage Games logo on a black background. Save this image as scriptsAndAssets/client/ui/letterboxTile.png.

www.urbanbrainstudios.com/public/ben/letterboxResource/letterboxTile.png

Next, edit scriptsAndAssets/client/ui/playGui.gui using the gui editor or just a text editor. Add four new guiBitmapControls named LetterboxTop, LetterboxBottom, LetterboxLeft, and LetterboxRight. The only settings that matter are that the bitmap is set to the letterboxTile.png that you made and that the wrap is set to true. The code generated in the gui file should look something like this:
...
   new GuiBitmapCtrl(LetterboxTop) {
      canSaveDynamicFields = "0";
      Enabled = "1";
      isContainer = "0";
      HorizSizing = "relative";
      VertSizing = "relative";
      position = "0 0";
      Extent = "64 64";
      MinExtent = "8 2";
      canSave = "1";
      Visible = "1";
      hovertime = "1000";
      bitmap = "scriptsAndAssets/client/ui/letterboxTile.png";
      wrap = "1";
   };
   new GuiBitmapCtrl(LetterboxBottom) {
      canSaveDynamicFields = "0";
      Enabled = "1";
      isContainer = "0";
      HorizSizing = "relative";
      VertSizing = "relative";
      position = "0 0";
      Extent = "64 64";
      MinExtent = "8 2";
      canSave = "1";
      Visible = "1";
      hovertime = "1000";
      bitmap = "scriptsAndAssets/client/ui/letterboxTile.png";
      wrap = "1";
   };
   new GuiBitmapCtrl(LetterboxLeft) {
      canSaveDynamicFields = "0";
      Enabled = "1";
      isContainer = "0";
      HorizSizing = "relative";
      VertSizing = "relative";
      position = "0 0";
      Extent = "64 64";
      MinExtent = "8 2";
      canSave = "1";
      Visible = "1";
      hovertime = "1000";
      bitmap = "scriptsAndAssets/client/ui/letterboxTile.png";
      wrap = "1";
   };
   new GuiBitmapCtrl(LetterboxRight) {
      canSaveDynamicFields = "0";
      Enabled = "1";
      isContainer = "0";
      HorizSizing = "relative";
      VertSizing = "relative";
      position = "0 0";
      Extent = "64 64";
      MinExtent = "8 2";
      canSave = "1";
      Visible = "1";
      hovertime = "1000";
      bitmap = "scriptsAndAssets/client/ui/letterboxTile.png";
      wrap = "1";
   };
   ...
In the same gui file, edit with a text editor and add the following below the //--- OBJECT WRITE END --- line:
...
//--- OBJECT WRITE END ---

// Ratio set to 16:9 wide screen format
$DisplayRatio = 9/16;  // The math turns out easier when the ratio is height/width

function LetterboxTop::onParentResize(%this, %width, %height)
{
   echo("New Window Dimensions:" SPC %width SPC %height SPC "New Ratio:" SPC (%height / %width));
   // Check to see if we even need this letterbox
   if (%height / %width <= $DisplayRatio)
   {
      LetterboxTop.setVisible(false);
      return;
   }
   else
      LetterboxTop.setVisible(true);
   
   // Calculate heights and vertical placement
   %wideScreenHeight = %width * $DisplayRatio;
   %letterboxHeight = (%height - %wideScreenHeight) * 0.5;

   // Scale and place letterbox in the correct position
   LetterboxTop.setExtent(%width, %letterboxHeight);
   LetterboxTop.setPosition(0, 0);
}

function LetterboxBottom::onParentResize(%this, %width, %height)
{
   // Check to see if we even need this letterbox
   if (%height / %width <= $DisplayRatio)
   {
      LetterboxBottom.setVisible(false);
      return;
   }
   else
      LetterboxBottom.setVisible(true);
   
   // Calculate heights and vertical placement
   %wideScreenHeight = %width * $DisplayRatio;
   %letterboxHeight = (%height - %wideScreenHeight) * 0.5;

   LetterboxBottom.setExtent(%width, %letterboxHeight);
   LetterboxBottom.setPosition(0, %letterboxHeight + %wideScreenHeight);
}

function LetterboxLeft::onParentResize(%this, %width, %height)
{
   // Check to see if we even need this letterbox
   if (%height / %width >= $DisplayRatio)
   {
      LetterboxLeft.setVisible(false);
      return;
   }
   else
      LetterboxLeft.setVisible(true);
   
   // Calculate widths and horizontal placement
   %wideScreenWidth = %height / $DisplayRatio;
   %letterboxWidth = (%width - %wideScreenWidth) * 0.5;

   // Scale and place letterbox in the correct position
   LetterboxLeft.setExtent(%letterboxWidth, %height);
   LetterboxLeft.setPosition(0, 0);
}

function LetterboxRight::onParentResize(%this, %width, %height)
{
   // Check to see if we even need this letterbox
   if (%height / %width >= $DisplayRatio)
   {
      LetterboxRight.setVisible(false);
      return;
   }
   else
      LetterboxRight.setVisible(true);
   
   // Calculate widths and horizontal placement
   %wideScreenWidth = %height / $DisplayRatio;
   %letterboxWidth = (%width - %wideScreenWidth) * 0.5;

   LetterboxRight.setExtent(%letterboxWidth, %height);
   LetterboxRight.setPosition(%letterboxWidth + %wideScreenWidth, 0);
}
...
This will allow the letterbox bitmaps to resize and position themselves when the window size changes. This example is set up to force a 16:9 ratio but it can be anything by changing the the $DisplayRatio variable. The last thing we need to do is get the letterboxes to initialize when the game starts. To do that,

In scriptsAndAssets/client/init.cs
...
 function initClient()
 {
    ...
    // Connect to server if requested.
    if ($JoinGameAddress !$= "") {
       // If we are instantly connecting to an address, load the
       // main menu then attempt the connect.
       loadMainMenu();
       connect($JoinGameAddress, "", $Pref::Player::Name);
    }
    else {
       // Otherwise go to the splash screen.
       Canvas.setCursor("DefaultCursor");
       loadStartup();
    }
    
+   // Initalize Letterboxes
+   %extent = PlayGui.getExtent();
+   %width = getWord(%extent, 0);
+   %height = getWord(%extent, 1);
+   LetterboxTop.onParentResize(%width, %height);
+   LetterboxBottom.onParentResize(%width, %height);
+   LetterboxLeft.onParentResize(%width, %height);
+   LetterboxRight.onParentResize(%width, %height);
 }
 ...
That's it! Run your game and you should see your letterboxes come up to force the ratio to whatever you define. Here are some example shots with the target ratio set to 16:9 and various window sizes:

www.urbanbrainstudios.com/public/ben/letterboxResource/letterbox_4_3.jpg www.urbanbrainstudios.com/public/ben/letterboxResource/letterbox_tall.jpg
www.urbanbrainstudios.com/public/ben/letterboxResource/letterbox_wide.jpg

About the author

Ben is the co-founder of Urban Brain Studios; a new independent developer of games and game development tools.


#1
07/29/2009 (6:46 pm)
Awesome!

Nice extension of your previous resource :D
#2
07/29/2009 (9:56 pm)
Pretty cool work!
#3
07/30/2009 (12:08 pm)
Nice resource, letterbox can be helpful. Thanks!
#4
08/14/2009 (7:46 am)
Thanks for this, I got it working in TGE with just a few changes.