目录
实现点按任务显示信息
检测和更新任务进度
?接受任务时检查任务物品
?控制任务对话显示
拿到任务奖励
保存任务数据
实现点按任务显示信息
- 按照逻辑去写每一个需要的函数方法
- 设置任务按钮显示对应任务名字
- 实现点击名字按钮能显示任务详情以及任务需求
- 调整 UI 布局
添加按钮点击事件显示任务信息:
添加显示需求:
在点击事件中调用该函数:
这样即可显示:
但是任务信息和需求重叠在了一起。
在这里可以使得它强制布局:
这样显示就正常了
- SetupRewardList的 方法实现
- 创建 ShowTooltip 实现鼠标滑入显示信息
- 创建多个任务并修复重复显示任务奖励的问题
对于任务信息的显示,除了之前的那种方法:
回顾一下之前的方法:
在QuestNameButton这个预制体的脚本里面给每个任务对应的text里声明该变量:
然后在设定任务时,将任务的需求的那个text赋值给它。
然后根据按钮更新数据的时候,再从任务的SO中获取任务内容再赋值:
这样的过程比较冗余。
现在删去其中在另外一个脚本QuestNameButton中声明变量并且传值然后修改的过程,直接在QuestUI中修改:
接下来实现展现需求的代码:
在questUI中,调用之前写的slotItem的方法:
这样即可实现
注意布局
接下来实现鼠标放到上面时显示信息的功能:
脚本并非挂在那个tooltip上,否则因为初始它是不可见的所以没法调用,应该放在reward item slot上。
那么和之前一样,需要实现接口即可:
然后放到上面时就显示设置active为true然后显示信息。
然后设置setupTooltip:
但是有个问题在于:
这个东西返回的是背包里的数据:
而不是实际存在的值。
所以此处需要添加新的变量:
将原来那个函数修改成这样:
这样即可实现,但是发现会出现遮挡现象:
将其放在最下方,就不会被遮挡了:
然后还有个问题在于背包关闭再次打开时,这个还是存在:这是因为即使关闭面板栏,tooltip依然是Active状态。
这样即可解决。
接下来实现多个任务时,出现了这样的问题:
任务来回切换会导致item Slot变多:
更新时清除奖励:
?
检测和更新任务进度
- 创建函数在 敌人死亡 和 拾取物品时 更新任务进度
- 使用 Linq 语句中的 Where 找到匹配的任务需求并检查是否满足
- 修改特殊情况使用任务物品消耗后更新进度
任务进度:检查背包中的物品数量和任务所需的物品数量是否相同,
敌人死亡时进行调用
代码中如果出现两个任务,则两个任务中的都要对应减少。
书写检查任务的函数
拾取物品时也进行调用:
这样即可实现完成任务。
但是我们发现使用物品后,任务不会更新。
在此处进行修改:
?接受任务时检查任务物品
- 考虑可能存在的情况在接受任务的时候检查背包是否有任务物品
- 在 QuestData_SO 中创建 RequireTargetName 拿到需求的名字列表
- 循环列表中每一项在 Inventory 中检查是否存在并更新数据
有个问题在于如果接任务前背包就有两个蘑菇了,此时并不会任务面板并不会更新; ?
检查任务物品:
由于一个questRequire中有多个物品:
因此需要一个包含其名字的list,循环时要判断每一个是否有
创建一个包含名字的链表:
这样就可以实现接受任务时就检查是否完成。
在questGiver里获取当前任务的任务状态:
?
?控制任务对话显示
根据任务完成的状态,需要给予npc不同的对话,所以给npc创建一个脚本:
然后有QuestGiver去修改Dialogue Controller里面的数据
[RequireComponent(typeof(DialogueController))]
public class QuestGiver : MonoBehaviour
{
DialogueController controller;
QuestData_SO currentQuest;
public DialougeData_SO startDialogue;
public DialougeData_SO progressDialogue;
public DialougeData_SO completeDialogue;
public DialougeData_SO finishDialogue;
private void Awake()
{
controller = GetComponent<DialogueController>();
}
}
对于发布任务的人,我们需要让它拿到任务的完成状态,才能根据不同状态执行不同对话:
然后赋值给当前任务:
在questGiver的update函数中则根据不同的状态进行切换。
然后自行设定四个对话的内容并传入:
有个bug会产生,问题在于不要出现这种情况
这样就实现了任务不同状态时的不同对话。
添加一个功能:
当玩家远离时则自动关闭对话:
、
拿到任务奖励
拿到任务奖励需扣除报酬,那么可以这样,将奖励设置为-2:
接受完任务后,背包和栏里面的物品会更新,因此需要写一个根据任务里的物品判断背包是否有该物品的函数?
在任务数据里书写给予奖励的函数:
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;//背包里的商品扣除为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();
InventoryManager.Instance.actionUI.RefreshUI();
}
}
在OptionUI中执行给与奖励:
这里做的时候出现了一个bug,问题在于之前的对话中没添加quest
:
这样即可实现交付任务和扣除东西。
但是有个问题在于:
蘑菇会显示负数。
改动如下:
接下来还有一个小小问题,在于任务完成了,继续拾取蘑菇,任务进度还是会更新,解决方法:
然后在执行更新需求的代码里:
如果完成了则执行完成的设定?
在对话时,我们希望保持鼠标是固定的样式:
为了避免已完成的任务再次受到数据更新的影响:?
保存任务数据
QuestManager里面保存的数据以List类型保存,而里面的数据不是SO类型的,所以不能用之前那样的方法保存?
以前的保存都是通过Object来保存的:
因此此处实现一个非SO类型的保存方法:
虽然也可以通过将其改成SO的方式来实现,但此处换种方式:
注意到tasks虽然不是SO,但是task里面的QuestData是SO类型的,我们可以保存它
书写保存和Load的方法:
public void SaveQuestManager()
{
PlayerPrefs.SetInt("QuestCount", tasks.Count);
for(int i = 0; i < tasks.Count; i++)
{
SaveManager.Instance.Save(tasks[i].questData, "task" + i);
}
}
//加载数据的方式是通过重新新创建一个SO,然后让SO读取数据,然后再加入到tasks链表当中
public void LoadQuestManager()
{
var quesCount = PlayerPrefs.GetInt("QuestCount");
for(int i = 0; i < quesCount; i++)
{
var newQuest = ScriptableObject.CreateInstance<QuestData_SO>();//
SaveManager.Instance.Load(newQuest, "task" + i);
tasks.Add(new QuestTask { questData = newQuest });
}
}
读取数据的方法可以放在初始时:
在QuestManager中添加
?这样即可实现切换场景保存任务:
代码汇总
背包部分
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum ItemType { Useable,Weapon,Armor}
[CreateAssetMenu(fileName ="New Item",menuName ="Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
public ItemType itemType;
public string itemName;
public Sprite itemIcon;
public int itemAmount;//这个物品有多少数量
[TextArea]
public string description = "";
public bool stackable;//是否可堆叠
[Header("Weapon")]
public GameObject WeaponPrefab;
public AttackData_SO weaponData;
public AnimatorOverrideController weaponAnimator;
[Header("Useable Item")]
public UseableItemData_SO useableData;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemPickUp : MonoBehaviour
{
public ItemData_SO itemData;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
Debug.Log("OnTrigger");
//GameManager.Instance.playerStats.EquipWeapon(itemData);
InventoryManager.Instance.inventoryData.AddItem(itemData, itemData.itemAmount);
InventoryManager.Instance.inventoryUI.RefreshUI();
QuestManager.Instance.UpdateQuestProgress(itemData.itemName, itemData.itemAmount);
//装备武器
Destroy(gameObject);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class InventoryManager : Singleton<InventoryManager>
{
public class DragData
{
public SlotHolder orginalHolder;
public RectTransform originalParent;
}
[Header("Inventory Data")]
//使用类似之前那样的模板,游戏新开始时复制一份的操作
//TODO:最后添加模板用于保存数据
public InventoryData_SO inventoryTemplate;
public InventoryData_SO inventoryData;
public InventoryData_SO actionTemplate;
public InventoryData_SO actionData;
public InventoryData_SO equipmentTemplate;
public InventoryData_SO equipmentData;
[Header("Containers")]
public ContainerUI inventoryUI;
public ContainerUI actionUI;
public ContainerUI equipmentUI;
[Header("Drag Canvas")]
public Canvas dragCanvas;
public DragData currentDrag;
protected override void Awake()
{
base.Awake();
if (inventoryTemplate != null)
inventoryData = Instantiate(inventoryTemplate);
if (actionTemplate != null)
actionData = Instantiate(actionTemplate);
if (equipmentTemplate != null)
equipmentData = Instantiate(equipmentTemplate);
}
private void Start()
{
LoadData();
inventoryUI.RefreshUI();
actionUI.RefreshUI();
equipmentUI.RefreshUI();
}
[Header("UI Panel")]
public GameObject bagPanel;
public GameObject statsPanel;
bool isOpen;
[Header("Stats Text")]
public Text healthText;
public Text attackText;
[Header("Tooltip")]
public ItemToolTip tooltip;
void Update()
{
if (Input.GetKeyDown(KeyCode.B))
{
isOpen = !isOpen;
bagPanel.SetActive(isOpen);
statsPanel.SetActive(isOpen);
}
UpdateStatsText(GameManager.Instance.playerStats.MaxHealth, GameManager.Instance.playerStats.attackData.minDamage,
GameManager.Instance.playerStats.attackData.maxDamage);
}
public void UpdateStatsText(int health,int min,int max)
{
healthText.text = health.ToString();
attackText.text = min + " - " + max;
}
#region 检查拖拽物品是否在每一个slot范围内
public bool CheckInInventoryUI(Vector3 position)//此处这个位置是要传输进来的位置
{
for(int i = 0; i < inventoryUI.slotHolders.Length; i++)
{
RectTransform t = inventoryUI.slotHolders[i].transform as RectTransform;
if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
{
return true;
}
}
return false;
}
public bool CheckInActionUI(Vector3 position)//此处这个位置是要传输进来的位置
{
for (int i = 0; i < actionUI.slotHolders.Length; i++)
{
RectTransform t = actionUI.slotHolders[i].transform as RectTransform;
if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
{
return true;
}
}
return false;
}
public bool CheckInEquipmentUI(Vector3 position)//此处这个位置是要传输进来的位置
{
for (int i = 0; i < equipmentUI.slotHolders.Length; i++)
{
RectTransform t = equipmentUI.slotHolders[i].transform as RectTransform;
if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
{
return true;
}
}
return false;
}
#endregion
public void SaveData()
{
SaveManager.Instance.Save(inventoryData, inventoryData.name);
SaveManager.Instance.Save(actionData, actionData.name);
SaveManager.Instance.Save(equipmentData, equipmentData.name);
}
public void LoadData()
{
SaveManager.Instance.Load(inventoryData, inventoryData.name);
SaveManager.Instance.Load(actionData, actionData.name);
SaveManager.Instance.Load(equipmentData, equipmentData.name);
}
#region 检测任务物品
public void CheckQuestItemInBag(string questItemName)
{
foreach(var item in inventoryData.items)
{
if (item.itemData != null)
{
if (item.itemData.itemName == questItemName)
QuestManager.Instance.UpdateQuestProgress(item.itemData.itemName, item.amount);
}
}
foreach (var item in actionData .items)
{
if (item.itemData != null)
{
if (item.itemData.itemName == questItemName)
QuestManager.Instance.UpdateQuestProgress(item.itemData.itemName, item.amount);
}
}
}
#endregion
//检测背包和快捷栏里的物体是否有和任务相同的
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);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public enum SlotType { BAG,WEAPON,ARMOR,ACTION}
public class SlotHolder : MonoBehaviour,IPointerClickHandler,IPointerEnterHandler,IPointerExitHandler
{
// Start is called before the first frame update
public SlotType slotType;//这个需要用来告诉属于哪一个UI面板,因为后面会有背包、行动
public ItemUI itemUI; //这是SlotHolder的子物体,Image和Text的父物体。
//SlotHolder可以理解为格子里的物品的UI信息。
//获取是为了给子物体中的image和text进行修改
public void UpdateItem()
{
switch (slotType)
{
case SlotType.BAG:
itemUI.Bag = InventoryManager.Instance.inventoryData;
break;
case SlotType.WEAPON:
itemUI.Bag = InventoryManager.Instance.equipmentData;
//装备武器 切换武器
if (itemUI.Bag.items[itemUI.Index].itemData != null)
{
GameManager.Instance.playerStats.ChangeWeapon(itemUI.Bag.items[itemUI.Index].itemData);
}
else
{
GameManager.Instance.playerStats.UnEquipWeapon();
}
break;
case SlotType.ARMOR:
itemUI.Bag = InventoryManager.Instance.equipmentData;
break;
case SlotType.ACTION:
itemUI.Bag = InventoryManager.Instance.actionData;
break;
}
var item = itemUI.Bag.items[itemUI.Index];//找到数据库中对应序号的对应物品
itemUI.SetupItemUI(item.itemData, item.amount);
}
public void OnPointerClick(PointerEventData eventData)
{
if (eventData.clickCount % 2 == 0)//代表是双击的话
{
UseItem();
}
}
public void UseItem()
{
if (itemUI.GetItem().itemType == ItemType.Useable&&itemUI.Bag.items[itemUI.Index].amount>0)
{
GameManager.Instance.playerStats.ApplyHealth(itemUI.GetItem().useableData.healthPoint);
itemUI.Bag.items[itemUI.Index].amount -= 1;
QuestManager.Instance.UpdateQuestProgress(itemUI.GetItem().itemName, -1);
}
UpdateItem();
}
public void OnPointerEnter(PointerEventData eventData)
{
if (itemUI.GetItem())
{
InventoryManager.Instance.tooltip.SetupTooltip(itemUI.GetItem());
InventoryManager.Instance.tooltip.gameObject.SetActive(true);
}
}
public void OnPointerExit(PointerEventData eventData)
{
InventoryManager.Instance.tooltip.gameObject.SetActive(false);
}
void OnDisable()
{
InventoryManager.Instance.tooltip.gameObject.SetActive(false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
[RequireComponent(typeof(ItemUI))]
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
ItemUI currentItemUI;
//
SlotHolder currentHolder;
SlotHolder targetHolder;
void Awake()
{
currentItemUI = GetComponent<ItemUI>();
currentHolder = GetComponentInParent<SlotHolder>();//原先的格子的信息
}
public void OnBeginDrag(PointerEventData eventData)
{
InventoryManager.Instance.currentDrag = new InventoryManager.DragData();
InventoryManager.Instance.currentDrag.orginalHolder = GetComponentInParent<SlotHolder>();
InventoryManager.Instance.currentDrag.originalParent = (RectTransform)transform.parent;
//记录原始数据
transform.SetParent(InventoryManager.Instance.dragCanvas.transform,true);
}
public void OnDrag(PointerEventData eventData)
{
//跟随鼠标位置移动
transform.position = eventData.position;
}
public void OnEndDrag(PointerEventData eventData)
{
//放下物品 交换数据
if (EventSystem.current.IsPointerOverGameObject())//是否指向UI组件
{
if(InventoryManager.Instance.CheckInActionUI(eventData.position)
|| InventoryManager.Instance.CheckInInventoryUI(eventData.position)
|| InventoryManager.Instance.CheckInEquipmentUI(eventData.position))
//判断是否在三个栏里面的格子里
{
if (eventData.pointerEnter.gameObject.GetComponent<SlotHolder>())
targetHolder = eventData.pointerEnter.gameObject.GetComponent<SlotHolder>();
else
targetHolder = eventData.pointerEnter.gameObject.GetComponentInParent<SlotHolder>();
//如果没找到,此时是因为被图片所挡住了,那么就获取其父类的component
Debug.Log(eventData.pointerEnter.gameObject);
//判断鼠标选中的物体是否有slot holder,
//判断是否目标holder是我的原holder
if(targetHolder!=InventoryManager.Instance.currentDrag.orginalHolder)
//由于例如食物不能放在武器栏里,所以需要对其做区分
switch (targetHolder.slotType)
{
case SlotType.BAG:
SwapItem();
break;
//下面这些if的判断就确保了只有相同的物品才能实现交换
case SlotType.WEAPON:
if (currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Weapon)
SwapItem();
break;
case SlotType.ARMOR:
if (currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Armor)
SwapItem();
break;
case SlotType.ACTION:
if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType==ItemType.Useable)
SwapItem();
break;
}
currentHolder.UpdateItem();//交换完毕后需要更新数据
targetHolder.UpdateItem();
}
}
transform.SetParent(InventoryManager.Instance.currentDrag.originalParent);
RectTransform t = transform as RectTransform;
t.offsetMax = -Vector3.one * 5;
t.offsetMin = Vector2.one * 5;
}
public void SwapItem()
{
//targetHolder是鼠标指向的位置的格子。
//获取目标格子上面显示的UI,UI图片对应它身上属于哪个背包的哪一个序号的物品
var targetItem = targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index];//实际上就是获取鼠标指向的物品item
var tempItem = currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index];//点击前的鼠标的格子的信息
//如果是相同的物品就进行合并
bool isSameItem = tempItem.itemData == targetItem.itemData;
if (isSameItem && targetItem.itemData.stackable)//并且还要可堆叠才行
{
targetItem.amount += tempItem.amount;
tempItem.itemData = null;
tempItem.amount = 0;
}
else
{
currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index] = targetItem;
//targetItem=tempItem;这样的写法不行因为targetItem只是我们获取的一个变量,应该直接用其本身去更换,即下一行的写法
targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index] = tempItem;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class DragPanel : MonoBehaviour,IDragHandler,IPointerDownHandler
{
RectTransform rectTransform;
Canvas canvas;
void Awake()
{
rectTransform = GetComponent<RectTransform>();
canvas = InventoryManager.Instance.GetComponent<Canvas>();
}
public void OnDrag(PointerEventData eventData)
{
rectTransform.anchoredPosition += eventData.delta/canvas.scaleFactor;//让其锚点的位置的改变量和鼠标的改变量相同即可
}
public void OnPointerDown(PointerEventData eventData)
{
rectTransform.SetSiblingIndex(2);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ContainerUI : MonoBehaviour
{
public SlotHolder[] slotHolders;
public void RefreshUI()
{
for(int i = 0; i < slotHolders.Length; i++)
{
slotHolders[i].itemUI.Index = i;
slotHolders[i].UpdateItem();
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ItemToolTip : MonoBehaviour
{
public Text itemNameText;
public Text itemInfoText;
RectTransform rectTransform;
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
public void SetupTooltip(ItemData_SO item)
{
itemNameText.text = item.itemName;
itemInfoText.text = item.description;
}
void OnEnable()
{
//开始时会闪烁是因为没有定位好坐标,开始时先更新一下即可避免这种效果
UpdatePosition();
}
private void Update()
{
UpdatePosition();
}
public void UpdatePosition()
{
Vector3 mousePos = Input.mousePosition;
rectTransform.position = mousePos;
Vector3[] corners = new Vector3[4];
rectTransform.GetWorldCorners(corners);
float width = corners[3].x - corners[0].x;
float height = corners[1].y - corners[0].y;
if (mousePos.y < height)
rectTransform.position = mousePos + Vector3.up * height * 0.6f;
else if (Screen.width - mousePos.x > width)
rectTransform.position = mousePos + Vector3.right * width * 0.6f;
else
rectTransform.position = mousePos + Vector3.left * width * 0.6f;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ItemUI : MonoBehaviour
{
public Image icon = null;
public Text amount = null;
public InventoryData_SO Bag { get; set; }
public InventoryData_SO Action { get; set; }
public InventoryData_SO Equipment { get; set; }
public ItemData_SO currentItemData;
public int Index { get; set; } = -1;//初始值设为-1是因为一开始序号是从0开始的,避免一开始去setup每个格子的时候出现数据的错位排序
public void SetupItemUI(ItemData_SO item,int itemAmount)
{
if (itemAmount == 0)
{
Bag.items[Index].itemData = null;
icon.gameObject.SetActive(false);
return;
}
//想要实现如果数量小于0则不显示,不能在上面的if条件改成<=0,因为此时并没有实际的背包
if (itemAmount < 0) item = null;//只需要跳过下面的null部分就行了,并且需要设置active为false
if (item != null)
{
icon.sprite = item.itemIcon;
amount.text = itemAmount.ToString();
icon.gameObject.SetActive(true);//默认是可见的,此处设为不可见
currentItemData = item;
}
else
icon.gameObject.SetActive(false);
}
public ItemData_SO GetItem()
{
return Bag.items[Index].itemData;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LootSpawner : MonoBehaviour
{
[System.Serializable]
public class LootItem
{
public GameObject item;
[Range(0, 1)]
public float weight;
}
public LootItem[] lootItems;
public void Spawnloot()
{
float currentValue = Random.value;
for(int i = 0; i < lootItems.Length; i++)
{
if (currentValue <= lootItems[i].weight)
{
GameObject obj = Instantiate(lootItems[i].item);
obj.transform.position = transform.position + Vector3.up * 2;
break;//确保一次只掉落一个物品
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ActionButton : MonoBehaviour
{
public KeyCode actionKey;
private SlotHolder currentSlotHolder;
void Awake()
{
currentSlotHolder = GetComponent<SlotHolder>();
}
private void Update()
{
if (Input.GetKeyDown(actionKey) && currentSlotHolder.itemUI.GetItem())
currentSlotHolder.UseItem();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName ="Useable Item",menuName ="Inventory/Useable Item Data")]
public class UseableItemData_SO : ScriptableObject
{
//所有你想改变的数据
public int healthPoint;
}
Dialogue部分
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DialogueController : MonoBehaviour
{
public DialougeData_SO currentData;
bool canTalk = false;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player") && currentData != null)
{
canTalk = true;
Debug.Log("can talk is true");
}
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player"))
{
DialogueUI.Instance.dialoguePanel.SetActive(false);
}
}
private void Update()
{
if (canTalk && Input.GetKeyDown(KeyCode.F))//TODO:此处是否可以改编成使用事件的形式?
{
OpenDialogue();
}
}
void OpenDialogue()
{
//打开UI面板
//传输对话内容信息
DialogueUI.Instance.UpdateDialogueData(currentData);
DialogueUI.Instance.UpdateMainDialogue(currentData.dialoguePieces[0]);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class DialogueOption
{
public string text;
public string targetID;
public bool takeQuest;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class DialoguePiece {
public string ID;
public Sprite image;
[TextArea]
public string text;
public QuestData_SO quest;
public List<DialogueOption> options = new List<DialogueOption>();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName ="New Dialogue",menuName ="Dialogue/Dialogue Data")]
public class DialougeData_SO : ScriptableObject
{
public List<DialoguePiece> dialoguePieces = new List<DialoguePiece>();
public Dictionary<string, DialoguePiece> dialogueIndex = new Dictionary<string, DialoguePiece>();
public QuestData_SO GetQuest()
{
QuestData_SO currentQuest = null;
//循环对话中的任务,找到该任务并返回
foreach (var piece in dialoguePieces)
{
if (piece.quest != null)
currentQuest = piece.quest;
}
return currentQuest;
}
//如果是在Unity编辑器中,则字典随时改变时则进行修改,如果是打包则字典信息不会更改
#if UNITY_EDITOR
void OnValidate()//一旦这个脚本中的数据被更改时会自动调用
{
dialogueIndex.Clear();
//一旦信息有所更新,就会将信息存储在字典中
foreach(var piece in dialoguePieces)
{
if (!dialogueIndex.ContainsKey(piece.ID))
dialogueIndex.Add(piece.ID, piece);
}
}
#else
void Awake()//保证在打包执行的游戏里第一时间获得对话的所有字典匹配
{
dialogueIndex.Clear();
foreach (var piece in dialoguePieces)
{
if (!dialogueIndex.ContainsKey(piece.ID))
dialogueIndex.Add(piece.ID, piece);
}
}
#endif
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
public class DialogueUI : Singleton<DialogueUI>
{
[Header("Basic Elements")]
public Image icon;
public Text mainText;
public Button nextButton;
public GameObject dialoguePanel;
[Header("Data")]
public DialougeData_SO currentData;
int currentIndex = 0;
[Header("Options")]
public RectTransform optionPanel;
public OptionUI optionPrefab;
protected override void Awake()
{
base.Awake();
nextButton.onClick.AddListener(ContinueDialogue);//点击时如果后续还有对话则继续进行
}
void ContinueDialogue()
{
if (currentIndex < currentData.dialoguePieces.Count)
{
UpdateMainDialogue(currentData.dialoguePieces[currentIndex]);
}
else dialoguePanel.SetActive(false);
}
public void UpdateDialogueData(DialougeData_SO data)
{
currentData = data;
currentIndex = 0;//保证每次都是从头开始对话
}
public void UpdateMainDialogue(DialoguePiece piece)
{
dialoguePanel.SetActive(true);
currentIndex++;
if (piece.image != null)
{
icon.enabled = true;
icon.sprite = piece.image;
}
else icon.enabled = false;
mainText.text = "";
//mainText.text = piece.text;
mainText.DOText(piece.text, 1f);
//如果后续还有对话就按next,没有就不按next
if (piece.options.Count == 0 && currentData.dialoguePieces.Count > 0)
{
nextButton.interactable = true;
nextButton.gameObject.SetActive(true);
//currentIndex++;//不应该放在这里++,应该每运行一次都让index++,因此应该放在上面
}
else
{
//nextButton.gameObject.SetActive(false);
nextButton.transform.GetChild(0).gameObject.SetActive(false);//让字看不见
nextButton.interactable = false;//并且删除交互功能
}
CreateOptions(piece);//根据对话来创建选项
}
void CreateOptions(DialoguePiece piece)
{
if (optionPanel.childCount > 0)//销毁旧的选项
{
for(int i = 0; i < optionPanel.childCount; i++)
{
Destroy(optionPanel.GetChild(i).gameObject);
}
}
//生成新的选项,并且调用选项,传入对话的选项信息,来更新option所显示的信息
for (int i = 0; i < piece.options.Count; i++)
{
var option = Instantiate(optionPrefab, optionPanel);
option.UpdateOption(piece,piece.options[i]);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class OptionUI : MonoBehaviour
{
public Text optionText;
private Button thisButton;
private DialoguePiece currentPiece;
private bool takeQuest;
private string nextPieceID;
void Awake()
{
thisButton = GetComponent<Button>();
thisButton.onClick.AddListener(OnOptionClicked);
}
public void UpdateOption(DialoguePiece piece,DialogueOption option)
{
currentPiece = piece;
optionText.text = option.text;
nextPieceID = option.targetID;
takeQuest = option.takeQuest;
}
public void OnOptionClicked()
{
if (currentPiece.quest != null)//如果当前选项含有任务
{
//则获取该任务
var newTask = new QuestManager.QuestTask
{
questData = Instantiate(currentPiece.quest)//将quest信息实例化然后赋值给QuestTask类里面的questData
};
if (takeQuest)
{
Debug.Log("takeQuest is true");
//判断是否在列表中
if (QuestManager.Instance.HaveQuest(newTask.questData))
{
Debug.Log("Quest in list");
//判断是否完成,若完成则给予奖励
if (QuestManager.Instance.GetTask(newTask.questData).IsComplete)
{
Debug.Log("任务完成,给予奖励");
newTask.questData.GiveRewards();
QuestManager.Instance.GetTask(newTask.questData).IsFinished = true;
}
}
else
{
QuestManager.Instance.tasks.Add(newTask);
//newTask.IsStarted = true;这样的做法并没有用,这样只是修改的临时变量
QuestManager.Instance.GetTask(newTask.questData).IsStarted = true;
//添加到任务列表
foreach (var requireItem in newTask.questData.RequireTargetName())
{
InventoryManager.Instance.CheckQuestItemInBag(requireItem);
}
}
}
}
if (nextPieceID == "")
{
DialogueUI.Instance.dialoguePanel.SetActive(false);
return;
}
else
{
//用ID去获取下一个对话
DialogueUI.Instance.UpdateMainDialogue(DialogueUI.Instance.currentData.dialogueIndex[nextPieceID]);
}
}
}
任务部分
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;
[CreateAssetMenu(fileName ="New Quest",menuName ="Quest/Quest Data")]
public class QuestData_SO : ScriptableObject
{
[System.Serializable]
public class QuestRequire
{
public string name;
public int requireAmount;
public int currentAmount;
}
public string questName;
[TextArea]
public string description;
//需要三种任务完成的状态,npc才会有不同的反应
public bool isStarted;
public bool isComplete;
public bool isFinished;
public List<QuestRequire> questRequires = new List<QuestRequire>();
public List<InventoryItem> rewards = new List<InventoryItem>();
public void CheckQuestProgress()
{
var finishRequires = questRequires.Where(r => r.requireAmount <= r.currentAmount);
isComplete = finishRequires.Count() == questRequires.Count;
if (isComplete)
{
Debug.Log("任务完成");
}
}
//当前任务需要收集/消灭的目标名字列表
public List<string> RequireTargetName()
{
List<string> targetNameList = new List<string>();
foreach(var require in questRequires)
{
targetNameList.Add(require.name);
}
return targetNameList;
}
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;//背包里的商品扣除为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();
InventoryManager.Instance.actionUI.RefreshUI();
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(DialogueController))]
public class QuestGiver : MonoBehaviour
{
DialogueController controller;
QuestData_SO currentQuest;
public DialougeData_SO startDialogue;
public DialougeData_SO progressDialogue;
public DialougeData_SO completeDialogue;
public DialougeData_SO finishDialogue;
private void Awake()
{
controller = GetComponent<DialogueController>();
}
#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 Start()
{
controller.currentData = startDialogue;
currentQuest = controller.currentData.GetQuest();
}
//根据状态切换对话
void Update()
{
if (IsStarted)
{
if (IsComplete)
{
controller.currentData = completeDialogue;
}
else
{
controller.currentData = progressDialogue;
}
}
if (IsFinished)
{
controller.currentData = finishDialogue;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class QuestManager : Singleton<QuestManager>
{
[System.Serializable]
public class QuestTask
{
public QuestData_SO questData;
public bool IsStarted {
get { return questData.isStarted; }
set { questData.isStarted = value; }
}
public bool IsComplete
{
get { return questData.isComplete; }
set { questData.isComplete = value; }
}
public bool IsFinished
{
get { return questData.isFinished; }
set { questData.isFinished = value; }
}
}
public List<QuestTask> tasks = new List<QuestTask>();
//敌人死亡,拾取物品时调用
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();
}
}
public bool HaveQuest(QuestData_SO data)//判断是否有这个任务
{
//在头文件中引入Ling,可以用于查找链表中的内容
if (data != null)
return tasks.Any(q => q.questData.questName == data.questName);
else return false;
}
//根据任务数据的名字查找链表中的某一个任务
public QuestTask GetTask(QuestData_SO data)
{
return tasks.Find(q => q.questData.questName == data.questName);
}
private void Start()
{
LoadQuestManager();
}
public void SaveQuestManager()
{
PlayerPrefs.SetInt("QuestCount", tasks.Count);
for(int i = 0; i < tasks.Count; i++)
{
SaveManager.Instance.Save(tasks[i].questData, "task" + i);
}
}
//加载数据的方式是通过重新新创建一个SO,然后让SO读取数据,然后再加入到tasks链表当中
public void LoadQuestManager()
{
var quesCount = PlayerPrefs.GetInt("QuestCount");
for(int i = 0; i < quesCount; i++)
{
var newQuest = ScriptableObject.CreateInstance<QuestData_SO>();//
SaveManager.Instance.Load(newQuest, "task" + i);
tasks.Add(new QuestTask { questData = newQuest });
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class QuestNameButton : MonoBehaviour
{
public Text questNameText;
public QuestData_SO currentData;
//public Text questContentText;
public void SetupNameButton(QuestData_SO quesData)
{
currentData = quesData;
if (quesData.isComplete)
questNameText.text = quesData.questName + "(完成)";
else
questNameText.text = quesData.questName;
}
private void Awake()
{
GetComponent<Button>().onClick.AddListener(UpdateQuestContent);
}
void UpdateQuestContent()
{
//questContentText.text = currentData.description;
QuestUI.Instance.SetupRequireList(currentData);
foreach(Transform item in QuestUI.Instance.rewardTransform)
{
Destroy(item.gameObject);
}
foreach(var item in currentData.rewards)//奖励可能不止一个所以需要循环列表
{
QuestUI.Instance.SetupRewardItem(item.itemData, item.amount);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class QuestRequirement : MonoBehaviour
{
private Text requireName;
private Text progressNumber;
private void Awake()
{
requireName = GetComponent<Text>();
progressNumber = transform.GetChild(0).GetComponent<Text>();
}
public void SetupRequirement(string name,int amount,int currentAmount)
{
requireName.text = name;
progressNumber.text = currentAmount.ToString() + "/" + amount.ToString();
}
public void SetupRequirement(string name, bool isFinished)
{
if (isFinished)
{
requireName.text = name;
progressNumber.text = "完成";
requireName.color = Color.gray;
progressNumber.color = Color.gray;
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class QuestUI : Singleton<QuestUI>
{
[Header("Elements")]
public GameObject quesPanel;
public ItemToolTip tooltip;
bool isOpen;
[Header("Quest Name")]
public RectTransform questListTransform;
public QuestNameButton questNameButton;
[Header("Text Content")]
public Text quesContentText;
[Header("Requirement")]
public RectTransform requireTransform;
public QuestRequirement requirement;
[Header("Reward Panel")]
public RectTransform rewardTransform;
public ItemUI rewardUI;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
isOpen = !isOpen;
quesPanel.SetActive(isOpen);
quesContentText.text = string.Empty;
SetupQuestList();
}
if (!isOpen)
tooltip.gameObject.SetActive(false);
}
public void SetupQuestList()
{
//清除原来已有的任务
foreach(Transform item in questListTransform)
{
Destroy(item.gameObject);
}
foreach(Transform item in rewardTransform)
{
Destroy(item.gameObject);
}
foreach (Transform item in requireTransform)
{
Destroy(item.gameObject);
}
//遍历列表中的list,接取任务
foreach(var task in QuestManager.Instance.tasks)
{
var newTask = Instantiate(questNameButton, questListTransform);
newTask.SetupNameButton(task.questData);
//newTask.questContentText = quesContentText;
}
}
public void SetupRequireList(QuestData_SO questData)
{
quesContentText.text = questData.description;
//将涉及到QuestNameButton中的三处questContentText关闭,不使用在里面传东西然后赋值的形式了,改为在此处直接修改
foreach (Transform item in requireTransform)
{
Destroy(item.gameObject);
}
foreach(var require in questData.questRequires)
{
var q = Instantiate(requirement, requireTransform);
if (questData.isFinished)
q.SetupRequirement(require.name, true);
else
q.SetupRequirement(require.name, require.requireAmount, require.currentAmount);
}
}
public void SetupRewardItem(ItemData_SO itemData,int amount)
{
var item = Instantiate(rewardUI, rewardTransform);
item.SetupItemUI(itemData, amount);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class ShowTooltip : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
private ItemUI currentItemUI;
void Awake()
{
currentItemUI = GetComponent<ItemUI>();
}
public void OnPointerEnter(PointerEventData eventData)
{
Debug.Log("mouse in slot");
QuestUI.Instance.tooltip.gameObject.SetActive(true);
QuestUI.Instance.tooltip.SetupTooltip(currentItemUI.currentItemData);
}
public void OnPointerExit(PointerEventData eventData)
{
QuestUI.Instance.tooltip.gameObject.SetActive(false);
}
}
|