• Hi Guest!

    We are extremely excited to announce the release of our first Beta1.1 and the first release of our Public AddonKit!
    To participate in the Beta, a subscription to the Entertainer or Creator Tier is required. For access to the Public AddonKit you must be a Creator tier member. Once subscribed, download instructions can be found here.

    Click here for information and guides regarding the VaM2 beta. Join our Discord server for more announcements and community discussion about VaM2.
  • Hi Guest!

    VaM2 Resource Categories have now been added to the Hub! For information on posting VaM2 resources and details about VaM2 related changes to our Community Forums, please see our official announcement here.
SceneDirector

Plugins + Scripts SceneDirector

Download [<1 MB]

Foost

Well-known member
Featured Contributor
Joined
Sep 23, 2024
Messages
159
Reactions
687
Foost submitted a new resource:

SceneDirector - The Swiss Army knife of scene scripting and management

Introduction​

SceneDirectory is meant to become the Swiss Army knife of atom state management and scene scripting.

It's based on my personal ScenePlugin, which was used to drive the DildoLanguage demo scene, as well as all my personal scenes. But it had no UI to speak of and was never intended for public release. This is a complete rewrite, based on similar ideas, with proper UI. Hopefully you will...

Read more about this resource...
 
Since the documentation for this might become quite long, to avoid spamming the Overview too much, I will reserve few posts in here and link them later from the docs, as they are updated. Hope you don't mind!

Reserved for Gotchas and FAQs
 
Reserved for detailed component documentation
 
(this document is up to date for version 3 of the plugin)

Variables, Expressions and Drivers​

If you are interested in using the power of math to control your scene, this is the right place to be. Variables, Expressions and Drivers provide many ways to script your scene. While not as powerful as a fully visual or language-based scripting system, you can still script a lot of features just with expressions. All without writing custom code. Is some ways they are similar to hooking up tons of LogicBricks together, but at least for me personally in a much more readable and easier to organize approach. You could use them to:
  • Display values of any public atom properties (anything available to triggers) on UI elements, using custom formatting.
  • Smoothly transition a value when flipping a switch, then use that to drive... anything... in the scene.
  • Implement a light blinking according to parameters coming from the scene's state.
  • Drive morph values by other morphs, user-facing sliders, or really... anything.

Global Variables
1761938471524.png

To use the system, first you need to define some Variables. Let's start with Global Variables, available on the Variables main tab. Current version of the plugin supports only scalar (single-value) variables. Use one of the buttons at the top to add a new variable, which can be of one of these types:
  • Boolean: True/False value. Usually represented as on On/Off toggle in VaM.
  • Integer: 32-bit integer (whole number) value. Usually represented as a slider limited to whole numbers in VaM.
  • Float: A real number value (stored as a 32-bit floating-point). Usually represented as a slider in VaM.
  • String: Any text
The data type of a variable is fixed once a variable is added.

Each new variable will add a section to the variables UI, where you can move the variables up/down (purely for UI organization purposes), collapse the variable block (also just a UI thing). You can also rename a variable or delete it, as long as it's not used from any expression elsewhere in the plugin.

The dropdown box on the variable section separator lets you choose the variable's source. That is - where the variable gets its value. This is where the fun begins.

Variable Sources
There are several sources available to variables of any types, and few sources specific to each type. The common sources are:
  1. Constant:
    A simple constant value. For booleans, you will get a toggle to set the value, for integers/floats a slider, for strings an edit box to type the text into. Rather simple so far.
  2. Public Value:
    At first glance, this might look the same as a constant. The UI to set the value will be the same. The difference is that the plugin will generate a triggerable JSON param for each public value. This means you can set the variable's variable via triggers from anywhere outside of the plugin. The triggers will be called "Set Variable xxx", where xxx is the variable's name.
  3. Param:
    In a way similar to public value, but inverted. Instead of creating a public JSON param that can be set via triggers from outside, this one will let you track any other public JSON param and have the variable automatically follow its value. The param can come from any atom, not just the atoms controlled by the plugin. If you can trigger it, you can track it in a variable.
  4. Expression:
    This is where the fun begins. Expressions will let you type in a mathematical expression, and the variable's value will be its result. Documented in more detail below. Available for boolean, integer, or floating-point. Not for strings.
  5. Formatted String:
    Alternative to expressions available for strings. It lets you include any variable/expression values inside a nicely formatted text. Also explained in more detail below.
  6. Built-inValues:
    Internal values set by the plugin. Different options are available based on the type. Booleans have none in the current version.

Built-in Sources​

Note: some of the sources are links to Unity's documentation, where the variable source matches Unity's feature.

Integer Variables​

  • Frame Index: A value which increments by one for each frame. Similar to Unity's Time.frameCount, but tracked internally by the plugin. It starts at 0 when the plugin is loaded, and increments only when the plugin is enabled. It increments even when the simulation is paused.
  • Pose Index: Index of the applied SceneDirector pose.
  • Pose Count: Total number of poses defined in the SceneDirector.
  • Pose Index in Chapter: Pose index within the set of poses which belong to the currently applied SceneDirector chapter.
  • Chapter Length: Total number of poses which belong to the currently applied SceneDirector chapter.
  • Chapter Index: Index of the currently applied SceneDirector chapter.
  • Chapter Count: Total number of chapters defined in the SceneDirector.
For an example of the pose/chapter-related variables, see the DildoLanguage Demo Scene. They are used to construct the texts on the buttons on the left side of the control UI.

Float Variables​

  • Scene Time: Current "game" time in seconds. Similar to Unity's Time.time, but tracked internally by the plugin. It starts at 0 when the plugin is loaded, and increments only when the plugin is enabled. It increments even when the simulation is paused.
  • Simulation Time: Similar to the above, but does not advance when the simulation is paused by the Freeze Motion/Sound button or other means.
  • Real Time: Real time in seconds. Starts at 0 when the plugin is loaded, then advances at the speed of the computer's clock, ignoring any Unity's quirks such as pausing or simulation speed multipliers.
  • Last Frame Duration: Same as Unity's Time.deltaTime. Useful when smoothly updating variable values over time in expressions.
The screenshot at the top which is used to show the Global Variables UI displays some frame time-related fun, using the Last Frame Duration to compute and then smooth current fps. The exponential-smoothing expressions is just some dark magic which ensures the smoothing happens at the same speed regardless of the current framerate (but it's not a simple linear smoothing).

String Variables​

  • Pose Name: Name of the applied SceneDirector pose.
  • Chapter Name: Name of the currently applied SceneDirector chapter.

Expressions​

A custom mathematical expression can be used to compute a variable's value. The same expression system is used for booleans, integers and floats. Internally all expressions are evaluated as floating points and converted as needed:
  • Float/Int -> Boolean: Zero = False (Off). Any non-zero value = True (On)
  • Boolean -> Float/Int: False is converted to zero (0). True is converted to one (1).
  • Float -> Integer: The value is rounded to nearest integer
You can see some examples of the expressions in the screenshot at the top. They support all you'd expect and them some extra bells and whistles.

Operators​

The usual unary and binary operators are supported, as well as few less than usual, with the expected operator precedence rules.

Arithmetic operators:
  • Unary minus operator: - a
  • The usual binary operators for addition, subtraction, multiplication, division: a + b, a - b, a * b, a / b
  • Power operator: a ^ b
  • Modulo operator: a % b
Logical operators. As explained above, in floating-point context, the results of these is 0 for False and 1 for True:
Value comparison:
  • Value comparison operators: a == b, a != b, a > b, a < b, a >= b, a <= b

Functions​

The following functions are implemented by the expression system. Most of them match standard C# or Unity functions, so the list below links to the corresponding docs rather than explaining everything here again.
Conditional function:
  • if(condition, trueExpression, falseExpression)
    This is similar to operator ?: in languages such as C++ or C#. First the condition expression is evaluated. If it's True, then the trueExpression is evaluated and that's the result. Otherwise the falseExpression is evaluated and that's the result.

Self-referencing Variables, Recursion​

It is allowed the reference the variable in its own expression. It will not result in an infinite recursion. Generally the logic is that each expression is evaluated at most once in each frame. This means that self-referencing expressions can be used to smoothly update variable values per-frame. For example, to smoothly advance an angle at given speed, you would do this:
  1. Define a speed variable. Either a constant, or a public value, or a param value. The interpreation is "angle change per second".
  2. Define a frameTime variable, with the builting source "Last Frame Duration".
  3. Define an angle variable, with this expression: (angle + frameTime * speed) % 360
    The module is there to keep the value reasonably large, since angles wrap around at 360 degrees anyway. It really depends on what you will use it for.
Another example. You have a user-defined value, maybe from a slider. But you want to move towards that value smoothly when the player changes it too fast:
  1. Define a targetValue variable. Probably as a public value, controlled by a slider via triggers for example.
  2. Define a frameTime variable, with the builting source "Last Frame Duration".
  3. Define an smoothValue variable, with this expression: advance(smoothValue, targetValue, 10 * frameTime)
    This means that the smooth value will always move towards the target value, at change by at most 10 per second.
A bit more technical detail about the internal update system:
Internally the expression system is optimized as much as possible without generating code at run-time (which is not allowed in VaM). The general flow is, once per each frame:
  1. The "source" variables are refreshed if needed. These include constant variables, public variables and param variables.
  2. Only if a source variable's value changes, active drivers which use the variable (directly or indirectly) are marked for evaluation.
  3. Marked drivers are evaluated, and recursively any expression variables needed by those drivers.
As a consequence, any expressions is evaluated at most once per frame. But if an expression is not needed by any of the active drivers, it won't be evaluated at all.

Formatted Strings​

Instead of expressions, strings support a "formatted string" source. This is basically a text which can reference other variables, which will then be replaced into the text. The syntax of variable replacement is similar to C#'s interpolated strings, but it's a bit more limited:
  • Only names of other variables can be used in the {} interpolated item, no expressions. But the variable itself can be an expression of course.
  • There is no width field, only the format string.
  • To include the { character in the formatted string literally, double it, just like you would in C#. But do not double the closing }.
Or in other words, the string can contain items using the syntax of {variable} to include that variable's value in the text, or {variable:format} to include the variable's value using the specific format.

The formatting string is interpreted depending on the variable's type, similar to the C#'s ToString function:
  • int/float: The usual C#'s numeric formatting is supported. Probably the most useful is simple {variable:F3} to print a floating-point value with 3 decimal digits (or any other number than 3).
  • string: No formats are supported, always returns the text as is.
  • boolan: With no format provided, returns "True" or "False".
    Special formatting is supported for booleans to allow for custom true/false strings: if use syntax like {variable:On;Off}, it will convert to "On" for true values and to "Off" for false values.
    Technical Note: at a technical level, booleans are converted to integer, 1 for true values, -1 for false values, and converted ToString. C# numeric conversion supports custom conversion, the ";" character is being used as a section separator. So you can use any other custom formatting, but the example above is the most useful one.

Variables and Expressions Defined at Atom Scope
1761943615387.png

So far the examples used only global variables. But variables (and expressions) can be also defined per-atom, on the Atoms-Components-Variables tab. The UI and its behavior is exactly the same as for the global variables, at least in version 3 of the plugin. The main difference is:
  • Global expressions can reference only other Global variables
  • Atom-scoped expressions can reference either other variables from the same atom, or any Global variables. If any Atom-scoped and a Global variables have the same name, the Atom-scoped one will be used.
There are more plans for Atom-scoped variables for the future, but for now they can be used mostly to organize variables and to remove the clutter at the Global scope if a variable is not needed anywhere outside of an atom.

Atom-scoped variables can still have Public Value source. In that case, the name of the trigger param will include the Atom's name.

Drivers​

But on their own, variables and expressions are not of much use. You can use complex expressions to compute magical values, but you then also must be able to use them to do something. That's what Drivers are for. Basically, they allow you to use any expression to drive a value of any(*) atom property. Through drivers, you can use the expressions system to change/animate a light's intensity or range, to scale texts on UI elements, to change the value of sliders or toggles, or to change morph values. If you can store it as an atom's state, you can (almost always) also control it by MATH.

Drivers are added on the Atoms-Components-Drivers tab. Simply choose a property from the dropdown to add a driver for it. For each component enabled on the atom, it will list all its properties compatible with the Drivers system.

Then type in an expression, and that property's value will be controlled by the expression. That's it! Check the version 3 update changes for some examples.

It can be somewhat annoying when a driver controls something in the scene at all times. Which is what (by default), drivers are not applied/running when:
  • The simulation is paused by the Freeze Motion/Sound VaM's toggle.
  • The atom's state has sub-states defined, and you directly apply a specific sub-state. You probably want to edit that sub-state then, without being distracted by drivers animating things. This applies only to that atom of course.
  • You apply an atom's transition mid-state.
These are the default settings, you can change them on the Settings tab.

Per-State Drivers​

By default, a driver takes over the value's control regardless of the applied atom's state. Optionally, drivers can be overridden per-state.
  1. First, tick the Enabled Overrides per State
  2. Switch to the Atom-States tab and scroll to the corresponding component of the atom. At the bottom of each component with drivers that have overrides enabled, you will see a special section listing those drivers.
  3. Here you can choose for each driver which expression it should use:
    • Parent Expression: This is the default, and if set for all states, will behave as if no overrides were even enabled for the driver:
      • For the main state, this will use the driver defined for this property at the component level.
      • For a non-main sub-state, if enabled for the state, this will use the main state's driver
      • For a transition mid-state, this will use the target state's driver (however, there is no UI where you could edit mid-states directly, yet)
    • Custom Expression: Type in an expression specific to this main state, sub-state, or mid-state.
    • State Value: Do not use driver at all in this state. Instead, use the value stored for the state, as if it had no driver defined.
As a side note, you can't set State Value directly for the global driver defined for a property at a component level. But using empty expression for it will achieve the same result.

(*) There are some limits to what a driver can control:
  • It must be a property of an existing component supported by SceneDirector. Until custom components are implemented (allowing you to build your own component from any atom's triggerable params), what you see in the plugin is what you get. But you can define custom morph components with any morphs, so you can drive those already!
  • Version 3 of the plugin supports only scalar variables and expressions. Therefore Drivers can be applied only to booleans, integers, floats or strings. Or in short, if it's a toggle/slider, you can most likely control it by a driver. You can't control colors through drivers (yet)
  • The Placement component is rather special and does not expose any of its properties to Drivers, just like you can't control any of them in the UI through sliders, you can only capture the state. There is a plan to expose at least some of it to drivers, once vector and rotation variables are implemented. But possibly only the main controller will be available, I am not sure yet.

Other Use of Variables​

For UIObjects, you can use formatted text directly to control the text on UIText, sliders, toggles and other UI objects. By default to avoid confusion, text formatting is not enabled for those fields, the text you type in will be used as is. To enable the formatting for a controlled UI object atom, turn on the toggle Process Variable references in Text on the Atoms-Components-General tab.

Examples​

Check the update of version 3 for some examples of how the drivers could be used.
Also as already mentioned, the DildoLanguage demo scene uses variables and formatted strings to update the button texts, and to show the penetration information box.
 
Last edited:
Reserved for Tips and Tricks
 
I am not a developer so I can't give any input. :(
It does seems powerful though.
Hehe yeah, explaining what's it all about and what one can do with the plugin will be challenge. I will probably have to create several instructional videos and scenes once it's more mature. Even then, I am not sure how many creators will adapt it for their scenes, if any at all.

To be honest I created it mostly for myself, since I hate fiddling with many logic bricks and tons of triggers connecting them together with other plugins to script simple scene behavior. And my previous plugin which I used for all my scenes had no UI at all ; which for a nerd like me is not always bad, setting up things in JSON can be quite often much faster than clicking in UI. But it started showing its limits. It still can do much more than SceneDirector though, the road ahead is a long one... (although the transition and sub-state idle animation system is already way more powerful in SceneDirector)
 
Foost updated SceneDirector with a new update entry:

v2 - variable params

New Variable Source - Param​

This allows you to track any public storable param of anything in the scene as a variable.
While usage of variables and expressions is still limited mostly to text formatting, you can already use this feature to show any boolean, floating-point or string param value in the scene on UI objects.
View attachment 533317

Other Changes​

  • Support for SexyFluids v10.f4
    • Automatically...

Read the rest of this update entry...
 
Foost updated SceneDirector with a new update entry:

v3 - drivers

Drivers​

The main new feature of this update is the addition of Drivers, which let you drive any(*) of the component properties by mathematical expressions. Some ideas/examples for use of drivers are shown at the bottom of this update.

Drivers for atom properties are defined on the Components-Drivers tab. For now the expression system supports only scalar values, so drivers are available only for boolean (toggle), int/float (slider) and string properties...

Read the rest of this update entry...
 
Amazing plugin... exactly what I was looking for as a new creator.

Any advice on how to use use SubStates with something like MacGruber Gaze? The person's head animation is indeed weird when enabled.
Also, would there be any way to capture the person Plugins enable/disable states? Is that possible with Drivers?
I am not sure what exactly are you trying to achieve. Do you want to use gaze in some states while not in others? Or are you trying to combine character animation via Placement component's substates with Gaze?

I have no experience with Gaze, but I assume it rotates the head controller to achieve the look direction? That would atm interfere with any placement-based animations and there is not much you could do about it. I'd have to add more detailed configuration to the Placement component to have it control only some controllers. Which I'll mostly do at some point, but it will take a while to get there. But if Gaze is using forces to rotate the head, it should cooperate just fine.

If you do not want to animate the character via Placements, you can just turn off the component in the person atom's component config, then it should not interfere with Gaze.

Plugin enable state is currently not supported by any of the components, so if you are trying to turn the gaze on/off, that is not possible yet either. I'll most likely not implement any direct component support for that, instead you will be able to build custom components from any triggerable params. In the near(tm) future, custom param components is one of the highest priority for the plugin atm, as it will unlock so many new options for it without implementing direct support for various VaM features.
 
Do you want to use gaze in some states while not in others?
Yes exactly. For example, chapter 1 has the person staring at the player (using Gaze) and in chapter 2, she starts staring at something else.

Custom param components would be the solution and totally awesome. Happy to hear it's highest prio. Looking forward to it!
 
Back
Top Bottom