Hi Fritz,
Yes, groups are very helpful to keep things organized.
There isn't any wrong way of doing "scenes", however you don't really
need extra viewport/camera couples for that. Or actually, I'm not sure
what the advantage of having more viewports/cameras is, did I miss
something?
When I use extra viewports/cameras it's usually for separating render
passes. In one of my current WIP, for example, I have 3 viewports and
2 cameras. Here's the breakdown of the setup:
[MainViewport]
Camera = MainCamera ; This one comes with Scroll
TextureList = BackTexture ; This viewport renders to a texture instead of screen
[MainCamera]
GroupList = Background # Game # Foreground
Then I have my extras like this:
[Game]
ViewportList = FrontViewport # CompositingViewport
In code I simply iterate over the list to create all the viewports at init
[FrontViewport]
Camera = FrontCamera
TextureList = FrontTexture
[FrontCamera]
GroupList = UI # Overlay # Fade
[CompositingViewport]
; No camera here, only a shader that will apply an effect on the
BackTexture when needed (like a blur when in menus) and display the
FrontTexture on top of it
When I need to create a menu, I simply create the root object that
uses the correct group:
[Menu]
Group = UI
ChildList = ... ; All the children will inherit the parent's group
unless they override it
All my UI code fits within a single function and handles press,
release, mouse on, mouse off. For a mobile game, I guess the last two
aren't very useful. It iterates through a list of group that I defined
as "pickable", this way it'll ignore layers I don't want to consider
for picking and will go through them from top to bottom:
[Game]
PickGroupList = Overlay # UI # Game
void BaW::UpdateInteraction(const orxCLOCK_INFO &_rstInfo)
{
orxVECTOR vMousePos, vPickPos;
orxU64 u64PickedID = 0;
ScrollObject *poPickedObject = orxNULL, *poInteractionObject;
// Gets world mouse position
if(orxRender_GetWorldPosition(orxMouse_GetPosition(&vMousePos),
orxNULL, &vMousePos) != orxNULL)
{
// Stores it
orxVector_Set(&mvMousePosition, vMousePos.fX, vMousePos.fY, orxFLOAT_0);
}
// Gets picking position
orxVector_Set(&vPickPos, mvMousePosition.fX, mvMousePosition.fY, -orxFLOAT_1);
// For all pickable groups
for(orxU32 i = 0, u32Number =
orxConfig_GetListCounter("PickGroupList"); i < u32Number; i++)
{
// Picks object in it
poPickedObject = PickObject(vPickPos,
orxString_GetID(orxConfig_GetListString("PickGroupList", i)));
// Found?
if(poPickedObject)
{
// Updates picked ID
u64PickedID = poPickedObject->GetGUID();
// Stops
break;
}
}
// Gets current interaction object
poInteractionObject = (mu64InteractionID != 0) ?
GetObject(mu64InteractionID) : orxNULL;
// New picking?
if(u64PickedID != mu64InteractionID)
{
// Has current interaction object?
if(poInteractionObject)
{
// Adds contextual track
poInteractionObject->AddConditionalTrack("OnLeaveTrack");
}
// Picked new object?
if(poPickedObject)
{
// Adds conditional track
poPickedObject->AddConditionalTrack(orxInput_IsActive("Action")
? "OnClickTrack" : "OnEnterTrack");
}
// Stores new interaction ID
mu64InteractionID = u64PickedID;
}
else
{
// Has interaction object?
if(poInteractionObject)
{
// Change of action?
if(orxInput_HasNewStatus("Action"))
{
// Adds conditional track
poInteractionObject->AddConditionalTrack(orxInput_IsActive("Action")
? "OnClickTrack" : "OnReleaseTrack");
}
}
}
}
Now when I just "clicked" on an object that defines a config property
called OnClickTrack, it'll execute it. From this hook, I can do
anything, like adding sound, FXs, creating menus, pausing the game,
exiting, etc.
I'm also using a config section I call RunTime as a blackboard, ie. a
pool of global variables accessible from everywhere, code or tracks.
This way I always know which menu is currently active, I can leave it,
pause the game, resume the game, etc.
I use a simple generic track to register objects to a specific variable name:
[T-StoreID]
0 = > Object.GetName ^ # ; Getting owner's name
> Config.GetValue < ID # ; Getting its "ID" property
Config.SetValue RunTime < ^ ; Registering the owner to the blackboard
And here's an example:
[O-PauseMenu]
TrackList = T-StoreID
ID = PauseMenu
Now from anywhere, I can retrieve the content of RunTime.PauseMenu and
know if it exists or not and interact with it. Imagine a resume game
button, what it will do is something like:
[O-ExitPause]
OnClickTrack = T-ExitPause
[T-ExitPause]
0 = > Config.GetValue RunTime Pause Menu # Retrieving the pause menu object
Object.SetLifeTime < 0 # ; Removing the pause menu
Input.SetValue Resume 1 ; Activating the "Resume" input that
will get caught on the code side to resume the game
In the useful category, you might also look at config-inlined command
execution. The way it works is that you can get some tracks executed
when the config is parsed, this way one can easily do conditional file
loads or making sure that some FXs are always happening at the right
time no matter the lifetime of an object, etc.
Let's say I have a splash screen and want a fade out to happen 0.5
seconds before the splashscreen disappears. However I'm not settled on
the lifetime of the splashscreen yet, or maybe a designer might want
to tweak it. In this case you can't hardcode the FX's StartTime as
it'll have to change accordingly. What can now be done is:
[O-Splash]
LifeTime = ...
FXList = F-SplashFadeOut
[F-SplashFadeOut]
SlotList = @
Type = alpha
EndTime = @O-Splash
StartValue = 0
EndValue = -1
Curve = smooth
; Notice we haven't set a StartTime yet, let's execute some commands
to do it "programmatically"
% > Config.GetValue O-Splash LifeTime ; Getting's splash object's lifetime
% > - < 0.5 ; Removing 0.5 seconds from it
% Config.SetValue F-SplashFadeOut StartTime < ; And storing the result
as F-SplashFadeOut.StartTime
I'll stop now, there are plenty other tricks, but it's better to go
one step at a time. ;) If you have any question, don't hesitate!
Cheers,
Rom