当前位置:   article > 正文

Unity3D RPG实现 3 —— 对话、任务系统_unity任务系统

unity任务系统

目录

成果展示

对话系统

对话的存储数据结构

对话的UI面板设置

创建对话&任务的 NPC

实现对话控制器显示主对话窗口的内容

创建对话的选项内容

任务系统

创建任务 UI 面板

 任务的存储数据结构

任务管理器与接受任务

任务控制相关脚本

实现点按任务显示信息

接受任务时检查任务物品是否契合

根据完成情况控制任务对话分支

拿到任务奖励

保存任务数据

代码汇总

背包部分

Dialogue部分

任务部分


成果展示

对话系统

对话的存储数据结构

一个对话框需要什么呢?我们先来想想:

首先需要一个字符串,表示内容,

为了方便对话间的跳转,我们还需要给每个对话唯一标识,因此需要ID

对话是由人说的,我们还需要人物头像

具体如下:

首先需要一个对话信息:

数据如下:含有当前对话的id、头像、文本以及选项

  1. [System.Serializable]
  2. public class DialoguePiece {
  3. public string ID;
  4. public Sprite image;
  5. public string text;
  6. public List<DialogueOption> options = new List<DialogueOption>();
  7. }

另一方面,每个对话可能会有选项,每个选项本身也有内容,并且一些选项可能意味着是否要接取任务,以及选择选项后,我们需要跳转到不同的对话,于是需要一个targetID

于是选项的数据结构如下:

  1. [System.Serializable]
  2. public class DialogueOption
  3. {
  4. public string text;
  5. public string targetID;
  6. public bool takeQuest;
  7. }

我们将所有的对话信息存储在SO中,因此:

  1. [CreateAssetMenu(fileName ="New Dialogue",menuName ="Dialogue/Dialogue Data")]
  2. public class DialougeData_SO : ScriptableObject
  3. {
  4. public List<DialoguePiece> dialoguePieces = new List<DialoguePiece>();
  5. }

此时完整的一个对话SO如下:

完整的数据结构如下

逻辑:在Dialogue Data中有若干个piece,每个piece中有若干个IDTextImageQuest,还包含有List<Option>

如果有Option,我们就通过获取其Target ID

对话的UI面板设置

创建一个面板:

设定Panel 

添加vertical layout Group

添加对话框还有按钮并设定其位置:

再添加一个头像:

在这种情况下, 如果有buttontext的话,布局就会比较乱,因此此处将头像图片放在Main Text下,并且锚点放在左边,这样锚点就是在对话框的左边了。

接下来还需要给Dialogue Panel添加verticalLayoutGroup,这样子物体才可以使用content size filter

此时头像的图片是以文字为基准点,所以就在对话框的外面了:

这样可以使得按钮在比较靠右的位置。

为了使其能在竖直方向上拉伸:

然后在DialoguePanelMain Text中都添加content Size Fitter

这样就可以根据文本数量的多少改变框的大小。

问题在于字少的时候,头像会超出位置。

设定一个最小高度即可:

记得给文字设定居中:

Button设定如下选项:

接下来为选择面板添加Button

将按钮拉长,并选择居于右部,就会发现此时Button位于右边。

选择text,改变其对话框长度:

多复制几个按钮,并将面板透明度设置为0

创建对话&任务的 NPC

导入素材后,要将场景素材升级到URP

别忘记添加基本的必要组件

  • 自己单独创建 Animator Controller 只添加一个 Idle 动画即可
  • 修改 Tag 及 Layer
  • 将这个人物保存成 Original Prefab 在你的文件夹中

实现对话控制器显示主对话窗口的内容

接下来实现在对话框中显示显示对话内容的效果。

首先要先将之前创建的UI的那些组件用脚本控制:

(UpdateDialogueData是用于根据传入的对话信息进行更新的函数,后续会补充完整)

  1. public class DialogueUI : Singleton<DialogueUI>
  2. {
  3. [Header("Basic Elements")]
  4. public Image icon;
  5. public Text mainText;
  6. public Button nextButton;
  7. public GameObject dialoguePanel;
  8. [Header("Data")]
  9. public DialougeData_SO currentData;
  10. int currentIndex = 0;
  11. public void UpdateDialogueData(DialougeData_SO data)
  12. {
  13. currentData = data;
  14. currentIndex = 0;
  15. }
  16. }

 把那些栏拖拽到里面

NPC 添加 DialogueController 脚本,用于控制对话

当触发和npc的对话,打开对话面板时,我们就要传给对话UI面板给对话数据:

以一个简单的例子:

进行赋值

具体根据对话数据存储的头像,对话语句进行更新

如果对话数据不止一条对话内容,则我们可以继续往下更新对话

这样即可实现点击进入下一行对话,没有对话时面板消失。

创建对话的选项内容

将Button作为预制体:

创建对话的选项OptionUI的脚本,并用该脚本控制选项的组件:

为了实现在对话里生成选项,首先得为选项单独创建一个面板,并在脚本中获取选项所在的Panel及脚本:

因为对话会切换,所以选项也需要生成与销毁

接下来实现选项的销毁与创建:

在选项的UI脚本中,还需要根据传入的参数设定UI

这样即可实现生成选项:

接下来实现点击选项的事件:

我们希望根据选项所存储的下一个对话的ID实现跳转:

所以需要在optionUI里添加下一个对话ID的变量

然后在选项中更新:

为了降低耦合度,使用委托来实现接下来添加点击时需要执行的事件:

给Button添加Onclick事件

                     

获取了ID之后,然后就通过ID去对话的List中去获取该对话信息:

实现打字的效果:下载DOTween插件。

这样即可实现打字效果

但是此处还有一个问题,在于当next按钮消失时布局会发生改变。

还有currentIndex的顺序++,这个和选项无关

任务系统

创建任务 UI 面板

创建canvas:

创建一个panel附上图片,然后为实现任务左侧的滚动栏:

然后将滚动的栏删掉变成这样,(删除滑动条后记得调整viewport大小)

设定vertical group:

 

添加item slot:

以及item tooltip

 任务的存储数据结构

  • 创建任务基本变量
  • 创建 QuestRequire 用来追踪我们要完成的任务目标
  • 在 DialoguePiece 中加入 Quest 并设置一个新对话可以接受任务

思考一下,一个任务需要什么?需要有任务的名字、描述、要求、完成的状态。

任务的需求,我们以物品或者杀的怪物来举例,我们需要名字,需要的数量,有的数量,于是不难得出数据结构如下:

(任务可能有多个需求,所以需要用List来存储)

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. [CreateAssetMenu(fileName = "New Quest", menuName = "Quest/Quest Data")]
  5. public class QuestData_SO : ScriptableObject
  6. {
  7. [System.Serializable]
  8. public class QuestRequire
  9. {
  10. public string name;
  11. public int requireAmount;
  12. public int currentAmount;
  13. }
  14. public string questName;
  15. [TextArea]
  16. public string description;
  17. //需要三种任务完成的状态,npc才会有不同的反应
  18. public bool isStarted;
  19. public bool isComplete;
  20. public bool isFinised;
  21. public List<QuestRequire> questRequires = new List<QuestRequire>();
  22. }

此处在这里将任务所需的怪物变量改的名字相同:

在对话一栏中加入任务作为成员变量,因为,我们有时候需要根据对话来接受任务

然后设置对话:

别忘了,任务还需要有奖励:

 添加奖励:

(bug记录:DondestroyOnLoad的物体不能是子物体,因此:不能放在别人的子集

任务管理器与接受任务

接下来书写管理任务的管理器:用于管理全部的任务信息,使用List来存储全部的任务以及一些控制任务的函数。

  1. public class QuestManager : Singleton<QuestManager>
  2. {
  3. public List<QuestTask> tasks = new List<QuestTask>();
  4. }

具体的任务的书写如下:包含任务数据,以及任务的三种状态。

  1. public class QuestTask
  2. {
  3. public QuestData_SO questData;
  4. public bool IsStarted {
  5. get { return questData.isStarted; }
  6. set { questData.isStarted = value; }
  7. }
  8. public bool IsComplete
  9. {
  10. get { return questData.isComplete; }
  11. set { questData.isComplete = value; }
  12. }
  13. public bool IsFinished
  14. {
  15. get { return questData.isFinished; }
  16. set { questData.isFinished = value; }
  17. }
  18. }

有了存储任务的数据结构后,作为一个管理器,还需要基础的增、查、获取的功能:

  1. public bool HaveQuest(QuestData_SO data)//判断是否有这个任务
  2. {
  3. //在头文件中引入Ling,可以用于查找链表中的内容
  4. if (data != null)
  5. return tasks.Any(q => q.questData.questName == data.questName);
  6. else return false;
  7. }
  8. //根据任务数据的名字查找链表中的某一个任务
  9. public QuestTask GetTask(QuestData_SO data)
  10. {
  11. return tasks.Find(q => q.questData.questName == data.questName);
  12. }

上面有了查和获取,

增加任务的功能只需要直接往List中调用Add函数即可。

而什么时候需要添加任务呢?就是当玩家和NPC进行对话的时候来处理。

具体如下:

思路很简单,当玩家在对话中做出选择,判断本次的选项中是否含有任务,然后判断玩家是否选择了接任务的选项,如果含有,则判断该任务是否在列表中了,如果不在列表中,则将该任务实例化,并且加入到QuestManager的单例的List中。

这样即可实现对话后加任务的思路。

除此之外,我们接取任务后还希望设置任务的状态为开始状态:

但是直接修改并没有用,这只是修改的临时变量的值。

因此需要在QuestManager中根据任务的数据来查找管理器的链表中对应的任务

随后在此处设定开始:

任务控制相关脚本

接下来继续创建任务按钮以及任务需求的面板,并用脚本进行控制。

  • 创建 QuestNameButton 添加在 任务名字按钮 上
  • 创建 QuestRequirement 添加在 Requirement 上
  • 设置好以上两个类的变量并且拿到赋值

这个Button是最终面板中的这里:

 任务按钮需要哪些东西呢?由于我们希望点击任务的名字可以跳转到该任务并展示信息,所以需要按钮,以及任务的内容text。

所以button的控制脚本如下(任务按钮会存储该任务的信息,所以需要QuestData——SO变量)

为每个按钮做一个预制体,并且用脚本去控制

完善上面的脚本,并且书写设定按钮的函数。

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class QuestNameButton : MonoBehaviour
  6. {
  7. public Text questNameText;
  8. public QuestData_SO currentData;
  9. //public Text questContentText;
  10. public void SetupNameButton(QuestData_SO quesData)
  11. {
  12. currentData = quesData;
  13. if (quesData.isComplete)
  14. questNameText.text = quesData.questName + "(完成)";
  15. else
  16. questNameText.text = quesData.questName;
  17. }
  18. private void Awake()
  19. {
  20. GetComponent<Button>().onClick.AddListener(UpdateQuestContent);
  21. }
  22. void UpdateQuestContent()
  23. {
  24. //questContentText.text = currentData.description;
  25. QuestUI.Instance.SetupRequireList(currentData);
  26. foreach(Transform item in QuestUI.Instance.rewardTransform)
  27. {
  28. Destroy(item.gameObject);
  29. }
  30. foreach(var item in currentData.rewards)//奖励可能不止一个所以需要循环列表
  31. {
  32. QuestUI.Instance.SetupRewardItem(item.itemData, item.amount);
  33. }
  34. }
  35. }

此处设定任务内容有两种方式,一种是,我们使用单例的QuestUI(也就是实际上的Quest Manager)来统一显示这些数据

由于很多数据的显示我们是通过一个统一的管理器QuestUI来实现的,所以需要将相关的一些UI组件交由脚本来控制

继续完善任务控制脚本所需的组件:

接下来书写任务需求的脚本,它与UI面板中,它储存的是每个任务的需求和名字。

可以看到每个任务都带有这个脚本

接下来将单独的每个需求做成预制体,并将任务需求所在的Panel交由QuestUI来统一控制:

任务的奖励

奖励就是实际的物品Item

并且还需要获取奖励所在的面板

展示物品信息的代码如下

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.EventSystems;
  5. public class ShowTooltip : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
  6. {
  7. private ItemUI currentItemUI;
  8. void Awake()
  9. {
  10. currentItemUI = GetComponent<ItemUI>();
  11. }
  12. public void OnPointerEnter(PointerEventData eventData)
  13. {
  14. Debug.Log("mouse in slot");
  15. QuestUI.Instance.tooltip.gameObject.SetActive(true);
  16. QuestUI.Instance.tooltip.SetupTooltip(currentItemUI.currentItemData);
  17. }
  18. public void OnPointerExit(PointerEventData eventData)
  19. {
  20. QuestUI.Instance.tooltip.gameObject.SetActive(false);
  21. }
  22. }

QuestUI完整代码

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class QuestUI : Singleton<QuestUI>
  6. {
  7. [Header("Elements")]
  8. public GameObject quesPanel;
  9. public ItemToolTip tooltip;
  10. bool isOpen;
  11. [Header("Quest Name")]
  12. public RectTransform questListTransform;
  13. public QuestNameButton questNameButton;
  14. [Header("Text Content")]
  15. public Text quesContentText;
  16. [Header("Requirement")]
  17. public RectTransform requireTransform;
  18. public QuestRequirement requirement;
  19. [Header("Reward Panel")]
  20. public RectTransform rewardTransform;
  21. public ItemUI rewardUI;
  22. private void Update()
  23. {
  24. if (Input.GetKeyDown(KeyCode.Q))
  25. {
  26. isOpen = !isOpen;
  27. quesPanel.SetActive(isOpen);
  28. quesContentText.text = string.Empty;
  29. SetupQuestList();
  30. }
  31. if (!isOpen)
  32. tooltip.gameObject.SetActive(false);
  33. }
  34. public void SetupQuestList()
  35. {
  36. //清除原来已有的任务
  37. foreach(Transform item in questListTransform)
  38. {
  39. Destroy(item.gameObject);
  40. }
  41. foreach(Transform item in rewardTransform)
  42. {
  43. Destroy(item.gameObject);
  44. }
  45. foreach (Transform item in requireTransform)
  46. {
  47. Destroy(item.gameObject);
  48. }
  49. //遍历列表中的list,接取任务
  50. foreach(var task in QuestManager.Instance.tasks)
  51. {
  52. var newTask = Instantiate(questNameButton, questListTransform);
  53. newTask.SetupNameButton(task.questData);
  54. //newTask.questContentText = quesContentText;
  55. }
  56. }
  57. public void SetupRequireList(QuestData_SO questData)
  58. {
  59. quesContentText.text = questData.description;
  60. //将涉及到QuestNameButton中的三处questContentText关闭,不使用在里面传东西然后赋值的形式了,改为在此处直接修改
  61. foreach (Transform item in requireTransform)
  62. {
  63. Destroy(item.gameObject);
  64. }
  65. foreach(var require in questData.questRequires)
  66. {
  67. var q = Instantiate(requirement, requireTransform);
  68. q.SetupRequirement(require.name, require.requireAmount, require.currentAmount);
  69. }
  70. }
  71. public void SetupRewardItem(ItemData_SO itemData,int amount)
  72. {
  73. var item = Instantiate(rewardUI, rewardTransform);
  74. item.SetupItemUI(itemData, amount);
  75. }
  76. }

实现点按任务显示信息

  • 按照逻辑去写每一个需要的函数方法
  • 设置任务按钮显示对应任务名字
  • 实现点击名字按钮能显示任务详情以及任务需求
  • 调整 UI 布局

添加按钮的监听事件:显示任务的描述(实现当按下按钮时会更新信息的操作)

实现显示任务的需求

首先在需求的预制体里写一个供外部调用的接口

然后在管理器中调用该接口

在按钮点击事件中调用该函数:

这样即可显示:

但是任务信息和需求重叠在了一起。

在这里可以使得它强制布局:

这样显示就正常了

  • SetupRewardList的 方法实现
  • 创建 ShowTooltip 实现鼠标滑入显示信息
  • 创建多个任务并修复重复显示任务奖励的问题

对于任务信息的显示,除了之前的那种方法:

回顾一下之前的方法:

在QuestNameButton这个预制体的脚本里面给每个任务对应的text里声明该变量:

然后在设定任务时,将任务的需求的那个text赋值给它。

然后根据按钮更新数据的时候,再从任务的SO中获取任务内容再赋值:

这样的过程比较冗余。

现在删去其中在另外一个脚本QuestNameButton中声明变量并且传值然后修改的过程,直接在QuestUI中统一修改:

接下来实现展现需求的代码:

在questUI中,调用之前写的slotItem的方法:

这样即可实现

注意布局

接下来实现鼠标放到上面时显示信息的功能:

脚本并非挂在那个tooltip上,否则因为初始它是不可见的所以没法调用,应该放在reward item slot上。

那么和之前一样,需要实现接口即可:

然后放到上面时就显示设置active为true然后显示信息。

然后设置setupTooltip:

但是有个问题在于:

这个东西返回的是背包里的数据:

而不是实际存在的值。

所以此处需要添加新的变量:

将原来那个函数修改成这样:

这样即可实现,但是发现会出现遮挡现象:

将其放在最下方,就不会被遮挡了:

然后还有个问题在于背包关闭再次打开时,这个还是存在:这是因为即使关闭面板栏,tooltip依然是Active状态。

这样即可解决。

接下来实现多个任务时,出现了这样的问题:

任务来回切换会导致item Slot变多:

更新时清除奖励:

 

检测和更新任务进度

  • 创建函数在 敌人死亡 和 拾取物品时 更新任务进度
  • 使用 Linq 语句中的 Where 找到匹配的任务需求并检查是否满足
  • 修改特殊情况使用任务物品消耗后更新进度

任务进度:检查背包中的物品数量和任务所需的物品数量是否相同,

敌人死亡时进行调用

代码中如果出现两个任务,则两个任务中的都要对应减少。

书写检查任务的函数

提问:为什么有些函数放在QuestUI中有些函数放在QuestManager中,有些函数放在QuestData_SO中呢?

QuestUI是用来处理和UI界面的显示相关的函数。是MVC模式中的模型 Model

QuestData_SO中需要的函数很少,只需要涉及到修改状态数据的函数。是MVC中的视图View

而QuestManager用来处理读取数据以及和UI界面交互的功能。是MVC中的控制器Controller

拾取物品时也进行调用:

这样即可实现完成任务。

但是我们发现使用物品后,任务不会更新。

在此处进行修改:

接受任务时检查任务物品是否契合

  • 考虑可能存在的情况在接受任务的时候检查背包是否有任务物品
  • 在 QuestData_SO 中创建 RequireTargetName 拿到需求的名字列表
  • 循环列表中每一项在 Inventory 中检查是否存在并更新数据

有个问题在于如果接任务前背包就有两个蘑菇了,此时并不会任务面板并不会更新;
所以需要在InventoryManager中设定一个刚开始的函数中就去检查背包中的物品

检查任务物品:

由于一个questRequire中有多个物品:

因此需要一个包含其名字的list,循环时要判断每一个是否有

创建一个包含名字的链表:

这样就可以实现接受任务时就检查是否完成。

在questGiver里获取当前任务的任务状态:


 

根据完成情况控制任务对话分支

根据任务完成的状态,需要给予npc不同的对话,所以给npc创建一个脚本:

然后有QuestGiver去修改Dialogue Controller里面的数据

QuestGiver包含多种对话

  1. [RequireComponent(typeof(DialogueController))]
  2. public class QuestGiver : MonoBehaviour
  3. {
  4. DialogueController controller;
  5. QuestData_SO currentQuest;
  6. public DialougeData_SO startDialogue;
  7. public DialougeData_SO progressDialogue;
  8. public DialougeData_SO completeDialogue;
  9. public DialougeData_SO finishDialogue;
  10. private void Awake()
  11. {
  12. controller = GetComponent<DialogueController>();
  13. }
  14. }

对于发布任务的人,我们需要让它拿到任务的完成状态,才能根据不同状态执行不同对话:

然后赋值给当前任务:

在questGiver的update函数中则根据不同的状态进行切换。

然后自行设定四个对话的内容并传入:

有个bug会产生,问题在于不要出现这种情况

这样就实现了任务不同状态时的不同对话。

添加一个功能:

当玩家远离时则自动关闭对话:

拿到任务奖励及扣除报酬

拿到任务奖励需扣除报酬,那么可以这样,将奖励设置为-2:

接受完任务后,背包和栏里面的物品会更新,因此需要写一个根据任务里的物品判断背包是否有该物品的函数 

在任务数据里书写给予奖励的函数:

  1. public void GiveRewards()
  2. {
  3. foreach(var reward in rewards)
  4. {
  5. if (reward.amount < 0)
  6. {
  7. int requireCount = Mathf.Abs(reward.amount);
  8. //优先在背包里找是否有该物品,
  9. if (InventoryManager.Instance.QuestItemInBag(reward.itemData) != null)
  10. {
  11. //这种情况是背包里的东西不够,那就先在背包里扣除一部分,
  12. if (InventoryManager.Instance.QuestItemInBag(reward.itemData).amount <= requireCount)
  13. {
  14. requireCount -= InventoryManager.Instance.QuestItemInBag(reward.itemData).amount;//所需的数量减少
  15. InventoryManager.Instance.QuestItemInBag(reward.itemData).amount = 0;//背包里的商品扣除为0
  16. //背包里东西不够,剩下的部分从行动栏里扣除
  17. if (InventoryManager.Instance.QuestItemInAction(reward.itemData) != null)
  18. {
  19. InventoryManager.Instance.QuestItemInAction(reward.itemData).amount -= requireCount;
  20. }
  21. }
  22. //这种情况就是背包里的东西直接够,那直接扣除就好
  23. else
  24. {
  25. InventoryManager.Instance.QuestItemInBag(reward.itemData).amount -= requireCount;
  26. }
  27. }
  28. //这种情况是背包里一点东西都没有,那就直接扣除行动栏里的物品
  29. else
  30. {
  31. InventoryManager.Instance.QuestItemInAction(reward.itemData).amount -= requireCount;
  32. }
  33. }
  34. else
  35. {
  36. InventoryManager.Instance.inventoryData.AddItem(reward.itemData, reward.amount);
  37. }
  38. InventoryManager.Instance.inventoryUI.RefreshUI();
  39. InventoryManager.Instance.actionUI.RefreshUI();
  40. }
  41. }

在OptionUI中执行给与奖励:

这里做的时候出现了一个bug,问题在于之前的对话中没添加quest:

这样即可实现交付任务和扣除东西。

但是有个问题在于:

蘑菇会显示负数。

改动如下:

接下来还有一个小小问题,在于任务完成了,继续拾取蘑菇,任务进度还是会更新,解决方法:

然后在执行更新需求的代码里:

如果完成了则执行完成的设定 

在对话时,我们希望保持鼠标是固定的样式:

为了避免已完成的任务再次受到数据更新的影响: 

保存任务数据

QuestManager里面保存的数据以List类型保存,而里面的数据不是SO类型的,所以不能用之前那样的方法保存 

以前的保存都是通过Object来保存的:

因此此处实现一个非SO类型的保存方法:

虽然也可以通过将其改成SO的方式来实现,但此处换种方式:

注意到tasks虽然不是SO,但是task里面的QuestData是SO类型的,我们可以保存它

书写保存和Load的方法:

  1. public void SaveQuestManager()
  2. {
  3. PlayerPrefs.SetInt("QuestCount", tasks.Count);
  4. for(int i = 0; i < tasks.Count; i++)
  5. {
  6. SaveManager.Instance.Save(tasks[i].questData, "task" + i);
  7. }
  8. }
  9. //加载数据的方式是通过重新新创建一个SO,然后让SO读取数据,然后再加入到tasks链表当中
  10. public void LoadQuestManager()
  11. {
  12. var quesCount = PlayerPrefs.GetInt("QuestCount");
  13. for(int i = 0; i < quesCount; i++)
  14. {
  15. var newQuest = ScriptableObject.CreateInstance<QuestData_SO>();//
  16. SaveManager.Instance.Load(newQuest, "task" + i);
  17. tasks.Add(new QuestTask { questData = newQuest });
  18. }
  19. }

读取数据的方法可以放在初始时:

在QuestManager中添加

 这样即可实现切换场景保存任务:

代码汇总

背包部分

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public enum ItemType { Useable,Weapon,Armor}
  5. [CreateAssetMenu(fileName ="New Item",menuName ="Inventory/Item Data")]
  6. public class ItemData_SO : ScriptableObject
  7. {
  8. public ItemType itemType;
  9. public string itemName;
  10. public Sprite itemIcon;
  11. public int itemAmount;//这个物品有多少数量
  12. [TextArea]
  13. public string description = "";
  14. public bool stackable;//是否可堆叠
  15. [Header("Weapon")]
  16. public GameObject WeaponPrefab;
  17. public AttackData_SO weaponData;
  18. public AnimatorOverrideController weaponAnimator;
  19. [Header("Useable Item")]
  20. public UseableItemData_SO useableData;
  21. }

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class ItemPickUp : MonoBehaviour
  5. {
  6. public ItemData_SO itemData;
  7. private void OnTriggerEnter(Collider other)
  8. {
  9. if (other.CompareTag("Player"))
  10. {
  11. Debug.Log("OnTrigger");
  12. //GameManager.Instance.playerStats.EquipWeapon(itemData);
  13. InventoryManager.Instance.inventoryData.AddItem(itemData, itemData.itemAmount);
  14. InventoryManager.Instance.inventoryUI.RefreshUI();
  15. QuestManager.Instance.UpdateQuestProgress(itemData.itemName, itemData.itemAmount);
  16. //装备武器
  17. Destroy(gameObject);
  18. }
  19. }
  20. }

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class InventoryManager : Singleton<InventoryManager>
  6. {
  7. public class DragData
  8. {
  9. public SlotHolder orginalHolder;
  10. public RectTransform originalParent;
  11. }
  12. [Header("Inventory Data")]
  13. //使用类似之前那样的模板,游戏新开始时复制一份的操作
  14. //TODO:最后添加模板用于保存数据
  15. public InventoryData_SO inventoryTemplate;
  16. public InventoryData_SO inventoryData;
  17. public InventoryData_SO actionTemplate;
  18. public InventoryData_SO actionData;
  19. public InventoryData_SO equipmentTemplate;
  20. public InventoryData_SO equipmentData;
  21. [Header("Containers")]
  22. public ContainerUI inventoryUI;
  23. public ContainerUI actionUI;
  24. public ContainerUI equipmentUI;
  25. [Header("Drag Canvas")]
  26. public Canvas dragCanvas;
  27. public DragData currentDrag;
  28. protected override void Awake()
  29. {
  30. base.Awake();
  31. if (inventoryTemplate != null)
  32. inventoryData = Instantiate(inventoryTemplate);
  33. if (actionTemplate != null)
  34. actionData = Instantiate(actionTemplate);
  35. if (equipmentTemplate != null)
  36. equipmentData = Instantiate(equipmentTemplate);
  37. }
  38. private void Start()
  39. {
  40. LoadData();
  41. inventoryUI.RefreshUI();
  42. actionUI.RefreshUI();
  43. equipmentUI.RefreshUI();
  44. }
  45. [Header("UI Panel")]
  46. public GameObject bagPanel;
  47. public GameObject statsPanel;
  48. bool isOpen;
  49. [Header("Stats Text")]
  50. public Text healthText;
  51. public Text attackText;
  52. [Header("Tooltip")]
  53. public ItemToolTip tooltip;
  54. void Update()
  55. {
  56. if (Input.GetKeyDown(KeyCode.B))
  57. {
  58. isOpen = !isOpen;
  59. bagPanel.SetActive(isOpen);
  60. statsPanel.SetActive(isOpen);
  61. }
  62. UpdateStatsText(GameManager.Instance.playerStats.MaxHealth, GameManager.Instance.playerStats.attackData.minDamage,
  63. GameManager.Instance.playerStats.attackData.maxDamage);
  64. }
  65. public void UpdateStatsText(int health,int min,int max)
  66. {
  67. healthText.text = health.ToString();
  68. attackText.text = min + " - " + max;
  69. }
  70. #region 检查拖拽物品是否在每一个slot范围内
  71. public bool CheckInInventoryUI(Vector3 position)//此处这个位置是要传输进来的位置
  72. {
  73. for(int i = 0; i < inventoryUI.slotHolders.Length; i++)
  74. {
  75. RectTransform t = inventoryUI.slotHolders[i].transform as RectTransform;
  76. if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
  77. {
  78. return true;
  79. }
  80. }
  81. return false;
  82. }
  83. public bool CheckInActionUI(Vector3 position)//此处这个位置是要传输进来的位置
  84. {
  85. for (int i = 0; i < actionUI.slotHolders.Length; i++)
  86. {
  87. RectTransform t = actionUI.slotHolders[i].transform as RectTransform;
  88. if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
  89. {
  90. return true;
  91. }
  92. }
  93. return false;
  94. }
  95. public bool CheckInEquipmentUI(Vector3 position)//此处这个位置是要传输进来的位置
  96. {
  97. for (int i = 0; i < equipmentUI.slotHolders.Length; i++)
  98. {
  99. RectTransform t = equipmentUI.slotHolders[i].transform as RectTransform;
  100. if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
  101. {
  102. return true;
  103. }
  104. }
  105. return false;
  106. }
  107. #endregion
  108. public void SaveData()
  109. {
  110. SaveManager.Instance.Save(inventoryData, inventoryData.name);
  111. SaveManager.Instance.Save(actionData, actionData.name);
  112. SaveManager.Instance.Save(equipmentData, equipmentData.name);
  113. }
  114. public void LoadData()
  115. {
  116. SaveManager.Instance.Load(inventoryData, inventoryData.name);
  117. SaveManager.Instance.Load(actionData, actionData.name);
  118. SaveManager.Instance.Load(equipmentData, equipmentData.name);
  119. }
  120. #region 检测任务物品
  121. public void CheckQuestItemInBag(string questItemName)
  122. {
  123. foreach(var item in inventoryData.items)
  124. {
  125. if (item.itemData != null)
  126. {
  127. if (item.itemData.itemName == questItemName)
  128. QuestManager.Instance.UpdateQuestProgress(item.itemData.itemName, item.amount);
  129. }
  130. }
  131. foreach (var item in actionData .items)
  132. {
  133. if (item.itemData != null)
  134. {
  135. if (item.itemData.itemName == questItemName)
  136. QuestManager.Instance.UpdateQuestProgress(item.itemData.itemName, item.amount);
  137. }
  138. }
  139. }
  140. #endregion
  141. //检测背包和快捷栏里的物体是否有和任务相同的
  142. public InventoryItem QuestItemInBag(ItemData_SO questItem)
  143. {
  144. return inventoryData.items.Find(i => i.itemData == questItem);
  145. }
  146. public InventoryItem QuestItemInAction(ItemData_SO questItem)
  147. {
  148. return actionData.items.Find(i => i.itemData == questItem);
  149. }
  150. }

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.EventSystems;
  5. public enum SlotType { BAG,WEAPON,ARMOR,ACTION}
  6. public class SlotHolder : MonoBehaviour,IPointerClickHandler,IPointerEnterHandler,IPointerExitHandler
  7. {
  8. // Start is called before the first frame update
  9. public SlotType slotType;//这个需要用来告诉属于哪一个UI面板,因为后面会有背包、行动
  10. public ItemUI itemUI; //这是SlotHolder的子物体,Image和Text的父物体。
  11. //SlotHolder可以理解为格子里的物品的UI信息。
  12. //获取是为了给子物体中的image和text进行修改
  13. public void UpdateItem()
  14. {
  15. switch (slotType)
  16. {
  17. case SlotType.BAG:
  18. itemUI.Bag = InventoryManager.Instance.inventoryData;
  19. break;
  20. case SlotType.WEAPON:
  21. itemUI.Bag = InventoryManager.Instance.equipmentData;
  22. //装备武器 切换武器
  23. if (itemUI.Bag.items[itemUI.Index].itemData != null)
  24. {
  25. GameManager.Instance.playerStats.ChangeWeapon(itemUI.Bag.items[itemUI.Index].itemData);
  26. }
  27. else
  28. {
  29. GameManager.Instance.playerStats.UnEquipWeapon();
  30. }
  31. break;
  32. case SlotType.ARMOR:
  33. itemUI.Bag = InventoryManager.Instance.equipmentData;
  34. break;
  35. case SlotType.ACTION:
  36. itemUI.Bag = InventoryManager.Instance.actionData;
  37. break;
  38. }
  39. var item = itemUI.Bag.items[itemUI.Index];//找到数据库中对应序号的对应物品
  40. itemUI.SetupItemUI(item.itemData, item.amount);
  41. }
  42. public void OnPointerClick(PointerEventData eventData)
  43. {
  44. if (eventData.clickCount % 2 == 0)//代表是双击的话
  45. {
  46. UseItem();
  47. }
  48. }
  49. public void UseItem()
  50. {
  51. if (itemUI.GetItem().itemType == ItemType.Useable&&itemUI.Bag.items[itemUI.Index].amount>0)
  52. {
  53. GameManager.Instance.playerStats.ApplyHealth(itemUI.GetItem().useableData.healthPoint);
  54. itemUI.Bag.items[itemUI.Index].amount -= 1;
  55. QuestManager.Instance.UpdateQuestProgress(itemUI.GetItem().itemName, -1);
  56. }
  57. UpdateItem();
  58. }
  59. public void OnPointerEnter(PointerEventData eventData)
  60. {
  61. if (itemUI.GetItem())
  62. {
  63. InventoryManager.Instance.tooltip.SetupTooltip(itemUI.GetItem());
  64. InventoryManager.Instance.tooltip.gameObject.SetActive(true);
  65. }
  66. }
  67. public void OnPointerExit(PointerEventData eventData)
  68. {
  69. InventoryManager.Instance.tooltip.gameObject.SetActive(false);
  70. }
  71. void OnDisable()
  72. {
  73. InventoryManager.Instance.tooltip.gameObject.SetActive(false);
  74. }
  75. }

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.EventSystems;
  5. [RequireComponent(typeof(ItemUI))]
  6. public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
  7. {
  8. ItemUI currentItemUI;
  9. //
  10. SlotHolder currentHolder;
  11. SlotHolder targetHolder;
  12. void Awake()
  13. {
  14. currentItemUI = GetComponent<ItemUI>();
  15. currentHolder = GetComponentInParent<SlotHolder>();//原先的格子的信息
  16. }
  17. public void OnBeginDrag(PointerEventData eventData)
  18. {
  19. InventoryManager.Instance.currentDrag = new InventoryManager.DragData();
  20. InventoryManager.Instance.currentDrag.orginalHolder = GetComponentInParent<SlotHolder>();
  21. InventoryManager.Instance.currentDrag.originalParent = (RectTransform)transform.parent;
  22. //记录原始数据
  23. transform.SetParent(InventoryManager.Instance.dragCanvas.transform,true);
  24. }
  25. public void OnDrag(PointerEventData eventData)
  26. {
  27. //跟随鼠标位置移动
  28. transform.position = eventData.position;
  29. }
  30. public void OnEndDrag(PointerEventData eventData)
  31. {
  32. //放下物品 交换数据
  33. if (EventSystem.current.IsPointerOverGameObject())//是否指向UI组件
  34. {
  35. if(InventoryManager.Instance.CheckInActionUI(eventData.position)
  36. || InventoryManager.Instance.CheckInInventoryUI(eventData.position)
  37. || InventoryManager.Instance.CheckInEquipmentUI(eventData.position))
  38. //判断是否在三个栏里面的格子里
  39. {
  40. if (eventData.pointerEnter.gameObject.GetComponent<SlotHolder>())
  41. targetHolder = eventData.pointerEnter.gameObject.GetComponent<SlotHolder>();
  42. else
  43. targetHolder = eventData.pointerEnter.gameObject.GetComponentInParent<SlotHolder>();
  44. //如果没找到,此时是因为被图片所挡住了,那么就获取其父类的component
  45. Debug.Log(eventData.pointerEnter.gameObject);
  46. //判断鼠标选中的物体是否有slot holder,
  47. //判断是否目标holder是我的原holder
  48. if(targetHolder!=InventoryManager.Instance.currentDrag.orginalHolder)
  49. //由于例如食物不能放在武器栏里,所以需要对其做区分
  50. switch (targetHolder.slotType)
  51. {
  52. case SlotType.BAG:
  53. SwapItem();
  54. break;
  55. //下面这些if的判断就确保了只有相同的物品才能实现交换
  56. case SlotType.WEAPON:
  57. if (currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Weapon)
  58. SwapItem();
  59. break;
  60. case SlotType.ARMOR:
  61. if (currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Armor)
  62. SwapItem();
  63. break;
  64. case SlotType.ACTION:
  65. if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType==ItemType.Useable)
  66. SwapItem();
  67. break;
  68. }
  69. currentHolder.UpdateItem();//交换完毕后需要更新数据
  70. targetHolder.UpdateItem();
  71. }
  72. }
  73. transform.SetParent(InventoryManager.Instance.currentDrag.originalParent);
  74. RectTransform t = transform as RectTransform;
  75. t.offsetMax = -Vector3.one * 5;
  76. t.offsetMin = Vector2.one * 5;
  77. }
  78. public void SwapItem()
  79. {
  80. //targetHolder是鼠标指向的位置的格子。
  81. //获取目标格子上面显示的UI,UI图片对应它身上属于哪个背包的哪一个序号的物品
  82. var targetItem = targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index];//实际上就是获取鼠标指向的物品item
  83. var tempItem = currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index];//点击前的鼠标的格子的信息
  84. //如果是相同的物品就进行合并
  85. bool isSameItem = tempItem.itemData == targetItem.itemData;
  86. if (isSameItem && targetItem.itemData.stackable)//并且还要可堆叠才行
  87. {
  88. targetItem.amount += tempItem.amount;
  89. tempItem.itemData = null;
  90. tempItem.amount = 0;
  91. }
  92. else
  93. {
  94. currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index] = targetItem;
  95. //targetItem=tempItem;这样的写法不行因为targetItem只是我们获取的一个变量,应该直接用其本身去更换,即下一行的写法
  96. targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index] = tempItem;
  97. }
  98. }
  99. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.EventSystems;
  5. public class DragPanel : MonoBehaviour,IDragHandler,IPointerDownHandler
  6. {
  7. RectTransform rectTransform;
  8. Canvas canvas;
  9. void Awake()
  10. {
  11. rectTransform = GetComponent<RectTransform>();
  12. canvas = InventoryManager.Instance.GetComponent<Canvas>();
  13. }
  14. public void OnDrag(PointerEventData eventData)
  15. {
  16. rectTransform.anchoredPosition += eventData.delta/canvas.scaleFactor;//让其锚点的位置的改变量和鼠标的改变量相同即可
  17. }
  18. public void OnPointerDown(PointerEventData eventData)
  19. {
  20. rectTransform.SetSiblingIndex(2);
  21. }
  22. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class ContainerUI : MonoBehaviour
  5. {
  6. public SlotHolder[] slotHolders;
  7. public void RefreshUI()
  8. {
  9. for(int i = 0; i < slotHolders.Length; i++)
  10. {
  11. slotHolders[i].itemUI.Index = i;
  12. slotHolders[i].UpdateItem();
  13. }
  14. }
  15. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class ItemToolTip : MonoBehaviour
  6. {
  7. public Text itemNameText;
  8. public Text itemInfoText;
  9. RectTransform rectTransform;
  10. private void Awake()
  11. {
  12. rectTransform = GetComponent<RectTransform>();
  13. }
  14. public void SetupTooltip(ItemData_SO item)
  15. {
  16. itemNameText.text = item.itemName;
  17. itemInfoText.text = item.description;
  18. }
  19. void OnEnable()
  20. {
  21. //开始时会闪烁是因为没有定位好坐标,开始时先更新一下即可避免这种效果
  22. UpdatePosition();
  23. }
  24. private void Update()
  25. {
  26. UpdatePosition();
  27. }
  28. public void UpdatePosition()
  29. {
  30. Vector3 mousePos = Input.mousePosition;
  31. rectTransform.position = mousePos;
  32. Vector3[] corners = new Vector3[4];
  33. rectTransform.GetWorldCorners(corners);
  34. float width = corners[3].x - corners[0].x;
  35. float height = corners[1].y - corners[0].y;
  36. if (mousePos.y < height)
  37. rectTransform.position = mousePos + Vector3.up * height * 0.6f;
  38. else if (Screen.width - mousePos.x > width)
  39. rectTransform.position = mousePos + Vector3.right * width * 0.6f;
  40. else
  41. rectTransform.position = mousePos + Vector3.left * width * 0.6f;
  42. }
  43. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class ItemUI : MonoBehaviour
  6. {
  7. public Image icon = null;
  8. public Text amount = null;
  9. public InventoryData_SO Bag { get; set; }
  10. public InventoryData_SO Action { get; set; }
  11. public InventoryData_SO Equipment { get; set; }
  12. public ItemData_SO currentItemData;
  13. public int Index { get; set; } = -1;//初始值设为-1是因为一开始序号是从0开始的,避免一开始去setup每个格子的时候出现数据的错位排序
  14. public void SetupItemUI(ItemData_SO item,int itemAmount)
  15. {
  16. if (itemAmount == 0)
  17. {
  18. Bag.items[Index].itemData = null;
  19. icon.gameObject.SetActive(false);
  20. return;
  21. }
  22. //想要实现如果数量小于0则不显示,不能在上面的if条件改成<=0,因为此时并没有实际的背包
  23. if (itemAmount < 0) item = null;//只需要跳过下面的null部分就行了,并且需要设置active为false
  24. if (item != null)
  25. {
  26. icon.sprite = item.itemIcon;
  27. amount.text = itemAmount.ToString();
  28. icon.gameObject.SetActive(true);//默认是可见的,此处设为不可见
  29. currentItemData = item;
  30. }
  31. else
  32. icon.gameObject.SetActive(false);
  33. }
  34. public ItemData_SO GetItem()
  35. {
  36. return Bag.items[Index].itemData;
  37. }
  38. }

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class LootSpawner : MonoBehaviour
  5. {
  6. [System.Serializable]
  7. public class LootItem
  8. {
  9. public GameObject item;
  10. [Range(0, 1)]
  11. public float weight;
  12. }
  13. public LootItem[] lootItems;
  14. public void Spawnloot()
  15. {
  16. float currentValue = Random.value;
  17. for(int i = 0; i < lootItems.Length; i++)
  18. {
  19. if (currentValue <= lootItems[i].weight)
  20. {
  21. GameObject obj = Instantiate(lootItems[i].item);
  22. obj.transform.position = transform.position + Vector3.up * 2;
  23. break;//确保一次只掉落一个物品
  24. }
  25. }
  26. }
  27. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class ActionButton : MonoBehaviour
  5. {
  6. public KeyCode actionKey;
  7. private SlotHolder currentSlotHolder;
  8. void Awake()
  9. {
  10. currentSlotHolder = GetComponent<SlotHolder>();
  11. }
  12. private void Update()
  13. {
  14. if (Input.GetKeyDown(actionKey) && currentSlotHolder.itemUI.GetItem())
  15. currentSlotHolder.UseItem();
  16. }
  17. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. [CreateAssetMenu(fileName ="Useable Item",menuName ="Inventory/Useable Item Data")]
  5. public class UseableItemData_SO : ScriptableObject
  6. {
  7. //所有你想改变的数据
  8. public int healthPoint;
  9. }

Dialogue部分

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. public class DialogueController : MonoBehaviour
  5. {
  6. public DialougeData_SO currentData;
  7. bool canTalk = false;
  8. private void OnTriggerEnter(Collider other)
  9. {
  10. if (other.CompareTag("Player") && currentData != null)
  11. {
  12. canTalk = true;
  13. Debug.Log("can talk is true");
  14. }
  15. }
  16. private void OnTriggerExit(Collider other)
  17. {
  18. if (other.CompareTag("Player"))
  19. {
  20. DialogueUI.Instance.dialoguePanel.SetActive(false);
  21. }
  22. }
  23. private void Update()
  24. {
  25. if (canTalk && Input.GetKeyDown(KeyCode.F))//TODO:此处是否可以改编成使用事件的形式?
  26. {
  27. OpenDialogue();
  28. }
  29. }
  30. void OpenDialogue()
  31. {
  32. //打开UI面板
  33. //传输对话内容信息
  34. DialogueUI.Instance.UpdateDialogueData(currentData);
  35. DialogueUI.Instance.UpdateMainDialogue(currentData.dialoguePieces[0]);
  36. }
  37. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. [System.Serializable]
  5. public class DialogueOption
  6. {
  7. public string text;
  8. public string targetID;
  9. public bool takeQuest;
  10. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. [System.Serializable]
  5. public class DialoguePiece {
  6. public string ID;
  7. public Sprite image;
  8. [TextArea]
  9. public string text;
  10. public QuestData_SO quest;
  11. public List<DialogueOption> options = new List<DialogueOption>();
  12. }

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. [CreateAssetMenu(fileName ="New Dialogue",menuName ="Dialogue/Dialogue Data")]
  5. public class DialougeData_SO : ScriptableObject
  6. {
  7. public List<DialoguePiece> dialoguePieces = new List<DialoguePiece>();
  8. public Dictionary<string, DialoguePiece> dialogueIndex = new Dictionary<string, DialoguePiece>();
  9. public QuestData_SO GetQuest()
  10. {
  11. QuestData_SO currentQuest = null;
  12. //循环对话中的任务,找到该任务并返回
  13. foreach (var piece in dialoguePieces)
  14. {
  15. if (piece.quest != null)
  16. currentQuest = piece.quest;
  17. }
  18. return currentQuest;
  19. }
  20. //如果是在Unity编辑器中,则字典随时改变时则进行修改,如果是打包则字典信息不会更改
  21. #if UNITY_EDITOR
  22. void OnValidate()//一旦这个脚本中的数据被更改时会自动调用
  23. {
  24. dialogueIndex.Clear();
  25. //一旦信息有所更新,就会将信息存储在字典中
  26. foreach(var piece in dialoguePieces)
  27. {
  28. if (!dialogueIndex.ContainsKey(piece.ID))
  29. dialogueIndex.Add(piece.ID, piece);
  30. }
  31. }
  32. #else
  33. void Awake()//保证在打包执行的游戏里第一时间获得对话的所有字典匹配
  34. {
  35. dialogueIndex.Clear();
  36. foreach (var piece in dialoguePieces)
  37. {
  38. if (!dialogueIndex.ContainsKey(piece.ID))
  39. dialogueIndex.Add(piece.ID, piece);
  40. }
  41. }
  42. #endif
  43. }

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. using DG.Tweening;
  6. public class DialogueUI : Singleton<DialogueUI>
  7. {
  8. [Header("Basic Elements")]
  9. public Image icon;
  10. public Text mainText;
  11. public Button nextButton;
  12. public GameObject dialoguePanel;
  13. [Header("Data")]
  14. public DialougeData_SO currentData;
  15. int currentIndex = 0;
  16. [Header("Options")]
  17. public RectTransform optionPanel;
  18. public OptionUI optionPrefab;
  19. protected override void Awake()
  20. {
  21. base.Awake();
  22. nextButton.onClick.AddListener(ContinueDialogue);//点击时如果后续还有对话则继续进行
  23. }
  24. void ContinueDialogue()
  25. {
  26. if (currentIndex < currentData.dialoguePieces.Count)
  27. {
  28. UpdateMainDialogue(currentData.dialoguePieces[currentIndex]);
  29. }
  30. else dialoguePanel.SetActive(false);
  31. }
  32. public void UpdateDialogueData(DialougeData_SO data)
  33. {
  34. currentData = data;
  35. currentIndex = 0;//保证每次都是从头开始对话
  36. }
  37. public void UpdateMainDialogue(DialoguePiece piece)
  38. {
  39. dialoguePanel.SetActive(true);
  40. currentIndex++;
  41. if (piece.image != null)
  42. {
  43. icon.enabled = true;
  44. icon.sprite = piece.image;
  45. }
  46. else icon.enabled = false;
  47. mainText.text = "";
  48. //mainText.text = piece.text;
  49. mainText.DOText(piece.text, 1f);
  50. //如果后续还有对话就按next,没有就不按next
  51. if (piece.options.Count == 0 && currentData.dialoguePieces.Count > 0)
  52. {
  53. nextButton.interactable = true;
  54. nextButton.gameObject.SetActive(true);
  55. //currentIndex++;//不应该放在这里++,应该每运行一次都让index++,因此应该放在上面
  56. }
  57. else
  58. {
  59. //nextButton.gameObject.SetActive(false);
  60. nextButton.transform.GetChild(0).gameObject.SetActive(false);//让字看不见
  61. nextButton.interactable = false;//并且删除交互功能
  62. }
  63. CreateOptions(piece);//根据对话来创建选项
  64. }
  65. void CreateOptions(DialoguePiece piece)
  66. {
  67. if (optionPanel.childCount > 0)//销毁旧的选项
  68. {
  69. for(int i = 0; i < optionPanel.childCount; i++)
  70. {
  71. Destroy(optionPanel.GetChild(i).gameObject);
  72. }
  73. }
  74. //生成新的选项,并且调用选项,传入对话的选项信息,来更新option所显示的信息
  75. for (int i = 0; i < piece.options.Count; i++)
  76. {
  77. var option = Instantiate(optionPrefab, optionPanel);
  78. option.UpdateOption(piece,piece.options[i]);
  79. }
  80. }
  81. }

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class OptionUI : MonoBehaviour
  6. {
  7. public Text optionText;
  8. private Button thisButton;
  9. private DialoguePiece currentPiece;
  10. private bool takeQuest;
  11. private string nextPieceID;
  12. void Awake()
  13. {
  14. thisButton = GetComponent<Button>();
  15. thisButton.onClick.AddListener(OnOptionClicked);
  16. }
  17. public void UpdateOption(DialoguePiece piece,DialogueOption option)
  18. {
  19. currentPiece = piece;
  20. optionText.text = option.text;
  21. nextPieceID = option.targetID;
  22. takeQuest = option.takeQuest;
  23. }
  24. public void OnOptionClicked()
  25. {
  26. if (currentPiece.quest != null)//如果当前选项含有任务
  27. {
  28. //则获取该任务
  29. var newTask = new QuestManager.QuestTask
  30. {
  31. questData = Instantiate(currentPiece.quest)//将quest信息实例化然后赋值给QuestTask类里面的questData
  32. };
  33. if (takeQuest)
  34. {
  35. Debug.Log("takeQuest is true");
  36. //判断是否在列表中
  37. if (QuestManager.Instance.HaveQuest(newTask.questData))
  38. {
  39. Debug.Log("Quest in list");
  40. //判断是否完成,若完成则给予奖励
  41. if (QuestManager.Instance.GetTask(newTask.questData).IsComplete)
  42. {
  43. Debug.Log("任务完成,给予奖励");
  44. newTask.questData.GiveRewards();
  45. QuestManager.Instance.GetTask(newTask.questData).IsFinished = true;
  46. }
  47. }
  48. else
  49. {
  50. QuestManager.Instance.tasks.Add(newTask);
  51. //newTask.IsStarted = true;这样的做法并没有用,这样只是修改的临时变量
  52. QuestManager.Instance.GetTask(newTask.questData).IsStarted = true;
  53. //添加到任务列表
  54. foreach (var requireItem in newTask.questData.RequireTargetName())
  55. {
  56. InventoryManager.Instance.CheckQuestItemInBag(requireItem);
  57. }
  58. }
  59. }
  60. }
  61. if (nextPieceID == "")
  62. {
  63. DialogueUI.Instance.dialoguePanel.SetActive(false);
  64. return;
  65. }
  66. else
  67. {
  68. //用ID去获取下一个对话
  69. DialogueUI.Instance.UpdateMainDialogue(DialogueUI.Instance.currentData.dialogueIndex[nextPieceID]);
  70. }
  71. }
  72. }

任务部分

  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. using System.Linq;
  6. [CreateAssetMenu(fileName ="New Quest",menuName ="Quest/Quest Data")]
  7. public class QuestData_SO : ScriptableObject
  8. {
  9. [System.Serializable]
  10. public class QuestRequire
  11. {
  12. public string name;
  13. public int requireAmount;
  14. public int currentAmount;
  15. }
  16. public string questName;
  17. [TextArea]
  18. public string description;
  19. //需要三种任务完成的状态,npc才会有不同的反应
  20. public bool isStarted;
  21. public bool isComplete;
  22. public bool isFinished;
  23. public List<QuestRequire> questRequires = new List<QuestRequire>();
  24. public List<InventoryItem> rewards = new List<InventoryItem>();
  25. public void CheckQuestProgress()
  26. {
  27. var finishRequires = questRequires.Where(r => r.requireAmount <= r.currentAmount);
  28. isComplete = finishRequires.Count() == questRequires.Count;
  29. if (isComplete)
  30. {
  31. Debug.Log("任务完成");
  32. }
  33. }
  34. //当前任务需要收集/消灭的目标名字列表
  35. public List<string> RequireTargetName()
  36. {
  37. List<string> targetNameList = new List<string>();
  38. foreach(var require in questRequires)
  39. {
  40. targetNameList.Add(require.name);
  41. }
  42. return targetNameList;
  43. }
  44. public void GiveRewards()
  45. {
  46. foreach(var reward in rewards)
  47. {
  48. if (reward.amount < 0)
  49. {
  50. int requireCount = Mathf.Abs(reward.amount);
  51. //优先在背包里找是否有该物品,
  52. if (InventoryManager.Instance.QuestItemInBag(reward.itemData) != null)
  53. {
  54. //这种情况是背包里的东西不够,那就先在背包里扣除一部分,
  55. if (InventoryManager.Instance.QuestItemInBag(reward.itemData).amount <= requireCount)
  56. {
  57. requireCount -= InventoryManager.Instance.QuestItemInBag(reward.itemData).amount;//所需的数量减少
  58. InventoryManager.Instance.QuestItemInBag(reward.itemData).amount = 0;//背包里的商品扣除为0
  59. //背包里东西不够,剩下的部分从行动栏里扣除
  60. if (InventoryManager.Instance.QuestItemInAction(reward.itemData) != null)
  61. InventoryManager.Instance.QuestItemInAction(reward.itemData).amount -= requireCount;
  62. }
  63. //这种情况就是背包里的东西直接够,那直接扣除就好
  64. else
  65. {
  66. InventoryManager.Instance.QuestItemInBag(reward.itemData).amount -= requireCount;
  67. }
  68. }
  69. //这种情况是背包里一点东西都没有,那就直接扣除行动栏里的物品
  70. else
  71. {
  72. InventoryManager.Instance.QuestItemInAction(reward.itemData).amount -= requireCount;
  73. }
  74. }
  75. else
  76. {
  77. InventoryManager.Instance.inventoryData.AddItem(reward.itemData, reward.amount);
  78. }
  79. InventoryManager.Instance.inventoryUI.RefreshUI();
  80. InventoryManager.Instance.actionUI.RefreshUI();
  81. }
  82. }
  83. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. [RequireComponent(typeof(DialogueController))]
  5. public class QuestGiver : MonoBehaviour
  6. {
  7. DialogueController controller;
  8. QuestData_SO currentQuest;
  9. public DialougeData_SO startDialogue;
  10. public DialougeData_SO progressDialogue;
  11. public DialougeData_SO completeDialogue;
  12. public DialougeData_SO finishDialogue;
  13. private void Awake()
  14. {
  15. controller = GetComponent<DialogueController>();
  16. }
  17. #region 获得任务状态
  18. public bool IsStarted
  19. {
  20. get
  21. {
  22. if (QuestManager.Instance.HaveQuest(currentQuest))
  23. {
  24. return QuestManager.Instance.GetTask(currentQuest).IsStarted;
  25. }
  26. else return false;
  27. }
  28. }
  29. public bool IsComplete
  30. {
  31. get
  32. {
  33. if (QuestManager.Instance.HaveQuest(currentQuest))
  34. {
  35. return QuestManager.Instance.GetTask(currentQuest).IsComplete;
  36. }
  37. else return false;
  38. }
  39. }
  40. public bool IsFinished
  41. {
  42. get
  43. {
  44. if (QuestManager.Instance.HaveQuest(currentQuest))
  45. {
  46. return QuestManager.Instance.GetTask(currentQuest).IsFinished;
  47. }
  48. else return false;
  49. }
  50. }
  51. #endregion
  52. private void Start()
  53. {
  54. controller.currentData = startDialogue;
  55. currentQuest = controller.currentData.GetQuest();
  56. }
  57. //根据状态切换对话
  58. void Update()
  59. {
  60. if (IsStarted)
  61. {
  62. if (IsComplete)
  63. {
  64. controller.currentData = completeDialogue;
  65. }
  66. else
  67. {
  68. controller.currentData = progressDialogue;
  69. }
  70. }
  71. if (IsFinished)
  72. {
  73. controller.currentData = finishDialogue;
  74. }
  75. }
  76. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using System.Linq;
  5. public class QuestManager : Singleton<QuestManager>
  6. {
  7. [System.Serializable]
  8. public class QuestTask
  9. {
  10. public QuestData_SO questData;
  11. public bool IsStarted {
  12. get { return questData.isStarted; }
  13. set { questData.isStarted = value; }
  14. }
  15. public bool IsComplete
  16. {
  17. get { return questData.isComplete; }
  18. set { questData.isComplete = value; }
  19. }
  20. public bool IsFinished
  21. {
  22. get { return questData.isFinished; }
  23. set { questData.isFinished = value; }
  24. }
  25. }
  26. public List<QuestTask> tasks = new List<QuestTask>();
  27. //敌人死亡,拾取物品时调用
  28. public void UpdateQuestProgress(string requireName,int amount)
  29. {
  30. foreach(var task in tasks)
  31. {
  32. if (task.IsFinished)
  33. continue;//为了避免已完成的任务受到影响
  34. var matchTask = task.questData.questRequires.Find(r => r.name == requireName);
  35. if (matchTask != null)
  36. matchTask.currentAmount += amount;
  37. task.questData.CheckQuestProgress();
  38. }
  39. }
  40. public bool HaveQuest(QuestData_SO data)//判断是否有这个任务
  41. {
  42. //在头文件中引入Ling,可以用于查找链表中的内容
  43. if (data != null)
  44. return tasks.Any(q => q.questData.questName == data.questName);
  45. else return false;
  46. }
  47. //根据任务数据的名字查找链表中的某一个任务
  48. public QuestTask GetTask(QuestData_SO data)
  49. {
  50. return tasks.Find(q => q.questData.questName == data.questName);
  51. }
  52. private void Start()
  53. {
  54. LoadQuestManager();
  55. }
  56. public void SaveQuestManager()
  57. {
  58. PlayerPrefs.SetInt("QuestCount", tasks.Count);
  59. for(int i = 0; i < tasks.Count; i++)
  60. {
  61. SaveManager.Instance.Save(tasks[i].questData, "task" + i);
  62. }
  63. }
  64. //加载数据的方式是通过重新新创建一个SO,然后让SO读取数据,然后再加入到tasks链表当中
  65. public void LoadQuestManager()
  66. {
  67. var quesCount = PlayerPrefs.GetInt("QuestCount");
  68. for(int i = 0; i < quesCount; i++)
  69. {
  70. var newQuest = ScriptableObject.CreateInstance<QuestData_SO>();//
  71. SaveManager.Instance.Load(newQuest, "task" + i);
  72. tasks.Add(new QuestTask { questData = newQuest });
  73. }
  74. }
  75. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class QuestNameButton : MonoBehaviour
  6. {
  7. public Text questNameText;
  8. public QuestData_SO currentData;
  9. //public Text questContentText;
  10. public void SetupNameButton(QuestData_SO quesData)
  11. {
  12. currentData = quesData;
  13. if (quesData.isComplete)
  14. questNameText.text = quesData.questName + "(完成)";
  15. else
  16. questNameText.text = quesData.questName;
  17. }
  18. private void Awake()
  19. {
  20. GetComponent<Button>().onClick.AddListener(UpdateQuestContent);
  21. }
  22. void UpdateQuestContent()
  23. {
  24. //questContentText.text = currentData.description;
  25. QuestUI.Instance.SetupRequireList(currentData);
  26. foreach(Transform item in QuestUI.Instance.rewardTransform)
  27. {
  28. Destroy(item.gameObject);
  29. }
  30. foreach(var item in currentData.rewards)//奖励可能不止一个所以需要循环列表
  31. {
  32. QuestUI.Instance.SetupRewardItem(item.itemData, item.amount);
  33. }
  34. }
  35. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class QuestRequirement : MonoBehaviour
  6. {
  7. private Text requireName;
  8. private Text progressNumber;
  9. private void Awake()
  10. {
  11. requireName = GetComponent<Text>();
  12. progressNumber = transform.GetChild(0).GetComponent<Text>();
  13. }
  14. public void SetupRequirement(string name,int amount,int currentAmount)
  15. {
  16. requireName.text = name;
  17. progressNumber.text = currentAmount.ToString() + "/" + amount.ToString();
  18. }
  19. public void SetupRequirement(string name, bool isFinished)
  20. {
  21. if (isFinished)
  22. {
  23. requireName.text = name;
  24. progressNumber.text = "完成";
  25. requireName.color = Color.gray;
  26. progressNumber.color = Color.gray;
  27. }
  28. }
  29. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. public class QuestUI : Singleton<QuestUI>
  6. {
  7. [Header("Elements")]
  8. public GameObject quesPanel;
  9. public ItemToolTip tooltip;
  10. bool isOpen;
  11. [Header("Quest Name")]
  12. public RectTransform questListTransform;
  13. public QuestNameButton questNameButton;
  14. [Header("Text Content")]
  15. public Text quesContentText;
  16. [Header("Requirement")]
  17. public RectTransform requireTransform;
  18. public QuestRequirement requirement;
  19. [Header("Reward Panel")]
  20. public RectTransform rewardTransform;
  21. public ItemUI rewardUI;
  22. private void Update()
  23. {
  24. if (Input.GetKeyDown(KeyCode.Q))
  25. {
  26. isOpen = !isOpen;
  27. quesPanel.SetActive(isOpen);
  28. quesContentText.text = string.Empty;
  29. SetupQuestList();
  30. }
  31. if (!isOpen)
  32. tooltip.gameObject.SetActive(false);
  33. }
  34. public void SetupQuestList()
  35. {
  36. //清除原来已有的任务
  37. foreach(Transform item in questListTransform)
  38. {
  39. Destroy(item.gameObject);
  40. }
  41. foreach(Transform item in rewardTransform)
  42. {
  43. Destroy(item.gameObject);
  44. }
  45. foreach (Transform item in requireTransform)
  46. {
  47. Destroy(item.gameObject);
  48. }
  49. //遍历列表中的list,接取任务
  50. foreach(var task in QuestManager.Instance.tasks)
  51. {
  52. var newTask = Instantiate(questNameButton, questListTransform);
  53. newTask.SetupNameButton(task.questData);
  54. //newTask.questContentText = quesContentText;
  55. }
  56. }
  57. public void SetupRequireList(QuestData_SO questData)
  58. {
  59. quesContentText.text = questData.description;
  60. //将涉及到QuestNameButton中的三处questContentText关闭,不使用在里面传东西然后赋值的形式了,改为在此处直接修改
  61. foreach (Transform item in requireTransform)
  62. {
  63. Destroy(item.gameObject);
  64. }
  65. foreach(var require in questData.questRequires)
  66. {
  67. var q = Instantiate(requirement, requireTransform);
  68. if (questData.isFinished)
  69. q.SetupRequirement(require.name, true);
  70. else
  71. q.SetupRequirement(require.name, require.requireAmount, require.currentAmount);
  72. }
  73. }
  74. public void SetupRewardItem(ItemData_SO itemData,int amount)
  75. {
  76. var item = Instantiate(rewardUI, rewardTransform);
  77. item.SetupItemUI(itemData, amount);
  78. }
  79. }
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.EventSystems;
  5. public class ShowTooltip : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
  6. {
  7. private ItemUI currentItemUI;
  8. void Awake()
  9. {
  10. currentItemUI = GetComponent<ItemUI>();
  11. }
  12. public void OnPointerEnter(PointerEventData eventData)
  13. {
  14. Debug.Log("mouse in slot");
  15. QuestUI.Instance.tooltip.gameObject.SetActive(true);
  16. QuestUI.Instance.tooltip.SetupTooltip(currentItemUI.currentItemData);
  17. }
  18. public void OnPointerExit(PointerEventData eventData)
  19. {
  20. QuestUI.Instance.tooltip.gameObject.SetActive(false);
  21. }
  22. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/90701
推荐阅读
相关标签
  

闽ICP备14008679号