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);
// }
}
}
主要是那個serializedObject.Update();serializedObject.ApplyModifiedProperties();這樣關鍵,會自動加入到動畫系統裡面去,看截圖:
一開始是白色的,只要在Animation面板開啟錄製模式,我在這裡修改任何一個引數,就會變紅,就像Unity自身的引數一樣!可以開心的調動畫啦~哈哈