目录
声明
1. QuestGiver 控制任务对话显示
2.GiveRewards 拿到任务奖励
3.SaveQuestManager 保存任务数据
声明
本教程学习均来自U3D中文课堂麦扣老师
1. QuestGiver 控制任务对话显示
现在来实现NPC能根据我任务的情况给我不同的对话,比如说第一次对话的时候是开始任务,任务过程进行当中要返回进行当中的对应的对话,结束的时候要有结束任务的,完成这个任务拿到奖励之后我再去找他对话的时候又是另外一个对话,所以可以看到我们需要在这里面不断地切换我们的Dialog Data,那么我们会创建一个代码来实时的替换我当前NPC要说的那个对话的数据,那我们先创建一下这个代码:
我们去到我们的Quest当中,我们在我们的Logic里面创建代码QuestGiver:
挂载到NPC身上
可以看到现在我们的NPC身上有一个DialogController,所以我们的Giver也要拿到Controller的使用权,然后将QuestGiver里保存的数据实时更新到我的currentData,那么我每次跟它对话的时候就会显示不同的内容了,所以我的任务也需要做一些更改,比如说我的NewTalk,我要给它改个名字,Hero Start,也就是Hero第一次开始的时候对话的内容,然后我也会创建更多的Data去检测它任务进行中的对话和结束的对话,
先来写一下QuestGiver:
[RequireComponent(typeof(DialogueController))]
public class QuestGiver : MonoBehaviour
{
DialogueController controller;
QuestData_SO currentQuest;
public DialogueData_SO startDialogue;
public DialogueData_SO progressDialogue;
public DialogueData_SO completeDialogue;
public DialogueData_SO finishDialogue;
#region 获得任务进度
public bool IsStarted
{
get
{
if (QuestManager.Instance.HaveQuest(currentQuest))
{
return QuestManager.Instance.GetTask(currentQuest).isStarted;
}
else return false;
}
}
public bool IsComplete
{
get
{
if (QuestManager.Instance.HaveQuest(currentQuest))
{
return QuestManager.Instance.GetTask(currentQuest).isComplete;
}
else return false;
}
}
public bool IsFinished
{
get
{
if (QuestManager.Instance.HaveQuest(currentQuest))
{
return QuestManager.Instance.GetTask(currentQuest).isFinished;
}
else return false;
}
}
#endregion
private void Awake()
{
controller = GetComponent<DialogueController>();
}
private void Start()
{
controller.currentData = startDialogue;//开始对话数据
}
}
DialogueData_SO:
public QuestData_SO GetQuest()
{
QuestData_SO currentQuest = null;
foreach(var piect in dialoguePieces)
{
if(piect.quest != null)
{
currentQuest = piect.quest;
}
}
return currentQuest;
}
QuestGiver:
private void Start()
{
controller.currentData = startDialogue;//任务开始的对话
currentQuest = controller.currentData.GetQuest();
}
private void Update()
{
if(IsStarted)
{
if(IsComplete)
{
controller.currentData = completeDialogue;//任务完成的对话
}
else
{
controller.currentData = progressDialogue;//任务进行的对话
}
}
if(IsFinished)
{
controller.currentData = finishDialogue;//领取完奖励的对话
}
}
设置对话内容:
当我们离开NPC的时候对话窗口关闭:
DialogueController:
private void OnTriggerExit(Collider other)
{
if(other.CompareTag("Player"))
{
DialogueUI.Instance.dialoguePanel.SetActive(false);//Player离开关闭对话窗口
}
}
这样在Player脱离NPC的范围之后就自动关闭对话窗口了
2.GiveRewards 拿到任务奖励
接下来实现完成任务后要领取我们任务的奖励,在我们写这个逻辑的时候我直接将我们的任务设计成一个比较复杂的情况,就是我们收集物品之后我们获得奖励的同时也要减少对应的物品,那这样我们直接添加对应的物品的奖励,奖励的数量是-2,
在Hero Complete当中就是我们当完成任务的时候再次跟NPC对话,这时候它会跟我说:你有这个任务,你已经完成了,takeQuest ,我希望用这个takeQuest按钮,也就是屏幕上的某一个选项就是谢谢你,点击这个Option之后,它要做的就是到我的当前的QuestData当中拿到所有的Reward,然后给我的Inventory,
找到OptionUI://判断是否完成给予奖励
//添加到任务列表里
//判断是否已经有任务
if (QuestManager.Instance.HaveQuest(newTask.questData))
{
//判断是否完成给予奖励
if (QuestManager.Instance.GetTask(newTask.questData).isComplete)
{
}
}
QuestData_SO:创建拿到任务奖励的方法
public void GiveRewards()//拿到任务奖励
{
foreach(var reward in rewards)
{
if(reward.amount < 0)
{
}
else
{
InventoryManager.Instance.InventoryData.AddItem(reward.ItemData, reward.amount);//添加物品到背包中
}
InventoryManager.Instance.inventoryUI.RefreshUI();//刷新UI
InventoryManager.Instance.actionUI.RefreshUI();//刷新UI
}
}
InventoryManager:
//检测背包和任务栏物品
public InventoryItem QuestItemInBag(ItemData_SO questItem)
{
return InventoryData.items.Find(i => i.ItemData == questItem);
}
public InventoryItem QuestItemInAction(ItemData_SO questItem)
{
return actionData.items.Find(i => i.ItemData == questItem);
}
QuestData_SO:在拿到任务奖励的方法中检测背包交出任务物品
public void GiveRewards()//拿到任务奖励
{
foreach (var reward in rewards)
{
if (reward.amount < 0)
{
int requireCount = Mathf.Abs(reward.amount);//需要交出的物品的数量
if (InventoryManager.Instance.QuestItemInBag(reward.ItemData) != null)//背包里有物品
{
if (InventoryManager.Instance.QuestItemInBag(reward.ItemData).amount <= requireCount)//物品部分在背包里
{
requireCount -= InventoryManager.Instance.QuestItemInBag(reward.ItemData).amount;
InventoryManager.Instance.QuestItemInBag(reward.ItemData).amount = 0;
if(InventoryManager.Instance.QuestItemInAction(reward.ItemData) != null)
{
InventoryManager.Instance.QuestItemInAction(reward.ItemData).amount -= requireCount;
}
}
else//物品全在背包里
{
InventoryManager.Instance.QuestItemInBag(reward.ItemData).amount -= requireCount;
}
}
else//物品全在快捷栏
{
InventoryManager.Instance.QuestItemInAction(reward.ItemData).amount -= requireCount;
}
}
else
{
InventoryManager.Instance.InventoryData.AddItem(reward.ItemData, reward.amount);//添加物品到背包中
}
InventoryManager.Instance.inventoryUI.RefreshUI();//刷新UI
InventoryManager.Instance.actionUI.RefreshUI();//刷新UI
}
}
OptionUI:拿到任务奖励并将任务状态改为Finish
//添加到任务列表里
//判断是否已经有任务
if (QuestManager.Instance.HaveQuest(newTask.questData))
{
//判断是否完成给予奖励
if (QuestManager.Instance.GetTask(newTask.questData).isComplete)
{
newTask.questData.GiveRewards();//拿到任务奖励
//将当前的任务状态改成Finish
QuestManager.Instance.GetTask(newTask.questData).isFinished = true;
}
}
要加一个ItemUI的判断,如果数量是小于0的那么不应该显示这个Item,
ItemUI:
public void SetupItemUI(ItemData_SO item,int itemAmount)//显示UI
{
if(itemAmount == 0)//物品数量为0则消失
{
Bag.items[Index].ItemData = null;
icon.gameObject.SetActive(false);
return;
}
if(itemAmount < 0)//物品数量小于0不显示
{
item = null;
}
if(item != null)
{
currentItemData = item;
icon.sprite = item.itemIcon;
amount.text = itemAmount.ToString();
icon.gameObject.SetActive(true);
}
else
{
icon.gameObject.SetActive(false);
}
}
还有一个小的内容可以解决,就是当我的任务完成之后我现在仍然显示2/2,接下来我去拿到更多的蘑菇的话它也会继续在这里面显示这个数据,我希望完成的时候这里显示完成就可以了,结束的任务就不追踪了,另外我们可以检测一下我继续跟NPC对话的话它就会显示Finish那句,我也不会重复接到这个任务了,
QuestRequirement:
public void SetupRequirement(string name,bool isFinished)//完成任务则任务需求物品显示完成
{
if(isFinished)
{
requireName.text = name;
progressNumber.text = "完成";
//文字变灰
requireName.color = Color.gray;
progressNumber.color = Color.gray;
}
}
QuestUI:
public void SetupRequireList(QuestData_SO questData)//刷新任务需求
{
questContentText.text = questData.description;//任务描述
foreach (Transform item in requirTransform)
{
Destroy(item.gameObject);
}
foreach (var require in questData.questRequires)
{
var q = Instantiate(requirement, requirTransform);
if(questData.isFinished)
{
q.SetupRequirement(require.name,true);//完成任务则任务需求物品显示完成
}
else
{
q.SetupRequirement(require.name, require.requireAmount, require.currentAmount);//更新任务需求的文字
}
}
}
这样就完成了交任务,接任务,完成任务所有的内容了。
3.SaveQuestManager 保存任务数据
把我们的任务也保存到我们的进度当中,这样的话我点击Continue可以加载回我之前已经做过的任务,并且能够确定它们的任务状态,那么在做这一步保存之前,还有一个小内容要做一个更改,首先第一个我的鼠标在UI面板上移动的时候我们的鼠标的指针,我们的Cursor仍然会根据它背后的物品产生变化,那这个时候怎么办呢?我们做一个小的修改,去到我们的
MouseManager:
void SetCursorTexture() //设置指针的贴图
{
...
if(InteractWithUI())//判断是否与UI互动,
{
Cursor.SetCursor(point,Vector2.zero, CursorMode.Auto);
return;
}
if(Physics.Raycast(ray,out hitInfo))
{
//切换鼠标贴图
...
}
这样的话能够在UI面案显示小手的那个造型了,另外我们还要做一个简单的修改就是QuestManager当中,与其说是修改,不妨说我们不希望看到的一种情况就是以后我们接了多个任务之后,假如跟以前完成过的任务有相同的元素,那我不希望这些所有的数值影响我在我的List列表当中显示的样子,因为一旦任务完成之后,我的任务名字后面有个括号完成,所以在我们的循环判断,也就是说UpdateQuestProgress里面我们现在执行的方法是无论你有多少个task我都循环里面的每一个,然后去更新它里面的进度,在上面我们可以加一个判断:
QuestManager:
//敌人死亡、拾取物品的时候调用
public void UpdateQuestProgress(string requireName,int amount)//更新任务进度
{
foreach(var task in tasks)
{
if(task.isFinished)
{
continue;//跳过检测去到下一个检测
}
var matchTask = task.questData.questRequires.Find(r => r.name == requireName);
if(matchTask != null)
{
matchTask.currentAmount += amount;
}
task.questData.CheckQuestProgress();//检测任务进度
}
}
完成保存任务系统,像这种非ScriptableObject我们要如何保存
SaveManager当中写了2个保存的方法,他们这里调用的都是Object类, 很多Monobehaviour以及我们手写的类型都是Object衍生的类,但是List并不在内,所以我们的列表并不能称之为Object,也就是不能用这种方法去保存,在这里提供一个新的思路,我当前是有一个task,比如说我的这个task里有2个任务了,那我可以逐一保存这2个任务,因为我们的这个QuestData它是一个ScriptableObject,我们可以用之前写过的Save的方法去保存它,然后我把它按照当前task的序号去保存起来当读取的时候再把它逐一读取回来,加载到task当中这样就实现了读取和加载,
QuestManager:
private void Start()
{
LoadQuestManager();//加载任务
}
public void LoadQuestManager()//加载任务
{
var questCount = PlayerPrefs.GetInt("QuestCount");//读取任务数量
for (int i = 0; i < questCount; i++)
{
var newQuest = ScriptableObject.CreateInstance<QuestData_SO>();//生成QuestData_SO的一个实例
SaveManager.Instance.Load(newQuest, "task" + i);//读取任务数据
tasks.Add(new QuestTask { questData = newQuest });//将任务添加到任务列表
}
}
public void SaveQuestManager()//保存任务
{
PlayerPrefs.SetInt("QuestCount", tasks.Count);//保存任务数量
for (int i = 0; i < tasks.Count; i++)
{
SaveManager.Instance.Save(tasks[i].questData, "task" + i);//保存任务数据
}
}
SceneController:
IEnumerator Transition(string sceneName,TransitionDestination.DestinationTag destinationTag)//传送目标场景、目标点
{
//TODO:保存数据
SaveManager.Instance.SavePlayerData();//保存Player数据
InventoryManager.Instance.SaveData();//保存背包数据
QuestManager.Instance.SaveQuestManager();//保存任务
...
}
这样就保存我们的数据了