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数据的格式只保留了namepath,去掉了IDtags等对图像(暂时)无用的信息。
{
    "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); }

}

版权声明:
作者:MWHLS
链接:https://mwhls.top/5018.html
来源:无镣之涯
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
< <上一篇
下一篇>>
文章目录
关闭
目 录