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

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:
In scriptsAndAssets/client/init.cs


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.
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:


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

Associate Michael Hall
Distracted...
Nice extension of your previous resource :D