Unity CircleLayoutGroup 如何实现一个圆形自动布局组件

news2024/11/18 9:44:42

文章目录

  • 简介
  • 实现原理
  • Editor 编辑器


简介

Unity中提供了三种类型的自动布局组件,分别是Grid Layou GroupHorizontal Layout GroupVertical Layout Group,本文自定义了一个圆形的自动布局组件Circle Layout Group,如图所示:

Circle Layout Group

  • Radius:Circle圆的半径
  • Angle Delta:两个元素之间的角度差
  • Start Direction:开始布局的方向(上、下、左、右)
  • Auto Refresh:是否自动刷新,开启后当子物体数量发生变化时自动刷新布局
  • Control Child Size:是否控制元素的大小
  • Child Size:控制元素大小

StartDirection:Right

StartDirection:Up

StartDirection:Left

StartDirection:Down

Auto Refresh

实现原理

已知圆的中心点(x0, y0),半径radius ,通过以下公式求得角度a的圆上的点坐标位置(x,y):

float x = x0 + radius * Mathf.Cos(angle * Mathf.PI / 180f);
float y = y0 + radius * Mathf.Sin(angle * Mathf.PI / 180f);

在这里我们的子物体元素以其父级为圆心,所以不需要考虑(x0,y0):

float x = radius * Mathf.Cos(angle * Mathf.PI / 180f);
float y = radius * Mathf.Sin(angle * Mathf.PI / 180f);

三角函数原理:
Sin正弦:y(对边) / radius(斜边)
Cos余弦:x(邻边)/ radius(斜边)

以右侧为0度起点,当方向为上方时加90度,当方向为左侧时加180度,当方向为下方时加270度,并根据角度差和元素的层级顺序计算其角度。

代码实现如下:

using UnityEngine;
using System.Collections.Generic;

namespace SK.Framework.UI
{
    /// <summary>
    /// 圆形自动布局组件
    /// </summary>
    public class CircleLayoutGroup : MonoBehaviour
    {
        //半径
        [SerializeField] private float radius = 100f;
        //角度差
        [SerializeField] private float angleDelta = 30f;
        //开始的方向 0-Right 1-Up 2-Left 3-Down
        [SerializeField] private int startDirection = 0;
        //是否自动刷新布局
        [SerializeField] private bool autoRefresh = true;
        //是否控制子物体的大小
        [SerializeField] private bool controlChildSize = true;
        //子物体大小
        [SerializeField] private Vector2 childSize = Vector2.one * 100f;

        //缓存子物体数量
        private int cacheChildCount;

        private void Start()
        {
            cacheChildCount = transform.childCount;
            RefreshLayout();
        }

        private void Update()
        {
            //开启自动刷新
            if (autoRefresh)
            {
                //检测到子物体数量变动
                if (cacheChildCount != transform.childCount)
                {
                    //刷新布局
                    RefreshLayout();
                    //再次缓存子物体数量
                    cacheChildCount = transform.childCount;
                }
            }    
        }

        /// <summary>
        /// 刷新布局
        /// </summary>
        public void RefreshLayout()
        {
            //获取所有非隐藏状态的子物体
            List<RectTransform> children = new List<RectTransform>();
            for (int i = 0; i < transform.childCount; i++)
            {
                Transform child = transform.GetChild(i);
                if (child.gameObject.activeSelf)
                {
                    children.Add(child as RectTransform);
                }
            }
            //形成的扇形的角度 = 子物体间隙数量 * 角度差
            float totalAngle = (children.Count - 1) * angleDelta;
            //总角度的一半
            float halfAngle = totalAngle * 0.5f;
            //遍历这些子物体
            for (int i = 0; i < children.Count; i++)
            {
                RectTransform child = children[i];
                /* 以右侧为0度起点 
                 * 方向为Up时角度+90 Left+180 Down+270
                 * 方向为Right和Up时 倒序计算角度 
                 * 确保层级中的子物体按照从左到右、从上到下的顺序自动布局 */
                float angle = angleDelta * (startDirection < 2 ? children.Count - 1 - i : i) - halfAngle + startDirection * 90f;
                //计算x、y坐标
                float x = radius * Mathf.Cos(angle * Mathf.PI / 180f);
                float y = radius * Mathf.Sin(angle * Mathf.PI / 180f);
                //为子物体赋值坐标
                Vector2 anchorPos = child.anchoredPosition;
                anchorPos.x = x;
                anchorPos.y = y;
                child.anchoredPosition = anchorPos;

                //控制子物体大小
                if (controlChildSize)
                {
                    child.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, childSize.x);
                    child.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, childSize.y);
                }
            }
        }
    }
}

Editor 编辑器

通过上述代码可以实现Runtime运行时的布局自动刷新,想要在Editor编辑环境编辑时自动刷新还需要自定义Editor编辑器,代码如下:

#if UNITY_EDITOR
    [CustomEditor(typeof(CircleLayoutGroup))]
    public class CircleLayoutGroupEditor : Editor
    {
        private enum Direction
        {
            Right = 0,
            Up = 1,
            Left = 2,
            Down = 3
        }

        private CircleLayoutGroup circleLayoutGroup;
        private SerializedProperty radius;
        private SerializedProperty angleDelta;
        private SerializedProperty startDirection;
        private SerializedProperty autoRefresh;
        private SerializedProperty controlChildSize;
        private SerializedProperty childSize;
        private Direction direction;

        private void OnEnable()
        {
            circleLayoutGroup = target as CircleLayoutGroup;
            radius = serializedObject.FindProperty("radius");
            angleDelta = serializedObject.FindProperty("angleDelta");
            startDirection = serializedObject.FindProperty("startDirection");
            autoRefresh = serializedObject.FindProperty("autoRefresh");
            controlChildSize = serializedObject.FindProperty("controlChildSize");
            childSize = serializedObject.FindProperty("childSize");

            direction = (Direction)startDirection.intValue;
            circleLayoutGroup.RefreshLayout();
        }

        public override void OnInspectorGUI()
        {
            float newRadius = EditorGUILayout.FloatField("Radius", radius.floatValue);
            if (newRadius != radius.floatValue)
            {
                Undo.RecordObject(target, "Radius");
                radius.floatValue = newRadius;
                IsChanged();
            }

            float newAngleDelta = EditorGUILayout.FloatField("Angle Delta", angleDelta.floatValue);
            if (newAngleDelta != angleDelta.floatValue)
            {
                Undo.RecordObject(target, "Angle Delta");
                angleDelta.floatValue = newAngleDelta;
                IsChanged();
            }

            Direction newDirection = (Direction)EditorGUILayout.EnumPopup("Start Direction", direction);
            if (newDirection != direction) 
            {
                Undo.RecordObject(target, "Start Direction");
                direction = newDirection;
                startDirection.intValue = (int)direction;
                IsChanged();
            }

            bool newAutoRefresh = EditorGUILayout.Toggle("Auto Refresh", autoRefresh.boolValue);
            if (newAutoRefresh != autoRefresh.boolValue)
            {
                Undo.RecordObject(target, "Angle Refresh");
                autoRefresh.boolValue = newAutoRefresh;
                IsChanged();
            }

            bool newControlChildSize = EditorGUILayout.Toggle("Control Child Size", controlChildSize.boolValue);
            if (newControlChildSize != controlChildSize.boolValue)
            {
                Undo.RecordObject(target, "Control Child Size");
                controlChildSize.boolValue = newControlChildSize;
                IsChanged();
            }

            if (controlChildSize.boolValue)
            {
                Vector2 newChildSize = EditorGUILayout.Vector2Field("Child Size", childSize.vector2Value);
                if (newChildSize != childSize.vector2Value)
                {
                    Undo.RecordObject(target, "Child Size");
                    childSize.vector2Value = newChildSize;
                    IsChanged();
                }
            }
        }

        private void IsChanged()
        {
            if (GUI.changed)
            {
                serializedObject.ApplyModifiedProperties();
                EditorUtility.SetDirty(target);
                circleLayoutGroup.RefreshLayout();
            }
        }
    }
#endif

工具已上传SKFramework框架Package Manager中:

SKFramework Package Manager

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349852.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Maven的安装步骤(保姆级安装教程)

一、安装本地Maven 选择你需要的maven版本下载&#xff1a;官网下载传送门 我使用的是3.6.1版本&#xff1a;maven-3.6.1-bin.zip 二、安装 把下载好的maven压缩包解压到一个没有中文&#xff0c;空格或其他特殊字符的文件夹&#xff0c;如&#xff1a; 三、配置环境变量…

Python 编程必备:盘点nginx和gunicorn的几大用法,建议收藏

程序员是新兴技术工种中比较高薪的一个&#xff0c;在互联网公司&#xff0c;程序员往往与秃头&#xff0c;压力大&#xff0c;找不到女朋友等等挂钩。 最近&#xff0c;最新技能类榜单出炉&#xff0c;这是一个关于程序员自己给自己贴的几个标签。 其中&#xff0c;不难看出…

美国CPC认证是什么?儿童玩具亚马逊CPC认证审核有哪些问题?

很多卖家都有遭遇listing下架&#xff0c;被要求提供CPC认证报告。这是因为亚马逊有时会加强对儿童产品的审查。本文带大家对CPC认证进行一个全面了解。什么是CPC认证&#xff1f;CPC认证&#xff0c;全称ChildrensProductCertification.是认可实验室&#xff0c;根据产品不同适…

Hive学习——单机版Hive的安装

目录 一、基本概念 (一)什么是Hive (二)优势和特点 (三)Hive元数据管理 二、Hive环境搭建 1.自动安装脚本 2./opt/soft/hive312/conf目录下创建hive配置文件hive-site.xml 3.拷贝一个jar包到hive下面的lib目录下 4.删除hive的guava&#xff0c;拷贝hadoop下的guava 5…

Java中常见的编码集问题

收录于热门专栏Java基础教程系列&#xff08;进阶篇&#xff09; 一、遇到一个问题 1、读取CSV文件 package com.guor.demo.charset;import java.io.BufferedReader; import java.io.FileReader; import java.util.ArrayList; import java.util.HashMap; import java.util.L…

Syntax-Aware Aspect-Level Sentiment Classification with PWCN 论文阅读笔记

一、作者 Chen Zhang, Qiuchi Li, and Dawei Song. 2019. Syntax-Aware Aspect-Level Sentiment Classification with Proximity-Weighted Convolution Network. In Proceedings of the 42nd International ACM SIGIR Conference on Research and Development in Information …

jsp游泳馆门票管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 jsp游泳馆门票管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql&#xff0c;…

MySQL的存储引擎

目录 一.概念 二.分类 操作 修改默认存储引擎 一.概念 数据库存储引擎是数据库底层软件组织&#xff0c;数据库管理系统&#xff08;DBMS&#xff09;使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能。现在许多不…

数据预处理——数据无量纲化(归一化、标准化)

文章目录1. 数据归一化1.1 数据归一化定义1.2 MinMaxScaler 归一化1.3 MinMaxScaler 使用样例2. 数据标准化2.1 数据标准化定义2.2 StandardScaler 标准化2.3 StandardScaler 使用样例StandardScaler和MinMaxScaler选哪个&#xff1f;在机器学习算法实践中&#xff0c;我们往往…

儿童玩具车扭扭车上架欧盟亚马逊CE认证EN71项目测试

扭扭车又称儿童健身车&#xff0c;摇摆车,主体由工程聚丙烯&#xff0c;经注塑而成&#xff0c;结构稳固&#xff0c;操作简单&#xff0c;无需电瓶和传动装置&#xff0c;只要左右转动方向盘&#xff0c;就可随意前后行驶。是一种环保的绿色玩具&#xff0c;最早出现在中国台湾…

c++:缺省参数,函数重载

今天介绍的是cpp中的缺省参数以及函数重载的知识。 首先我们先看看缺省参数&#xff1a; 缺省参数 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时&#xff0c;如果没有指定实 参则采用该形参的缺省值&#xff0c;否则使用指定的实参。 例如&#…

项目——博客系统

文章目录项目优点项目创建创建相应的目录&#xff0c;文件&#xff0c;表&#xff0c;导入前端资源实现common工具类实现拦截器验证用户登录实现统一数据返回格式实现加盐加密类实现encrypt方法实现decrypt方法实现SessionUtil类实现注册页面实现前端代码实现后端代码实现登录页…

JS 动态爱心(HTML+CSS+JS)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

webstorm开发electron,调试主进程方案

官网教程地址&#xff1a;https://www.electronjs.org/zh/docs/latest/tutorial/debugging-main-process 我只能说官网太看得起人了&#xff0c;整这么简易的教程…… 命令行开关 第一步还是要按要求在我们的package.json里加上端口监听&#xff1a;–inspect5858 我的命令…

恭喜山东翰林“智慧园区管理系统”获易知微可视化设计大赛二等奖

数字化经济发展是全球经济发展的重中之重&#xff0c;“数字孪生&#xff08;Digital Twin&#xff09;”这一词汇正在成为学术界和产业界的一个热点。数字孪生作为近年来的新兴技术&#xff0c;其与国民经济各产业融合不断深化&#xff0c;推动着各大产业数字化、网络化、智能…

关于服务连接器(Servlet)你了解多少?

Servlet 1 简介 Servlet是JavaWeb最为核心的内容&#xff0c;它是Java提供的一门动态web资源开发技术。 使用Servlet就可以实现&#xff0c;根据不同的登录用户在页面上动态显示不同内容。 Servlet是JavaEE规范之一&#xff0c;其实就是一个接口&#xff0c;将来我们需要定义…

血液透析过滤芯气密性检测装置中的高精度多段压力控制解决方案

摘要&#xff1a;针对目前血液过滤芯气密性检测过程中存在的自动化水平较低、多个检测压力之间需人工切换和压力控制精度较差的问题&#xff0c;为满足客户对高精度和自动化气密性检测的要求&#xff0c;本文提出了相应的解决方案。解决方案的主要特点是全过程的可编程压力控制…

Git的使用方法(保姆级)

一、安装git二、创建凭据 ①打开电脑的凭据管理器git:https://gitee.com是固定写法用户名、密码是你创建gitee的用户名、密码三、在gitee中创建一个仓库四、项目提交到仓库的方法①选择一个项目交由git管理按照步骤一中召唤小黑窗口输入 git init 就可以出现.git文件夹②右键选…

Golang基础 函数详解 匿名函数与闭包

文章目录01 匿名函数1.1 定义匿名函数1.2 匿名函数使用场景02 闭包2.1 闭包实现公有变量2.2 闭包实现缓存效果参考资料匿名函数是指不需要定义函数名的一种函数实现方式&#xff08;即没有名字的函数&#xff09;。匿名函数多用于实现回调函数和闭包。 01 匿名函数 Golang 支持…

财报解读:营收增长、亏损扩大,Shopify如何度过阵痛期?

后疫情时代&#xff0c;Shopify阵痛不断。 图源&#xff1a;Shopify 北京时间2023年2月16日&#xff0c;Shopify披露了2022年四季度财报&#xff0c;营收17.3亿美元&#xff0c;同比增长25.4%&#xff0c;高于分析师预期的16.5亿美元&#xff1b;净亏损为6.24亿美元&#xff0…