Multi-App User Interace (MAUI)
Graphic User Interfaces (GUIs) are a essential component of any 3D game development. Out of all the systems of a Game, I have yet to find a GUI solution designed specifically with GAMES in mind. In using other GUI Libs, I have often found myself in a situation that requires significant modification or re-invention. As much as I dread writing a Yet Another GUI, I just could not find a solution that provided all the features I desired.
Feature 1: Operate Like a Web Browser.
For DarkMORG, I desired that the GUI operate similar to a Web Browser, in which the Menus are downloaded from a Remote Server and formatted and displayed on the client. This functionality would allow me to provide web-like applications within the Client with minimum need to patch. Thus, was born Multi-App User Interface (MAUI). MAUI is a hybrid of Markup and Scripting Languages (HTML/XML/PHP/LUA). A Custom Set of XML Tags (based on the HTML Image Map Tag) is used to format GUI Controls, define their audio/visual properties. Like Webpages, MAUI files are stored on a Host HTTP Server as .PHP (Server-Side Scripting Support) and downloaded and saved to the client as .MAUI files. Client-Side Scripting is supported with LUA. However, unlike webpages, all Source Files (images, sounds, other media) are required to be preloaded on the Client to minimize menu loading time. All Media downloading is handled by DarkMORG's FTP Manager.
Feature 2: One Pointer, Mulitple Controllers.
The Pointer, simply put is a GUI object that moves about the screen controlled by a User via Input Device (Keyboard, Mouse, Joystick, etc). It's important to me that the Pointer be decoupled from the mouse, allowing various input devices including the keyboard to be used in controlling it. The Cursor is a GUI object thats assists the Pointer in doing its job. It can be used to mark position or highlight a selection.
Feature 3: Gizmos
Gizmos are GUI objects that define hotspots on screen which can interact with the Pointer. This was a inspiration I took from the HTML Image Map. Of course, I made radical modifications in MAUI. Gizmos use Sprites for display. I chose the name
Gizmo based on its definition 'A mechanical device or part whose name is unknown'. Essentially, A Gizmo is a GUI control whose behavior is unknown, as there are no pre-defined behaviors for buttons (which are commonly called
gadgets in other GUIs.)
Feature 3b. 3D Gizmos
Although, not implemented at this time, I intend to add 3D Gizmos to MAUI. Their function will be very similar to 2D Gimzos using 3D Meshes in place of hotspots/sprites that can be positioned anywhere in 3D space to interact with the Pointer.
Feature 4: Event-Based Finite State Machines
MAUI is completely designed around the concept of GUI Events which are the basis for a model of behavior composed of a finite number of states, transitions between those states, and actions. An Event is a timed state change based on Pointer-vs-Gizmo Collision Detection, Key/Button Detection, and Event Flags. These `condition checks` are the basis for many different types of Events in MAUI.
For Example: Lets say the Pointer is controlled by a mouse and theres a single gizmo 64 x 16 pixels in the center of the screen. A `ENTER` Event occurs when the Pointer initially collides with a Gizmo. If the Pointer remains within the bounds of Gizmo for a specified time, the Event is changed to 'HOVER'. While 'HOVER'-ing, if a button press occurs, the event type is changed to `DOWN`. Upon release of the button during 'DOWN' changes the event to 'UP' the gizmo Action is executed.
Feature 5: Scriptable Behaviors
We are all familar with typical Buttons, Labels, Sliders, etc. These are often referred to as Gadgets in other GUI's. In MAUI these are referred to as Behaviors. In other words, a Behavior is event-driven logic that controls the `mechanical` operation of a gizmo. The example above, describes the Behavior of a typical Button. However, Games can require Gizmos with unusual Behaviors. To support any type of Behavior, I implemented a Scriptable Behavior System. The implementation allows the creation of many types of behaviors both traditional and non-traditional.
Feature 6: Image-based Styles
When I originally conceived MAUI, the goal was support lots of text and nothing but text. I thought this would be necessary for good text-heavy storytelling. A Nothing-But-Text UI was extremely ugly so I reduce the text and added some border drawing functions to improve the visual definition of a Gizmo. Additionally, I quickly discovered performance issue with using text generated with true type fonts. I found myself employing several image capturing and rendering techniques to improve performance. Shortly after, I decided to fully integrate pre-generated Images, Bitmap Fonts, Bitmap Border Sets into the event-based Style System.
Feature 7: Transition Effects
Modern Games have very active GUIs complete with sound and animation. I like games with Active GUIs. I want a Active GUI. I want a Scriptable Transitions Effect System. Transitions are animation effects executed between Event Changes. Animations are simple and programmable via scripts. MAUI implements Transitions in the same fashion Styles and Behaviors are implemented.
Feature 8: Controls Keyboard/Mouse Input
Keyboard & Pointer Device Input control is integrated into MAUI.
MAUI v2 Example
<maui version = "2.0">
<!-- S T Y L E S -->
<style name="default" description ="MAUI Default Event Style.">
<event type="IDLE"><font face="Arial" color="255,255,255" bgcolor="0,0,0" size="16"/></event>
<event type="ENTER">
<font face="Arial" color="0,255,255" bgcolor="0,0,0" size="16"/>
<bgsound src="Audio/Sound/mouseover.wav"/>
</event>
<event type="HOVER"><font face="Arial" color="0,255,255" bgcolor="0,0,0" size="16"/></event>
<event type="DOWN"><font face="Arial" color="0,255,255" bgcolor="0,0,0" size="16"/></event>
<event type="HOLD"><font face="Arial" color="0,255,127" bgcolor="0,0,0" size="16"/></event>
<event type="DRAG"><font face="Arial" color="0,255,127" bgcolor="0,0,0" size="16"><i>caption</i></font></event>
<event type="UP"><font face="Arial" color="0,255,0" bgcolor="0,0,0" size="16"/></event>
<event type="OUT">
<font face="Arial" color="255,255,255" bgcolor="0,0,0" size="16"/>
<bgsound src="Audio/Sound/mouseout.wav"/>
</event>
<cursor color="255,0,0" bgcolor="255,255,255" text="0,0,127" src=""/>
<pointer>
<event type="IDLE" src=""/>
<event type="WAITING" src=""/>
</pointer>
</style>
<!-- B E H A V I O R S -->
<behavior name="label">
<event type="IDLE"></event>
</behavior>
<behavior name="button">
<event type="IDLE">
<script language="lua" type="behavior">
if MAUI_PointerGizmoCollision == 1 then
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','ENTER'))
end
</script>
</event>
<event type="ENTER">
<script language="lua" type="behavior">
if MAUI_PointerGizmoCollision == 1 then
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','HOVER'))
else
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','OUT'))
end
MAUI_StyleEventID = DBPro.Call('MAUI_StyleEventIDTableGet',DBPro.Call('AsInt',DBPro.Call('MAUI_GizmoPropertyGet',MAUI_GizmoID,'StyleID')), DBPro.Call('AsInt',DBPro.Call('MAUI_GizmoEventTypeGetAsString','ENTER')))
if DBPro.Call('MAUI_StyleEventPropertyGet',MAUI_StyleEventID,'AudioID') ~= '0' then
DBPro.Call('MMO_AudioSoundPlay',DBPro.Call('AsInt',DBPro.Call('MAUI_StyleEventPropertyGet',MAUI_StyleEventID,'AudioID')))
end
</script>
</event>
<event type="HOVER">
<script language="lua" type="behavior">
MAUI_GizmoEventTypeCounter = DBPro.Call('MAUI_GizmoPropertyGet',MAUI_GizmoID,'EventTypeCounter')
MAUI_GizmoEventTypeCounter = MAUI_GizmoEventTypeCounter + 1
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventTypeCounter',DBPro.Call('AsString',MAUI_GizmoEventTypeCounter))
if MAUI_GizmoEventTypeCounter == 60 then
DBPro.Call('MAUI_PointerPropertySet','Description',DBPro.Call('MAUI_GizmoPropertyGet',MAUI_GizmoID,'Description'))
end
if MAUI_PointerGizmoCollision == 1 then
if MAUI_PointerClick == 1 then DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','DOWN')) end
MAUI_PointerZ = DBPro.Call('MAUI_PointerPropertyGet','Z')
<?php /*
if MAUI_PointerZ > 0 then DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','SCROLLUP')) end
if MAUI_PointerZ < 0 then DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType', DBPro.Call('MAUI_GizmoEventTypeGetAsString','SCROLLDOWN') ) end
*/ ?>
else
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','OUT'))
end
</script>
</event>
<event type="DOWN">
<script langauge="lua" type="behavior">
if MAUI_PointerGizmoCollision == 1 then
if MAUI_PointerClick == 0 then
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','UP'))
DBPro.Call('MAUI_PointerPropertySet','ClickedGizmoID','0')
elseif MAUI_PointerClick == 1 then
MAUI_GizmoEventTypeCounter = DBPro.Call('MAUI_GizmoPropertyGet',MAUI_GizmoID,'EventTypeCounter')
MAUI_GizmoEventTypeCounter = MAUI_GizmoEventTypeCounter + 1
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventTypeCounter',DBPro.Call('AsString',MAUI_GizmoEventTypeCounter))
if MAUI_GizmoEventTypeCounter == 16 then DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','HOLD')) end
DBPro.Call('MAUI_PointerPropertySet','ClickedGizmoID',MAUI_GizmoID)
end
else
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','OUT'))
end
</script>
</event>
<event type="UP" click="1">
<script langauge="lua" type="behavior">
DBPro.Call('MAUI_PointerPropertySet','DraggedGizmoID',0)
DBPro.Call('MAUI_PointerPropertySet','ClickedGizmoID',0)
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','IDLE'))
MAUI_StyleEventID = DBPro.Call('MAUI_StyleEventIDTableGet',DBPro.Call('MAUI_GizmoPropertyGet',MAUI_GizmoID,'StyleID')), DBPro.Call('MAUI_GizmoEventTypeGetAsString','UP'))
MAUI_StyleEventFontSetID = DBPro.Call('MAUI_StyleEventSet','MAUI_StyleEventID')
if DBPro.Call('MAUI_StyleEventPropertyGet',MAUI_StyleEventID,'AudioID') ~= '0' then
DBPro.Call('MMO_AudioSoundPlay',DBPro.Call('AsInt',DBPro.Call('MAUI_StyleEventPropertyGet',MAUI_StyleEventID,'AudioID')))
end
DBPro.Call('MAUI_GizmoEventScriptExecute',MAUI_GizmoID,DBPro.Call('AsInt',DBPro.Call('MAUI_GizmoPropertyGet','EventTypeOld')))
</script>
</event>
<event type="OUT">
<script langauge="lua" type="behavior">
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'EventType',DBPro.Call('MAUI_GizmoEventTypeGetAsString','IDLE'))
MAUI_StyleEventID = DBPro.Call('MAUI_StyleEventIDTableGet',DBPro.Call('MAUI_GizmoPropertyGet',MAUI_GizmoID,'StyleID')), DBPro.Call('AsInt',DBPro.Call('MAUI_GizmoEventTypeGetAsString','OUT')))
if DBPro.Call('MAUI_StyleEventPropertyGet',MAUI_StyleEventID,'AudioID') ~= '0' then
DBPro.Call('MMO_AudioSoundPlay',DBPro.Call('AsInt',DBPro.Call('MAUI_StyleEventPropertyGet',MAUI_StyleEventID,'AudioID')))
end
</script>
</event>
<!-- T R A N S I T I O N S -->
<transition name="ghost">
<event type="HOVER">
<script language="lua" type="transition" name="fadein">
if MAUI_TransitionEventAlpha == 255 then
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'State','ACTIVE')
else
MAUI_TransitionEventAlpha = MAUI_TransitionEventAlpha + 15
DBPro.Call('SetSpriteAlpha',MAUI_GizmoEventTypeImageID,MAUI_TransitionEventAlpha)
end
</script>
</event>
<event type="OUT">
<script language="lua" type="transition" name="fadeout">
if MAUI_TransitionEventAlpha == 0 then
DBPro.Call('MAUI_GizmoPropertySet',MAUI_GizmoID,'State','ACTIVE')
else
MAUI_TransitionEventAlpha = MAUI_TransitionEventAlpha - 15
DBPro.Call('SetSpriteAlpha',MAUI_GizmoEventTypeImageID,MAUI_TransitionEventAlpha)
end
</script>
</event>
</transition>
<!-- G I Z M O S -->
<img src="2D/blank.jpg" width="1024" height="768" border="0" usemap="MMO_Editor" bgsound="Audio/Music/mmo_clientstart.mp3"/>
<map name="MMO_Editor">
<area shape="rect" coords="0,0,1024,24" alt="" style="default" behavior="label" align="center" border="3">
<event type="idle" count="0" caption="Modular Entity Construction Set (MECs) Editor v.1 by F.L.Taylor"></event>
</area>
<area shape="rect" coords="0,24,100,48" alt="Construct" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Construct"></event>
<event type="up" count="0" caption="Construct"></event>
</area>
<area shape="rect" coords="100,24,200,48" alt="Entity" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Entity"></event>
<event type="up" count="0" caption="Entity"></event>
</area>
<area shape="rect" coords="200,24,300,48" alt="Map" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Map"></event>
<event type="up" count="0" caption="Map"></event>
</area>
<area shape="rect" coords="300,24,400,48" alt="Quest" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Quest"></event>
<event type="up" count="0" caption="Quest"></event>
</area>
<area shape="rect" coords="400,24,500,48" alt="Dialog" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Dialog"></event>
<event type="up" count="0" caption="Dialog"></event>
</area>
<area shape="rect" coords="500,24,600,48" alt="Animation" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Animation"></event>
<event type="up" count="0" caption="Animation"></event>
</area>
<area shape="rect" coords="600,24,700,48" alt="Audio" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Audio"></event>
<event type="up" count="0" caption="Audio"></event>
</area>
<area shape="rect" coords="700,24,800,48" alt="Particle" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Particle"></event>
<event type="up" count="0" caption="Particle"></event>
</area>
<area shape="rect" coords="800,24,900,48" alt="Lensflare" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Lensflare"></event>
<event type="up" count="0" caption="Lensflare"></event>
</area>
<area shape="rect" coords="900,24,1000,48" alt="Asset" style="default" behavior="button" transition="ghost" align="center" border="3">
<event type="idle" count="0" caption="Asset"></event>
<event type="up" count="0" caption="Asset"></event>
</area>
<area shape="rect" coords="1000,24,1024,48" alt="" style="default" behavior="lable" align="center" border="3">
<event type="idle" count="0" caption=""></event>
</area>
<area name="exit" shape="rect" coords="934,745,1015,763" style="default" behavior="button" transition="ghost" alt="Exit MMO Client" border="1">
<event type="idle" count="0" function="" action="1" src="2D/mmo_clientstartexit.jpg" caption="Exit"></event>
<event type="hover" count="0" caption="Leaving?"></event>
<event type="up" count="0" caption="Bye Bye!">
<script language="lua" type="action">
DBPro.Call('MMO_ClientTerminate');
</script>
</event>
</area>
</map>
</maui>