在本文中,我将解释如何将ink与Unity项目集成,以及如何使用ink API与我们的小说网站源码系统进行交互。
在我们开始之前,请确保您有一个包含一些对话和选择的墨水文件。您可能想下载并使用我准备的示例故事。这实际上是来自Guilt Free的简化场景,通过分享这一点,我想向您展示一个真实的视觉小说系统源码示例和实际代码的技术。
完整源码:xsymz.icu
项目设置
继续在 Unity 中创建一个新的 2D 项目。然后添加您可以在此处找到的墨水插件。
在ink插件文件夹(Plugins/Ink)里会有一个Example场景,你可以在里面玩很简单的小说。这个场景的逻辑将用 BasicInkExample 脚本编写。它处理开始故事、点击对话和选择选项。如果您愿意,请查看它,因为我们将密切模仿它在我们自己的游戏中的作用。不过,我们不会使用它,因为最好边做边学!
用户界面设置

在我们做任何 UI 工作之前,让我们确保画布设置正确。在 Canvas 上,您应该找到 Canvas Scaler 组件。确保 UI Scale Mode 设置为 Scale With Screen Size。这将确保屏幕上的元素在任何屏幕尺寸上看起来都一样。我还喜欢将参考分辨率设置为 1920 x 1080,因为这是我最常使用的分辨率,但我将把它留给你。
如果您熟悉视觉小说,您就会知道大多数时候它们的屏幕底部都有一个大文本框。它显示故事的线条,我们可以通过按下按钮来点击。这就是我们现在所需要的。继续,将这些组件添加到您的场景中。它可能看起来类似于:


连接墨水
现在让我们在层次结构中创建一个空对象并将其命名为 InkManager。然后向其中添加一个具有相同名称的新脚本。这将是我们游戏的核心,充当故事和统一项目之间的桥梁。在 InkManager 类的顶部写下这些行:

[SerializeField]
private TextAsset _inkJsonAsset;
private Story _story;

[SerializeField]
private Text _textField;

_inkJsonAsset是对故事文件的引用,该文件将用于创建 Story 类的实例。这个 json 文件是由我们在 Inky 中编写的 .ink 文件中的 ink 插件自动编译的。让我们继续将我们的故事文件放入 Assets 文件夹中。您应该立即看到两个文件,如下所示:
将 .json 文件分配给 InkManager 脚本上的相应字段。不要忘记分配文本字段!
在这个阶段,您的场景层次结构和 InkManager 应该类似于以下内容:
现在让我们做一些编码吧!
我们需要一种方式来开始我们的故事。首先,我们必须创建一个 Story 类的实例:
_story = new Story(_inkJsonAsset.text);
此类包含与墨水交互所需的所有内容。我们会经常使用它。
在您的 InkManager 中,创建一个void StartStory()函数并将上面的行添加到其中。这应该从您的Start()函数中调用,因为我们希望在运行游戏的那一刻开始故事。我们还需要一种方式来展示我们故事的台词。让我们创建另一个函数:

public void DisplayNextLine()
{
  if (!_story.canContinue) return;

  string text = _story.Continue(); // gets next line
  text = text?.Trim(); // removes white space from text
  _textField.text = text; // displays new text
}

你看到的第一行在很多地方都会非常有用。_story.Continue()在实际用于获取下一行之前,您通常需要检查故事是否可以继续。故事无法继续的时刻是,例如,当我们等待用户的输入(选择)、故事结束或出现问题时。我们稍后会更好地处理这个问题,但现在,退出函数就足够了。
现在我们有了一种展示故事的方法,让我们DisplayNextLine()从StartStory()函数中调用。您的脚本应如下所示:

using Ink.Runtime;
using UnityEngine;
using UnityEngine.UI;

public class InkManager : MonoBehaviour
{
  [SerializeField]
  private TextAsset _inkJsonAsset;
  private Story _story;

  [SerializeField]
  private Text _textField;

  void Start()
  {
    StartStory();
  }

  private void StartStory()
  {
    _story = new Story(_inkJsonAsset.text);
    DisplayNextLine();
  }

  public void DisplayNextLine()
  {
    if (!_story.canContinue) return;

    string text = _story.Continue(); // gets next line
    text = text?.Trim(); // removes white space from text
    _textField.text = text; // displays new text
  }
}

现在让我们添加一些交互,并确保我们可以使用之前添加的按钮点击故事。我们需要创建一个脚本,它会InkManager.DisplayNextLine()在每次点击时调用函数。

using UnityEngine;

public class NextButtonScript : MonoBehaviour
{
  private InkManager _inkManager;

  void Start()
  {
    _inkManager = FindObjectOfType<InkManager>();

    if (_inkManager == null)
    {
      Debug.LogError("Ink Manager was not found!");
    }
  }

  public void OnClick()
  {
    _inkManager?.DisplayNextLine();
  }
}

这也确保了当 InkManager 不在场景中时我们会得到一个错误,这可以派上用场。
在编辑器中,创建一个 OnClick 事件处理程序并使用它来调用我们的新OnClick函数。您现在应该可以在运行游戏时点击故事了!
选择
没有选择就没有视觉小说,这就是我们接下来要做的。让我们首先创建一个空的游戏对象并将其命名为“Choice Buttons”。这将是我们动态创建的选项的容器,我们需要添加一个垂直布局组以正确显示它们。创建一些按钮,将它们添加到您的容器中,然后调整设置以确保一切正常。你会希望你的按钮相当大,以确保它们适合文本。您还可以在按钮文本上选择“最佳匹配”属性以确保安全。
现在将其中一个按钮保存为 ChoiceButton 预制件,并从按钮容器中删除所有这些按钮。我们现在不需要它们。
让我们将以下行添加到 InkManager 脚本中。

[SerializeField]
private VerticalLayoutGroup _choiceButtonContainer;

[SerializeField]
private Button _choiceButtonPrefab;

现在将我们刚刚创建的 Choice Buttons 对象和 ChoiceButton 预制件分配给编辑器中的相应字段。
我们现在需要编写一些代码来创建和显示选项。您将看到我们可以使用 访问当前选择_story.currentChoices。
 

private void DisplayChoices()
{
  // checks if choices are already being displaye
  if (_choiceButtonContainer.GetComponentsInChildren<Button>().Length > 0) return;

  for (int i = 0; i < _story.currentChoices.Count; i++) // iterates through all choices
  {

    var choice = _story.currentChoices[i];
    var button = CreateChoiceButton(choice.text); // creates a choice button

    button.onClick.AddListener(() => OnClickChoiceButton(choice));
  }
}

在我们的主要选择处理函数中,我们首先检查我们是否还没有显示一些选择。然后我们遍历所有可用的选项并为每个选项创建一个按钮。最后,我们添加了一个 OnClick 事件侦听器,以便在玩家按下选择按钮时运行。现在让我们实现那些缺少的功能。

Button CreateChoiceButton(string text)
{
  // creates the button from a prefab
  var choiceButton = Instantiate(_choiceButtonPrefab);
  choiceButton.transform.SetParent(_choiceButtonContainer.transform, false);

  // sets text on the button
  var buttonText = choiceButton.GetComponentInChildren<Text>();
  buttonText.text = text;

  return choiceButton;
}

在这个函数中,我们从预制件中创建一个新按钮,将其插入到我们的容器中,并设置我们作为参数传递的文本。然后我们将按钮返回给我们的DisplayChoices()函数,在那里我们分配OnClick事件处理程序:

void OnClickChoiceButton(Choice choice)
{
  _story.ChooseChoiceIndex(choice.index); // tells ink which choice was selected
  RefreshChoiceView(); // removes choices from the screen
  DisplayNextLine();

}

首先,我们需要使用 告诉 Ink 哪个选项被点击了ChooseChoiceIndex()。然后你会在这里看到一个新功能。单击其中一个选项后,我们需要从屏幕上删除所有选项,这就是RefreshChoiceView()将要执行的操作。之后,我们将使用我们的旧DisplayNextLine()函数继续这个故事。

void RefreshChoiceView()
{
  if (_choiceButtonContainer != null)
  {
    foreach (var button in _choiceButtonContainer.GetComponentsInChildren<Button>())
    {
      Destroy(button.gameObject);
    }
  }
}

刷新选项非常简单,我们只需销毁按钮容器内的所有对象。
现在我们可以创建和显示选择,我们需要知道何时实际执行。这让我回到了_story.canContinue我之前提到的财产。如果我们不能继续,可能是因为我们面临一些选择。确保这种情况的一种简单方法是简单地检查_story.currentChoices属性。现在让我们更新DisplayNextLine()函数。

您可能会注意到,ink 会将选择文本视为故事的一部分,因此我们将其显示在文本视图中。如果您不希望它这样做,例如,您可以在_story.Continue()其中运行OnClickChoiceButton()以跳过该行。

更多推荐

小说网站系统源码|PHP付费小说网站源码带app