I was the sole programmer for Sacrifice For Sale, a visual novel developed in the Unity game engine.
The Game
There's nothing particularly noteworthy about the game itself from a technical perspective, except that it's all done without a visual novel framework, just Unity. The game includes save/load functionality, skipping already-read dialog, a log, various settings, text animations, sprite tweening animations, and other expected features.
The game's runtime integrates with the Steamworks API via Steamworks.NET.
IVNT
Aside from the game itself and the runtime for the branching story, I programmed the internal tools used to design the story, most notably the "Iota Visual Novel Tool" (IVNT):
The tool was developed to make it as easy as possible to write radically branching dialog and storylines while minimizing organizational work. While the screenshots might look very complex, bear in mind that the game has over 500 choices and is very choice-dense, which would be functionally impossible with a conventional text-based approach like in Ren'Py.
Since IVNT allows creating, reading, modifying, and writing variables, as well as branching and recursion, it's effectively a Turing-complete visual scripting language.
The main issue when working with graph-based languages is organizing the nodes. This is solved by having the nodes arrange themselves into a tree-based layout. That means that writers and editors don't need to constantly rearrange everything, they can just create a new node anywhere in the graph, and the tool takes care of the layout.
IVNT also includes a basic Damerau-Levenshtein-based spellchecker written completely from scratch (based on the Wagner-Fischer algorithm with the single-row optimization) that leverages SIMD and multi-threading.
XDS
Technically, IVNT is based on the "Extensible Dialog System" (XDS), which implements most of the underlying functionality. I wrote XDS to be trivially extensible, so while IVNT is currently the only functional implementation, it's easy to customize XDS to fit any game with branching dialog.
To demonstrate XDS' API, here's the actual implementation of the
SetPositionNode in IVNT:
using Iota.TweenStateMachine;
using Iota.TweenStateMachine.Tweeners;
using ITSM;
using UnityEngine;
using XDS;
using XDS.NodeViewFieldSpecifiers;
namespace Iota.Ivnt {
#if !IVNT_OVERRIDE_NODE_CREATE_SetScreenPosition
[ContextMenuCreate("'Set Position' Node", "IVNT/Create 'Set Position' Node")]
#endif
[ViewMode(ViewMode.Classic)]
public sealed class SetPositionNode : Node {
public enum Axis {
X,
Y
}
[Version(0)]
public SceneObjectRef obj = new();
[Version(0)]
public bool WaitForTweensToEnd = false;
[Version(0)]
public Axis axis;
/// <summary>
/// -1.0 is bottom/left, 1.0 is top/right
/// <summary>
[Version(0)]
public float pos;
public override void Execute () {
Transform t = this.obj.Resolve();
switch (this.axis) {
case Axis.X: {
t.TweenPositionX(this.pos);
break;
}
case Axis.Y: {
t.TweenPositionY(this.pos);
break;
}
}
if (this.WaitForTweensToEnd && Tweener._currentSettings.Time != 0) {
XdsMaster.Interrupts.Add(new WaitUntil(() => Tweener.ActiveTweens.Count == 0));
}
}
}
}