游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

如何为小型游戏创造简单的GUI系统(2)

发布时间:2014-05-31 09:17:42 Tags:,,,,,

作者:Martin Prantl

我们在这篇GUI教程的第1部分中已经讨论过定位和范围系统。

现在,我们将花点时间熟悉一下本篇教程所用的一些基本元素类型。当然,你可以自由设计内容。本部分所提到的控制方式是一些常规内容,也是每个GUI都含有的元素。(请点击此处阅读本系列第13部分

Game GUI(from behance.bet)

Game GUI(from behance.bet)

*面板:通常不渲染,仅用于含有相似功能的群组元素。你可以轻松移动其所有内容或隐藏它。

*按钮:按钮就是普遍的旧式按钮。

*复选框:其基本原理与按钮相同,但有更多状态。

*图像:可用于图标,图像形象化等。

*文本字幕:用于文本渲染。

控制逻辑

控制逻辑是从一个类维护的。这个类负责状态的变化,并且包含与实际控制机制——鼠标或触摸控制相关的参照。目前来看,只能解决单次点触的控制方式。如果我们想设置多点触的GUI控制方式,这就会更复杂一些。我们就必须解决多个操作问题,如果一个手指落下了,另一个手指还在屏幕上“移动”怎么办?如果移动的手指碰到一个已经激活的元素时又会怎样?如果我们放开第一个手指,保持第二个手指的动作又会怎样?我们可以通过观察现成GUI系统的行为来解决这些问题,但如果有更多系统,并且每个系统行为都不一样时又该如何?哪一个更正确?出于这些问题的考虑,我取消了多点触控支持。针对主菜单和其他相似的屏幕,它通常还算OK。但主游戏却会引发一些问题。例如,我们若创造赛车游戏,就必须有多点触控支持。一只手指控制踏板,另一只手指负责方向盘,第三个手指负责变速。因为这几类情况,我们必须采用多点触控支持。但这并非本文讨论的重点,因为我目前还没有使用。我将其牢记在心,也相信所描述的系统可以升级以支持这一控制方式。

我们必须针对每个元素测试控制点(鼠标,手指)在元素上的定位。在此我们要使用前文所计算的位置。由于每个元素基本上都是一个2D AABB(轴线对齐边界框),我们可以在X和Y轴间采用一个简单的间隔测试。注意,我们只测试可视元素。如果某个点位于隐藏元素之中,我们就会绕过它继续测试。

我们还必须解决一个问题。假如元素位于彼此之内,哪一个要先接受操作?我使用了一个简单的深度测试。将屏幕作为其他元素的母体,其深度为0。每个屏幕内的子元素都有一个深度,即母体深度+位移。如此递回推算子元素的子元素深度。拥有最大深度的元素及其内部点就是所谓的“含有焦点”。我们在之后的命名规则中会用到这一点。

我对用户控制器有三个基本陈述:

*CONTROL_NONE:没有摁压控制按钮。

*CONTROL_OVER:控制结束,但没有摁压按钮。

*CONTROL_CLICK:控制结束,摁压按钮。

这完全适用于鼠标控制方式。对于手指和触摸控制来说,CONTROL_OVER并没有实际意义。为了保持简单和移植性,我们保存了这个状态,并在一个含有条件环节的代码逻辑中进行处理。对此我使用了一个前缀 (#ifdef),但它也可以用一个简单的if分支在运行时间中确定。

当我们辨别出含当前焦点的元素时,我们必须做一些事情。首先,对比最后和实际焦点元素。我将在一行注解码中解释这一理念。

if (last != NULL) //there was some focused element as last
{
//update state of last focused element as currently no control state
UpdateElementState(CONTROL_NONE, last);
}

if (control->IsPressed())
{
//test current state of control (mouse / finger)
//if control is down, do not trigger state change for mouse over
return;
}

if (act != NULL)
{
//set state of current element as control (mouse / finger) over
//if control is mouse – this will change state to HOVERED, with finger
//it will go directly to same state as mouse down
UpdateElementState(CONTROL_OVER, act);
}

如果最后和实际焦点元素一致,我们就必须做出一系列不同的反应。

if (act == NULL)
{
//no selected element  – no clicking on it => do nothing
return;
}

if (control->IsPressed())
{
//control (mouse / finger) is down – send state to element
UpdateElementState(CONTROL_CLICK, act);
}

if (control->IsReleased())
{
//control (mouse / finger) is released – send state to element
UpdateElementState(CONTROL_OVER, act);
}

在以上代码中,NULL测试很重要,因为如果该时刻没有任何元素处于焦点,这个条件就是无效的。此外,每回更新也会发送一次控制状态,所以我们必须想出如何将它们变为元素状态,以及如何正确调用触发器。

元素变化和触发行为现在是针对不同类型的元素而设置。我将用以下部分进行总结。为了启动触发器,我使用了来自FastDelegate库/标题(Member Function Pointers and the Fastest Possible C++ Delegates)的指令。这个库非常容易使用,并且极具移植性(iOS、Android、Win……)。在C++11中有数个内置解决方案,但我情愿采用这个库。

针对需要一些触发器的每个元素,我通过Set函数进行添加。如果触发了相关操作,就会调用该指令。另一个方法就是使用操作指针。它们的问题通常与类和成员函数有关。借助指令,你可以轻松维护代码,可以让指令与类函数或成员函数相关联。在这两种情况下,代码仍然相同。唯一的区别在于指令创造。

在C#语言中,你可以在语言核心支持中找到指令,所以根本就不存在问题。使用Java,可能也有一些解决方法,但我并不使用Java,所以不知道究竟有没有。至于其他语言,可能也有一些相似的功能。

元素

首先,我们有理由创造一个每隔一者都能扩展的抽象元素。在该抽象元素中,你将得到位置、维度、色彩及其他有用的信息。特定的功能将分别以扩展这个抽象类的不同类来编码。

1.面板&图像

这两者都没有真正的功能。面板的存在纯粹是为了组合元素,而图像只是为了展示图片。这两者极为相似,你可以为其选择背景色或者为之选择一些纹理。我之所以创造两个不同元素的原因就是为了代码的易读性,并让图像拥有更多背景功能,正如令目标形象化的助手方法一样。

1.1控制逻辑

这相当简单。对这两个元素我没有使用任何状态。当然,你可以自由添加。

2.按钮

这是我准备深入研究的两大有趣元素之一。我建议你在创造GUI时首先为按钮编码。你可以在其中尝试不同的场景——显示纹理,更改纹理,控制交互,渲染等。其他元素基本上是修改过的按钮。

我们的按钮有3个状态:

*非活跃:典型的默认状态

*悬停:这仅适用于鼠标控制模式,它表示鼠标指针在元素之上,但并没有摁压按钮。这个状态并不适用于触摸控制。

*活跃:点击按钮或者鼠标/手指摁压了按钮。

你可以添加更多状态,但一般来说这三者就已足够你实现基本效果和制作美观的按钮。

你至少要为每个按钮添加两种不同的纹理。一者表示默认状态,另一者用于操作状态。没有必要区分活跃和悬念状态,因为它们看起来是一样的。在触摸控制模式中甚至都没有悬念状态,所以这根本就没有什么区别。

与状态变化关系紧密的当属触发器。当按钮切换状态时或者它正处于某状态时就会发生这些操作。你可以想到许多可能的操作,我只使用了有限的触发器。我经常使用的包括:

*onDown:鼠标或手指已经摁压到按钮。

*onClick:放开摁压的按钮后产生的点击(需要一些额外条件)。

*onHover:只适用于鼠标控制。鼠标在按钮上,但没有摁下。

*onUp:鼠标或手指已经在按钮上松开(可以视为没有额外条件的onClick)。

*whileDown:按钮鼠标或手指摁压在按钮上的调用时刻。

*whileHover:按钮鼠标在按钮上,但没有摁压的调用时刻。

我几乎从来没有见过“时刻”触发器。在我看来,它们适用于重复操作,例如点触控制的赛车游戏中的油门踏板。你几乎时时刻刻都要保持对它的控制。

有时候,你需要与含有按钮的复选框相似的功能。这在媒体播放器中的典型例子就是“播放/暂停”按钮。当你点击按钮时,就触发了一个动作,其图标也发生了变化。你可以使用真正的复选框或对按钮进行一点更改。在触发器动作代码中,你只需要更改该按钮所使用的图标设置。请看以下的代码范例。在此我使用一个按钮作为复选框来启动/禁用声音。

void OnClickAction(GUISystem::GUIElement * el)
{
//emulate check box    behaviour with button

if (this->sound_on)
{
//sound is currently on – we are turning it off
//change icon set

GUISystem::GUIButtonTextures t;
t.textureName = “soundoff”; //default texture
t.textureNameClicked = “soundon”; //on click
t.textureNameHover = “soundon”; //on mouse over
el->GetButton()->SetTextures(t);
}
else
{
//sound is currently off – we are turning it on
//change icon set

GUISystem::GUIButtonTextures t;
t.textureName = “soundon”; //default texture
t.textureNameClicked = “soundoff”; //on click
t.textureNameHover = “soundoff”; //on mouse over
el->GetButton()->SetTextures(t);
}

//do some other actions needed to enable / disable sound

}

2.1 控制逻辑

按钮的控制逻辑比起之前提到的状态似乎更简单。它们只有三种基本情况。但其主代码更复杂一点。我已经将其执行方法划分为两部分。第一部分是一条从控制器类向按钮发送状态变化的“信息”(它其实并不是一条信息,而是一些函数调用,但它可以视为一条信息),第二个部分则处理状态变化,并触发基于已接收“信息”的调用。这个部分是直接在按钮类执行中编码。

第一部分,位于控制类中,负责发送“信息”。

if (ctrl == CONTROL_OVER) //element has focus from mouse
{
#ifdef __TOUCH_CONTROL__
//touch control has no CONTROL_OVER state !
//CONTROL_OVER => element has been touched => CONTROL_CLICK
//convert it to CONTROL_CLICK
ctrl = CONTROL_CLICK;
#else
//should not occur for touch control
if (btn->GetState() == BTN_STATE_CLICKED) //last was clicked
{
btn->SetState(BTN_STATE_NON_ACTIVE); //trigger actions for onRelease
btn->SetState(BTN_STATE_OVER); //hover it – mouse stays on top of element after click
//that is important, otherwise it will look odd
}
else
{
btn->SetState(BTN_STATE_OVER); //hover element
}
#endif
}

if (ctrl == CONTROL_CLICK) //element has focus from mouse and we have touched mouse button
{
btn->SetState(BTN_STATE_CLICKED); //trigger actions for onClick
}

if (ctrl == CONTROL_NONE) //element has no mouse focus
{
#ifndef __TOUCH_CONTROL__
btn->SetState(BTN_STATE_OVER); //deactivate (on over)
#endif

if (control->IsPressed())
{
btn->SetState(BTN_STATE_DUMMY); //deactivate – use dummy state to prevent some actions
//associtaed with releasing control (most of the time used in touch control)
}
btn->SetState(BTN_STATE_NON_ACTIVE); //deactivate
}

第二个部分在按钮内编码,并处理已接收“信息”。这还包括点触控制的区别(按钮永远不会接收到悬停状态)。当然,有时候你想保留悬停状态来移植应用,并保持相同的功能。这种情况下,悬停触发器通常与onDown一起被调用。

if (this->actState == newState)
{
//call repeat triggers
if ((this->hasBeenDown) && (this->actState == BTN_STATE_CLICKED))
{
//call whileDown trigger
}

if (this->actState == BTN_STATE_OVER)
{
//call while hover trigger
}

return;
}

//handle state change

if (newState == BTN_STATE_DUMMY)
{
//dummy state to “erase” safely states without trigger
//delegates associated with action
//dummy = NON_ACTIVE state
this->actState = BTN_STATE_NON_ACTIVE;
return;
}

//was not active => now mouse over
if ((this->actState == BTN_STATE_NON_ACTIVE) && (newState == BTN_STATE_OVER))
{
//trigger onHover
}

//was clicked => now non active
if ((this->actState == BTN_STATE_CLICKED) && (newState == BTN_STATE_NON_ACTIVE))
{
if (this->hasBeenDown)
{
//trigger onClick
}
else
{
//trigger onUp
}
}

#ifdef __TOUCH_CONTROL__
//no hover state on touch control => go directly from NON_ACTIVE to CLICKED
if ((this->actState == BTN_STATE_NON_ACTIVE) && (newState == BTN_STATE_CLICKED))
#else
//go from mouse OVER state to CLICKED
if ((this->actState == BTN_STATE_OVER) && (newState == BTN_STATE_CLICKED))
#endif
{
this->hasBeenDown = true;
//trigger onDown
}
else
{
this->hasBeenDown = false;
}

this->actState = newState;

我所展示的代码几乎涵盖了处理按钮控制的一切内容。

3.复选框

第二个复杂的元素是复选框。它的功能类似于按钮,但拥有更多状态。我不打算像描述按钮那样详细讨论其状态变化和处理方法。它与按钮极为相似,你可以借鉴按钮代码并进行扩展。另外,它需要更多空间。

我们的复选框有6种状态。

*非活跃:典型的默认状态。

*悬停:这个控制仅适用于鼠标控制,表明鼠标指标在元素之上,但没有摁压鼠标按钮。这个状态不适用于点触控制。

*点击:被点击之后的状态 => 下一个“帧”状态会被选中。

*选中:复选框被选中。这是点击状态之后的状态。

*选中+悬停:对于选中状态我们要有不同的悬停状态。因为图标通常也会发生变化。

*选中+点击:在选中状态时被点击后的状态=>下一个“帧”状态将不会激活。

你需要两套不同的纹理,一者用于未选中状态,一者用于选中状态。至于触发器,你可以使用与按钮相同,但含有两个额外状态的触发器。

*onCheck:复选框状态已被更改为选中状态。

*onUncheck:复选框状态已被更改为未选中状态。

“时刻”触发器也可以同选中状态一起使用,就像whileChecked一样。但是,我现在还没有看到真正的运用实例。

3.1 控制逻辑

控制逻辑基本上与按钮相似,只是需要处理更多状态。如果你很懒,甚至可以彻底抛弃复选框,并用一个简单的按钮模拟其行为。你将把一个代码置于onClick触发器操作之中。在此你将更改按钮纹理。这里有一个非选中状态的纹理集以及选中纹理集,你只需要在发生这两种状态时进行切换即可。这只会影响元素的视觉形象,不会产生像onCheck这种特殊的触发器。你可以用按钮触发器和一些临时变量来进行模拟。

4.文本字幕

文本字幕是一个非常简单的元素。它没有特殊纹理,但包括文字和字母。通常令用于小字幕,所以可以添加到按钮上来创造字幕。如果你需要更长的文本,你就必须添加一些特殊的功能。这个基本元素仅适用于简单的文本。

更高级的文本元素需要支持分段,如果它太长了还要支持自动隐藏等。

4.1控制逻辑

在本教的第2部分中我已经概括了你多数时候会用到的基本元素。如果没有这些元素,就无法制作完整的GUI。我讨论了更多关于按钮的细节,因为复选框与此十分相似,并且可以用简单的按钮和一些临时变量来模拟,所以就不在此赘述这方面内容。

在第3部分中,我们将研究渲染等一些技巧。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

Creating a Very Simple GUI System for Small Games – Part II

By Martin Prantl

In the first part of the GUI tutorial (link), we have seen the positioning and dimension system.

You can also look at other chapters:

Part I – Positioning

Part II – Control logic

Part III – Rendering

Today, before rendering, we will spend some time and familiarize ourselves with basic element types used in this tutorial. Of course, feel free and design anything you like. The controls mentioned in this part are some sort of a standard, that every GUI should have. Those are

Panel – usually not rendered, used only to group elements with similar functionality. You can easily move all of its content or hide it.

Button – what else to say. Button is just a plain old button

Checkbox – in basic principles similar to button, but has more states. We all probably know it.

Image – can be used for icons, image visualization etc.

TextCaption – for text rendering

Control logic

The control logic is maintained from one class. This class is taking care of changes of states and contains reference to the actual control mechanism – mouse or touch controller. So far, only single touch is solved. If we want to have a multi-touch GUI control, it will be more complicated. We would need to solve problems and actions, if one finger is down and the other is “moving” across screen. What happens if a moving finger crosses an element, that is already active? What if we release the first finger and keep only the second, that arrived on our element during movement? Those questions can be solved by observing existing GUI systems how they behave, but what if there are more systems and every one of them behaves differently? Which one is more correct? Due to all those questions, I have disabled multi-touch support. For main menu and other similar screens, it is usually OK. Problems can be caused by the main game. If we are creating for example a racing game, we need to have multi-touch support. One finger controls pedals and the other steering, and third one maybe shifting. For these types of situations, we need multi-touch support. But that will not be described here, since I have not used it so far. I have it in mind and I believe the described system can easily be upgraded to support this.

For each element we need to test a position of our control point (mouse, finger) against the element. We use positions calculated in the previous article. Since every element is basicly a 2D AABB (axis aligned bounding box), we can use a simple interval testing in axes X and Y. Note, that we test only visible elements. If a point is inside an invisible element, we usually discard it and continue.

We need to solve one more thing. If elements are inside each other, which one will receive the action? I have used a simple depth testing. A screen, as a parent to all other elements, has depth 0. Every child within the screen has depth = parentDepth + offset. And so on, recursively for children of children. A found element with the biggest depth and point inside is called “with focus”. We will use this naming convention in later parts.

I have three basic states for a user controller

CONTROL_NONE – no control button is pressed

CONTROL_OVER – controller is over, but no button is pressed

CONTROL_CLICK – controller is over and a button is pressed

This is 1:1 applicable to a mouse controller. For fingers and a touch control in general, CONTROL_OVER state has no real meaning. To keep things simple and portable, we preserve this state and handle it in a code logic with some condition parts. For this I have used a prepsocessor (#ifdef), but it can also be decided in a runtime with a simple if branch.

Once we identify an element with current focus, we need to do several things. First of all, compare last and actual focused elements. I will explain this idea on a commented code.

if (last != NULL) //there was some focused element as last
{
//update state of last focused element as currently no control state
UpdateElementState(CONTROL_NONE, last);
}

if (control->IsPressed())
{
//test current state of control (mouse / finger)
//if control is down, do not trigger state change for mouse over
return;
}

if (act != NULL)
{
//set state of current element as control (mouse / finger) over
//if control is mouse – this will change state to HOVERED, with finger
//it will go directly to same state as mouse down
UpdateElementState(CONTROL_OVER, act);
}

If last and actual focused elements are the same, we need to do a different chain of responses.

if (act == NULL)
{
//no selected element  – no clicking on it => do nothing
return;
}

if (control->IsPressed())
{
//control (mouse / finger) is down – send state to element
UpdateElementState(CONTROL_CLICK, act);
}

if (control->IsReleased())
{
//control (mouse / finger) is released – send state to element
UpdateElementState(CONTROL_OVER, act);
}

In the above code, tests on NULL are important, since if no element is focused at the moment, NULL is used for this state. Also, control states are sent in every update, so we need to figure how to change them into element states and how to correctly call triggers.

An element changes and trigger actions are now special for a different types of elements. I will sumarize them in a following section. To fire up triggers, I have used delegetes from FastDelegate library / header (Member Function Pointers and the Fastest Possible C++ Delegates). This library is very easy to use and is perfectly portable (iOS, Android, Win…). In C++11 there are some built-in solutions, but I woud rather stick with this library.

For each element that need some triggers, I add them via Set functions. If the associated action is triggered the delegate is called. Instead of this, you can use function pointers. Problem with them is usually associated with classes and member functions. With delagates, you will have easy to maintain code and you can associate delagets with classic functions or meber functions. In both cases, the code remains the same. Only difference will be in a delegate creation (but for this, see article about this topic on codeproject – link above).

In C#, you have delegates in language core support, so there is no problem at all. In Java, there is probably also some solution, but I am not Java positive, so I dont know this :-) For other languages, there will also be some similar functionality.

Elements

First, there is a good reason to create an abstract element that every other will extend. In that abstract element, you will have position, dimensions, color and some other useful things. The specialized functionality will be coded in separate classes that extend this abstract class.

1. Panel & Image

Both of them have no real functionality. A panel exists simply for grouping elements together and an image for showing images. That’s all. Basically, both of them are very similar. You can choose its background color or set some texture on it. The reason why I have created two different elements is for better readibility of code and Image has some more background functionality, like helper methods for render targets visualization (used in debugging shadow maps, deferred rendering etc.).

1.1 Control logic

Well… here it is really simple. I am using no states for these two elements. Of course, feel free to add some of them.

2. Button

One of the two more interesting elements I am going to investigate in detail. A button is reccomended as a first for what you should code, when you are creating a GUI. You can try various scenarios on it – show texture, change texture, control interaction, rendering etc. Other elements are basically just a modified button :-)

Our button has three states

non active – classic default state

hovered – this is correct only for mouse control and indicates that a mouse pointer is over the element, but no mouse button is pressed. This state is not used for a touch control

active – button has been clicked or mouse / finger has been pressed on top of it

You could add more states, but those three are all you need for basic effects and a nice looking button.

You should have at least two different textures for each button. One that indicates the default state and the one used for an action state. There is often no need to separate active and hovered state, they can look the same. On a touch controller there is even no hovered state, so there is no difference at all.

Closely related to the state changes are triggers. Those are actions that will occur when a button state changes from one to another or if it is in some state. You can think of many possible actions (if you don’t know, the best way is to look for example into C# properties for UI button). I have used only a limited set of triggers. My basic used ones are

onDown – mouse or finger has been pressed on the button

onClick – click is generated after releasing pressed control (with some additional prerequisites)

onHover – valid only for mouse control. Mouse is on the button, but not pressed

onUp – mouse or finger has been released on the button (it can be seen similar to onClick without additional prerequisites)

whileDown – called while button mouse or finger is pressed on the button

whileHover – called while button mouse is on the button, but not pressed

I have almost never seen “while” triggers. In my oppinion, they are good for repeating actions, like a throttle pedal in a touch-based racing game. You are holding it most of the time.

Sometimes, you need functionality similar to a checkbox with a button. Typical case is a “play / pause” button in a media player. Once you hit the button, an action is trigerred and also the icon is changed. You can either use a real checkbox or alter the button a little bit (that is what I am using). In a trigger action code, you just simply change the icon set used for the button. See a sample code. In this I am using a button as a checkbox to enable / disable sound.

void OnClickAction(GUISystem::GUIElement * el)
{
//emulate check box    behaviour with button

if (this->sound_on)
{
//sound is currently on – we are turning it off
//change icon set

GUISystem::GUIButtonTextures t;
t.textureName = “soundoff”; //default texture
t.textureNameClicked = “soundon”; //on click
t.textureNameHover = “soundon”; //on mouse over
el->GetButton()->SetTextures(t);
}
else
{
//sound is currently off – we are turning it on
//change icon set

GUISystem::GUIButtonTextures t;
t.textureName = “soundon”; //default texture
t.textureNameClicked = “soundoff”; //on click
t.textureNameHover = “soundoff”; //on mouse over
el->GetButton()->SetTextures(t);
}

//do some other actions needed to enable / disable sound

}

2.1. Control logic

Control logic of a button seems relatively simple from already mentioned states. There are only three basic ones. Hovewer, main code is a bit more complex. I have divided implementation into two parts. First is a “message” (basically it is not a message, it’s just some function call, but it can be seen as a message) sending on a state change to the button from a controller class and a second part handles a state change and trigger calls based on a received “message”. This part is coded directly inside a button class implementation.

First part, inside a control class, that is sending “messages”.

if (ctrl == CONTROL_OVER) //element has focus from mouse
{
#ifdef __TOUCH_CONTROL__
//touch control has no CONTROL_OVER state !
//CONTROL_OVER => element has been touched => CONTROL_CLICK
//convert it to CONTROL_CLICK
ctrl = CONTROL_CLICK;
#else
//should not occur for touch control
if (btn->GetState() == BTN_STATE_CLICKED) //last was clicked
{
btn->SetState(BTN_STATE_NON_ACTIVE); //trigger actions for onRelease
btn->SetState(BTN_STATE_OVER); //hover it – mouse stays on top of element after click
//that is important, otherwise it will look odd
}
else
{
btn->SetState(BTN_STATE_OVER); //hover element
}
#endif
}

if (ctrl == CONTROL_CLICK) //element has focus from mouse and we have touched mouse button
{
btn->SetState(BTN_STATE_CLICKED); //trigger actions for onClick
}

if (ctrl == CONTROL_NONE) //element has no mouse focus
{
#ifndef __TOUCH_CONTROL__
btn->SetState(BTN_STATE_OVER); //deactivate (on over)
#endif

if (control->IsPressed())
{
btn->SetState(BTN_STATE_DUMMY); //deactivate – use dummy state to prevent some actions
//associtaed with releasing control (most of the time used in touch control)
}
btn->SetState(BTN_STATE_NON_ACTIVE); //deactivate
}

Second part is coded inside a button and handles received “messages”. Touch control difference is also covered (a button should never receive a hover state). Of course, sometimes you want to preserve hover state to port your application and keep the same functionality. In that case, hover trigger is often called together with onDown.

if (this->actState == newState)
{
//call repeat triggers
if ((this->hasBeenDown) && (this->actState == BTN_STATE_CLICKED))
{
//call whileDown trigger
}

if (this->actState == BTN_STATE_OVER)
{
//call while hover trigger
}

return;
}

//handle state change

if (newState == BTN_STATE_DUMMY)
{
//dummy state to “erase” safely states without trigger
//delegates associated with action
//dummy = NON_ACTIVE state
this->actState = BTN_STATE_NON_ACTIVE;
return;
}

//was not active => now mouse over
if ((this->actState == BTN_STATE_NON_ACTIVE) && (newState == BTN_STATE_OVER))
{
//trigger onHover
}

//was clicked => now non active
if ((this->actState == BTN_STATE_CLICKED) && (newState == BTN_STATE_NON_ACTIVE))
{
if (this->hasBeenDown)
{
//trigger onClick
}
else
{
//trigger onUp
}
}

#ifdef __TOUCH_CONTROL__
//no hover state on touch control => go directly from NON_ACTIVE to CLICKED
if ((this->actState == BTN_STATE_NON_ACTIVE) && (newState == BTN_STATE_CLICKED))
#else
//go from mouse OVER state to CLICKED
if ((this->actState == BTN_STATE_OVER) && (newState == BTN_STATE_CLICKED))
#endif
{
this->hasBeenDown = true;
//trigger onDown
}
else
{
this->hasBeenDown = false;
}

this->actState = newState;

Code I have shown is almost everything that handles a button control.

3. Checkbox

Second complex element is a checkbox. Its functionality is similar to a button, but it has more states. I will not describe state changes and handling of those as detailed as I have done for a button. It’s very similar, you can learn from button code and extend it. Plus it would take a little bit more space.

Our checkbox has six states

non active – classic default state

hovered – this control is correct only for a mouse control and indicates that mouse pointer is over element, but no mouse button is pressed. This state is not used in touch controls

clicked – state right after it has been clicked => in next “frame” state will be checked

checked – checkbox is checked. We go to this state after clicked state

checked + hovered – for checked state we need to have different hover state. It makes sense, since icon is usually also different

checked + clicked – state right after it has been clicked in checked state => next “frame” state will be non active

You will need two different sets of textures, one for unchecked and one for checked states. As for triggers, you can use the same as for a button, but with two additional ones.

onCheck – state of the checkbox has been changed to checked

onUncheck- state of the checkbox has been changed to unchecked

“While” triggers can be also used together with check state, like whileChecked. Hovewer, I don’t see a real use for this at the moment.

3.1. Control logic

Control logic is, in its basic sense, similar to a button. You only need to handle more states. If you are lazy, you can even discard the checkbox all together and simulate its behavior with a simple button. You will put a code into an onClick trigger action. In there you will have to change the texture of the button. The is one set of textures for non-checked states and the second set for checked states and you will just swap them if one or the other state occurs. This will only affect visual appereance of the element, you will have no special triggers, like onCheck. You can emulate those with button triggers and some temporary variables.

4. Text caption

Text caption is a very simple element. It has no specific texture, but contains words and letters. It’s used only for small captions, so it can be added on top of a button to create a caption. If you need texts that are longer, you have to add some special functionality. This basic element is only for very simple texts (one line, no wrap if text is too long etc).

More advance text elements should support multi-lines, auto wrap of a text if it is too long, padding or just anything else you can think of.

4.1. Control logic

Text caption has no control logic. Its only purpose is to show you some text :-)

Discussion

In the second part of our “tutorial” I have covered basic elements that you will need most of the time. Without those, no GUI can be complete. I have shown more details for a button, since a checkbox is very similar and can be emulated with a simple button and some temporary variables (and some magic, of course).

If you think something can be done better or is not accurate, feel free and post a comment. In an attachment, you can download source code (C++) for a descibed functionality. Code is not usable as it is, because of the dependencies on the rest of my engine.

In future parts, we will investigate rendering and some tips & tricks.(source:gamedev


上一篇:

下一篇: