Hi all,
I've been stalling again with the skeletal animation support, I blame Legend of Grimrock.
Anyway, that's only part of the truth as I'm still thinking on the best way to declare animations, especially when they are hybrid animations (bone + sprite) across children, and try to duplicate as less as possible info (especially the anim graphs).
I haven't found a great solution to make it easy to not only author said hybrid anims but also to trigger them in code. I want it to be as painless as possible...
So in the meantime I've been thinking about the other features I'd like to implement before 1.4 gets released: TimeLines.
I think I'm getting a clear picture of what I want to make for those. There'll be two parts:
- a Command module
- a TimeLine module
The Command module:
It would take command registration as plain text. Any orx (or user) module can register commands and will only get an array of strings as parameters (argc/argv style).
For orx I'll add a basic set of commands at first with some Object Oriented naming such as Object.Create, Object.AddFX, Object.SetSpeed, etc...
Those commands can then be invoked via code (not sure of the interest except maybe for some scripting support in the future?) but, more important, will be the main listener to events sent by the TimeLine module.
The TimeLine module:
It will be very simple: an array of tracks with the option of adding/removing tracks on the fly, looping and using the owner's clock for timing.
Tracks are simple lists of strings associated to timestamp.
When TimeLine are evaluated, they'll send events with the string in payload. That's about it. So anyone can hook any string to a time line and listen to them for custom sequencing. The interesting part will be that the Command module will also be listening to those and recognize strings that are formatted in a certain way. It'll then execute the command if recognized.
That's about it. That sounds a bit like a lite scripting version, which it is to some extent. That should mostly help with complex creation patterns that spawners can't solve or with menu creation, scene creations, etc.
I'm not sure right now if command should only accept strings and do their own parsing later or if I should centralize that in the command module, making it more complex to write as it'll need to register types for parameter checking, etc.
Any suggestions on all that?
I'd like also to be able to specify a target for commands other than the current object or uniquely named structures but in the worst case scenario I think there's an easy config trick that could rely on the new GUID system to achieve this at runtime.
This actually sounds much more exciting to me than skeletal animation to me as it'll give huge possibilities to orx with even less code to write while still remaining performance-focused.
Comments
Those functions sounds good, I think I understand them, but I can't find some precise example in my mind. So it's difficult to do some suggestions for me.
You said that TimeLine/Commands can be usefull for scene creation and menu. Since I already do this kind of task with orx, maybe it can help to have more details on "how these functionnality can inprove that".
Can you explain more your idea about that ? How menu creation can be easy with command ?
Of course that's a very complex example but all of it can be done in config with TimeLines.
Any actions involving timings/delays can be achieved with TimeLines.
Could one use this to script complex enemy boss behaviors? Like a repeating series of boss attack behaviors?
By default commands will target the object owning the timeline but I want to be able to address other objects too.
I think I'll add a command to store the GUID of the last created object to a specific section.key config value and a command to set the GUID to use for object commands (which can then be retrieved from config).
Now if only there was an easy way to add conditionals, that'd make a fully featured light scripting utility...
Any suggestion for the syntax?
Something like using a character to tell the Command module to store the result of the command somewhere and a single character to tell that the parameter needs to be extracted from the stack.
Or should storing the return value on stack be defined at the command registration instead?
Something like:
All the commands of this track will get executed at Time = 0.
- means get the owner's GUID (ie. the current object owning that TimeLine)
> means push the return value on stack
< means pop the argument from the stack, I'll be using the config system to store that stack so it'll be easy to do the numeric/string conversions
In order here:
>Object.GetSpeed - = Get current object's speed (pushed on stack)
>Object.GetName - = Get current object's name (pushed on stack)
>String.Print 64 "Object " < "Speed " < = Print in a buffer of 64 characters "Object <pop arg from stack> Speed <pop arg from stack>"
Object.SetTextString - < = Set's the string of the text associated on this object with what String.Print stored on the stack.
That's another way of "perverting" the config system, in the same spirit of what I've done for the generic input system or the localization module. I've to admit I like finding new ways of using it.
And this of course would allow to do conditionals by having a command called Logic.if: Logic.if < "CommandA ArgA1" "CommandB ArgB1 ArgB2" which would execute CommandA if the arg poped from stack is not FALSE, CommandB otherwise.
Any suggestion?
What you've written looks good to me. The single characters for push, pop, and GUID make good sense to keep a complex timeline from becoming too verbose.
I've read your posts on this thread a few times and I think I'm starting to understand a bit better how this might work. I do a lot of sequenced animations in Equilibria (e.g. scene transitions between levels and menus) and I think I could apply what you have planned for Timelines to that kind of work.
And yes, I think that the timelime/command would have worked great in Equilibria for things like your scene transitions or the delayed opening of the gates and probably other things that I haven't seen yet.
Because I have so many of those things in Equilibria, it'd be a good place for me to start testing it when it's ready.
I'll try to write the core in those coming days but it will then take time to register all the functions.
I'll start registering most of the functions from the object module and a couple misc ones from config and string. Any obvious candidates I've missed?
It could also be nice to tell the visible objects in a scene. In a cutscene if you have many objects it would be a hassle to individually hide each object when they're not in the current scene.
An example : It would be nice to be able to run commands all the time, not only at a a given time, to run a command during the whole timeline, a particular scene or between 2 given times (following the tick of the clock).
Something like :
It would be nice to have basic operations add, multiply... on parameters so you can know the distance between 2 objects, or compare the components of their positions.
For example in a scene you could trigger an FX when 2 objects get close to eachother. Or to make a fake 3d effect : to make an object look in the direction of another one in the back you could tell the object to change sprite automatically depending on the position of both objects so the 1st one looks oriented in the right direction.
It could be nice to have a parameter to know if an object is in the visible area of the camera too.
And also I didn't understand what the - was for in your example. Why is there a - everytime you access a parameter.
Just to make sure we're on the same page, timelines are not related in anyway to animations. Or anything for that matter: they're simply text events that will get sent at specific "dates", nothing more.
One of the listener will be the command module which will try to interpret these events and execute them if successful.
Users can of course listen to those events and set their own handling code.
In that context a timeline is just a container for event tracks and a time cursor, nothing more. They'll be attached to object in order to get their time cursor updated.
It's a good idea, I'll keep it in mind. However I'd recommend to actually destroy/create objects when you need them or not if you have a lot of them. There won't be any memory allocation made and having a lot of objects in your scene, even inactive/invisible ones can slowdown iterating through the object container, especially for rendering as there's no partitioner in orx yet.
I'm not sure what your goto is supposed to mean in that case.
Also you can't refer to object instances by their name as those are not unique. However you can do something like:
If you need to do looping section, put them on a separate self-adding track. Example:
Sure, I'll see how it goes though as the only option to communicate from one command to a subsequent one is by using the stack so it might turn up a bit too difficult for doing this, which is more of a full-fledged scripting system than a sequencing one, admittedly.
I believe we've completely entered the realm of scripting here, not sequencing. Some of those thing might be achievable with sequences but it might turn up to be harder than simply write the code.
The - is a special parameter that will substitute to it the owner's GUID (which is always an object which holds the timeline). This will be the only work done by the timeline itself as the command module is not attached to any object instance.
But since a timeline is the child of an object, for cutscenes and patterns of monsters I think it forces you to make a dummy object that will handle the timing of events and keep that empty object alive for the duration of the cutscene or level. It looks cleaner than parenting the timeline to a random object in the scene.
Since tracks are already a property or a child of the timeline, an idea is to make the timeline the root instead of the object and still keep the ability to create a new timeline inside objects. You'd be able to set global timings regardless of objects being alive in the scene. Also I don't see what's inherited from the object to the timeline besides its clock so if the timeline was the root and objects were children it would be the same because both clocks are linked anyways.
A small suggestion is to replace the character "-" with a word like parent, self or this or a more intuitive word. It's easier to read and correct mistakes. It's better to keep it consistant and have a word in both cases : if you create an object in the timeline you'll write >Object.Create Object1 and if you have your object and then make your timeline you'd write >Object.GetSpeed parent. You'll have a word in both cases (Object1 and parent) rather than a word and a -
Another suggestion is to have a command to give an object a non-linear trajectory, spline interpolation or smoothstep. You'd give a list of points and key timings as arguments and the object path would be smoothed. Rather than making a batch of timings with small portions of linear paths.
Ah I see, with that context I understand the reasons behind your proposal.
To be more precise, a timeline isn't a child of an object, it's a property attached to it. This is important for how things work and have been designed as explained in more details below.
This is an interesting idea, however only objects and cameras can exist by themselves in the world. (Well, with the semi-exception of spawners, but I discourage using them this way and it was a bad decision in retrospect.)
Objects are meant to be generic containers, not only "game" objects. As mentioned above only objects and cameras can exist in the world by themselves, everything else is attached to them (with the exception of core modules like config, input, localization, ..., which don't really interact with the world at all or really exist, for that matter).
That means that "empty" objects, as far as gameplay is concerned, should be used often: they're very efficient containers that are part of the whole world.
One should create empty objects to hold the background music, for example, or to group game objects, or even to trigger post-effect shaders, ...
In the same spirit, they can "contain" more abstract structures such as clocks or timelines.
I made sure to wrap 90% of the most used features of "contained" structures into the object API to centralize accesses. Well, that's for the philosophy.
It also reduce code redundancy as no other structure can exist in the scene (beside camera) and, more importantly, gets automatically updated: all the updates are centralized in the object module, allowing for easy and efficient threading when the time comes).
All the updates for animations, frames, FXs, spawners, sounds are orchestrated from the object hub. And it'll be the same for the timeline updates.
For the record, there's one exception which is the body/physics as physics engine require a monolithic update of the full world, due to the iterative solving of collisions, nothing that can be done about it, unfortunately.
That's a good point. I'm still not sure which one to do as I though of that myself. Faistoiplaisir also made the same remark a couple of days ago.
Here's my thinking so far:
none- a single character is much easier to parse in code (the parser is written in pure C and the more complex the patterns to recognize are, the more buggy the implementation gets, not even mentioning supporting the non case sensitiveness and the fact that we're handling a unicode stream)
- it's much easier to screw up when typing a word than a character, and as we're speaking of pure text editing (config file) there's no parser that will point that the user made a typo, and I know a lot of you already raged while trying to find why something isn't working because of simple typos in config (and I'm part of that group)
- the keywords 'self' and 'this' have an object-oriented co-notation which is not what it really is in this case, but granted that we can find something else
- an isolated single character stands out much more in a stream of text than a keyword and are easier to spot
- using a single character would be consistent with how the config system is working, all the special features are all handled by single characters ('@', '~', '#', ';', '=', '.', '', '"none')
- I'm lazy and I like the conciseness of it
Also, your example isn't quite completely correct:
Object.Create takes a string as parameter
Object.GetSpeed takes a GUID as parameter (ie. a 64bit unsigned integer)
So Object.GetSpeed Object1 is invalid but Object.GetSpeed 0x1234567890ABCDEF is valid.
That's an excellent suggestion, but it's slightly unrelated as it would actually be the creation of a orxPATH structure (and the registration of a Object.SetPath command subsequently).
I'll think about it and see what I can do in a short/medium term future.
Now comes the long and tedious process of adding all the commands (so far I've only added two of them: Object.Create and Object.GetName).
All the timeline events will be evaluated by the command module but not error message will be issued if it's not a valid command that has been sent (after all you can send whatever you want via the timelines...). The stack feature only exist for command that are evaluated from timeline events. If you call orxCommand_Execute() or orxCommand_Evaluate() manually, there's no stack available.
You can ask to push the result of a function as many time as you want but make sure to have the same numbers of push and pop, as usual for a stack.
Here are the current special characters:
Example: The first command retrieves the name of the object owning the timeline and pushes the result on the stack.
The second command pops the name from the stack and creates an object with it. It'll then push its GUID 3 times on the stack, probably to apply 3 sequential modifications on it after creation.
The last command retrieves pops one of the pushed GUID from the stack and retrieve the name of that object.
Right now nothing is really optimized but I have some ideas on how to achieve this if need be. It'll all depend on the use of the system, of course, for now I'll focus on adding new commands.
Which brings me to: which commands would you like to have? Please let me know!
(Objects)
Create (to make new objects)
Delete (to destroy them)
AddFX (to apply some effects to them)
SetColor (to fade objects in and out)
GetSibling/GetChild (to chain things across related objects)
(Sounds)
SetVolume (adjust intensity of background music)
(Clock)
So.. if we have some functions for manipulating the clocks here, someone could do the kind of crazy slow-fast-slow time-stretching effects that are trendy in cinema right now.
Does this list make sense?
SetPath : like you said you could constrain the object to an orxPath curve, and a parameter could tell the object to follow or not the tangent of the curve
PathPercentage : to animate the object's location on the curve, like going back and forth on it. It would be cool if you could use an FX to interpolate smoothly the location on the curve like if it goes back and forth on the curve it would slow down then ramp the other way.
SetAnimation : not sure but maybe telling how to link to another animation rather than the raw animation to switch to
SetFrequency : to change the speed of the animation (maybe if you could ramp up or down with an FX rather than brutally changing speed ?)
SetGraphic : to change clothes of a character. Also if you could change the position and size of the graphics you'd be able to make some kind of level of detail
SetVisibility : to hide objects
IsInsideBox : to know if the object is there
Functions to change the physics body parameters : if I have a cutscene with a falling object I may want to change how it bounces if I know it bounces on a different ground.
Maybe something to know if the object has collided since last evaluation so you can change its graphics.
Maybe a way to access other objects like :
Raycast (0,0,0) (90)< : if I have a cutscene with a character shooting at other things I can change their graphics or animation
GetClosestObject < : If I want the character to take an arrow in the knee, I could use this to know which part to parent it to
or GetClosestNeighbors[5] < would pick the 5th closest object in this array if I'd need to select more objects than the closest one
Will do!
Sounds good!
I'll think about it more when the path module will be there.
Sounds good but I don't think I understand the last part, could you give more details?
Well I guess you'll have to get the value from a FX and then apply it to the frequency, but for now I'll add SetFrequency that takes a numeric value as argument.
Sure, why not, but again I'm not sure I understand the last part about LoD.
This one isn't exactly supported by orx but I'll see what I can do.
Mmh why not, but what would you do of the information? You can only push it on the stack and use it later as an input for another command.
First part ok, but second part feels like you are trying to put more logic (as in a scripting module) than command sequences are intended.
Ok, but again it feels like those rely on the implementation of many other commands.
Just to make sure we're on the same page: commands aren't cheap to execute and it's in no way replacement for a full-fledged scripting support, the goal is to allow ordered sequences of "events" (as in create this object, move it, play a sound, deletes everything), not actually putting game logic in them.
Another use later will be through a console module allowing to query existing commands, type them with auto-completion and execute them (to change global variables, instantiate an object, etc...).
When you say commands aren't cheap to execute do you mean that the Timeline file is parsed every time something happens or just that running commands every frame is slow ? If it's saved somewhere in memory it wouldn't be slower than running commands with code.
For SetAnimation, in the new animation module you say you link animations directly like Anim1-Anim2 = immediate|cleartaget|low|medium|high(priority). It'd be nice to set the animation to this link rather than switching directly to Anim2 or rewriting the link in the timeline.
For the lod I meant a manual mipmap. If I know the object gets close or far from the camera I can change its texture to a bigger or smaller one.
IsInsideBox would be used with an if : Logic.if < "IsInsideBox 1" "SetGraphics Wrecked # TimeLine.AddTrack NewTrack"
Here you would make a new timeline, delete the current one and stop evaluating the if.
Mmh, why not. It sounds a bit overkill to me and usually in most cases cutscenes are smoke and mirrors instead of being actual simulation, but I can see the appeal.
What I mean is that it's much more expensive than running native code. Commands are stored in memory but are not compiled: they're interpreted on every calls, with special characters being replaced with their value.
Typically scripting languages, when compiled, are one order of magnitude slower than native code. In that case, everything being interpreted, it's probably at least two orders of magnitude slower. Commands are meant to be called sporadically, not for scripting tight logic or being executed every frame.
Mmh, again I'm not sure I follow you. When asking for a new animation the graph will always be used. Let's take an example,
if you have those links defined: A<->B<->C->A (last one is unidirectional), then being in A and asking for anim C will make you go to B first. If the animation is cut or played till the end is decided by the "immediate" property of the link.
Being in B and asking for A will go directly to A (there's a direct link). Being in C and asking for A will also go directly there as there's a link.
Mmh, I haven't heard of anyong using mipmaps for 2D rendering, looks to me that replacing the texture on a face that is directly facing the camera is going to pop in a very ugly way but I might be wrong.
Anyway I think this kind of usage will be pretty marginal, if any, so it should be handled on the user side.
Also keep in mind that users can register their own commands on the fly and execute them exactly like the ones I'm adding directly inside orx.
Ok, but in that case the syntax would be more something like:
>> IsInside2DBox ^ vector vector # Logic.If < "Object.SetGraphic ^ Wrecked" # Logic.If < "Object.AddTrack ^ NewTrack"
- Object.AddTrack / Object.RemoveTrack (TimeLine)
- Object.AddFX / Object.RemoveFX
- Object.AddShader / Object.RemoveShader
- Object.AddSound / Object.RemoveSound
Though I haven't tested them yet. :P
No hurry for me since I probably won't be able to look into it until the weekend.
There are a few ways commands can be used, so let's have a look at simple examples.
- Executing a command directly from code: stResult.u64Value will contain the GUID of the created object
Of course the string can be loaded from config for something more data-driven.
Now, if you want to use commands + timelines, let's say for creating a Timer object that will play the beep sounds before a race starts.
It's a very silly and simple example of course, so if you want something more elaborate, let me know!
- Object.SetVolume
- Object.SetPitch
- Config.CreateSection
- Config.HasSection
- Config.SetParent
- Config.GetParent
- Config.SetValue
- Config.GetValue
- Config.HasValue
When describing commands here, I'll put the optional parameters within []. I'll also add a way to query which commands are available at runtime (and see their prototype).
Later I'll add command auto-completion as well as a console (basic overlay render + history).
But for now, I've added some commands:
- Object.SetPosition GUID Position [Global=false]
- Object.SetRotation GUID Rotation [Global=false]
- Object.SetScale GUID Scale [Global=false]
- Object.GetPosition GUID [Global=false]
- Object.GetRotation GUID [Global=false]
- Object.GetScale GUID [Global=false]
The system finally becomes usable for real-case scenarios.
- Object.GetID GUID
- Object.SetSpeed GUID Speed [Relative=false]
- Object.GetSpeed GUID [Relative=false]
- Object.SetAngularVelocity Velocity
- Object.GetAngularVelocity
- Object.SetCustomGravity Gravity
- Object.GetCustomGravity
- Object.SetOwner GUID OwnerGUID
- Object.GetOwner GUID
- Object.GetOwnedChild GUID
- Object.GetOwnedSibling GUID
- Object.SetLifeTime GUID LifeTime
- Object.Enable GUID Enable [Recursive=false]
- Object.Pause GUID Pause [Recursive=false]
- Mouse.SetPosition Position
- Mouse.GetPosition
- Mouse.ShowCursor Show
- Locale.SelectLanguage Language
- Locale.GetCurrentLanguage
- Locale.SetString Name String
- Locale.GetString Name
- Config.GetParent Section
- Config.SetParent Section [Parent=<empty>]
- Config.CreateSection Section
- Config.HasSection Section
- Config.HasValue Section Key
- Config.GetValue Section Key
- Config.SetValue Section Key Value
An alias is a shortcut to a command or another alias. It can also define arguments. An alias can point to a command that doesn't exist, it's valid but it won't be able to execute.
The advantage is to rerouting an alias to different commands while not changing the caller (often a timeline).
If you open the console (default key '~'), you can type: Commands and aliases are actually *not* case sensitive.
This will display a list of all defined aliases (tab does auto-completion on alias and commands, btw).
Command.AddAlias is used to add a new alias.
Some are shipped with orx by default, for example typing 'help' in the console will actually trigger the command Command.Help.
Now about arguments, let's say we add an alias SetWall this way: Now if we type: It'll actually call which will change the config value Wall.Alpha to 1 in memory.
If we go further and add a new alias SetWallAlpha And now execute: It'll have the same effect as the previous call to SetWall.
Aliases can be defined in config for auto-loading, simply add them to the [Console] section:
I'm currently using them for all our game's menu, tutorials, UI, pause, reset, credits, music changes, etc... Everything's handled from config and doesn't require any code change.
I know Lydesik used it for his latest project for menu transitions and maybe other things (he also added his own commands, if I'm correct).
I'm trying to think of ways I can use it in my project. Anything that could make menu handling code better would be greatly appreciated. An example for menu transitions would be awesome.
A list in the wiki would be great but I don't have the time to do it myself, unfortunately.
As for the example for the menu, I'll try to post something in a couple of days, maybe a wiki tutorial if I get the opportunity.
I can't use Commands.ListCommands. It might have been added in a later revision? Oh well, I need to update all my stuff anyway.
EDIT: Have you considered commands for changing the input set? Just wondering.
You'll get:
Anyway, I figured out a lot about commands. I probably won't need that big of an example now, but it would be nice to see how others are using it to give me a few ideas or something.
I love how flexible the system is. I'm gonna have fun with this thing!
I wanted to work on a tutorial for using commands/timelines to make some menu screens, but I don't have any assets.
Any idea where I could fine preferably nice/cute assets to make such a tutorial (something akin to what Danc propose on http://www.lostgarden.com but more oriented buttons/background screens)?
Another quick question about commands. Is there a command that can quit the game? I don't really need one since I can turn on the "quit" input, but I'm pretty curious as to whether there is one.
EDIT: An "AddDelayedFX" command would be great too, but only if it's not too much trouble.
Another EDIT: Or, just a Command.Wait, which could be more general-purpose.
I considered adding support for the delay for FXs, but it's actually getting a bit obsolete now that we have timelines: you can simply create a timeline that will add your FX with the exact desired timing and use Object.AddTrack to add it to your target object.
This is a very simple example of a generic Delay 2 second track that we then use to either add an FX or play a sound.
We could even make the 2 second a variable parameter, but I'll let you think about it.
Untested as I wrote it in the forum but I think it should work.
[TestTrack]
0 = SomeCommand
1 = SomeOtherCommand
I did this in one of my files and it seemed to work. But then I tried putting the commands together and it didn't work right... what's up with that?
Here's one track I use in the bounce sample that ships with orx to create the walls:
Not sure I understand you question though, what stopped working when you did what?
As an aside, my lists are getting really long. Is there a way to make multiple line strings like in C with the character?
Config.ClearSection doesn't exist...
Simply removing them should make everything work.
As for multi-line lists, doesn't https://forum.orx-project.org/discussion/2912#Comment_3127 cover your needs?
Lastly, you're out of sync: I added Config.ClearSection a couple of days ago, when I was writing that post.