文字游戏引擎 ink 初探(下):Unity 整合

作者:eastecho
2018-11-06
38 42 0

上一篇里面,我们初次接触了 ink 语言,并且完成了一段非常简单的代码。在这一篇中,我们要试着将它放到 Unity 里面去跑一下!

Unity 插件

inkle 提供了将 ink 整合到 Unity 中的插件,我们可以到如下地址下载:

ink Unity 插件

去下载

我们下载的是 v 0.8.1 的稳定版。UnityInkIntegration0.8.1.unitypackage

准备我们的 Unity 项目

创建一个新的 2D 项目,并且保存 SceneMain,然后引入 Unity 插件。

再把我们上一篇完成的 ink demo 拖进项目,我们的叫做 demo.ink

引入 ink 文件之后,会自动编译成 JSON 的可用格式,我们的素材库看起来会像下面这样:

然后创建 Canvas 画布并且增加两个必要的元素:Button(按钮)和 Text(文本),并且将它们做成 Prefab,供后面使用。按钮是用来显示选项并做出选择的,而文本使用来显示基本内容的。

因为要使用 Prefab 所以将 Canvas 清空即可,然后给 Canvas 增加 C# 脚本,取名 Script.cs 吧!

这样,我们的素材就准备完毕了:

其实 ink 脚本引入以后,是可以直接在 Unity 中测试的。选中 .ink 文件,属性栏里面就会出现 Play 按钮,可以直接运行并测试。

可以直接在 Unity 编辑器中调试

编写脚本

脚本代码如下,已经添加了注释:

using UnityEngine;
using Ink.Runtime;

public class Script: MonoBehaviour
{
  // ink JSON
  [SerializeField] private TextAsset inkFile;
  // 画布
  [SerializeField] private Canvas canvas;

  // ink 故事
  private Story _inkStory;
  // 是否需要新的故事片段
  private bool _storyNeeded;
  // 距离(用来确定内容和按钮位置)
  private int _padding = 10;

  /* UI Prefabs */
  // 文本
  [SerializeField] private UnityEngine.UI.Text text;
  // 按钮
  [SerializeField] private UnityEngine.UI.Button btnChoice;

  // 初始化
  private void Awake()
  {
    _storyNeeded = true;
    _inkStory = new Story(inkFile.text);
    
    // 设置玩家名称
    _inkStory.variablesState["player_name"] = "Someone";
  }

  // Update is called once per frame
  private void Update()
  {
    if (!_storyNeeded) return;

    // 清空画布内容
    RemoveChildren();

    // 各个元素的偏移,需重新计算
    float vOffset = 0;

    // 如果故事可以继续
    if (_inkStory.canContinue)
    {
      var storyText = Instantiate(text);
      
      // 取得要显示的文字
      storyText.text = _inkStory.Continue();
      
      // 定位相关
      storyText.transform.SetParent(canvas.transform, false);
      storyText.transform.Translate(new Vector2(0, vOffset));
      vOffset -= (storyText.fontSize + _padding);
    }

    // 如果有多个选择,那么逐个处理
    if (_inkStory.currentChoices.Count > 0)
    {
      for (var i = 0; i < _inkStory.currentChoices.Count; i++)
      {
        // 定位相关
        var choiceButton = Instantiate(btnChoice);
        choiceButton.transform.SetParent(canvas.transform, false);
        choiceButton.transform.Translate(new Vector2(0, vOffset));

        // 取得选择
        var choice = _inkStory.currentChoices[i];

        // 设置按钮的选择文本
        var choiceText = choiceButton.GetComponentInChildren<UnityEngine.UI.Text>();
        choiceText.text = choice.text;

        // 定位相关
        var layoutGroup = choiceButton.GetComponent<UnityEngine.UI.HorizontalLayoutGroup>();
        vOffset -= (choiceText.fontSize 
            + layoutGroup.padding.top 
            + layoutGroup.padding.bottom 
            + _padding);

        // 获取按钮点击后对应的路径
        var path = choice.pathStringOnChoice;
        choiceButton.onClick.AddListener(delegate { ChoicePathSelected(path); });
      }
    }

    // 本次操作完成,等待响应
    _storyNeeded = false;
  }

  /// <summary>
  /// 清空画布内容
  /// </summary>
  private void RemoveChildren()
  {
    var childCount = canvas.transform.childCount;
    for (var i = childCount - 1; i >= 0; --i)
    {
      Destroy(canvas.transform.GetChild(i).gameObject);
    }
  }

  /// <summary>
  /// 选择路径
  /// </summary>
  /// <param name="path">路径名称</param>
  private void ChoicePathSelected(string path)
  {
    _inkStory.ChoosePathString(path);
    _inkStory.Continue();
    _storyNeeded = true;
  }
}

代码还是很简单的,基本上跟官方提供的差不多,我们加入了:

_inkStory.variablesState["player_name"]

来进行玩家名称的设置,并且通过 path 来进行导航。

收尾工作

我们回到 Unity 编辑界面,将程序运行所需要的对象一一绑定,如图所示:

不过这还没有完,我们需要给按钮和文本加上自动居中的设置,并且做一些边距控制颜色调整等等的美化工作,基本上是这样:

如果一切顺利,就可以跑啦:

Unity 运行效果

项目文件

如果懒得自己尝试,那么我们也提供了源代码供您下载使用:

项目源代码

去下载

结束语

好了,这一次的 ink 初次尝试就完了啦,后续有什么问题或者需要交流的,请前往我们设立的小组继续探讨:

ink 小组

去看看