Page 1 of 1

Howto: Resolution Independent GUIs

Posted: Thu Jan 15, 2004 4:57 pm
by saigumi
Howto: Resolution Independent GUIs

I have a co-worker that hates GUIs. He prefers Text command shells more than any fancy interface. I don't hate GUIs, I just hate games that don't think about their GUIs at different resolutions.

I have played many AAA titles that have GUI's that are unreadable at any resolution over 800x600 because they are so small. Even worse, other games "work around" this limitation by locking you to either 640x480, 800x600, or 1024x768. In this day and age where most LCD monitors have a native resolution of 1280x1024 and many normal users have 19" monitor with the resolution set to 1280x1024 or 1600x1200, developers should strive to allow the player to set the game to whatever resolution they desire and share a similiar experience. (Of course, the 3D at higher resolutions will look better.)

Lets start out by looking at the most commonly used resolutions.

Code: Select all

Resolution  Ratio(to 640x480)
640x480     1:1
800x600     1.25:1
1024x768    1.6:1
1280x960    2:1
1600x1200   2.5:1

Special
1280x1024   2:1 / 2.1333~:1
I consider 1280x1024 as special because it doesn't follow similiar scaling rules as all of the other resolutions. For some reason, it is mostly used by games published in the US, while to more approriate 1280x960 comes from the Old World countries. Us crazy Americans and our inches instead of meters.

Anyhow, at this point, I quickly generated a GUI class that wraps the Irrlicht scenemanager functions to handle any scaling needed on the GUI level. This allows me to concentrate my mind on one resolution for positioning elements and everything will look the same at any resolution.

GUIHandler.h

Code: Select all

#ifndef __C_GUIHANDLER_H_INCLUDED__
#define __C_GUIHANDLER_H_INCLUDED__

#include <irrlicht.h>

class GUIHandler {

public:

	GUIHandler(irr::scene::ISceneManager* iSmgr);
	~GUIHandler();

	irr::f32 GUIHandler::ScaleValuebyScreenHeight(irr::f32 iValue);

private:

	irr::scene::ISceneManager* smgr;
	irr::video::IVideoDriver* driver;
	irr::dimension2d<irr::f32> scale;

};
#endif
GUIHandler.cpp

Code: Select all

#include "GUIHandler.h"

/*----------------------------------------------------------------------------------------------
Constructor
----------------------------------------------------------------------------------------------*/
GUIHandler::GUIHandler(irr::scene::ISceneManager* iSmgr) {
	smgr = iSmgr;
	driver = smgr->getVideoDriver()
	scale.Height = driver->getScreenSize().Height / 640;
	scale.Width = driver->getScreenSize().Width / 480;
}

/*----------------------------------------------------------------------------------------------
Destructor
----------------------------------------------------------------------------------------------*/
GUIHandler::~GUIHandler() {
}

/*----------------------------------------------------------------------------------------------
Scale a Variable
----------------------------------------------------------------------------------------------*/
irr::u32 GUIHandler::ScaleValuebyScreenHeight(irr::u32 iValue) {
	return (irr::u32)(iValue * scale.Height);
}
Here, we have a simple helper class that will scale GUI objects based on a resolution. In the constructor, the scale for the height and width is set in reference to a base 640x480 resolution. If you plan to make your minimal resolution higher, just change those values. In my actual code, I set 800x600 to be the minimum base and everything scales off of that.

This class only has one function at the moment which is to Scale a Value by the Screen's Height. All it does is take a value and apply the proper scaling. It is just an example and will not be used in the rest of the class.

At this point, functions need to be added to wrap all of the Irrlicht GUI functions to adjust them all for the scaling. The two points to remember to scale is the Position and the Size of the elements.

For example, lets make a wrapper for the creation of a scrollbar.

Code: Select all

/*----------------------------------------------------------------------------------------------
Create a Scroll Bar
----------------------------------------------------------------------------------------------*/
irr::IGUIScrollBar* GUIHandler::createScrollBar(irr::dimension<irr::u32> Position; irr::dimension<irr::u32> Size, irr::IGUIElement* Parent = 0, irr::s32 id = -1;) {
	irr::IGUIScrollBar* tempBar = smgr->addScrollBar(
		true,
		irr::core<irr::s32>(
			Size.Width*scale.Width,
			Size.Height*scale.Height), 
		Parent, 
		id);
	tempBar->setPosition(
			irr::core<irr::s32>(
				Position.Width*scale.Width,
				Position.Height*scale.Height));

	return tempBar;
}
Note that both the Position and Height were multiplied by the scale. So all that would need to be done in the main application is to call createScrollBar() with the intended size and position at your base resolution.

We can then take this one step further and put some good design practices into play. When I code, I have a defines include that has all of my defines in it so that I can tweak settings there and have every class pick it up. This allows me to have a common place so that I can change one variable and make all the elements based off of it change.

This time, we can extend createScrollBar() into a new function called createSpecificToolbar(). From this, I can call the function to make a specific ToolBar type that I have created the defines for. Note that I no longer include the size.

Code: Select all

/* At the top of the class */
include "defines.h"

/* In the class */
/*----------------------------------------------------------------------------------------------
Create a Specific Type of Scroll Bar
----------------------------------------------------------------------------------------------*/
irr::IGUIScrollBar* GUIHandler::createSpecificBar(irr::dimension<irr::u32> Position; irr::IGUIElement* Parent = 0, irr::s32 id = -1;) {
	irr::IGUIScrollBar* tempBar = smgr->addScrollBar(
		true,
		irr::core<irr::s32>(
			SPECIFICSCROLLBAR_WIDTH*scale.Width, 
			SPECIFICSCROLLBAR_HEIGHT*scale.Height),
		Parent, 
		id);
	tempBar->setPosition(
			irr::core<irr::s32>(
				Position.Width*scale.Width,
				Position.Height*scale.Height));

	return tempBar;
}
The defines file

Code: Select all

//!GUI Elements
#define SPECIFICSCROLLBAR_HEIGHT		20
#define SPECIFICSCROLLBAR_WIDTH			200
Of course, you can change SPECIFIC to be the name of the type of Element you are making like: STATBAR_HEIGHT, GUNSELECTOR_WIDTH...

Posted: Thu Jan 29, 2004 5:21 am
by Masdus1
Nice, but would this work for GUI images. I think the reason alot of game interfaces look wrong in different resolutions is that the use images for most of the gui elements

Posted: Sat Jan 31, 2004 5:24 pm
by rt
nice approach. what i do to keep my gui's (and game objects) independant of screen resolution is to define a macro and use it when i create an object.. like so

Code: Select all

// these macros will return the scaled value 
// based on current screen size
// (i assume 1024x768 is the normal res)
#define SX(val) ( (int)(device->getVideoDriver()->getScreenSize().Width*(float)(val)/1024.0f) )
#define SY(val) ( (int)(device->getVideoDriver()->getScreenSize().Height*(float)(val)/768.0f) )

//usage
rect = rect<irr::s32> buttonpos(SX(10),SY(200),SX(140),SY(240));
that will create a rectangle that if viewed in 1024x768 would look like it's from pixel 10 to 140 in the x-direction, and 200 to 240 in the y-direction. it will look identical in all video modes, however the position will be changed to match the resolution.

note that if the user changes screen resolution on the fly then you will need to re-init any objet which uses the macro.

in addition, i have an internal scaling function which uses the macros to scale the images for different resolutions so that everything looks the same.

Posted: Sat Jan 31, 2004 7:14 pm
by keless
there is one other thing that should be said here--

there are 3 kinds of textured resources
1) splash screens (resolution dependant)
2) textures (resolution in-dependant)
3) animations

now, since we're in 3D, you can simply scale animations, so they're also in-dependant. (this would not be the case if you were working on, say, a 2D cell phone game :P )

but splash screens ARE resolution-dependant. if you want your image to come out crisp and clear, it shouldnt be stretched, which means you should have one of the same resolution for each of your supported resolution sizes.

of course, IrrLicht currently has issues drawing images clearly anyway, so maybe its not a big problem.

Posted: Sat Feb 21, 2004 3:33 am
by Robomaniac
Just to let people know, there is an error in the above code. They line should be

Code: Select all

   irr::core::dimension2d<irr::u32> scale;
just thought i'd let people know

Posted: Mon Feb 23, 2004 3:20 pm
by Guest
I think that the way of rt is better mainly because of one simple reason:

Saigumi has to change his wrapper every time new function appears in the ISceneManager intereface, but rt doesn't ;)

P.S.: just a mall not for Saigumi: I hope you posted here not your code but just an example from your mind cause you have to use scale as a float size, not integer. Otherwise e.g. when user uses 800x600 you'll get scale = {1, 1} in your constructor... ;)

Posted: Mon Feb 23, 2004 6:07 pm
by saigumi
Yeah, I fixed the scale in the GUIHandler, but didn't come back to this and fix it here.

This is a pre-cursor piece to GUIHandler, so it was pulled from more robust code that didn't just set scale, but applied other settings for each gui element. If your just doing scaling though, rt's example works nicely.

Posted: Mon Jul 05, 2004 9:44 pm
by reservoirman
what else would u do besides scaling to gui elements?