Unity2D:关于如何实现RPG游戏的对话系统

222000324郑淏 学生 2023-06-06 19:16:10

目录

  • 0、前言
  • 一、前期准备
  • 二、脚本编写
  • 2.1 Dialogue Manager 脚本
  • 2.3 Talkable脚本编写
  • 三、技术难点
  • 四、参考文献


0、前言

       如果把游戏比作一道菜的话,那么游戏中的故事就是盐,盐的量刚刚好,菜会非常美味,盐放多了,菜就毁了。游戏性和故事性的权重,一直是设计师们争辩已久的话题,有些游戏完全没有剧情,但不妨碍诸如俄罗斯方块等游戏成为经典,但是随着玩家审美水平的上升和游戏质量的提高,越来越多的游戏会用一个或宏大、或轻巧的故事背景来支撑游戏的整体叙事结构,游戏中多种多样的对话剧情,不仅能够增加玩家的代入感,也能推动剧情的发展。如果你想开发一款优秀的RPG游戏,那么对话系统将是不可或缺的,如果你有这方面的需要,不妨看看我的这篇文章。

一、前期准备

       首先,我们先准备一个已经搭建好的Unity地图,地图中包含一个可以由玩家控制的角色,和若干个NPC。

在这里插入图片描述


       接着,我们来设计对话UI。首先创建一个Dialogue Panel,其中,Top Image 是对话框的顶部图片,用来使我们的对话框看起来更好看。Dialogue Text是对话框中的文本。Name Background是对话框显示对话者姓名的背景图片,也是为了使我们的对话框看起来更好看。

在这里插入图片描述

       Diaglogue Panel 的相关配置如下:

在这里插入图片描述

       这是我最后设计出来的效果,您可以根据您游戏的具体风格进行自定义:

在这里插入图片描述

二、脚本编写

2.1 Dialogue Manager 脚本

-

  • 完成Dialogue Manager 脚本后,将其挂载在Dialogue Panel上:
    ```csharp
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;

public class DialogueManager : MonoBehaviour
{
public static DialogueManager instance;
public GameObject dialogueBox;//显示或隐藏
public Text dialogueText, nameText;

[TextArea(1, 3)]
public string[] dialogueLines;
[SerializeField] private int currentLine;

private bool isScrolling;
[SerializeField] private float textSpeed;

public Questable currentQuestable;

public QuestTarget questTarget;

public Talkable talkable;

public void Awake()
{
    if(instance == null)
    {
        instance = this;
    }
    else
    {
        if (instance != this)
        {
            Destroy(gameObject);
        }
    }
    DontDestroyOnLoad(gameObject);
}

// Start is called before the first frame update
private void Start()
{
    dialogueText.text=dialogueLines[currentLine];
}

// Update is called once per frame
private void Update()
{
    if (dialogueBox.activeInHierarchy)
    {
        FindObjectOfType<QuestManager>().questPanel.SetActive(false);

        if (Input.GetMouseButtonUp(0) && dialogueText.text == dialogueLines[currentLine])
        { 
            if (isScrolling == false)
            {
                currentLine++;

                if (currentLine < dialogueLines.Length)
                {
                    CheckName();
                    StartCoroutine(ScrollingText());
                }
                else
                {
                    if (CheckQuestComplete() && currentQuestable.isFinished == false)
                    {
                        showDialogue(talkable.congratslines);
                        currentQuestable.isFinished = true;
                    }
                    else
                    {
                        dialogueBox.SetActive(false);
                        FindObjectOfType<PlayerControllerBase>().canMove = true;

                        if (currentQuestable == null)
                        {
                            Debug.Log("There is no Quest in this person.");
                        }
                        else
                        {
                            currentQuestable.DelegateQuest();

                            QuestManager.instance.UpdateQuestList();

                            if (CheckQuestComplete() && currentQuestable.isFinished == false)
                            {
                                showDialogue(talkable.congratslines);
                                currentQuestable.isFinished = true;
                            }
                        }

                        if (questTarget != null)
                        {
                            for (int i = 0; i < Player.instance.questList.Count; i++)
                            {
                                if (Player.instance.questList[i].questName == questTarget.questName)
                                {
                                    questTarget.hasTalked = true;
                                    questTarget.QuestComplete();
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

public void showDialogue(string[] _newLines)
{
    dialogueLines = _newLines;
    currentLine = 0;

    CheckName();
    StartCoroutine(ScrollingText()); 
    dialogueBox.SetActive(true);

    FindObjectOfType<PlayerControllerBase>().canMove = false;
}

private void CheckName()
{
    if (dialogueLines[currentLine].StartsWith("n-"))
    {
        nameText.text = dialogueLines[currentLine].Replace("n-","");
        currentLine++;
    }
}

private IEnumerator ScrollingText()
{
    isScrolling = true;
    dialogueText.text = "";

    foreach(char letter in dialogueLines[currentLine].ToCharArray() )
    {
        dialogueText.text += letter;
        yield return new WaitForSeconds(textSpeed);
    }

    isScrolling = false;
}

public bool CheckQuestComplete()
{
    if (currentQuestable == null)
    {
        return false;
    }

    for(int i = 0; i < Player.instance.questList.Count; i++)
    {
        if (currentQuestable.quest.questName == Player.instance.questList[i].questName
            && Player.instance.questList[i].questStatus == Quest.QuestStatus.Completed)
        {
            currentQuestable.quest.questStatus = Quest.QuestStatus.Completed;
            return true;
        }
    }
    return false;
}

}


## 2.2 ShowFIn脚本编写
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;编写完ShowFIn脚本后,将其挂载在玩家角色上,使得角色靠近可对话的NPC时,会显示按键提示:
```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class showFIn : MonoBehaviour
{
    private PlayerIdentity player;//角色


    void OnTriggerEnter2D(Collider2D other)//角色进入触发器打开交互状态
    {
        player = other.GetComponent<PlayerIdentity>();
        if (player != null)
        {
            player.OpenInteract();

        }
    }

    void OnTriggerExit2D(Collider2D other)//角色离开触发器
    {
        player = other.GetComponent<PlayerIdentity>();
        if (player != null)
        {
            player.CloseInteract();

        }
        player = null;//角色为空
    }
}

2.3 Talkable脚本编写

       编写完Talkable脚本后,将其挂载到NPC上,使得场景中的对象可对话:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Talkable : MonoBehaviour
{
    [SerializeField] private bool isEntered;
    [TextArea(1, 5)]
    public string[] lines;

    [TextArea(1, 4)]
    public string[] congratslines;

    [TextArea(1, 4)]
    public string[] completedlines;

    public Questable questable;

    public QuestTarget questTarget;


    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            isEntered = true;

            DialogueManager.instance.currentQuestable = questable;
            DialogueManager.instance.questTarget = questTarget;
            DialogueManager.instance.talkable = this;
        }
    }

    private void OnTriggerExit2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            isEntered = false;

            DialogueManager.instance.currentQuestable = null;
        }
    }


    // Update is called once per frame
    private void Update()
    {
        if(isEntered && Input.GetKeyDown(KeyCode.F) && DialogueManager.instance.dialogueBox.activeInHierarchy == false)
        {
            if (questable == null)
            {
                DialogueManager.instance.showDialogue(lines);
            }
            else
            {
                if (questable.quest.questStatus == Quest.QuestStatus.Completed)
                {
                    DialogueManager.instance.showDialogue(completedlines);
                }
                else
                {
                    DialogueManager.instance.showDialogue(lines);
                }
            }
        }
    }
}

三、技术难点

难点1:如何识别每句话中对话者姓名的部分?

       我们可以在每次要切换对话者姓名时,在句子的开始使用n-加上对话者姓名的方式来标识出姓名的部分:

在这里插入图片描述


       然后,使用StartWith方法,来判断该行对话是否需要进行名字的切换,如果需要切换,再把n-替换为空,具体通过编写一个CheckName()函数进行实现:

private void CheckName()
    {
        if (dialogueLines[currentLine].StartsWith("n-"))
        {
            nameText.text = dialogueLines[currentLine].Replace("n-","");
            currentLine++;
        }
    }

难点2:如何实现对话内容逐字显示?

       我们可以通过开启协程函数,将对话中的每个字母,按照相同的时间间隔,一个个地显示到对话窗口中。我们这里将当前行的字符串,通过ToCharArray方法赋值字符串中的所有字符,到一个统一的数组中。

private IEnumerator ScrollingText()
    {
        isScrolling = true;
        dialogueText.text = "";

        foreach(char letter in dialogueLines[currentLine].ToCharArray() )
        {
            dialogueText.text += letter;
            yield return new WaitForSeconds(textSpeed);
        }

        isScrolling = false;
    }

       然后我们要在开始对话处通过StartCoroutine开启协程函数调用这个方法:

public void showDialogue(string[] _newLines)
    {
        dialogueLines = _newLines;
        currentLine = 0;

        CheckName();
        StartCoroutine(ScrollingText()); 
        dialogueBox.SetActive(true);

        FindObjectOfType<PlayerControllerBase>().canMove = false;
    }

       同时我们需要在Update()函数中也调用这个方法:

private void Update()
    {
        if (dialogueBox.activeInHierarchy)
        {
            FindObjectOfType<QuestManager>().questPanel.SetActive(false);

            if (Input.GetMouseButtonUp(0) && dialogueText.text == dialogueLines[currentLine])
            { 
                if (isScrolling == false)
                {
                    currentLine++;

                    if (currentLine < dialogueLines.Length)
                    {
                        CheckName();
                        StartCoroutine(ScrollingText());
                    }
                    else
                    {
                        if (CheckQuestComplete() && currentQuestable.isFinished == false)
                        {
                            showDialogue(talkable.congratslines);
                            currentQuestable.isFinished = true;
                        }
                        else
                        {
                            dialogueBox.SetActive(false);
                            FindObjectOfType<PlayerControllerBase>().canMove = true;

                            if (currentQuestable == null)
                            {
                                Debug.Log("There is no Quest in this person.");
                            }
                            else
                            {
                                currentQuestable.DelegateQuest();

                                QuestManager.instance.UpdateQuestList();

                                if (CheckQuestComplete() && currentQuestable.isFinished == false)
                                {
                                    showDialogue(talkable.congratslines);
                                    currentQuestable.isFinished = true;
                                }
                            }

                            if (questTarget != null)
                            {
                                for (int i = 0; i < Player.instance.questList.Count; i++)
                                {
                                    if (Player.instance.questList[i].questName == questTarget.questName)
                                    {
                                        questTarget.hasTalked = true;
                                        questTarget.QuestComplete();
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

四、参考文献

       【对话系统】文字滚动效果的单人叙述对话与双人互动对话

...全文
1119 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
文件名称:2D Action-RPG Kit v1.0.unitypackage 2D Action-RPG Kit 是一个专为 Unity 制作的插件,旨在帮助开发者快速创建 2D 动作 RPG 游戏。该插件提供了丰富的功能模块,包括角色控制、战斗系统、任务管理、物品和装备系统对话系统等,适合用来开发带有探索、战斗、剧情的 2D 冒险 RPG 游戏。 主要功能 角色控制与动画: 支持流畅的 2D 动作控制,包括移动、攻击、翻滚、跳跃等,适合紧凑的动作 RPG 风格。 包含动画系统,可轻松集成自定义角色动画,以增强游戏的视觉表现。 提供状态控制(如无敌、减速等),便于实现多样化的动作设计。 战斗系统: 支持多种战斗类型:近战、远程攻击、法术攻击等。 包含基础的敌人 AI,支持敌人追踪、攻击、逃避等行为。 提供多样的技能和状态效果,如中毒、燃烧、眩晕等,让战斗更具策略性。 配备命中检测和伤害计算系统,保证打击感和操作流畅度。 任务和对话系统: 支持任务系统,允许玩家完成收集、打怪、寻找等任务,增强游戏的互动性。 对话系统支持多分支选择,玩家可以与 NPC 互动,触发任务或推进剧情。 任意对话和任务结果可触发不同的事件,适合剧情丰富的游戏。 物品和装备系统: 包含背包和物品管理系统,支持玩家拾取、使用和丢弃物品。 装备系统允许角色穿戴多种装备,如武器、护甲、饰品等,每件装备可赋予不同的属性或技能加成。 支持可自定义的物品属性、效果和数量控制,增强游戏的策略性。 经验与升级系统: 提供经验值和升级系统,角色可以通过战斗和任务获得经验并提升等级。 每次升级后可以提升角色属性、解锁新技能或获得技能点。 支持技能树或属性点分配,让玩家自由塑造角色成长。

688

社区成员

发帖
与我相关
我的任务
社区描述
2023年福州大学软件工程实践课程W班的教学社区
软件工程团队开发软件构建 高校 福建省·福州市
社区管理员
  • FZU_SE_teacherW
  • 张书旖
  • 郭渊伟
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧