TimeLine and Commands

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

  • edited April 2012
    Hi Iarwain !

    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 ?
  • edited April 2012
    The most obvious example will be delayed object creation. Or randomized idle animations. Or cutscene cinematics, like this one:

    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.
  • edited April 2012
    I'm trying to imagine specific examples myself.

    Could one use this to script complex enemy boss behaviors? Like a repeating series of boss attack behaviors?
  • edited April 2012
    Definitely. TimeLine can spawn new TimeLines and can be associated with objects via config.

    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...
  • edited April 2012
    I was thinking of a very simple stack-based return/parameter transfer between command calls to allow some dynamism.
    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:
    0 = >Object.GetSpeed -
      # >Object.GetName -
      # >String.Print 64 "Object " < "Speed " <
      #  Object.SetTextString - <
    

    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?
  • edited April 2012
    I am on Level 4 in Legend of Grimrock. I've resisted the urge to go on any forums for answers so far :)

    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.
  • edited April 2012
    I have to admit I've looked a couple of times for puzzles in level 8-9 but only to get some pointers. Some puzzles are much more clever than what I would have expected at first. I'm playing the beta of Diablo III at the same time and it's not really the same level of brain stimulation. ;)

    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. :)
  • edited April 2012
    Are you going to roll it out on SVN sometime soon?

    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.
  • edited April 2012
    I wanted to code most of it this week end but Diablo III caught me off-guard. ^^
    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?
  • edited April 2012
    (ie. the current object owning that TimeLine)
    I'm not sure to understand if the tracks you talk about are defined for each object or for the global scene. If a track can mean an animation like walk, run, jump or if it means a scene in a cutscene. Dividing the global scene into smaller scenes would be handy for cutscenes, you could change the duration of a scene without changing the other ones, and also switch from a scene to another one with triggers like in Flash.

    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 :
    [Timeline]
    SceneList = Scene1 # Scene2
    
    [Scene1] 
    VisibleObjects = Object1 # Object2 # Object3
    0.0 = "SetEvent Object1 Event1" ;walk animation
        ;move it 20 units to the right in 5 seconds
        # "Transform Object1 (20, 0, 0) (1, 1, 1) (0.0) 5.0"
    5.0 = "Goto Scene2"
    
    [Scene2]
    VisibleObjects = Object3 # Object4 # Object5
    ...
    
    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 :
    0.0     = "SetEvent Object1 Event1" ;walk animation
            ;move it 20 units to the right in 5 seconds
            # "Transform Object1 (20, 0, 0) (1, 1, 1) (0.0) 5.0"
    
    Anytime = Logic.if < "Greaterthan Object1.PosX 10.0" "Goto Scene2"
            ;when the character is halfway, switch to scene 2
    

    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.
  • edited April 2012
    grumly wrote:
    (ie. the current object owning that TimeLine)
    I'm not sure to understand if the tracks you talk about are defined for each object or for the global scene. If a track can mean an animation like walk, run, jump or if it means a scene in a cutscene. Dividing the global scene into smaller scenes would be handy for cutscenes, you could change the duration of a scene without changing the other ones, and also switch from a scene to another one with triggers like in Flash.

    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 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.

    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.
    An example :
    [Timeline]
    SceneList = Scene1 # Scene2
    
    [Scene1] 
    VisibleObjects = Object1 # Object2 # Object3
    0.0 = "SetEvent Object1 Event1" ;walk animation
        ;move it 20 units to the right in 5 seconds
        # "Transform Object1 (20, 0, 0) (1, 1, 1) (0.0) 5.0"
    5.0 = "Goto Scene2"
    
    [Scene2]
    VisibleObjects = Object3 # Object4 # Object5
    ...
    
    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:
    [Scene1Track]
    0 = >Object.Create Object1 ; Pushed on stack
      # >Object.Create Object2 ; Pushed on stack
      # Object.Create Object3
      # Object.SetLifeTime < 5 ; Object2 SetLifeTime
      # >Object.SetLifeTime < 5 ; Object1 SetLifeTime + push it back on stack
      # Object.SetSpeed < ; Object1 SetSpeed
    5 = TimeLine.AddTrack Scene2Track
    
    [Scene2Track]
    0 = Object.Create Object4
      # Object.Create Object5
    
    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 :
    0.0     = "SetEvent Object1 Event1" ;walk animation
            ;move it 20 units to the right in 5 seconds
            # "Transform Object1 (20, 0, 0) (1, 1, 1) (0.0) 5.0"
    
    Anytime = Logic.if < "Greaterthan Object1.PosX 10.0" "Goto Scene2"
            ;when the character is halfway, switch to scene 2
    

    If you need to do looping section, put them on a separate self-adding track. Example:
    [Every2SecondTrack]
    0 = DoSomething
    2 = TimeLine.AddTrack Every2SecondTrack
    
    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.

    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. :)
    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.

    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. ;)
    And also I didn't understand what the - was for in your example. Why is there a - everytime you access a parameter.

    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.
  • edited April 2012
    I didn't think you could play several tracks at the same time. It looks powerful not just for cutscenes but for levels as well to synchronize patterns of monsters and platforms.

    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.
  • edited April 2012
    grumly wrote:
    I didn't think you could play several tracks at the same time. It looks powerful not just for cutscenes but for levels as well to synchronize patterns of monsters and platforms.

    Ah I see, with that context I understand the reasons behind your proposal.
    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.

    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.
    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.

    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.
    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 -

    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.
    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.

    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.
  • edited April 2012
    Also, I wrote the orxTimeLine module last night and it's 95% complete. However the orxCommand module hasn't been started yet so it might not be that useful except for those who want to use custom sequences to do whatever they want (like music/visual synchronization in the demoscene spirit, for example).
  • edited May 2012
    The command module followed the timeline module and has been finished last night. I tested a few cases but I wouldn't be surprised if there were some issues left.

    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:
    • none" is used as a block delimiter, exactly in the same way as in config
    • > is used for pushing the result of a command and has to be typed before the command
    • < is used for poping an element from the stack
    • ^ is used to replace an argument by the GUID of the timeline's owner object

    Example:
    [MyTimeLine]
    0 = > Object.GetName ^ #
        >>> Object.Create < #
        Object.GetName <
    
    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!
  • edited May 2012
    Hmm, for a cinematic sequence like a game intro, you probably need a list like the following:

    (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?
  • edited May 2012
    All that sounds good! Keep them coming! =)
  • edited May 2012
    On objects, adding and removing shaders and FX would be awesome.
  • edited May 2012
    SetParent : Parent an object to an other so a character can grab an object/take an arrow in the knee. Setting it to none would release the object
    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
  • edited May 2012
    sonicbhoc wrote:
    On objects, adding and removing shaders and FX would be awesome.

    Will do!
  • edited May 2012
    grumly wrote:
    SetParent : Parent an object to an other so a character can grab an object/take an arrow in the knee. Setting it to none would release the object

    Sounds good!
    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.

    I'll think about it more when the path module will be there. :)
    SetAnimation : not sure but maybe telling how to link to another animation rather than the raw animation to switch to

    Sounds good but I don't think I understand the last part, could you give more details?
    SetFrequency : to change the speed of the animation (maybe if you could ramp up or down with an FX rather than brutally changing speed ?)

    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.
    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

    Sure, why not, but again I'm not sure I understand the last part about LoD.
    SetVisibility : to hide objects

    This one isn't exactly supported by orx but I'll see what I can do.
    IsInsideBox : to know if the object is there

    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.
    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.

    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.
    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

    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...).
  • edited May 2012
    The suggestions I made were in the case of cutscenes. I think it's more simple to make a cutscene when you don't have to worry about the timing of some events. Like if I want a car to drive into a wall it's easier to stop the car and change the graphics to a wrecked car when it collides with the wall than finding the right timing with the risk the car may not look touching the wall. And with more objects in the cutscene there are more timings to guess.

    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.
  • edited May 2012
    grumly wrote:
    The suggestions I made were in the case of cutscenes. I think it's more simple to make a cutscene when you don't have to worry about the timing of some events. Like if I want a car to drive into a wall it's easier to stop the car and change the graphics to a wrecked car when it collides with the wall than finding the right timing with the risk the car may not look touching the wall. And with more objects in the cutscene there are more timings to guess.

    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.
    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.

    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.
    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.

    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.
    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.

    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.
    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.
    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" :)
  • edited May 2012
    Also last night I added:
    - 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
  • edited May 2012
    Hi iarwain, would you mind posting a minimum complete example (.ini file) of one command at some point?

    No hurry for me since I probably won't be able to look into it until the weekend.
  • edited May 2012
    No worries.

    There are a few ways commands can be used, so let's have a look at simple examples.

    - Executing a command directly from code:
    In code:
    
    orxCOMMAND_VAR stResult;
    orxCommand_Evaluate("Object.Create RedCar", &stResult).
    
    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.
    In config.
    
    [Timer]
    TrackList = BeepTrack
    
    [BeepTrack]
    0 = Object.AddSound ^ RegularBeep
    1 = Object.AddSound ^ RegularBeep
    2 = Object.AddSound ^ HighPitchBeep
    3 = Object.Delete ^
    

    It's a very silly and simple example of course, so if you want something more elaborate, let me know!
  • edited May 2012
    Added some commands:

    - Object.SetVolume
    - Object.SetPitch
    - Config.CreateSection
    - Config.HasSection
    - Config.SetParent
    - Config.GetParent
    - Config.SetValue
    - Config.GetValue
    - Config.HasValue
  • edited May 2012
    2 days ago I added support for optional parameters for commands (similar to C++, optional parameters are the last parameters sent).
    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.
  • edited August 2012
    A bunch of new commands have been added lately, including:
    - 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
  • edited September 2012
    I stopped tracking new commands but I wanted to note that orx now supports aliases.

    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:
    Command.ListAliases
    
    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:
    command.addalias SetWall Config.SetValue Wall
    
    Now if we type:
    setwall Alpha 1
    
    It'll actually call
    Config.SetValue Wall Alpha 1
    
    which will change the config value Wall.Alpha to 1 in memory.

    If we go further and add a new alias SetWallAlpha
    command.addalias SetWallAlpha setwall Alpha
    
    And now execute:
    setwallalpha 1
    
    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:
    [Console]
    SetWall = Config.SetValue Wall
    
  • edited September 2012
    That's cool. I should figure out how to use this timeline thing. Not even sure what it's supposed to be used for...
  • edited September 2012
    Depends on your needs.

    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).
  • edited September 2012
    Is there any way I could get some example code or something? A list of available commands and what they do would be nice too.

    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.
  • edited September 2012
    Well most of the commands are listed above, but if you open the console, you can type
    Commands.ListCommands
    
    to get the full list. They're pretty explicit but you can get the prototype of a command by typing
    help <command>
    

    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.
  • edited September 2012
    Thanks, you're the best!

    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.
  • edited September 2012
    When you're all synced up, try
    command.listcommands input
    
    :)
    You'll get:
    Input.GetCurrentSet
    Input.GetValue
    Input.ResetValue
    Input.SelectSet
    Input.SetValue
    
  • edited September 2012
    Just as a reminder, you now need to sync orx with hg at http://code.orx-project.org, the svn version isn't updated anymore.
  • edited September 2012
    Yeah, I figured. Fancy hg. Back in my day, we passed around floppy disks. :P

    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! :D
  • edited September 2012
    Glad you like it.

    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)?
  • edited September 2012
    That's a pretty cool website. Unfortunately, I don't have any clue where to find anything like that, and I have no skills to make them. Ah, well.

    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.
  • edited September 2012
    Wait isn't very practical as we'd have to spawn a timer and then continue where we left (ie. restore the command execution context, etc).

    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.
  • edited September 2012
    As for a general purpose wait, consider this:
    In config:
    
    [Delay2]
    2 = > Config.GetValue LiveData Track #
          Object.AddTrack ^ <
    
    [AddFX]
    0 = > Config.GetValue LiveData FX #
          Object.AddFX ^ <
    
    [AddSound]
    0 = > Config.GetValue LiveData Sound #
          Object.AddSound ^ <
    
    
    [AddDelayedFX1]
    0 = Config.SetValue LiveData Track AddFX #
        Config.SetValue LifeData FX MyDelayedFX1 #
        Object.AddTrack ^ Delay2
    
    [AddDelayedFX2]
    0 = Config.SetValue LiveData Track AddFX #
        Config.SetValue LifeData FX MyDelayedFX2 #
        Object.AddTrack ^ Delay2
    
    [AddDelayedSound]
    0 = Config.SetValue LiveData Track AddSound #
        Config.SetValue LifeData Sound MyDelayedSound #
        Object.AddTrack ^ Delay2
    

    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. ;)
  • edited September 2012
    Ok, last example: delayed execution of any command. You can really see tracks like functions and Object.AddTrack like a function call.
    In config:
    
    [ExecDelayed]
    0 = > Config.GetValue LiveData Command #
        > Config.GetValue LiveData Delay #    
          Config.ClearSection Exec #
          Config.SetValue Exec < < #
          Object.AddTrack ^ Exec
    
    [DelayedAddFX1]
    0 = Config.SetValue LiveData Delay 12 #
        Config.SetValue LiveData Command "Object.AddFX ^ MyFX1" #
        Object.AddTrack ^ ExecDelayed
    

    Untested as I wrote it in the forum but I think it should work. :)
  • edited September 2012
    Wow, this thing really is flexible. Thanks!
  • edited September 2012
    Can you have multiple times in a timeline? Like, for instance:

    [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?
  • edited September 2012
    Yes, you can have as many time entries in a track as you want.

    Here's one track I use in the bounce sample that ships with orx to create the walls:
    [WallTrack]
    0.5         = > Object.Create Wall1 # Object.AddFX < FadeIn
    1.0         = > Object.Create Wall2 # Object.AddFX < FadeIn
    1.5         = > Object.Create Wall3 # Object.AddFX < FadeIn
    2.0         = > Object.Create Wall4 # Object.AddFX < FadeIn
    

    Not sure I understand you question though, what stopped working when you did what?
  • edited September 2012
    My original command looks like this:
    [T-IntroTimeline]
    6.50 = "Config.Load Data/Config/Scenes/Menus/MainMenu.ini"
    6.53 = "Object.Create O-MenuContainer"
    6.54 = "Object.Delete ^"
    
    With the exception of Object.Delete ^, I can guarantee that the other two commands worked. However, when I made it this:
    [T-IntroTimeline]
    6.50 = "Config.Load Data/Config/Scenes/Menus/MainMenu.ini #  Object.Create O-MenuContainer # Object.Delete ^"
    
    It loaded the config file but didn't create the MenuContainer object. Also, I don't know how to check to see if the delete was successful, so I concluded that the delete somehow took place before the creation of the MenuContainer. When I opened the console and typed the command in myself, it worked, so the config file got loaded...

    As an aside, my lists are getting really long. Is there a way to make multiple line strings like in C with the character?
  • edited September 2012
    iarwain wrote:
    Ok, last example: delayed execution of any command. You can really see tracks like functions and Object.AddTrack like a function call.
    In config:
    
    [ExecDelayed]
    0 = > Config.GetValue LiveData Command #
        > Config.GetValue LiveData Delay #    
          Config.ClearSection Exec #
          Config.SetValue Exec < < #
          Object.AddTrack ^ Exec
    
    [DelayedAddFX1]
    0 = Config.SetValue LiveData Delay 12 #
        Config.SetValue LiveData Command "Object.AddFX ^ MyFX1" #
        Object.AddTrack ^ ExecDelayed
    

    Untested as I wrote it in the forum but I think it should work. :)

    Config.ClearSection doesn't exist...
  • edited September 2012
    You're using the block marker " which prevents orx from interpreting # as a list separator.
    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. :)
  • edited October 2012
    Yep, the block marker fixed everything. I wonder what other strange problems were caused by those...
Sign In or Register to comment.