Skip to first unread message

iarwain

unread,
Oct 31, 2013, 12:40:38 AM10/31/13
to orx...@googlegroups.com
Hey all,

Let's talk about a handy new feature called object groups.

Groups are free-form words that can be attached to orxOBJECTs. By default, all orxOBJECTs will belong to a group named... "default". =)

To specify a new group, you can either do it at runtime via code or via config:

[MyObject]
Group = SuperGroup

MyObject will belong to the group "SuperGroup" when created. If MyObject has a ChildList, every child will also belong to this group unless they have been explicitly linked to a different group. So if we have:

[Parent]
Group = GroupA
ChildList = Child1 # Child2

[Child1]

[Child2]
Group = GroupB

When creating Parent, both Parent & Child1 will be part of GroupA whereas Child2 will be part of GroupB.

If you want to get/set groups via code, you'll have to use StringIDs (cf. the String ID post) and call orxObject_GetGroupID()/orxObject_SetGroupID().
There's also an orxObject_GetDefaultGroupID() that can sometimes be handy.

Now you might ask what is the purpose of those groups?

Well, it's mostly about rendering: objects are now rendered on a per group basis.

The way it works is that in addition to setting the Group property of an object, one should also set the GroupList property of a camera.
A camera will rendered groups of objects in the exact order they've been specified in its config property.

Let's say we have the Parent/Child1/Child2 objects as described above and we now add a camera:

[Camera]
GroupList = GroupA # GroupB

That means this camera will first render all the objects from GroupA then all the objects from GroupB, in this strict order, and no other objects (ie. all the "default" objects won't be seen by that camera).

Within a group Z coordinates are still used for render sorting.

Let's say we have Parent.Z = 1, Child1.Z = 2 & Child2.Z = 3 (in absolute world coordinates), even if Child2 is further than Parent & Child1, it'll still get rendered before them as being part of GroupA.

There are also ways to access/modify the group list using the orxCAMERA API: orxCamera_AddGroupID, orxCamera_RemoveGroupID, orxCamera_GetGroupIDCounter & orxCamera_GetGroupID (which takes an index).

Now this simple system might not look as much but it is actually very powerful!
It helps to keep an easy organization of objects when games tend to have hundreds of them living at the same time. It's also a very powerful tool to control multiple rendering passes used for compositing, for example.

Here's a setup I'm using in one of my current projects:

[Game]
ViewportList  = GameViewport # FrontViewport # LightingViewport # CompositingViewport

Here I define all the viewports I'm using, in the order they'll get rendered and I use a simple loop in code to create them all.

[GameViewport]
Camera          = GameCamera
BackgroundColor = (0, 0, 0)
TextureList     = BackTexture

[GameCamera]
GroupList       = Background # Game
FrustumWidth    = @Display.ScreenWidth
FrustumHeight   = @Display.ScreenHeight
FrustumFar      = 2
Position        = (0, 0, -1)

The viewport/camera couple above renders all the background and game objects in a texture named BackTexture (created automatically by the viewport).

That's the first render pass. Then comes:

[FrontViewport]
Camera          = FrontCamera
BackgroundColor = (0, 0, 0)
BackgroundAlpha = 0
TextureList     = FrontTexture

[FrontCamera@GameCamera]
GroupList       = UI # Overlay

Here this viewport/camera will render UI and Overlay objects in a second texture called FrontTexture. This is a second render pass, separate from the first one.

Note that camera share the exact same world space coordinated, no need to disjoint them anymore as the filtering is done via the object groups. This is actually very handy and can easily be modified via config (moving an object from a group to another, etc).
Note also that Overlay will always be rendered after UI, I'm using that group for fade in/out effects and I'll be sure they'll always "cover" my whole scenes, no matter what their Z coordinates are.

Now comes another pass, for lighting:

[LightingViewport]
Camera          = LightingCamera
BackgroundColor = (255, 255, 255)
TextureList     = LightingTexture

[LightingCamera@GameCamera]
GroupList       = Lighting

I'm using a white background (fully lit) and add objects that will act later on as mask/shadows, if any, and render them to a LightingTexture. That's the 3rd pass.

[CompositingViewport]
BackgroundColor = (255, 0, 0)
BlendMode       = none
ShaderList = @
...

Lastly comes a viewport without a camera. It's actually using all the previously rendered textures (Back, Lighting and Front) to composite the result on screen.
I won't paste the shader code here as it's out of topic, but mostly I'm multiplying the back texture by the lighting texture to mask out/shade objects that are not/partially lit and I add the UI/Overlay texture on top of everything to make sure they're not affected by the lighting.

As you can see, there isn't any code to write beside the 2 lines for loop-creating the viewports at init. The system is very flexible and can adapt to any render pass combination that your game would require without even having to recompile anything.

Performance wise, those render passes are only partial as each pass will only consider the objects being part of the groups seen by the current camera. So if there's only one object in a group, orx will only process that object and won't iterate over the full scene. Objects are internally stored on a per group basis.

NB: Quick tip, if you want to debug your multiple-pass setup, you can save the content of one of the intermediate texture on disk to see if things are working as expected. In this case, let's say I want to analyze the LightingTexture, I open a console and type (the [tab] is the actual key press as it'll be replaced by the former command's result):
> texture.find LightingTexture
> texture.save [tab] light.png
I can now open light.png and see if it is what I expect it to be. =)


Lastly, object groups have another use: they can be used to filter objects when using the picking functions.

In my config I also added:
[Game]
PickGroupList = UI # Game

In code, when doing picking, I iterate over that list and will pass the groups to the picking function. That means that objects that are not part of those groups will never be picked, even if they are above the objects you are interested in. This is also very handy as you won't have to change your picking Z value in order to filter out some fade out quad or similar.
For example, here, no objects from the Overlay or the Lighting groups won't ever be pickable. I'll only pick objects from the UI group and if not found, I'll then try to pick objects from the Game group.

Thanks to Loki who brought the initial reflection to that feature and mentioned the idea of using groups for picking as well! =)

If you have any questions, please lemme know!

Cheers,

Rom

faistoiplaisir

unread,
Nov 1, 2013, 12:10:20 PM11/1/13
to orx...@googlegroups.com
Hey!

Great idea here too !

A little tutorial/sample to illustrate that in real time could be usefull. I'll try that as soon as possible.

Gilles.

orx-project

unread,
Nov 5, 2013, 11:39:54 PM11/5/13
to orx...@googlegroups.com
Actually I've modified the compositing tutorial to use groups, if that
can help. I still need to update the mac binaries as well as the wiki
page. Not sure when I'll get to do the second one, sorry about that!

Cheers,

Rom


2013/11/1 faistoiplaisir <faistoi...@gmail.com>:
> --
> You received this message because you are subscribed to the Google Groups
> "orx-dev" group.
> To post to this group, send email to orx...@googlegroups.com.
> Visit this group at http://groups.google.com/group/orx-dev.

iarwain

unread,
Apr 13, 2014, 2:40:58 AM4/13/14
to orx...@googlegroups.com
I just realized that the following line from the original post is both confusing and inexact:

Let's say we have Parent.Z = 1, Child1.Z = 2 & Child2.Z = 3 (in absolute world coordinates), even if Child2 is further than Parent & Child1, it'll still get rendered before them as being part of GroupA.

It should read:

Let's say we have Parent.Z = 1, Child1.Z = 2 & Child2.Z = 3 (in absolute world coordinates), even if Child2 is further than Parent & Child1, it'll still get rendered *in front of* them as being part of *GroupB*: render GroupA: Child1 (Z = 2) then Parent (Z=1) *then* render GroupB: Child2 (Z = 3).

acksys

unread,
Jul 2, 2014, 2:15:16 AM7/2/14
to orx...@googlegroups.com
I played with the groups feature a bit tonight.

I didn't go crazy yet, but it seems like groups can be used to implement "layers" in orx. This is a great convenience, because I think we tend to think of game objects as being rendered in groups or layers. Without support for layers, the developer must keep track of the Z order of all objects (and their children) and this gets complicated fast.

[GameViewport]
Camera          = GameCamera
 
[GameCamera]
GroupList       = Background # Game

This is a simple case, but games need different types of scenes with different types and numbers of layers/groups, of course. Is the best way to implement this to create a Viewport/Camera configuration for each type of scene needed and create/destroy them along with their respective scenes in code? I might have this, for instance:

[GameViewport]
Camera          = GameCamera
 
[GameCamera]
GroupList       = Background # Game

[MainMenuViewport]
Camera          = MainMenuCamera

[MainMenuCamera]
GroupList       = Background # UI # ForegroundEmbellishments

When I load up my main menu, I create my MainMenuViewport. When I switch from the main menu to the game, I delete MainMenuViewport and GameViewport. Is this the right way?

-Fritz

orx-project

unread,
Jul 2, 2014, 3:52:08 AM7/2/14
to orx...@googlegroups.com
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
Reply all
Reply to author
Forward
0 new messages