Return to Previous Script Path

Posted: 11 Jul 2020 21:46
by Nysalie

Is it possible to call a naninovel script from a c# method and then return to a previously running naninovel script when it stops?

As an example let's say I have a dialogue running, in the scene, there is a "hotspot character", it is essentially a ScriptableButton that has a serialized field referencing a Naninovel script, another manager class handles hotspots and onClick event triggers a scriptPlayer.Play(hotspot.NaniScript) method, where hotspot.NaniScript is the Nani script assign to the particular clicked Hotspot. Basically, when we click a hotspot it plays a nani script if not null.

When that script finishes I would like to go back to the previously running dialogue, resuming where it last paused. I've looked at the goto and gosub/return commands but if i'm not mistaken those commands must be assigned on the script itself and can't be changed at runtime right? Can we dynamically assign a temporary local playback spot and return to it with perhaps a OnStop action?

Obs: I'm not sure if this is the proper place for the question but since this is c# related I think it fits here.


Re: Return to Previous Script Path

Posted: 11 Jul 2020 23:06
by Elringus

Won't invoking @gosub on button click do the job? You can both use PlayScript component or invoke await new GoSub().ExecuteAsync() manually in a C# handler.

If the above won't work, use IScriptPlayer.Play(scriptName, startLineIndex, startInlineIndex) to start playing the previous script at the required position (the args can be taken from a PlaybackSpot struct).


Re: Return to Previous Script Path

Posted: 12 Jul 2020 21:33
by Nysalie

Awesome, thank you for the guidance. I haven't used Commands in c# directly yet, so I was a bit unsure how to use them and, of course, since they are simply another class, instantiating them with the new keyword makes sense.

I played around with Gosub() but had some trouble sending the proper Path argument to the Goto().
Tried to store new NamedStringParameter().SetValueFromScriptText(scriptPlaybackspot, scriptName, out errors) into a "path" variable and send that to the Gosub with await new Gosub(){ Path = path }.ExecuteAsync() and even though it was somewhat functional it had some issues.

Scripts somehow skipped a line, so for example, in a 3 line script if we pause execution on the first line it would return at line 3 and not line 2. Since Gosub() increments one line of the playbackspot.LineIndex for the next play position I figured that this might be a problem of the initial Playbackspot sent to the method, I was using IScriptPlayer.PlaybackSpot to get the current position but since I haven't thoroughly debug the variables at runtime I'm still not sure why this happened.

I also triggered the Goto:72 error for an empty label.
This is because the path I sent through Gosub() would be "Scriptname.null" (ex. "MyTestScript.null") and there is no "null" label to look for in the script. However, this error was not disruptive and the game run fine.

Anyway, this approach was too clunky and I decided to take a look at the Gosub() and Goto() classes to try a more direct approach without the need for commands. It was just a matter of pushing the current playbackspot to the GosubReturnSpots stack. Finalizing the triggered nani script with a @return command will return to the previous position without any issues whatsoever. Quite simple and quite awesome! This is the snippet of the relevant code.

Code: Select all

scriptPlayer.GosubReturnSpots.Push(scriptPlayer.PlaybackSpot);
scriptPlayer.Play(hotspot.NaniScript);

For future reference, I'll leave here the classes where I implemented this so that anyone trying it can have a practical example to use. This is part of a navigation system I'm working on. Locations are objects that hold several LocationHotspots, the hotspots can be a reference to another location, a character or an item, if the hotspot is of Location type the target Location object will be loaded and the process repeats. A NavigationUI (CustomUI) will then manage these Locations and change them accordingly, it also handles changes to the background actor based on current location and time (using my calendar extension variables).

Location.cs:

Code: Select all

using System.Collections.Generic;
using Naninovel;
using UniRx.Async;
using UnityEngine;
using UnityEngine.UI;

namespace NaninovelNavigation.UI
{
    [System.Serializable]
    public enum LocationType
    {
        Interior,
        Exterior
    }

[System.Serializable]
public class Location : ScriptableUIBehaviour
{
    public string LocationName { get => locationName; private set=> locationName = value; }
    public LocationType LocationType { get => locationType; private set => locationType = value; }

    [SerializeField] private string locationName;
    [SerializeField] private LocationType locationType;
    [SerializeField] private List<LocationHotspot> hotspots;

    private NavigationUI navigationUi;
    private IScriptPlayer scriptPlayer;

    protected override void Awake()
    {
        base.Awake();

        navigationUi = Engine.GetService<IUIManager>().GetUI<NavigationUI>();
        scriptPlayer = Engine.GetService<IScriptPlayer>();

        foreach (var hotspot in hotspots)
        {
            hotspot.gameObject.GetComponent<Button>().onClick.AddListener(delegate{HotspotClicked(hotspot);});
        }
    }

    protected override void HandleVisibilityChanged(bool visible)
    {
        base.HandleVisibilityChanged(visible);

        if (visible)
        {
            foreach (var hotspot in hotspots)
            {
                hotspot.SetVisibility(true);
            }
        }
    }

    private async UniTask HotspotClicked(LocationHotspot hotspot)
    {
        switch (hotspot.InteractionType)
        {
            case HotspotInteraction.Location:
                navigationUi.Location = hotspot.InteractionTarget;
                await navigationUi.ChangeLocation();
                break;
        }

        if (hotspot.NaniScript != null)
        {
            scriptPlayer.GosubReturnSpots.Push(scriptPlayer.PlaybackSpot);
            scriptPlayer.Play(hotspot.NaniScript);

        }
    }
}
}

LocationHotspot.cs:

Code: Select all

using Naninovel;
using UnityEngine;

namespace NaninovelNavigation.UI
{
    [System.Serializable]
    public enum HotspotInteraction
    {
        Location,
        Character,
        Item
    }

public class LocationHotspot : ScriptableButton
{
    public HotspotInteraction InteractionType { get => interactionType; private set => interactionType = value; }
    public string InteractionTarget { get => interactionTarget; private set => interactionTarget = value; }
    public Script NaniScript { get => naniScript; private set => naniScript = value; }

    [SerializeField] private HotspotInteraction interactionType;
    [SerializeField] private string interactionTarget;
    [SerializeField] private Script naniScript = null;
}
}

Re: Return to Previous Script Path

Posted: 12 Jul 2020 21:56
by Elringus

Glad you've worked it out and thanks for sharing the solution!

Regarding the path parameter, it can be set as follows:

Code: Select all

var gotoCommand = new Goto {
    Path = new NamedString("ScriptName", "LabelName")
};

Re: Return to Previous Script Path

Posted: 12 Jul 2020 22:45
by Nysalie

Oh I see, I was trying to use a new NamedStringParameter instead. For this case the Push method is simpler to use but good to know how to do it properly, might be useful in the future.

Cool redesign on the code's widget thing by the way. Looks all fancy now! 8-)