Scripter: High performance scripting in Virt-A-Mate (Patreon)
Content
I'm doing something a little bit crazy, but I feel like this may be a missing piece of VaM. We have _great_ plugins like LogicBricks (if you never saw it, it's amazing) and VUML, but they both require building the logic visually.
But if you know how to program stuff, or if you want to make more complex things, it can quickly become a problem.
Sooooo I tried writing my own tokenizer, abstract syntax tree, expression parser and runtime language (greatly inspired from JavaScript and C#) to allow high-performance code.
Example use cases:
- A Keybindings mapping that does multiple things, or does different things based on conditions
- Dynamic things like states, arousal meters, etc.
- Integrating multiple plugins together
- Adding conditions to triggers
Right now the language is fairly simple but already powerful enough for most; there aren't many integration points with VaM and Unity though, and I plan (if there is interest) to improve on the language.
If you want to try it, please keep in mind I'll definitely break your scenes with some language-level changes.
Language Features
- Types (string, float, int, bool)
- Boolean operators (||, &&, ==, !=, <, >, <=, >=)
- Math operators (+, -, *, /)
- String concatenation (+)
- Assignment operators (++, --, +=, -=, *=, /=)
- Comments (//, /* */)
- Keywords (var, if, else, for, while, true, false, throw, static)
- Code blocks ({, })
- Lexical scope (scoped variables)
Notable Omissions
- Objects and dictionaries cannot be created
- Custom functions cannot be declared, nor classes
- Order of precedence, like 1 + 2 * 3 + 4
- Code blocks without brackets, like if(condition) something();
Virt-A-Mate Functions
- getDateTime(format) (.NET: DateTime.Now.ToString(format))
- logMessage(message) (VAM: SuperController.LogMessage)
- logError(message) (VAM: SuperController.LogError)
- getTime() (Unity: Time.time)
- getDeltaTime() (Unity: Time.deltaTime)
- getRandom (Unity: Random.value when no arguments are provided, Random.Range(min, max) when two arguments are provided)
- getFloatParamValue(atomName, storableName, paramName)
- setFloatParamValue(atomName, storableName, paramName, value)
- getBoolParamValue(atomName, storableName, paramName)
- setBoolParamValue(atomName, storableName, paramName, value)
- getStringParamValue(atomName, storableName, paramName)
- setStringParamValue(atomName, storableName, paramName, value)
- getStringChooserParamValue(atomName, storableName, paramName)
- setStringChooserParamValue(atomName, storableName, paramName, value)
- invokeTrigger(atomName, storableName, paramName)
- invokeKeybinding(bindingName)
Demo Scene
The demo scene shows how it works (for now at least). The Person atom has the Scripter plugin, which has two triggers. An Action trigger (invoked by the button) and an Update trigger (invoked every frame). For those of you who'd like to see before they download:
OnAction: OnButtonClicked
static var currentAnimation = "";
static var expectedAnimation = "Play Happy";
static var currentBubbleText = "";
static var expectedBubbleText = "Check out my button!";
var annoyedLevel = getFloatParamValue("UISlider", "Trigger", "value");
annoyedLevel += 0.05;
setFloatParamValue("UISlider", "Trigger", "value", annoyedLevel);
if(annoyedLevel > 0.8) {
expectedBubbleText = "STOP IT!";
expectedAnimation = "Play Angry";
} else if(annoyedLevel > 0.5) {
expectedBubbleText = "That's annoying...";
expectedAnimation = "Play Annoyed";
} else if(annoyedLevel > 0.2) {
expectedBubbleText = "Okay...";
expectedAnimation = "Play Confused";
} else {
expectedBubbleText = "Hello!";
expectedAnimation = "Play Happy";
}
if(currentAnimation != expectedAnimation) {
setStringParamValue("Person", "SpeechBubble", "bubbleText", "STOP IT!");
invokeTrigger("Person", "plugin#1_VamTimeline.AtomPlugin", expectedAnimation);
currentAnimation = expectedAnimation;
}
if(currentBubbleText != expectedBubbleText) {
setStringParamValue("Person", "SpeechBubble", "bubbleText", expectedBubbleText);
currentBubbleText = expectedBubbleText;
}
OnUpdate: AnnoyMeter
var annoyedLevel = getFloatParamValue("UISlider", "Trigger", "value");
static var angry = false;
if(annoyedLevel == 0) {
return 0;
}
if(annoyedLevel > 0.8) {
angry = true;
}
if(annoyedLevel < 0.8 && !angry) {
return 0;
}
annoyedLevel -= 0.001;
if(annoyedLevel <= 0) {
annoyedLevel = 0;
angry = false;
setStringParamValue("Person", "SpeechBubble", "bubbleText", "I'm calm now.");
invokeTrigger("Person", "plugin#1_VamTimeline.AtomPlugin", "Play Happy");
}
setFloatParamValue("UISlider", "Trigger", "value", annoyedLevel);
What do you think?
I believe that with a little bit of work, this could become the ultimate power tool for more advanced scenes, and the easiest way to do stuff in VaM without tons of plugins for basic things. But this might also be overkill for users without programming knowledge, and useless because of all the amazing plugins already out there.
I'd _love_ to get feedback on that! Would you use this?
Files
Previews only