Unity3d-UGUI特效之Image的Skew变形、倾斜效果
这次在项目中,做动画时,想要多UI的图片做倾斜动画,比如进场有一定的斜度,然后又变回原来的样子。于是在网上搜索有关Image变形或倾斜的做法,后面找到根据文档发现可以通过继承Image来做一些修改,关键是在Image渲染之后,我们拿到顶点坐标,对坐标做一定的偏移,就可以达到目的了。
先来看看效果:
看着效果还不错,看代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
[DisallowMultipleComponent]
[RequireComponent(typeof(RectTransform))]
[RequireComponent(typeof(CanvasRenderer))]
[AddComponentMenu("UI/UISkewImage (UI)", 99)]
[ExecuteInEditMode]
public class UISkewImage : Image {
[SerializeField]
private Vector3 offsetLeftButtom = Vector3.zero;
public Vector3 OffsetLeftButtom{
get{return offsetLeftButtom;}
set{
offsetLeftButtom = value;
SetAllDirty();
}
}
[SerializeField]
private Vector3 offsetRightButtom = Vector3.zero;
public Vector3 OffsetRightButtom{
get{return offsetRightButtom;}
set{
offsetRightButtom = value;
SetAllDirty();
}
}
[SerializeField]
private Vector3 offsetLeftTop = Vector3.zero;
public Vector3 OffsetLeftTop{
get{return offsetLeftTop;}
set{
offsetLeftTop = value;
SetAllDirty();
}
}
[SerializeField]
private Vector3 offsetRightTop = Vector3.zero;
public Vector3 OffsetRightTop{
get{return offsetRightTop;}
set{
offsetRightTop = value;
SetAllDirty();
}
}
Vector3 GetOffsetVector(int i){
if (i == 0){
return offsetLeftButtom;
}else if (i == 1){
return offsetLeftTop;
}else if (i == 2){
return offsetRightTop;
}else {
return offsetRightButtom;
}
}
protected override void OnPopulateMesh(VertexHelper toFill){
base.OnPopulateMesh(toFill);
int count = toFill.currentVertCount;
for(int i=0;i<count;i++){
UIVertex vertex = new UIVertex();
toFill.PopulateUIVertex(ref vertex, i);
// Debug.Log(i.ToString() + ": " + oldPosition[i].ToString());
vertex.position += GetOffsetVector(i);
toFill.SetUIVertex(vertex, i);
}
}
}
上面四个Offset分别对应四个顶点,如果要想丰富它,我觉得还是用一个插件好了,哈哈~数学不好,不知道怎么算顶点和三角面。
在上面代码中,虽然参数加了,但是自己定义的属性,没法显示在Inspector上面,因为我们直接继承了Image类,Image有一个ImageEditor的编辑器类,里面并没有把我们的参数显示出来,所以我们还需要写一个类,继承ImageEditor,然后在这个类里面,把我们自己定义的类显示在面板上,看截图:
这样就可以在编辑器里面直接操作了,编辑器类在这里:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using UnityEditor.UI;
[CustomEditor(typeof(UISkewImage), true)]
[CanEditMultipleObjects]
public class UISkewImageEditor: ImageEditor {
public override void OnInspectorGUI(){
base.OnInspectorGUI();
UISkewImage uiSkewImage = (UISkewImage)target;
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
if (GUILayout.Button("重置", GUILayout.Width(40))){
uiSkewImage.OffsetLeftTop = Vector3.zero;
}
uiSkewImage.OffsetLeftTop = EditorGUILayout.Vector3Field("左上", uiSkewImage.OffsetLeftTop, GUILayout.ExpandWidth(true));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
if (GUILayout.Button("重置", GUILayout.Width(40))){
uiSkewImage.OffsetRightTop = Vector3.zero;
}
uiSkewImage.OffsetRightTop = EditorGUILayout.Vector3Field("右上", uiSkewImage.OffsetRightTop, GUILayout.ExpandWidth(true));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
if (GUILayout.Button("重置", GUILayout.Width(40))){
uiSkewImage.OffsetLeftButtom = Vector3.zero;
}
uiSkewImage.OffsetLeftButtom = EditorGUILayout.Vector3Field("左下", uiSkewImage.OffsetLeftButtom, GUILayout.ExpandWidth(true));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
if (GUILayout.Button("重置", GUILayout.Width(40))){
uiSkewImage.OffsetRightButtom = Vector3.zero;
}
uiSkewImage.OffsetRightButtom = EditorGUILayout.Vector3Field("右下", uiSkewImage.OffsetRightButtom, GUILayout.ExpandWidth(true));
EditorGUILayout.EndHorizontal();
if (GUI.changed){
EditorUtility.SetDirty(uiSkewImage);
}
}
}
到这里,基本上是可以用了,但是为了能够在任何时候都可以直接创建它,就和你在某个UI物体下面右键创建Image一样,我又写了一个编辑器类,可以直接创建SkewImage,这样用起来就方便啦。代码如下:
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
using UnityEngine.EventSystems;
public class UICustomCreator {
[MenuItem("GameObject/UI/UICustom/SkewImage", false, 0)]
static void CreateUICustomSkewImageGuiObject(MenuCommand command){
Canvas canvas = CreateCanvas();
GameObject go = CreateUISkewImage(command, canvas);
CreateEventSystem();
Selection.activeGameObject = go;
}
// Create the UISkewImage Object
static GameObject CreateUISkewImage(MenuCommand command, Canvas canvas){
GameObject go = new GameObject("UISkewImage");
RectTransform goRectTransform = go.AddComponent<RectTransform>();
Undo.RegisterCreatedObjectUndo((Object)go, "Create " + go.name);
// Check if object is being create with left or right click
GameObject contextObject = command.context as GameObject;
if (contextObject == null) {
goRectTransform.sizeDelta = new Vector2(100f, 100f);
GameObjectUtility.SetParentAndAlign(go, canvas.gameObject);
}else {
goRectTransform.sizeDelta = new Vector2(100f, 100f);
GameObjectUtility.SetParentAndAlign(go, contextObject);
}
UISkewImage image = go.AddComponent<UISkewImage>();
image.OffsetLeftTop = Vector3.zero;
image.OffsetRightTop = Vector3.zero;
image.OffsetLeftButtom = Vector3.zero;
image.OffsetRightButtom = Vector3.zero;
image.raycastTarget = true;
return go;
}
// Check if there is a Canvas in the scene
static Canvas CreateCanvas(){
Canvas canvas = Object.FindObjectOfType<Canvas>();
if (canvas == null)
{
// Create new Canvas since none exists in the scene.
GameObject canvasObject = new GameObject("Canvas");
canvas = canvasObject.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.gameObject.AddComponent<CanvasScaler>();
// Add a Graphic Raycaster Component as well
canvas.gameObject.AddComponent<GraphicRaycaster>();
Undo.RegisterCreatedObjectUndo(canvasObject, "Create " + canvasObject.name);
}
return canvas;
}
// Check if an event system already exists in the scene
static void CreateEventSystem(){
if (!Object.FindObjectOfType<EventSystem>())
{
GameObject eventObject = new GameObject("EventSystem", typeof(EventSystem));
eventObject.AddComponent<StandaloneInputModule>();
#if UNITY_5_3_OR_NEWER
// Nothing
#else
eventObject.AddComponent<TouchInputModule>();
#endif
Undo.RegisterCreatedObjectUndo(eventObject, "Create " + eventObject.name);
}
}
}
这样就可以咯,注意,Editor类一定要放在Assets/.../Editor/的子目录里面,中间省略是随意哪个目录,但是最终必须是Editor,否则是不会起作用的。
看效果:
看到了么,超级方便!
再来两个截图:
到这里就结束了吗?还没有,我后面做动画时,发现我改变自定义的值,却不能像其他已有参数一样,直接在Animation编辑器中显示这个参数,这就有点头疼了。因为这样没法在编辑器调整数值,还是一个个的手动在Animation面板中添加参数,然后还要在这里调整数值,实在是太麻烦了。于是去Unity官网提过的UI源码看他们源码是怎么做到的,这里有UGUI的源码,可以参考哦:Unity UGUI 源码
通过我对方尝试,终于成功了,只需要改改ImageEditor类就可以了,修改后的类如下:
[CustomEditor(typeof(UISkewImage), true)]
[CanEditMultipleObjects]
public class UISkewImageEditor: ImageEditor {
SerializedProperty m_OffsetLeftTop;
SerializedProperty m_OffsetRightTop;
SerializedProperty m_OffsetLeftButtom;
SerializedProperty m_OffsetRightButtom;
GUIContent m_LTContent;
GUIContent m_RTContent;
GUIContent m_LBContent;
GUIContent m_RBContent;
protected override void OnEnable()
{
base.OnEnable();
m_OffsetLeftTop = serializedObject.FindProperty("offsetLeftTop");
m_OffsetRightTop = serializedObject.FindProperty("offsetRightTop");
m_OffsetLeftButtom = serializedObject.FindProperty("offsetLeftButtom");
m_OffsetRightButtom = serializedObject.FindProperty("offsetRightButtom");
m_LTContent = new GUIContent("左上");
m_RTContent = new GUIContent("右上");
m_LBContent = new GUIContent("左下");
m_RBContent = new GUIContent("右下");
}
public override void OnInspectorGUI(){
base.OnInspectorGUI();
serializedObject.Update();
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
if (GUILayout.Button("重置", GUILayout.Width(40))){
m_OffsetLeftTop.vector3Value = Vector3.zero;
}
EditorGUILayout.PropertyField(m_OffsetLeftTop, m_LTContent);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
if (GUILayout.Button("重置", GUILayout.Width(40))){
m_OffsetRightTop.vector3Value = Vector3.zero;
}
EditorGUILayout.PropertyField(m_OffsetRightTop, m_RTContent);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
if (GUILayout.Button("重置", GUILayout.Width(40))){
m_OffsetLeftButtom.vector3Value = Vector3.zero;
}
EditorGUILayout.PropertyField(m_OffsetLeftButtom, m_LBContent);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
if (GUILayout.Button("重置", GUILayout.Width(40))){
m_OffsetRightButtom.vector3Value = Vector3.zero;
}
EditorGUILayout.PropertyField(m_OffsetRightButtom, m_RBContent);
EditorGUILayout.EndHorizontal();
serializedObject.ApplyModifiedProperties();
// UISkewImage uiSkewImage = (UISkewImage)target;
// EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
// if (GUILayout.Button("重置", GUILayout.Width(40))){
// uiSkewImage.OffsetLeftTop = Vector3.zero;
// }
// uiSkewImage.OffsetLeftTop = EditorGUILayout.Vector3Field("左上", uiSkewImage.OffsetLeftTop, GUILayout.ExpandWidth(true));
// EditorGUILayout.EndHorizontal();
// EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
// if (GUILayout.Button("重置", GUILayout.Width(40))){
// uiSkewImage.OffsetRightTop = Vector3.zero;
// }
// uiSkewImage.OffsetRightTop = EditorGUILayout.Vector3Field("右上", uiSkewImage.OffsetRightTop, GUILayout.ExpandWidth(true));
// EditorGUILayout.EndHorizontal();
// EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
// if (GUILayout.Button("重置", GUILayout.Width(40))){
// uiSkewImage.OffsetLeftButtom = Vector3.zero;
// }
// uiSkewImage.OffsetLeftButtom = EditorGUILayout.Vector3Field("左下", uiSkewImage.OffsetLeftButtom, GUILayout.ExpandWidth(true));
// EditorGUILayout.EndHorizontal();
// EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
// if (GUILayout.Button("重置", GUILayout.Width(40))){
// uiSkewImage.OffsetRightButtom = Vector3.zero;
// }
// uiSkewImage.OffsetRightButtom = EditorGUILayout.Vector3Field("右下", uiSkewImage.OffsetRightButtom, GUILayout.ExpandWidth(true));
// EditorGUILayout.EndHorizontal();
// if (GUI.changed){
// EditorUtility.SetDirty(uiSkewImage);
// }
}
}
主要是那个一开始是白色的,只要在Animation面板开启录制模式,我在这里修改任何一个参数,就会变红,就像Unity自身的参数一样!可以开心的调动画啦~哈哈