项目里给很多按钮做了一个 按下缩小的处理。 用Animation做的。
测试觉得下面的是有问题的,按住边缘就会不停的抖啊抖:
解决方案肯定个旧市, 按钮整体缩放时,点击区域不要缩放。
目里已经做了很多UI了,要全部重新制作一套规则不太可能。
想到一个方案 按钮父节点下挂一个点击区域, 按钮缩小时,这个区域反比放大。
这样对原来UI的改动最小。
不过项目里还是有很多这样按钮,需要做一个工具批量添加这个节点。
牵涉到如何批量修改prefab,并且对这个prefab增加一个节点的处理。
代码如下:
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;
public class AddBtnScaleRaycast : EditorWindow
{
static List<Object> errorObjList = new List<Object>();
const string BtnHitAreaName = "BtnHitArea";
delegate void PrefabHandler(GameObject prefab);
/// <summary>
/// 给缩放按钮添加一个固定大小的点击区域
/// </summary>
[MenuItem("Tools/Art/给缩放按钮添加点击区域", false, 1000)]
static void OptimizeNullAnimator()
{
HandlePrefabs(delegate (GameObject go)
{
Animator[] animators = go.GetComponentsInChildren<Animator>(true);
if (animators == null || animators.Length == 0)
{
return;
}
var inst_go = PrefabUtility.InstantiatePrefab(go) as GameObject; //直接在prefab上挂 子节点 SetParent时会报错, 需要实例化一个对象出来 - -
animators = inst_go.GetComponentsInChildren<Animator>(true);
bool modify = false;
foreach (var anim in animators)
{
if (anim.runtimeAnimatorController != null)
{
var name = anim.runtimeAnimatorController.name;
if (name == "FrameButton" || name == "NormalButton" || name == "ScaleButton") //项目里一些缩放按钮用到的动画控制器名字
{
Transform _bht_tf = anim.transform.Find(BtnHitAreaName);
if (_bht_tf == null) //不存在这个子节点,则自动在下面添加一个 BtnHitAreaName 节点
{
//先判断按钮节点上有没有图片? 有则上面的点击接收可以去掉了,用新加的节点来判断 : 如果没有图片则是特殊情况要特殊处理
var img = anim.gameObject.GetComponent<MaskableGraphic>(); //不一定图片,可能其他点击脚本有就行
if (img != null)
{
if (img.enabled == false)
{
Debug.LogErrorFormat(go, "prefab {0}, 这个{1}节点Image enabled 为false,记得确认一下", go.name, anim.gameObject.name);
}
else if (img.raycastTarget == false)
{
Debug.LogErrorFormat(go, "prefab {0}, 这个{1}节点Image raycastTarget 已经为false,记得确认一下", go.name, anim.gameObject.name);
}
else if (img is Empty4Raycast) //放在后面直接删除
{
//img.enabled = false; //点击区域可以移除
Debug.LogWarningFormat(go, "【提示】 prefab {0}, 这个{1}节点 Empty4Raycast脚本,用BtnHitArea子节点代替了,原脚本移除了!!!", go.name, anim.gameObject.name);
}
img.raycastTarget = false;
}
else
{
Debug.LogErrorFormat(go, "prefab {0}, 这个{1}节点没有Image图片,记得确认一下", go.name, anim.gameObject.name);
}
//新加节点,绑脚本,社布局
var _bht_go = new GameObject();
_bht_go.transform.SetParent(anim.transform);
_bht_go.name = BtnHitAreaName;
_bht_go.AddComponent<Empty4Raycast>(); //让其能被点击
var rt = _bht_go.GetComponent<RectTransform>();
if (rt == null)
{
rt = _bht_go.AddComponent<RectTransform>(); //添加一个RectTransform
}
bool set_full_size = true;
if (img == null)
{
//按钮上没有图片节点,则尝试找它的子节点第一个有效Image, 尺寸设成一样
for (int i = 0; i < anim.transform.childCount; ++i)
{
var child = anim.transform.GetChild(i);
var child_img = child.GetComponent<Image>();
var img_rt = child.GetComponent<RectTransform>();
if (child_img != null && img_rt != null && child_img.enabled && child_img.raycastTarget)
{
child_img.raycastTarget = false; //这个image的点击关闭
//尝试新节点布局和这个img一致
rt.anchorMin = img_rt.anchorMin;
rt.anchorMax = img_rt.anchorMax;
rt.pivot = img_rt.pivot; //沿用原来的对齐方式
rt.anchoredPosition = img_rt.anchoredPosition;
rt.sizeDelta = img_rt.sizeDelta;
rt.localScale = img_rt.localScale;
rt.localRotation = img_rt.localRotation;
if (img_rt.pivot != Vector2.one * 0.5f) //没中心对齐可能要再处理一下。
{
//rt.pivot = Vector2.one - img_rt.pivot; //这个还是需要手动调整
Debug.LogErrorFormat(go, "【【手动调整提示】】prefab {0}, 这个{1}节点没有中心对齐", go.name, anim.gameObject.name);
}
set_full_size = false;
break;
}
}
}
if (set_full_size)
{
//布局设为自动撑满父节点的面积
rt.anchorMin = Vector2.zero;
rt.anchorMax = Vector2.one;
rt.anchoredPosition = Vector2.zero; //这里要用anchoredPosition 对齐,否则用LocalPosition 不同的布局会有差异
rt.sizeDelta = Vector2.zero;
rt.localScale = Vector3.one;
var parent_rt = anim.gameObject.GetComponent<RectTransform>();
if (parent_rt != null)
{
rt.pivot = parent_rt.pivot; //pivot对齐方式要和父节点一致,这样缩放时位置不会偏。
}
}
if (img != null && img is Empty4Raycast)
{
DestroyImmediate(img);
}
modify = true;
}
}
}
}
if (modify)
{
errorObjList.Add(go);
Debug.Log("prefab " + go.name + " 已修改", go);
PrefabUtility.ReplacePrefab(inst_go, go, ReplacePrefabOptions.ConnectToPrefab);
}
DestroyImmediate(inst_go); // 实例的对象要销毁
});
}
static string path = Path.Combine(Application.dataPath, "log.txt");
public static void Log(string content)
{
File.AppendAllText(path, content + "\r\n", Encoding.UTF8);
}
static void HandlePrefabs(PrefabHandler handler)
{
List<Object> objs = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets).ToList().Where(a => Path.GetExtension(AssetDatabase.GetAssetPath(a)).ToLower() == ".prefab").ToList();
if (objs.Count == 0)
{
Debug.Log("请先选中一个要处理的父目录");
return;
}
errorObjList.Clear();
int index = 0;
EditorApplication.update = delegate ()
{
Object obj = objs[index];
EditorUtility.DisplayProgressBar("拼命处理中", string.Format("prefab:{0} 进度:{1}/{2}", obj.name, index, objs.Count), (float)index / objs.Count);
handler(obj as GameObject);
if (++index >= objs.Count)
{
EditorUtility.ClearProgressBar();
EditorApplication.update = null;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
if (errorObjList.Count > 0)
{
AssetListWindow.Show(errorObjList, "已处理的列表");
}
Debug.Log("Finish");
}
};
}
}
另外附带一个实现空白点击的替换透明Image 方案的脚本:
namespace UnityEngine.UI
{
/// <summary>
/// 代替空的Image接受点击
/// </summary>
public class Empty4Raycast : MaskableGraphic
{
protected Empty4Raycast()
{
useLegacyMeshGeneration = false;
}
protected override void OnPopulateMesh(VertexHelper toFill)
{
toFill.Clear();
}
}
}