It looks like you're new here. If you want to get involved, click one of these buttons!
Hello,
i'm looking for the most elegant way to manage game scenes ?
My first thought was to create a GameScene class and then create "MainMenuScene", "PlayScene" and "OptionsScene" classes and simply call current_scene.run()
in my Run
function.
But this solution felt ... hacky, so what's the official way to achieve this ?
Thanks
Comments
Interesting timing as I am actually working on a video sample on that topic. The video isn't ready but the project is already available if you want to have a look: https://github.com/orx/sceneswap
To quickly summarize, the scenes are just simple objects, there's no dedicated code to handle them, another object provides the transition between scenes as we store the current scene in the
Runtime
section with theScene
property.If you're curious the other two videos can be watched at
Don't hesitate if you have any questions regarding the samples, they are a bit more advanced that beginner-level, focusing on the data/config-driven approach.
Thanks for the reply.
I'll be honest, I understand close to nothing in your source.
So here is what i've (or at least I guess i've) understood, please correct me if i'm wrong.
OK so I guess the
orxConfig_GetListString function return
ToSceneRedand the orxInput_SetValue simulate a keypress on the Key wich have
ToSceneRed` as a value ? (KEY_1).Why do we do that instead of loading the first scene simply by createing
SceneRed
. It's the first one, we don't need transition ? And using input to change scenes isn't possible in a real project ?The
orxClock_FindFirst
is returning a clock and I supposed the second parameter means « give me the main clock ».For the
orxClock_Register
it register a callback method we give it the main clock, the function we want to call, but the last 3 parameters are blurry to me. is the module something thread related ? What are the priority and when do that matter ?[ToSceneYellow@ToScene]
0 = > Get Runtime Scene, Object.AddTrack < MoveOut, > Object.Create SceneYellow, Object.AddTrack < MoveIn```
0 = 0s i got it but the value of this key is giving me headache .
The only thing I understand is Object.Create SceneYellow, pretty obvious name (and we used this command in the Beginner's Guide).
I understand that
MoveOut
andMoveIn
are track with contain animation (scroll?) to display the move out the scene and move in the new one. SoGet Runtime Scene
is probably something like "give me the current scene". But the whole syntax using<
,>
and^
(in the [MoveOut] & [MoveIn]) is totally new to me and I don't have any idea on what that mean 🤔I think that's all, sorry for all the questions i'm still a beginner in game dev so I probably don't understand very basic concepts.
Thanks I didn't took the [ToSceneBlue] track as an example but yeah basically all those symbol (
,
,<
,>
,^
,#
) are super confusing. I tought the#
symbol was used to chain thing but sometimes you use it and I don't understand why.My pleasure!
No worries, as I mentioned those samples are more advanced and make the assumptions people are already familiar with orx itself.
Right, in this case, we are using the same name for the input and the scene transition object. It's a choice to make the whole thing simpler, but it's just a mean for the user to trigger the transitions themselves and play around with them.
In the end, the inputs here are not very relevant to the feature we want to showcase (scene swapping).
As stated above, the inputs is simply to let the user play around with the transitions, interrupting them and see what happens. We also use it for the first scene this way they can modify it the way they want to play around with the system, while not having them touch the code at all. For example, if they wanted to have the first scene be the spinning one, they would simply need to modify the
Transitions
property.Correct, let's go over the last 3 parameters:
orxNULL
-> it's a context that will be passed to the function you register, every time it's called from the clock module. In this case we do not need any extra info so we set the context toorxNULL
orxMODULE_ID_MAIN
-> this can be used to freeze some of the registered functions if the corresponding module becomes unavailable. This is almost never used and you can simply passorxMODULE_ID_MAIN
as a user. Internal calls will use different module IDs, but that's not relevant unless you want to replace parts of the engine itself.orxCLOCK_PRIORITY_NORMAL
-> this one is actually somewhat relevant as it can help you find your place in the main loop sequence. Usually gameplay-related update functions will use either the normal or the low priority. When a clock is updated, it'll call the functions registered on it from the highest priority to the lowest one, ensuring some ordering in the overall sequence.Exactly, it's not really related to the scene swapping itself, just the way I chose for the users to interact with the whole process.
I guess we could call it this way, some would call it template as well. The config system supports inheritance, you can inherit from an entire section or with a key by key approach. In this case, we want to make sure that all our transition objects, which will inherit from this section:
TrackList = @
, you can find more info about@
in the wiki or at the top ofCreationTemplate.ini
)LifeTime = track
), making sure that when the transition's been orchestrated, the transition object will get deleted automaticallyYep, you'd need to check commands first. Those are a very simple way to interact with parts of the engine from either config (again, check the top of
CreationTemplate.ini
to see the two ways they can be called from the config data) or from the interactive console (press the backtick/tilde key to open it, quake-style).More info on that topic at:
[Part 1/2]
[Part 2/2]
Yep, to summarize, this track executes commands at time = 0 second (ie. immediately upon the object's creation). The commands being:
> Get Runtime Scene
-> We retrieve the config value from the sectionRuntime
with the keyScene
(that's where we store the current's scene GUID, which is a orxU64 value, cf theStoreScene
track that runs on all theScene
objects) and push it on the result stack (that's the>
symbol)Object.AddTrack < MoveOut
-> We pop the GUID from the stack (<
), which is our current scene as retrieved above, and add a track on it calledMoveOut
(defined somewhere else in config)> Object.Create SceneYellow
-> We createSceneYellow
and push its GUID on the stack (>
)Object.AddTrack < MoveIn
-> We pop it from the stack (<
) and add a track on it calledMoveIn
If I remember correctly,
MoveOut
moves the old scene outside of the camera's frustum to the left andMoveIn
does the opposite, giving a sliding effect to this transition.Yep, I think you'll find the info you need in the wiki (in the two links mentioned above). To summarize:
>
means push the result of the commands on the stack<
means pop a parameter from the stack^
means replace with the GUID of the object that's running this command (you can conceptualize it asthis
orself
)No worries, don't hesitate with all the questions. Orx's approach is data-driven, which means a lot of the behaviors can be entirely driven from the config files (like being able to create a whole scene from a single object creation call). Even in the gamedev scene, this is not that common, at least not to that extent, so I completely understand if you find it confusing.
That being said, everything we did here could be done in code as well, but this would have a few disadvantages:
In the last case, for example, by giving you an example, you could be testing the Android version of your game from your computer by simply modifying a single include line in config that would override a bunch of properties and would let you test a different platform natively, with the same codebase (no need for an emulator, better performances, etc...).
Yep, that one is quite advanced. We actually modify the entire rendering pipeline by creating new viewports, having both scenes exist at the same time, render to off-screen textures and use a cross-fade shader to compose the whole thing during the transition, then finally restoring the default rendering pipeline, and cleaning all the intermediate things (viewports, off-screen textures, cameras, etc...) as well as the old scene.
All in all it's quite concise for such dramatic changes, especially at runtime, but it's definitely not beginner's level, so I wouldn't pay too much attention to it at the moment.
The
#
works for any config values, it's the element separator (ie. the value will be considered as a list and not as a single value), while the,
separator is a command-only concept. I use both to achieve the formatting I wanted (#
allows for continuing the list on the next line, while,
, not being a config concept, would be ignored and everything would need to be on the same line).Again, quite an advanced use, don't worry about it.
Actually, you know what, let's dissect that
ToSceneBlue
and you'll see that in the end, even though it looks quite busy, it's also quite straightforward.So at time T = 0, we run 4 sets of commands, all separated by
#
. Each set of commands will then be split into sequential commands by the command module, using the,
separator.First set:
>> Get Runtime Scene
-> We retrieve the current scene and push it twice on the stackSet Runtime Out <
-> We pop it, and store it in theRuntime
section under the keyOut
(for later use)Object.SetPosition < (0, 0, 200)
-> We pop it again (hence why we pushed it twice in the first place), and move it 200 units further on the Z axis, to make sure it's not seen by our main camera anymore, ie. it won't be rendered with our current rendering pipelineSecond set:
> Object.Create SceneBlue
-> We create the new scene,SceneBlue
, and push it on the stackObject.SetPosition < (0, 0, 100)
-> we move it 100 units further on the Z axis, still out of view of our main cameraWe now have both our scenes, the old one around Z = 200 and the new one around Z = 100. If we left it at that, we'd see nothing on screen anymore.
Third set:
> Viewport.Get Viewport
-> We retrieve our current viewport namedViewport
(yeah, that was original) and push it on the stackViewport.Delete <
-> We delete it (now we don't have any rendering pipeline whatsoever leftViewport.Create ViewportIn
-> We create a new viewport calledViewportIn
(and in its section, it'll define that it renders to a texture namedTextureIn
, that will be created at the correct size on the fly, with a camera calledCameraIn
, also created on the fly, and centered around Z = 100, ie. "seeing" our new scene)Viewport.Create ViewportOut
-> We create a new viewport calledViewportOut
, same idea than withViewportIn
, but this one will render the old scene around Z = 200 into the textureTextureOut
Viewport.Create Viewport
-> we recreate our original viewport. So why did we delete it in the first place? well, we want it to be the last, so that it can be rendered afterTextureIn
andTextureOut
have been filled. The creation order does matter for viewports.If we stopped there, we'd get the new scene rendered to a texture called
TextureIn
, the old scene rendered toTextureOut
, but still a black screen as we wouldn't be using those textures.Fourth set:
> Object.Create SceneDissolve
-> We create an object calledSceneDissolve
. This object will use the main camera as parent, stretch itself to cover its entire frustum, and use a shader that will take both textures as inputs and do a cross-dissolve with them. We also push it on the stack.Object.SetOwner < ^
-> We change its parent to the current object (ToSceneBlue
), this way whenToSceneBlue
gets deleted, so willSceneDissolve
, and we won't have to clean it up manually.And now we have a nice effects rendered on screen, with a single object,
SceneDissolve
, seen by the main camera.We let 0.8 seconds go and now it's time to clean everything up with two sets of commands.
First set:
> Viewport.Get ViewportIn
-> We retrieveViewportIn
and push it on the stackViewport.Delete <
-> We delete it, with it are also goneCameraIn
andTextureIn
, no more rendering of the new scene to an offscreen texture> Viewport.Get ViewportOut
-> We retrieveViewportOut
and push it on the stack (sounds familiar? )Viewport.Delete <
-> We delete it -> no moreCameraOut
andTextureOut
Second set:
> Get Runtime Out
-> We retrieve the content of the keyOut
in the sectionRuntime
, which is where we saved our old scene at the very beginning. As the new scene has overridden the keyScene
in that same section, we wouldn't be able to find it otherwise. We push it on the stack.Object.Delete <
-> We delete it, no more old scene, all objects contained in it have been deleted as well> Get Runtime Scene
-> We retrieve the new scene and push it on the stack.Object.SetPosition < (0, 0, 0)
-> We put our scene back at Z = 0, where the main camera can now render it as if nothing happened.And all done, the whole render pipeline is back to its old self, the new scene is rendered correctly and where we expect it in the world.
And because the track's now over,
ToSceneBlue
gets deleted as well, due to theLifeTime = track
property, and with itSceneDissolve
disappears as well.I know I'm a bit biased, but I think this example is rather cool and illustrates how flexible orx can be, especially with touchy things like modifying render passes or creating/deleting entire scenes.
Let's say now that for low end hardware you want a simple instant replacement (like with
ToSceneRed
), you can have a file calledLowEnd.ini
, and in it you'd redefineToSceneBlue
to not have this crazy track and simply behave likeToSceneRed
. All you'd have to do is to load that config file, either from another config file or from code, and the entire transition system for that scene would now be different.Hey @iarwain thanks for all those awesome informations.
The commandnotes page and your explanations really help.
One thing I still don't understand is how to make use of this. Ok now we switch between SceneYellow, SceneGreen, SceneCyan etc ... but in a real game I would need to define the content of the scene in my code ?
Let's take a simple example, a sokoban game
I have 5 scenes: MainMenuScene, GameScene, LevelEditorScene, PauseMenuScene.
In my config I create some objects, all the scenes, PlayerObject, BoxObject, BoxOkObject, WallObject and GoalObject.
The thing is, i'm in my MainMenuScene and I press start, so it switch to the GameScene. Now in my code I would probably like to do multiple things like :
So in my code I still need to define my scenes using classes ? So I can have a Scene::update() method to handle all of this ?
(I'm working on a sokoban example because it would be easier for me to show you with code)
Thanks again for your help
Nothing prevents you from using classes if that's how you want to organize your code, all I'm saying is that it's a choice, not something mandatory.
In the example of a Sokoban, why not load all the levels at once? Each level expressed in config form will probably be a handful of kb, having 1000 levels would then be a handful of mb and should be loaded in a split second. Unless you're targeting extremely low end devices (or you want to change the list of levels dynamically for some reason, like UGC content), I don't see why not loading them all at once.
Your objects would already be children of the level itself. You can either use your own format or @sausage's Tiled exporter if you wanted to use Tiled as a level editor, for example. In the end, you'd create the entire level with a single call to
orxObject_CreateFromConfig
.Now that being said, I'd recommend checking
Scroll
(there are info on it in the wiki), which would bring you a thin C++ layer on top of orx that facilitates binding C++ classes to config sections as well as the handling of per-object events (like collisions or animations).Lastly, you can switch input sets to handle different situations, like pause menu vs game. You can also use classes of course, I tend to simply have an enum that tells me which Update function I should be using at the moment, as I don't usually have too many different states (main menu, in-game, pause, game over), things like this.
For example, creating a
Pause
object could not only disable or pause the game scene as well as swap the current input set. And when exiting, it'd put the original set back and re-enable or un-pause the old scene, those could be done from tracks or in code, up to your preference.Again, you should go with the way you feel more comfortable, I tend to like having a minimal amount of classes, usually one for the game and one per type of entity (player, npc, weapon, etc...) and that's about it, I then usually put all the variations in config.
This is an interesting topic for me as well. I've been playing with orx for less than a week in the evenings after work to see what I can do, so probably premature for me to have much of a relevant opinion!
I'm trying to work up a simple "space invaders" type game with a couple of levels and a boss. The idea is to exercise the various things I know I'd like to be able to do (ie menu, multiple levels, intro/outro for levels, etc) to see how it works in orx.
So far I am getting convinced by the data driven approach. If you are used to coding behaviors yourself, it is hard to change. There is a learning curve to the orx system which I suppose will take some time to digest. My first impression is that is it very well thought out and implemented. I've had no problems at all with orx itself, only a few confusions of my own on how to use it. Without @iarwain and others here, it would indeed be a little difficult to navigate.
For the scene handling, I'd like to ultimately go to something like what @iarwain is outlining above, though I haven't had time to really look at it yet. For my initial test though, I've done the scenes (levels?) in the way I'm familiar with.
I make a SceneBase class with each level/scene as a subclass of that, containing an update function that can be called by the main orx update function. Each subclass has the ability to set the desired scene/level at any time, which is reacted to in the main orx update function. I can elaborate further if anyone is interested.
In any case, looking forward to learning more. I'm really glad I stumbled onto orx.
I know I'm advocating for the put-as-many-things-on-data-side approach as much as I can, but in the end, if you feel more comfortable with a code driven approach, or anywhere in-between, it's completely fine. You should be able to take the approach that feels the most comfortable to you without having to fight against the engine, and hopefully that's the case.
I know someone recently used the config to bind different update function on the fly as well, that's an approach I never thought about and it turned out to work perfectly fine for them as much as I can tell.
Lastly, if you want to use dedicated C++ classes, I really recommend to check Scroll as that's one of the big advantages it offers on pure orx.
There's a
ScrollObject
based class, which can be derived and bound to config sections (it works with section inheritence as well). This way, you can simply bind your different scene classes to the different scene objects and those would get created as soon as the underlying orx object is created.To be fair, I almost never use orx without Scroll myself, unless it's for short tests and demonstrations. However for bigger projects, I'm always using Scroll.
I still have on my todo list a task to add Scroll to the project init feature, I'll try to get it finished before the new year.
There's an old multi-part tutorial at https://wiki.orx-project.org/en/tutorials/community/acksys/scroll0
Some passages might be a bit outdated, but most of it should still be relevant.
Thanks! Scroll is next on my list to try out. Looks like a good match for how I am approaching things.
Hi, I'm exhuming this post because I've got a problem with this particular situation. I've read this post from start to finish multiple times, studied and followed the example from Github, I think I Understand the concept but I'm stuck even if I've simplified it. I've got One working title screen with highlighted button when you hover over them and one Working Level. So, what I want, is when I click play to go to level 1.
The code :
Main.ini
Main.cpp
If I'm not mistaken, It should at least show the title screen. The log is showing the Key just fine and orxInput_SetValue should trigger KEY_1 which is defined in input.ini as "ToTitle". Did I miss something crucial to trigger this track ? I tried outputting a log message with 0= log "Message" inside [ToTitle@ToLevel] but nothing triggers.
To anticipate another problem, my Character controller was originaly triggered by a method creating all the object and containing some code that was originally in the orxFASTCALL run() method and called every cycle to control the movement and the reticle. When switching to level one, how to trigger this code from a config file ?
Thanks in advance.
It works. I don't know what I did differently this time but it works...
Still, I can now load my level on the fly but I still don't know how to start the logic and method that was originally launched at the start...
In the first example, I believe the issue was the lack of a space separation in
Object.Delete<
.An easy way to check if objects have been created (as they might be there but not rendered because of their position, a lack of graphic, etc...) is to open the console by pressing the
~
key and type:If it returns something that's not 0xFFFFFFFFFFFFFFFF, then the object exists and you can manipulate it with commands.
Another useful command is
structure.logall
which will display a hierarchy of all structures existing at that moment.It'll look like this:
Going back to your question on how to instantiate the code for your Character controller, I'd recommend using Scroll on top of orx: when you create a new project with
init
, you can request Scroll to be added withinit path/to/project +scroll
. You can find more info here.Using Scroll, you can bind C++ classes (deriving from
ScrollObject
) to config sections used to createorxOBJECTs
. When an object is created, if it's bound to a C++ class, this class will be instantiated as well. There are a few relevant entries in the wiki as well, regarding Scroll.You can also check a recent gamejam entry I made with a friend here: https://github.com/iarwain/ld46
It leverages
ScrollObjects
to create the train wagons and the playable characters.If you'd rather stick with pure orx (or pure C), one way to do it would be to add an event listener to the
orxEVENT_TYPE_OBJECT
/orxOBJECT_EVENT_CREATE
event and run your code when the desired scene has been created.Another option would be to set a config variable in the
Runtime
section (the name doesn't matter, it's just the one I usually use) and in your main Update code (the function that has been registered to the main core clock), check for that value there in order to know when to run that specific code.It really is a matter of personal preference at this point, I think. Let us know if you need more details.
PS: If you're not familiar with the console, I'd recommend checking this wiki article.
Thanks, I had the last one ( and tried it : Segfault Fest ! Need some lessons on pointers) but the first two are more elegant and easier when you have more level to manage. Scroll seems the way to go but would require a "reboot " of the project. The "pure orx" solution is obscure to me and would require me to digest some Event Handler tutorials.
I think, I will go with the Scroll one.
Ok, It's working fine ! I made a global variable, increment it each time I switch level, filter the functions with it and then delete the previous level in my transition object. (And took a crash course on pointers and classes on the way, it was much needed)
I will try scroll for the next project it looks waaaay easier for my mindset.
Again, thanks you for your guidance, I can know focus on Levels and design.
Edit : Grammar
My pleasure. Also regarding pointers, you can (and should) store any orx-related structures, like
orxOBJECTs
through their GUID and not raw pointers. This way orx can verify for you if the structure is still valid.Here's a quick example:
Also note that you might need to handle orx events in the future, depending on your needs. Things like updating shader parameters on the fly will require it. Don't worry, it's really not that complicated.
There's no dedicated tutorial however it's virtually part of all the tutorials we have as you can see here: https://wiki.orx-project.org/doku.php?do=search&id=event