Unity记录7.3-界面-生成子UI以关闭与拖动父UI

汇总:Unity 记录
开源地址:asd123pwj/asdGame
摘要:从本地图片生成UI;为UI生成控制组件的子UI,通过子UI拖动与关闭父UI。
从本地图片生成UI-2024/05/18
从json加载UI图片为Sprite-2024/05/15
- 和Unity记录6.2-动作-角色生成的步骤差不多,我直接copy过来,然后改了一部分
- 角色生成的时候,加载的对象是
xxx.prefab
,所用的类型是GameObject
。 - 这里用的是Object,因为svg图片是文本形式的
TextAsset
类型,而png和jpg是Texture2D
类型, - 因此在
action_resource_loaded
中,额外增加了类型判断。 - 此外,这部分只是加载UI图片,而非直接生成一个可控的UI(对于角色而言,加载的是角色图像)
- 因此,json数据的格式只保留了
name
和path
,去掉了ID
、tags
等对图像(暂时)无用的信息。
- 角色生成的时候,加载的对象是
{
"version": "0.0.1",
"UIResources":{
"Settings":{
"name": "Settings",
"path": "Assets/Resources_addressable/Graphics/UI/Settings.png"
}
}
}
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Cysharp.Threading.Tasks;
using Unity.VectorGraphics;
public struct UIResourceInfo{
public string name;
public string path;
}
public struct UIResourcesInfo{
public string version;
public Dictionary<string, UIResourceInfo> UIResources;
}
public class UIResourceList{
GameConfigs _game_configs;
public UIResourcesInfo _UIResource_info;
public Dictionary<string, Sprite> _name2UI = new();
public UIResourceList(GameConfigs game_configs){
_game_configs = game_configs;
load_UIResources();
}
public Sprite _load_UIResource(string name){
return _name2UI[name];
}
async UniTaskVoid load_UIResource(string name){
string UIResource_path = _UIResource_info.UIResources[name].path;
AsyncOperationHandle<Object> handle = Addressables.LoadAssetAsync<Object>(UIResource_path);
handle.Completed += action_resource_loaded;
await UniTask.Yield();
}
void load_UIResources(){
string jsonText = File.ReadAllText(_game_configs.__UIResourcesInfo_path);
_UIResource_info = JsonConvert.DeserializeObject<UIResourcesInfo>(jsonText);
foreach (var object_kv in _UIResource_info.UIResources){
load_UIResource(object_kv.Key).Forget();
}
}
void action_resource_loaded(AsyncOperationHandle<Object> handle){
if (handle.Status == AsyncOperationStatus.Succeeded){
if (handle.Result is Texture2D texture){
Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
_name2UI.Add(handle.Result.name, sprite);
}
else if (handle.Result is TextAsset textAsset){
// wait to add
}
}
else Debug.LogError("Failed to load prefab: " + handle.DebugName);
Addressables.Release(handle);
}
}
从png sprite生成UI-2024/05/18
- 如下,几乎是照搬GPT,待我把svg也正常加载后,再优化。
- 因为我本来是先加载svg的,但svg文件会报错不存在,转png后马上就正常,奇怪。
- 这份代码在底部中心位置生成UI。
public void _generate_UI(PointerEventData eventData){
// RectTransformUtility.ScreenPointToWorldPointInRectangle(rt, eventData.position, eventData.pressEventCamera, out Vector3 globalMousePos);
Sprite childSprite = UIConfig._UISystem._UIResourceList._load_UIResource("Settings");
GameObject childUI = new("Settings");
Image childImage = childUI.AddComponent<Image>();
childImage.sprite = childSprite;
childUI.transform.SetParent(UIConfig._self.transform, false);
RectTransform rectTransform = childImage.GetComponent<RectTransform>();
rectTransform.anchorMin = new Vector2(0.5f, 0);
rectTransform.anchorMax = new Vector2(0.5f, 0);
rectTransform.pivot = new Vector2(0.5f, 0);
rectTransform.anchoredPosition = Vector2.zero;
rectTransform.sizeDelta = new Vector2(100, 100);
}
为UI生成多个控制组件-2024/05/19-2024/05/20
为UI生成组件-2024/05/19-2024/05/20
- 和6.4类似,定义一系列组件,包括关闭、背景、调整大小等。
- 在创建UI的时候,自动生成该组件。
- 这或许可以帮助后面调整所有UI大小。
- 但相较于动作的定义,组件还需要加载图片资源,而图片资源是异步加载,所以这部分的难点在于等待资源加载完成后,再创建组件。
- 大致流程是,
- 实例化组件子类时,即为当前UI生成一个UI组件,例如关闭按钮。
- 在实例化中,先设置特有的属性,例如位置、名称、图片名,
- 随后开始加载图片,加载图片时,用
UniTask
等待图片资源加载完成。 - 如何判断图片加载完成?我有一个加载类
_UISpriteList
,其中有一个字典用于存储加载好的图片, - 那么由于这次的UI和加载类是同时初始化的,导致UI读取加载类时会报错,因此先判断加载类是否存在,
- 随后在加载类内部提供一个字典查询函数,判断待加载的图片是否存在,
- 两个存在都通过,则表明资源加载完成。
- 此外,也可以用
Task
实现等待,不过UniTask
支持xxx().Forget();
,这使得我在非异步代码中使用异步等待时,编译器不会警告。
- 下面是代码,分别是组件基类和组件子类,子类为在右上角生成一个关闭按钮,但关闭按钮暂时只有图片。
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine.EventSystems;
using Cysharp.Threading.Tasks;
public delegate void _set_UIPos();
public class UIComponentBase{
// ---------- Sub Tools ----------
public UIConfig _UIConfig;
// ---------- Unity ----------
public GameObject _self;
// ---------- Config ----------
public string _name;
// ----- Image
public string _image_name;
// ----- Position
public _set_UIPos _set_UIPos;
public Vector2 _anchorMin = new (0.5f, 0.5f);
public Vector2 _anchorMax = new (0.5f, 0.5f);
public Vector2 _pivot = new (0.5f, 0.5f);
public Vector2 _anchoredPosition = new (0, 0);
public Vector2 _sizeDelta = new (50, 50);
// ---------- Status ----------
public UIComponentBase(UIConfig UIConfig){
_UIConfig = UIConfig;
}
public void _update(){
}
public async UniTaskVoid _init(){
while (!_check_UISprite_loaded()) await UniTask.Delay(100);
_set_UIPos();
create_gameObject();
create_image();
}
bool _check_UISprite_loaded(){
if (!(_UIConfig._UISystem._UISpriteList != null)) return false;
if (!_UIConfig._UISystem._UISpriteList._check_UISprite_loaded(_image_name)) return false;
return true;
}
void create_gameObject(){
_self = new(_name);
_self.transform.SetParent(_UIConfig._self.transform, false);
}
void create_image(){
Image img = _self.AddComponent<Image>();
img.sprite = _UIConfig._UISystem._UISpriteList._get_UISprite(_image_name);
RectTransform rt = img.GetComponent<RectTransform>();
_set_UIPosition(rt);
rt.sizeDelta = _sizeDelta;
}
public void _set_UIPos_RightTop() { _set_UIPosition(new(1, 1), new(1, 1), new(1, 1), new(0, 0)); }
public void _set_UIPosition(Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot, Vector2 anchoredPosition){
_anchorMin = anchorMin; _anchorMax = anchorMax; _pivot = pivot; _anchoredPosition = anchoredPosition;
}
public void _set_UIPosition(RectTransform rt){
rt.anchorMin = _anchorMin; rt.anchorMax = _anchorMax; rt.pivot = _pivot; rt.anchoredPosition = _anchoredPosition;
}
}
public class UIClose: UIComponentBase{
public UIClose(UIConfig UIConfig): base(UIConfig){
_name = "Close";
_image_name = "Close";
_set_UIPos = _set_UIPos_RightTop;
_sizeDelta = new (50, 50);
_init().Forget();
}
}
UI置顶-2024/05/20
- 这个简单,给这个触发器
EventTriggerType.PointerDown
添加下面的执行即可 UIConfig._self.transform.SetAsLastSibling();
UI关闭-2024/05/20
- 同上,
UIConfig._self.SetActive(false);
生成控制组件-2024/05/20
- 这部分比较麻烦,又是和前面一样的等待资源加载问题,不过这里的是等待脚本加载。
- 逻辑略复杂,说起来绕口,不说了,简述也复杂,不想想
- 大概的解决方案,是把需要脚本加载的初始化,放到下一帧去,
- 父脚本A生成一个Unity对象B后,A通过委托,把自身的初始化脚本放到至B的Update中,
- 这样,在B的Update中即可实现A的初始化脚本,A的初始化脚本是初始化B的信息。
- 其中遇到了一个报错,UI组件如果带上了脚本,就会报下面的错误
ArgumentNullException: Value cannot be null. Parameter name: _unity_self
- 这个报错可以通过在游戏时增加脚本来避免
- 根据这篇讨论的说法,任意脚本都会这样:Question - ArgumentNullException: Value cannot be null. Parameter name: _unity_self
- 不过后来莫名其妙就好了,我还想试试,当我脚本都正常时,在游戏前启用脚本的报错,会不会影响脚本运行。
- 此外,我目前的UI方案是,一个空Canvas,然后生成两个控制组件,一个背景,一个关闭按钮
- 背景按左键可以拖动,按钮按左键可以关闭Canvas
- 因此其影响的对象是控制组件的父对象,但好在很好改动,只要把原本的当前对象改为父对象就好
- 随后是关键代码,
- 最终的效果,游戏开始前有一个空Canvas,带有一个UI脚本,
- 游戏开始后,这个UI脚本会生成两个控制组件,
- 产生的影响是,这个空Canvas会有背景和关闭按钮,它可以被拖动和关闭。
- 首先是两个控制组件及其基类,这里是三个文件,每个类对应一个文件
public class UIClose: UIComponentBase{
public UIClose(GameObject parent): base(parent, "Close"){
_image_name = "Close";
_set_UIPos = _set_UIPos_RightTop;
_sizeDelta = new (50, 50);
}
public override void _init_eventInteraction(){
// ----- set top
_Cfg._Event._event_PointerDown.Add(_Cfg._Interact._set_top);
// ----- close
_Cfg._Event._event_PointerDown.Add(_Cfg._Interact._close);
}
}
public class UIBackground: UIComponentBase{
public UIBackground(GameObject parent): base(parent, "Background"){
_image_name = "Default";
_set_UIPos = _set_UIPos_Full;
_sizeDelta = new (0, 0);
_isButtom = true;
}
public override void _init_eventInteraction(){
// ----- set top
_Cfg._Event._event_PointerDown.Add(_Cfg._Interact._set_top);
// ----- drag
_Cfg._Event._event_PointerDown.Add(_Cfg._Interact._update_mousePosOffset);
_Cfg._Event._event_Drag.Add(_Cfg._Interact._drag);
}
}
public delegate void _set_UIPos();
public class UIComponentBase{
// ---------- Sub Tools ----------
public UIConfig _Cfg;
// ---------- Unity ----------
public GameObject _self;
public GameObject _parent;
// ---------- Config ----------
public string _name;
// ----- Image
public string _image_name;
// ----- Position
public _set_UIPos _set_UIPos;
public bool _isButtom;
public Vector2 _anchorMin = new (0.5f, 0.5f);
public Vector2 _anchorMax = new (0.5f, 0.5f);
public Vector2 _pivot = new (0.5f, 0.5f);
public Vector2 _anchoredPosition = new (0, 0);
public Vector2 _sizeDelta = new (50, 50);
// ---------- Status ----------
public UIComponentBase(GameObject parent, string name){
_parent = parent;
_name = name;
create_gameObject();
}
void init(){
_init().Forget();
}
public async UniTaskVoid _init(){
create_subScripts();
while (!_check_UISprite_loaded()) await UniTask.Delay(100);
_set_UIPos();
create_image();
set_layer();
_init_eventInteraction();
init_eventTrigger();
}
bool _check_UISprite_loaded(){
if (_Cfg._UISys._UISpriteList == null) return false;
if (!_Cfg._UISys._UISpriteList._check_UISprite_loaded(_image_name)) return false;
return true;
}
void create_gameObject(){
_self = new(_name);
_self.transform.SetParent(_parent.transform, false);
_self.AddComponent<UIIndividual>();
_self.GetComponent<UIIndividual>()._UIInits.Add(init);
}
void create_subScripts(){
_Cfg = _self.GetComponent<UIIndividual>()._Cfg;
}
void init_eventTrigger(){
_Cfg._Trigger._init_eventTrigger();
}
public virtual void _init_eventInteraction(){ }
void set_layer() { if (_isButtom) set_bottom(); else set_top(); }
void set_bottom() {_self.transform.SetAsFirstSibling(); }
void set_top() {_self.transform.SetAsLastSibling(); }
void create_image(){
Image img = _self.AddComponent<Image>();
img.sprite = _Cfg._UISys._UISpriteList._get_UISprite(_image_name);
RectTransform rt = img.GetComponent<RectTransform>();
_set_UIPosition(rt);
rt.sizeDelta = _sizeDelta;
}
public void _set_UIPos_Full() { _set_UIPosition(new(0, 0), new(1, 1), new(0.5f, 0.5f), new(0, 0)); }
public void _set_UIPos_RightTop() { _set_UIPosition(new(1, 1), new(1, 1), new(1, 1), new(0, 0)); }
public void _set_UIPosition(Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot, Vector2 anchoredPosition){_anchorMin = anchorMin; _anchorMax = anchorMax; _pivot = pivot; _anchoredPosition = anchoredPosition; }
public void _set_UIPosition(RectTransform rt){rt.anchorMin = _anchorMin; rt.anchorMax = _anchorMax; rt.pivot = _pivot; rt.anchoredPosition = _anchoredPosition; }
}
- 然后是事件交互类,原先是手动写好,现在是循环列表中的函数
public class UIEvent{
// ---------- Sub Tools ----------
UIConfig Cfg;
// ---------- Config ----------
// ----- Event
public List<_UIInteraction> _event_PointerEnter = new();
public List<_UIInteraction> _event_PointerExit = new();
public List<_UIInteraction> _event_PointerDown = new();
public List<_UIInteraction> _event_PointerUp = new();
public List<_UIInteraction> _event_PointerClick = new();
public List<_UIInteraction> _event_Drag = new();
public UIEvent(UIConfig UIConfig){
this.Cfg = UIConfig;
this.Cfg._Event = this;
}
public void _update(){
}
public void _action_pointer_down(PointerEventData eventData){
foreach(var UIInteraction in _event_PointerDown){
UIInteraction(eventData);
}
}
public void _action_pointer_up(PointerEventData eventData){
switch (eventData.button){
case PointerEventData.InputButton.Left:
break;
case PointerEventData.InputButton.Middle:
break;
case PointerEventData.InputButton.Right:
Cfg._Interact._update_mouseUp(eventData);
break;
}
}
public void _action_drag(PointerEventData eventData){
foreach(var UIInteraction in _event_Drag){
UIInteraction(eventData);
}
}
}
- 同样的,事件触发器也不再是所有事件都触发,而是根据各事件列表是否存在交互函数,来判断是否要添加这个事件触发器。
- 虽然不知道这个触发器占用资源多不多,但是实现起来不麻烦,随手优化还是要的
public class UITrigger{
UIConfig Cfg;
EventTrigger eventTrigger;
public UITrigger(UIConfig UIConfig){
this.Cfg = UIConfig;
this.Cfg._Trigger = this;
_init_eventTrigger();
}
public void _update(){
}
public void _init_eventTrigger(){
eventTrigger = Cfg._self.GetComponent<EventTrigger>();
if (eventTrigger == null) eventTrigger = Cfg._self.AddComponent<EventTrigger>();
else eventTrigger.triggers.Clear();
init_eventTrigger_trigger();
}
void init_eventTrigger_trigger(){
EventTrigger.Entry entry;
if (Cfg._Event._event_PointerEnter.Count > 0) { entry = new() { eventID = EventTriggerType.PointerEnter }; entry.callback.AddListener((data) => Cfg._Event._action_pointer_enter((PointerEventData)data)); eventTrigger.triggers.Add(entry); }
if (Cfg._Event._event_PointerExit.Count > 0) { entry = new() { eventID = EventTriggerType.PointerExit }; entry.callback.AddListener((data) => Cfg._Event._action_pointer_exit((PointerEventData)data)); eventTrigger.triggers.Add(entry); }
if (Cfg._Event._event_PointerDown.Count > 0) { entry = new() { eventID = EventTriggerType.PointerDown }; entry.callback.AddListener((data) => Cfg._Event._action_pointer_down((PointerEventData)data)); eventTrigger.triggers.Add(entry); }
if (Cfg._Event._event_PointerUp.Count > 0) { entry = new() { eventID = EventTriggerType.PointerUp }; entry.callback.AddListener((data) => Cfg._Event._action_pointer_up((PointerEventData)data)); eventTrigger.triggers.Add(entry); }
if (Cfg._Event._event_PointerClick.Count > 0) { entry = new() { eventID = EventTriggerType.PointerClick }; entry.callback.AddListener((data) => Cfg._Event._action_pointer_click((PointerEventData)data)); eventTrigger.triggers.Add(entry); }
}
文章目录
关闭
共有 0 条评论