目录
声明
1.Action Button 快捷栏按键
2.Stats Info 显示 Player 相关信息
3.Change Animator 切换动画控制器
4.Item Tooltip 物品信息显示栏
5..Loot Items 掉落物品
声明
本教程学习均来自U3D中文课堂麦扣老师
保证我们武器栏当中有武器,运行游戏发现没有生成武器 ,可以的话尽量使用MainMenu去进入游戏去测试我们背包里的每一个功能,确保它们没问题
现在做一个方法够关闭背包和Stats,先实现这个CloseButton按钮,
这样就可以关闭背包和Stats了,那么如何再次打开呢,我们要在按键上做一个设置:
InventoryManager:
[Header("UI Panel")]
public GameObject bagPanel;
public GameObject statsPanel;
bool isOpen = false;//背包是否打开
private void Update()
{
if(Input.GetKeyDown(KeyCode.B))//按下B键显示背包
{
isOpen = !isOpen;
bagPanel.SetActive(isOpen);
statsPanel.SetActive(isOpen);
}
}
这样就实现了打开和关闭背包了
接下来我们要完成的就是Action Bar了,我们要Action Bar上面的每一个按键来添加匹配的键盘的按键,所以单独创建一个代码ActionButton,挂载到ActionButton预制体上,
ActionButton:
public class ActionButton : MonoBehaviour
{
public KeyCode actionKey;
}
设置按键1-6,
ActionButton:
public class ActionButton : MonoBehaviour
{
public KeyCode actionKey;
private SlotHolder currentSlotHolder;
private void Awake()
{
currentSlotHolder = GetComponent<SlotHolder>();
}
private void Update()
{
if(Input.GetKeyDown(actionKey) && currentSlotHolder.itemUI.GetItem())//按下匹配按键且格子里有物品
{
currentSlotHolder.UseItem();//使用物品
}
}
}
现在就可以使用快捷栏按键和双击使用物品了
但是如果双击空格子,会出现报错,解决:
SlotHolder:在使用物品方法中做一个非空的判断
public void UseItem()//使用物品
{
if (itemUI.GetItem() != null)
{
if (itemUI.GetItem().itemType == ItemType.Useable && itemUI.Bag.items[itemUI.Index].amount > 0)//是可使用物品且数量大于0 itemUI.Bag.items[itemUI.Index].ItemData.itemType
{
GameManager.Instance.playerStats.ApplyHealth(itemUI.GetItem().useableData.healthPoint);
itemUI.Bag.items[itemUI.Index].amount -= 1;//使用后数量减1
}
}
UpdateItem();//更新物品
}
这样就不会报错了
现在就实现了打开和关闭背包和人物的窗口,还有用快捷键来操作实现物品了
2.Stats Info 显示 Player 相关信息
我们来实现一下我们的信息面板,希望能显示Player的基本模型,并且可以在下面Update它的一些数据,
新建RawImage
新建一个Render Texture
现在我们要为我们的Player创建一个摄像机专门用来拍摄它,CullingMask选择Player,修改Environment和Output输出到Render Texture
现在就设置好人物的显示了,接下来要获得下面信息的更改,在InventoryManager里来获得这两个变量
InventoryManager:
using UnityEngine.UI;
[Header("Stats Text")]
public Text healthText;
public Text attackText;
private void Update()
{
UpdateStatsText(GameManager.Instance.playerStats.MaxHealth, GameManager.Instance.playerStats.attactData.minDamge,
GameManager.Instance.playerStats.attactData.maxDamge);//更新玩家属性文本
}
public void UpdateStatsText(int health,int min,int max)//更新玩家属性文本
{
healthText.text = health.ToString();
attackText.text = min + " - " + max;
}
拖拽赋值
这样就更新显示 Player 相关信息了。
3.Change Animator 切换动画控制器
接下来我们要实现的是当我们的玩家切换武器的时候它伴随的动画也可以随之切换,为了达到这个效果我们要制作几套这个动画,
新建Animator Ovrride Controller
删除Run动画里的事件
将原来的PlayerController更名为Player_Sword&Shield
那么如何切换呢?
在这里面我希望这个武器有自己的攻击信息以外,也保留配套的动画信息,这样我们就能把动画信息传到Player身上,
ItemData_SO:
[Header("Weapon")]
····
public AnimatorOverrideController weaponAnimator;
因为我们创建的是Animator把Player_Sword&Shield更改为Player_Base,在创建一个AnimatorOverrideController,命名为Player_Sword&Shield,不做任何更改
接下来去到装备武器的时候同时切换我们的动画,
CharacterStats:
private RuntimeAnimatorController baseAnimator;//基础动画控制器
private void Awake()
{
...
baseAnimator = GetComponent<Animator>().runtimeAnimatorController;
}
#region Equip Weapon
public void ChangeWeapon(ItemData_SO weapon)//切换武器
{
UnequipWeapon();//卸下武器
EquipWeapon(weapon);//装备武器
}
public void EquipWeapon(ItemData_SO weapon)//装备武器
{
if(weapon.weaponPrefab != null)
{
Instantiate(weapon.weaponPrefab, weaponSlot);
}
//TODO:更新属性
//TODO:切换动画
GetComponent<Animator>().runtimeAnimatorController = weapon.weaponAnimator;
attactData.ApplyWeaponData(weapon.weaponData);
//InventoryManager.Instance.UpdateStatsText(MaxHealth,attactData.minDamge,attactData.maxDamge);//更新玩家属性文本
}
public void UnequipWeapon()//卸下武器
{
if(weaponSlot.transform.childCount != 0)
{
for (int i = 0; i < weaponSlot.transform.childCount; i++)
{
Destroy(weaponSlot.transform.GetChild(i).gameObject);
}
}
attactData.ApplyWeaponData(baseAttackData);
//TODO:切换动画
GetComponent<Animator>().runtimeAnimatorController = baseAnimator;
}
#endregion
现在可以看到可以切换动画,但是在Stats面板看不到Player拿武器,因为人物的摄像机只渲染属于Player图层上的物品。
将武器设置为Player的Layer:
这样就能切换武器同时切换动画了。
制作物品当鼠标划过去的时候会显示详情:
新建Image:Item Tooltip
希望在调整大小的时候4个边框不会受到转变,那么就需要把它设置为Slice
因为我们背包当中的物品的描述都是不一样的,有些非常长,有些非常短,我希望Item Tooltip会根据信息的长度自动调整大小:
添加Content Size Fitter组件:
添加Text:
如果手动在这里面去调整它的大小的话,非常不利于我们后期自动去填充显示它所有文字的内容,所以排版的过程中也需要按照一定的规则,添加 Virtual Layout Group组件
下面的文字我希望它也可以根据我的字数进行拉伸,这样的话就可以保证我的Item Tooltip可以根据它的比例进行拉伸了,同样添加Content Size Fitter组件
现在就可以自动调整大小了
接下来写代码控制一下:
ItemTooltip:
using UnityEngine.UI;
public class ItemTooltip : MonoBehaviour
{
public Text itemNameText;
public Text itemInfoText;
public void SetupTooltip(ItemData_SO item)//显示物品信息
{
itemNameText.text = item.itemName;
itemInfoText.text = item.description;
}
}
Inventory Manager:控制Tooltip显示
[Header("Tooltip")]
public ItemTooltip tooltip;
什么时候把格子 Set up好呢,去到SlotHolder,每一个格子有物品信息的显示,怎么判断鼠标是否悬停在格子上呢?我们用到IPointerClickHandler系列当中的另外的一个接口:
有悬停,所以有悬停离开,我们添加这2个接口:
SlotHolder:
public class SlotHolder : MonoBehaviour,IPointerClickHandler,IPointerEnterHandler,IPointerExitHandler
{
public void OnPointerEnter(PointerEventData eventData)//鼠标悬停查看物品详情
{
if (itemUI.GetItem() != null)
{
InventoryManager.Instance.tooltip.SetupTooltip(itemUI.GetItem());//显示物品信息
InventoryManager.Instance.tooltip.gameObject.SetActive(true);
}
}
public void OnPointerExit(PointerEventData eventData)//鼠标离开取消查看物品详情
{
InventoryManager.Instance.tooltip.gameObject.SetActive(false);
}
}
现在可以显示详情了,但是Tooltip没有跟着我的鼠标来移动,所以接下来我们写移动的方法:
ItemTooltip:
RectTransform rectTransform;
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
private void Update()
{
UpdatePosition();//跟着鼠标更新显示物品详情位置
}
public void UpdatePosition()//跟着鼠标更新显示物品详情位置
{
Vector3 mousePos = Input.mousePosition;
rectTransform.position = mousePos;
}
发现Tooltip在一直闪烁,原因就是因为图片设置在鼠标一起,然后鼠标被遮挡住了,
在这个方块格子中间的那个原点,也就是锚点,也就是Pivot,为0.5,0.5,那么也就代表:左下角是0,0,中间的点是0.5,0.5,所以它是有4角的范围以及它的坐标的位置的,所以我设置的RectTransform.position就是设置中间锚点所在的位置了,我希望它在鼠标上方,应该将我的鼠标坐标加上一半的高度,同样,如果显示在鼠标的右侧,我也要加上一半的宽度,那么如何获得它的高度和宽度呢?接下来学习一个新的函数方法来获得一个物体的世界坐标的4点位置,那么我们用这2点之间的差就可以获得宽和高了,
拿到宽和高之后,我们也可以判断,我的Tooltip是否超出了屏幕,如果超出了屏幕我应该反向生成它,我们一起来完成这个代码:
ItemTooltip:
public void UpdatePosition()//跟着鼠标更新显示物品详情位置
{
Vector3 mousePos = Input.mousePosition;
Vector3[] conors = new Vector3[4];
rectTransform.GetWorldCorners(conors);//拿到4个点的数组
//拿到宽和高
float width = conors[3].x - conors[0].x;
float height = conors[1].y - conors[0].y;
}
接下来做一些判断:
我希望实时判断如果我鼠标的坐标跟屏幕之间的距离如果小于Tooltip的宽度的话那么它应该生成在左边,否则生成右边,同理鼠标距离下方之间的高度小于Tooltip的高度的话应该生成在上面,
可以Debug(mousepos.y)知道屏幕坐标是以左下角为0,0的,所以y轴我们之间可以判断当前鼠标y的坐标的高度是否小于Tooltip的高度,如果小于它应该放在上面
ItemTooltip:
private void OnEnable()
{
UpdatePosition();//跟着鼠标更新显示物品详情位置
}
private void Update()
{
UpdatePosition();//跟着鼠标更新显示物品详情位置
}
public void UpdatePosition()//跟着鼠标更新显示物品详情位置
{
Vector3 mousePos = Input.mousePosition;
Vector3[] conors = new Vector3[4];
rectTransform.GetWorldCorners(conors);//拿到4个点的数组
//拿到Tooltip的宽和高
float width = conors[3].x - conors[0].x;
float height = conors[1].y - conors[0].y;
if(mousePos.y < height)//判断鼠标高度是否小于Tooltip高度
{
rectTransform.position = mousePos + Vector3.up * height * 0.6f;//Tooltip显示在鼠标上面
}
else if(Screen.width - mousePos.x > width)//判断鼠标右侧宽度是否大于Tooltip高度
{
rectTransform.position = mousePos + Vector3.right * width * 0.6f;//Tooltip显示在鼠标右侧
}
else
{
rectTransform.position = mousePos + Vector3.left * width * 0.6f;//Tooltip显示在鼠标左侧
}
}
}
现在就能正常显示了
把背包关闭也希望物品详情也同时关闭:
SlotHolder:
private void OnDisable()//关闭背包显示,也要取消查看物品详情
{
InventoryManager.Instance.tooltip.gameObject.SetActive(false);
}
这样就做好物品信息显示栏了
5..Loot Items 掉落物品
现在来完成敌人掉落物品的方法:首先创建一个代码,挂载到所有的敌人身上,让它计算要掉落的是什么:
LootSpawner:
public class LootSpawner : MonoBehaviour
{
[System.Serializable]
public class LootItem
{
public GameObject item;
[Range(0, 1)]
public float weight;
}
public LootItem[] lootItems;
}
LootSpawner:
public class LootSpawner : MonoBehaviour
{
[System.Serializable]
public class LootItem
{
public GameObject item;
[Range(0, 1)]
public float weight;
}
public LootItem[] lootItems;
public void SawnLoot()//生成要掉落的物品
{
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;
}
}
}
}
EnemyController:
private void OnDisable()//销毁完成之后执行
{
if (!GameManager.IsInitialized) return;
GameManager.Instance.RemoveObserver(this);//让观察者移除列表
if(GetComponent<LootSpawner>() && isDead)//死亡掉落物品
{
GetComponent<LootSpawner>().SawnLoot();//生成要掉落的物品
}
}
LootSpawner:
public void SawnLoot()//生成要掉落的物品
{
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;
}
}
}
现在就实现掉落物品了。