diff --git a/Assets/Scenes/ui animation Sample Scene.unity.meta b/Assets/Scenes/ui animation Sample Scene.unity.meta new file mode 100644 index 0000000..4048b51 --- /dev/null +++ b/Assets/Scenes/ui animation Sample Scene.unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2145e601083d97c4ba64d70455d21a11 +timeCreated: 1594958189 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/2D/Colliders/ScreenEdgeColliders.cs b/Assets/Scripts/2D/Colliders/ScreenEdgeColliders.cs index bf94311..a3ae1c5 100644 --- a/Assets/Scripts/2D/Colliders/ScreenEdgeColliders.cs +++ b/Assets/Scripts/2D/Colliders/ScreenEdgeColliders.cs @@ -1,35 +1,74 @@ // adds EdgeCollider2D colliders to screen edges // only works with orthographic camera +//Includes two different ways of implementation into your project +//One is a method that uses cached fields on Awake() that requires this entire class but is more slightly more efficient (should use this if you plan to use the method in Update()) +//The other is a standalone method that doesn't need the rest of the class and can be copy-pasted directly into any project but is slightly less efficient + using UnityEngine; -using System.Collections; namespace UnityLibrary { - public class ScreenEdgeColliders : MonoBehaviour - { - void Awake () + public class ScreenEdgeColliders : MonoBehaviour { - AddCollider(); - } + private Camera cam; + private EdgeCollider2D edge; + private Vector2[] edgePoints; - void AddCollider () - { - if (Camera.main==null) {Debug.LogError("Camera.main not found, failed to create edge colliders"); return;} + void Awake() + { + if (Camera.main == null) Debug.LogError("Camera.main not found, failed to create edge colliders"); + else cam = Camera.main; + + if (!cam.orthographic) Debug.LogError("Camera.main is not Orthographic, failed to create edge colliders"); + + // add or use existing EdgeCollider2D + edge = GetComponent() == null ? gameObject.AddComponent() : GetComponent(); + + edgePoints = new Vector2[5]; + + AddCollider(); + } + + //Use this if you're okay with using the global fields and code in Awake() (more efficient) + //You can just ignore/delete StandaloneAddCollider() if thats the case + void AddCollider() + { + //Vector2's for the corners of the screen + Vector2 bottomLeft = cam.ScreenToWorldPoint(new Vector3(0, 0, cam.nearClipPlane)); + Vector2 topRight = cam.ScreenToWorldPoint(new Vector3(cam.pixelWidth, cam.pixelHeight, cam.nearClipPlane)); + Vector2 topLeft = new Vector2(bottomLeft.x, topRight.y); + Vector2 bottomRight = new Vector2(topRight.x, bottomLeft.y); + + //Update Vector2 array for edge collider + edgePoints[0] = bottomLeft; + edgePoints[1] = topLeft; + edgePoints[2] = topRight; + edgePoints[3] = bottomRight; + edgePoints[4] = bottomLeft; + + edge.points = edgePoints; + } + + //Use this if you want a single function to handle everything (less efficient) + //You can just ignore/delete the rest of this class if thats the case + void StandaloneAddCollider() + { + if (Camera.main == null) { Debug.LogError("Camera.main not found, failed to create edge colliders"); return; } - var cam = Camera.main; - if (!cam.orthographic) {Debug.LogError("Camera.main is not Orthographic, failed to create edge colliders"); return;} + var cam = Camera.main; + if (!cam.orthographic) { Debug.LogError("Camera.main is not Orthographic, failed to create edge colliders"); return; } - var bottomLeft = (Vector2)cam.ScreenToWorldPoint(new Vector3(0, 0, cam.nearClipPlane)); - var topLeft = (Vector2)cam.ScreenToWorldPoint(new Vector3(0, cam.pixelHeight, cam.nearClipPlane)); - var topRight = (Vector2)cam.ScreenToWorldPoint(new Vector3(cam.pixelWidth, cam.pixelHeight, cam.nearClipPlane)); - var bottomRight = (Vector2)cam.ScreenToWorldPoint(new Vector3(cam.pixelWidth, 0, cam.nearClipPlane)); + Vector2 bottomLeft = cam.ScreenToWorldPoint(new Vector3(0, 0, cam.nearClipPlane)); + Vector2 topRight = cam.ScreenToWorldPoint(new Vector3(cam.pixelWidth, cam.pixelHeight, cam.nearClipPlane)); + Vector2 topLeft = new Vector2(bottomLeft.x, topRight.y); + Vector2 bottomRight = new Vector2(topRight.x, bottomLeft.y); - // add or use existing EdgeCollider2D - var edge = GetComponent()==null?gameObject.AddComponent():GetComponent(); + // add or use existing EdgeCollider2D + var edge = GetComponent() == null ? gameObject.AddComponent() : GetComponent(); - var edgePoints = new [] {bottomLeft,topLeft,topRight,bottomRight, bottomLeft}; - edge.points = edgePoints; + var edgePoints = new[] { bottomLeft, topLeft, topRight, bottomRight, bottomLeft }; + edge.points = edgePoints; + } } - } } diff --git a/Assets/Scripts/2D/Geometry/Triangle2D.cs b/Assets/Scripts/2D/Geometry/Triangle2D.cs new file mode 100644 index 0000000..6d16785 --- /dev/null +++ b/Assets/Scripts/2D/Geometry/Triangle2D.cs @@ -0,0 +1,67 @@ +using UnityEngine; + +namespace UnityLibrary +{ + // if you need to run collision checks within a triangular area, the easiest + // way to do that in Unity is to use a polygon collider. this is not very + // performant. this class allows you to run those collision checks without + // the performance overhead. + + public class Triangle2D + { + Vector2[] vertices = new Vector2[3]; + + public Triangle2D(Vector2 v1, Vector2 v2, Vector2 v3) + { + Update(v1, v2, v3); + } + + // update triangle by defining all its vertices + public void Update(Vector2 v1, Vector2 v2, Vector2 v3) + { + vertices[0] = v1; + vertices[1] = v2; + vertices[2] = v3; + } + + // update triangle by redefining its origin (remaining points update relative to that) + public void Update(Vector2 v1) + { + Vector2 delta = v1 - vertices[0]; + vertices[0] = v1; + vertices[1] += delta; + vertices[2] += delta; + } + + // update triangle with rotation and pivot point + public void Update(Vector2 v1, Vector2 v2, Vector2 v3, float rotation, Vector2 pivot) + { + vertices[0] = v1.Rotate(rotation, pivot); + vertices[1] = v2.Rotate(rotation, pivot); + vertices[2] = v3.Rotate(rotation, pivot); + } + + float Sign(Vector2 p1, Vector2 p2, Vector2 p3) + { + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y); + } + + public bool Contains(Vector2 pt, bool debug = false) + { + float d1, d2, d3; + bool has_neg, has_pos; + + d1 = Sign(pt, vertices[0], vertices[1]); + d2 = Sign(pt, vertices[1], vertices[2]); + d3 = Sign(pt, vertices[2], vertices[0]); + + has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0); + has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0); + + bool contains = ! (has_neg && has_pos); + + return contains; + } + } +} + diff --git a/Assets/Scripts/2D/Geometry/Triangle2D.cs.meta b/Assets/Scripts/2D/Geometry/Triangle2D.cs.meta new file mode 100644 index 0000000..db09c09 --- /dev/null +++ b/Assets/Scripts/2D/Geometry/Triangle2D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5b3d23802c4b0df4db68d12d425fb251 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/AssetBundles/AssetBundleLoader.cs b/Assets/Scripts/AssetBundles/AssetBundleLoader.cs index 215d6de..db53c54 100644 --- a/Assets/Scripts/AssetBundles/AssetBundleLoader.cs +++ b/Assets/Scripts/AssetBundles/AssetBundleLoader.cs @@ -2,47 +2,34 @@ using UnityEngine; using UnityEngine.Networking; -// AssetBundle cache checker & loader with caching -// worsk by loading .manifest file from server and parsing hash string from it - namespace UnityLibrary { public class AssetBundleLoader : MonoBehaviour { public string assetBundleURL = "http://localhost/bundle"; + private string bundleName = "bundle"; void Start() { - //StartCoroutine(DownloadAndCache(assetBundleURL)); + StartCoroutine(DownloadAndCache(assetBundleURL)); } - /// - /// load assetbundle manifest, check hash, load actual bundle with hash parameter to use caching - /// instantiate gameobject - /// - /// full url to assetbundle file - /// optional parameter to access specific asset from assetbundle - /// IEnumerator DownloadAndCache(string bundleURL, string assetName = "") { - // Wait for the Caching system to be ready while (!Caching.ready) { yield return null; } - // if you want to always load from server, can clear cache first - // Caching.CleanCache(); + // Clear cache for previous versions of the asset bundle + Caching.ClearOtherCachedVersions(bundleName, Hash128.Parse("0")); - // get current bundle hash from server, random value added to avoid caching UnityWebRequest www = UnityWebRequest.Get(bundleURL + ".manifest?r=" + (Random.value * 9999999)); - Debug.Log("Loading manifest:" + bundleURL + ".manifest"); + Debug.Log("Loading manifest: " + bundleURL + ".manifest"); - // wait for load to finish - yield return www.Send(); + yield return www.SendWebRequest(); - // if received error, exit - if (www.isNetworkError == true) + if (www.isNetworkError) { Debug.LogError("www error: " + www.error); www.Dispose(); @@ -50,44 +37,39 @@ IEnumerator DownloadAndCache(string bundleURL, string assetName = "") yield break; } - // create empty hash string - Hash128 hashString = (default(Hash128));// new Hash128(0, 0, 0, 0); + Hash128 hashString = default(Hash128); - // check if received data contains 'ManifestFileVersion' if (www.downloadHandler.text.Contains("ManifestFileVersion")) { - // extract hash string from the received data, TODO should add some error checking here var hashRow = www.downloadHandler.text.ToString().Split("\n".ToCharArray())[5]; hashString = Hash128.Parse(hashRow.Split(':')[1].Trim()); - if (hashString.isValid == true) + if (hashString.isValid) { - // we can check if there is cached version or not - if (Caching.IsVersionCached(bundleURL, hashString) == true) + if (Caching.IsVersionCached(bundleURL, hashString)) { Debug.Log("Bundle with this hash is already cached!"); - } else + } + else { - Debug.Log("No cached version founded for this hash.."); + Debug.Log("No cached version found for this hash.."); } - } else + } + else { - // invalid loaded hash, just try loading latest bundle - Debug.LogError("Invalid hash:" + hashString); + Debug.LogError("Invalid hash: " + hashString); yield break; } - - } else + } + else { Debug.LogError("Manifest doesn't contain string 'ManifestFileVersion': " + bundleURL + ".manifest"); yield break; } - // now download the actual bundle, with hashString parameter it uses cached version if available - www = UnityWebRequest.GetAssetBundle(bundleURL + "?r=" + (Random.value * 9999999), hashString, 0); + www = UnityWebRequestAssetBundle.GetAssetBundle(bundleURL + "?r=" + (Random.value * 9999999), hashString, 0); - // wait for load to finish - yield return www.Send(); + yield return www.SendWebRequest(); if (www.error != null) { @@ -97,42 +79,26 @@ IEnumerator DownloadAndCache(string bundleURL, string assetName = "") yield break; } - // get bundle from downloadhandler AssetBundle bundle = ((DownloadHandlerAssetBundle)www.downloadHandler).assetBundle; - GameObject bundlePrefab = null; - // if no asset name is given, take the first/main asset if (assetName == "") { bundlePrefab = (GameObject)bundle.LoadAsset(bundle.GetAllAssetNames()[0]); - } else - { // use asset name to access inside bundle + } + else + { bundlePrefab = (GameObject)bundle.LoadAsset(assetName); } - // if we got something out if (bundlePrefab != null) { - - // instantiate at 0,0,0 and without rotation Instantiate(bundlePrefab, Vector3.zero, Quaternion.identity); - - /* - // fix pink shaders, NOTE: not always needed.. - foreach (Renderer r in go.GetComponentsInChildren(includeInactive: true)) - { - // FIXME: creates multiple materials, not good - var material = Shader.Find(r.material.shader.name); - r.material.shader = null; - r.material.shader = material; - }*/ } www.Dispose(); www = null; - // try to cleanup memory Resources.UnloadUnusedAssets(); bundle.Unload(false); bundle = null; diff --git a/Assets/Scripts/Camera/AutoResolution.cs b/Assets/Scripts/Camera/AutoResolution.cs new file mode 100644 index 0000000..87fd874 --- /dev/null +++ b/Assets/Scripts/Camera/AutoResolution.cs @@ -0,0 +1,58 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class AutoResolution : MonoBehaviour +{ + public int setWidth = 1440; + public int setHeight = 2560; + + private void Start() + { + // Get the main camera and its current dimensions + Camera camera = Camera.main; + Rect rect = camera.rect; + + // Calculate the scale height and width of the screen + float scaleHeight = ((float)Screen.width / Screen.height) / ((float)9 / 16); + float scaleWidth = 1f / scaleHeight; + + // Adjust the camera's dimensions based on the scale height and width + if (scaleHeight < 1) + { + rect.height = scaleHeight; + rect.y = (1f - scaleHeight) / 2f; + } + else + { + rect.width = scaleWidth; + rect.x = (1f - scaleWidth) / 2f; + } + + camera.rect = rect; + + SetResolution(); + } + + public void SetResolution() + { + // Get the current device's screen dimensions + int deviceWidth = Screen.width; + int deviceHeight = Screen.height; + + // Set the screen resolution to the desired dimensions, while maintaining aspect ratio + Screen.SetResolution(setWidth, (int)(((float)deviceHeight / deviceWidth) * setWidth), true); + + // Adjust the camera's dimensions based on the new resolution + if ((float)setWidth / setHeight < (float)deviceWidth / deviceHeight) + { + float newWidth = ((float)setWidth / setHeight) / ((float)deviceWidth / deviceHeight); + Camera.main.rect = new Rect((1f - newWidth) / 2f, 0f, newWidth, 1f); + } + else + { + float newHeight = ((float)deviceWidth / deviceHeight) / ((float)setWidth / setHeight); + Camera.main.rect = new Rect(0f, (1f - newHeight) / 2f, 1f, newHeight); // 새로운 Rect 적용 + } + } +} diff --git a/Assets/Scripts/Camera/CameraShake.cs b/Assets/Scripts/Camera/CameraShake.cs index d8814bb..48a88e6 100644 --- a/Assets/Scripts/Camera/CameraShake.cs +++ b/Assets/Scripts/Camera/CameraShake.cs @@ -1,58 +1,66 @@ using UnityEngine; using System.Collections; -// usage: attach this script into camera, call Shake() method to start -// source: http://answers.unity3d.com/answers/992509/view.html +/* + * Usage: attach this script to a camera or any other object, call Shake() method to start shaking it +* To turn off, change influence to zero +* Attach the camera to an empty game object to keep its local position and rotation +*/ namespace UnityLibrary { public class CameraShake : MonoBehaviour { - public bool shakePosition; - public bool shakeRotation; - - public float shakeIntensityMin = 0.1f; - public float shakeIntensityMax = 0.5f; - public float shakeDecay = 0.02f; + [Range(0f, 1f)] + public float shakeInfluence = 0.5f; + [Range(0f, 10f)] + public float rotationInfluence = 0f; private Vector3 OriginalPos; private Quaternion OriginalRot; - private bool isShakeRunning = false; - // call this function to start shaking - public void Shake() +/// +/// Will shake the camera with a random value between minIntensity and maxIntensity for duration +/// +/// +/// +/// + public void Shake(float minIntensity, float maxIntensity, float duration) { + if (isShakeRunning) + return; + OriginalPos = transform.position; OriginalRot = transform.rotation; - StartCoroutine("ProcessShake"); + + float shake = Random.Range(minIntensity, maxIntensity) * shakeInfluence; + duration *= shakeInfluence; + + StartCoroutine(ProcessShake(shake, duration)); } - IEnumerator ProcessShake() + IEnumerator ProcessShake(float shake, float duration) { - if (!isShakeRunning) + isShakeRunning = true; + float countdown = duration; + float initialShake = shake; + + while (countdown > 0) { - isShakeRunning = true; - float currentShakeIntensity = Random.Range(shakeIntensityMin, shakeIntensityMax); - - while (currentShakeIntensity > 0) - { - if (shakePosition) - { - transform.position = OriginalPos + Random.insideUnitSphere * currentShakeIntensity; - } - if (shakeRotation) - { - transform.rotation = new Quaternion(OriginalRot.x + Random.Range(-currentShakeIntensity, currentShakeIntensity) * .2f, - OriginalRot.y + Random.Range(-currentShakeIntensity, currentShakeIntensity) * .2f, - OriginalRot.z + Random.Range(-currentShakeIntensity, currentShakeIntensity) * .2f, - OriginalRot.w + Random.Range(-currentShakeIntensity, currentShakeIntensity) * .2f); - } - currentShakeIntensity -= shakeDecay; - yield return null; - } - - isShakeRunning = false; + countdown -= Time.deltaTime; + + float lerpIntensity = countdown / duration; + shake = Mathf.Lerp(0f, initialShake, lerpIntensity); + + transform.position = OriginalPos + Random.insideUnitSphere * shake; + transform.rotation = Quaternion.Euler(OriginalRot.eulerAngles + Random.insideUnitSphere * shake * rotationInfluence); + + yield return null; } + + transform.position = OriginalPos; + transform.rotation = OriginalRot; + isShakeRunning = false; } } } \ No newline at end of file diff --git a/Assets/Scripts/Camera/EditorCardboardCamera.cs b/Assets/Scripts/Camera/EditorCardboardCamera.cs new file mode 100644 index 0000000..c73bd45 --- /dev/null +++ b/Assets/Scripts/Camera/EditorCardboardCamera.cs @@ -0,0 +1,23 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +// editor only Google Carboard VR cam simulation with left alt + mouse + +public class EditorCardboardCamera : MonoBehaviour +{ +#if UNITY_EDITOR + Vector2 rotation = Vector2.zero; + public float speed = 3; + + void Update() + { + if (Input.GetKey(KeyCode.LeftAlt)) + { + rotation.y += Input.GetAxis("Mouse X"); + rotation.x += -Input.GetAxis("Mouse Y"); + transform.eulerAngles = (Vector2)rotation * speed; + } + } +#endif +} diff --git a/Assets/Scripts/Camera/MarrtsSmoothedMouseLook.cs.meta b/Assets/Scripts/Camera/MarrtsSmoothedMouseLook.cs.meta new file mode 100644 index 0000000..3eccce9 --- /dev/null +++ b/Assets/Scripts/Camera/MarrtsSmoothedMouseLook.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 10e595b96bab7724a99b2b273e92011e +timeCreated: 1594958193 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Camera/MobileCamera.cs b/Assets/Scripts/Camera/MobileCamera.cs new file mode 100644 index 0000000..99fd6f5 --- /dev/null +++ b/Assets/Scripts/Camera/MobileCamera.cs @@ -0,0 +1,135 @@ +// https://forum.unity.com/threads/mobile-touch-to-orbit-pan-and-zoom-camera-without-fix-target-in-one-script.522607/#post-3531342 + +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class MobileCamera : MonoBehaviour +{ + public Transform target; + public Vector3 targetOffset; + public float distance = 5.0f; + public float maxDistance = 20; + public float minDistance = .6f; + public float xSpeed = 5.0f; + public float ySpeed = 5.0f; + public int yMinLimit = -80; + public int yMaxLimit = 80; + public float zoomRate = 10.0f; + public float panSpeed = 0.3f; + public float zoomDampening = 5.0f; + + private float xDeg = 0.0f; + private float yDeg = 0.0f; + private float currentDistance; + private float desiredDistance; + private Quaternion currentRotation; + private Quaternion desiredRotation; + private Quaternion rotation; + private Vector3 position; + + private Vector3 FirstPosition; + private Vector3 SecondPosition; + private Vector3 delta; + private Vector3 lastOffset; + private Vector3 lastOffsettemp; + //private Vector3 CameraPosition; + //private Vector3 Targetposition; + //private Vector3 MoveDistance; + + public float offsetSpeed = 0.03f; + + void Start() { Init(); } + void OnEnable() { Init(); } + + public void Init() + { + //If there is no target, create a temporary target at 'distance' from the cameras current viewpoint + if (!target) + { + GameObject go = new GameObject("Cam Target"); + go.transform.position = transform.position + (transform.forward * distance); + target = go.transform; + } + + distance = Vector3.Distance(transform.position, target.position); + currentDistance = distance; + desiredDistance = distance; + + //be sure to grab the current rotations as starting points. + position = transform.position; + rotation = transform.rotation; + currentRotation = transform.rotation; + desiredRotation = transform.rotation; + + //xDeg = Vector3.Angle(Vector3.right, transform.right); + //yDeg = Vector3.Angle(Vector3.up, transform.up); + + xDeg = transform.eulerAngles.y + Vector3.Angle(Vector3.right, transform.right); + yDeg = transform.eulerAngles.x + Vector3.Angle(Vector3.up, transform.up); + + } + + void LateUpdate() + { + // If Control and Alt and Middle button? ZOOM! + if (Input.touchCount == 2) + { + Touch touchZero = Input.GetTouch(0); + Touch touchOne = Input.GetTouch(1); + Vector2 touchZeroPreviousPosition = touchZero.position - touchZero.deltaPosition; + Vector2 touchOnePreviousPosition = touchOne.position - touchOne.deltaPosition; + float prevTouchDeltaMag = (touchZeroPreviousPosition - touchOnePreviousPosition).magnitude; + float TouchDeltaMag = (touchZero.position - touchOne.position).magnitude; + float deltaMagDiff = prevTouchDeltaMag - TouchDeltaMag; + desiredDistance += deltaMagDiff * Time.deltaTime * zoomRate * 0.0025f * Mathf.Abs(desiredDistance); + } + + // If middle mouse and left alt are selected? ORBIT + if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Moved) + { + Vector2 touchposition = Input.GetTouch(0).deltaPosition; + xDeg += touchposition.x * xSpeed * 0.002f; + yDeg -= touchposition.y * ySpeed * 0.002f; + yDeg = ClampAngle(yDeg, yMinLimit, yMaxLimit); + + } + + desiredRotation = Quaternion.Euler(yDeg, xDeg, 0); + currentRotation = transform.rotation; + rotation = Quaternion.Lerp(currentRotation, desiredRotation, Time.deltaTime * zoomDampening); + transform.rotation = rotation; + + if (Input.GetMouseButtonDown(1)) + { + FirstPosition = Input.mousePosition; + lastOffset = targetOffset; + } + + if (Input.GetMouseButton(1)) + { + SecondPosition = Input.mousePosition; + delta = SecondPosition - FirstPosition; + targetOffset = lastOffset + transform.right * delta.x * offsetSpeed + transform.up * delta.y * offsetSpeed; + + } + + ////////Orbit Position + // affect the desired Zoom distance if we roll the scrollwheel + desiredDistance = Mathf.Clamp(desiredDistance, minDistance, maxDistance); + currentDistance = Mathf.Lerp(currentDistance, desiredDistance, Time.deltaTime * zoomDampening); + position = target.position - (rotation * Vector3.forward * currentDistance); + position = position - targetOffset; + transform.position = position; + } + + private static float ClampAngle(float angle, float min, float max) + { + if (angle < -360) + angle += 360; + if (angle > 360) + angle -= 360; + return Mathf.Clamp(angle, min, max); + } +} + diff --git a/Assets/Scripts/Camera/PlayerMovement.cs b/Assets/Scripts/Camera/PlayerMovement.cs new file mode 100644 index 0000000..901af41 --- /dev/null +++ b/Assets/Scripts/Camera/PlayerMovement.cs @@ -0,0 +1,41 @@ +using UnityEngine; +using System.Collections.Generic; +using System.Collections; + +//Script for moving a gameObject smoothly +//Usage: Attach the character controller component to the gameobject that you want to move + +namespace UnityLibary +{ + public class PlayerMovement : MonoBehaviour + { + // place the gameobject that you want to move to the controller placeholder + public CharacterController controller; + + public float speed = 5f; + + void Update() + { + float x = Input.GetAxis("Horizontal"); + float z = Input.GetAxis("Vertical"); + + Vector3 move = transform.right * x + transform.forward * z; + + controller.Move(move * speed * Time.deltaTime); + + //Rotate clockwise + if (Input.GetKey(KeyCode.E)) + { + transform.RotateAround(transform.position, Vector3.up, 100 * Time.deltaTime); + } + + // Rotate anticlockwise + if (Input.GetKey(KeyCode.Q)) + { + transform.RotateAround(transform.position, -Vector3.up, 100 * Time.deltaTime); + } + + } + + } +} diff --git a/Assets/Scripts/Camera/PlayerMovement.cs.meta b/Assets/Scripts/Camera/PlayerMovement.cs.meta new file mode 100644 index 0000000..058aaca --- /dev/null +++ b/Assets/Scripts/Camera/PlayerMovement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1dd692b70ceca4439544f5a72fa7f70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Camera/SimpleSmoothMouseLookNewInput.cs b/Assets/Scripts/Camera/SimpleSmoothMouseLookNewInput.cs new file mode 100644 index 0000000..2864d24 --- /dev/null +++ b/Assets/Scripts/Camera/SimpleSmoothMouseLookNewInput.cs @@ -0,0 +1,260 @@ +using UnityEngine; +using UnityEngine.InputSystem; + +namespace UnityLibrary +{ + public class SimpleSmoothMouseLookNewInput : MonoBehaviour + { + [Header("Mouse Look")] + [Tooltip("Hold right mouse button down to rotate")] + public bool rotateWithRightMouse = true; + public Vector2 clampInDegrees = new Vector2(360, 180); + public bool lockCursor = false; + public Vector2 sensitivity = new Vector2(2, 2); + public Vector2 smoothing = new Vector2(3, 3); + Vector2 targetDirection; + Vector2 _mouseAbsolute; + Vector2 _smoothMouse; + + [Header("Camera Pan")] + public bool enablePanning = true; + public float panSpeed = 40; + + [Header("Camera Orbit")] + public bool enableOrbit = false; + public KeyCode orbitKey = KeyCode.LeftAlt; + [Tooltip("Set this to -1, if you dont require any mousebutton pressed for orbit")] + public int orbitMouseKey = 2; + public float orbitXSpeed = 400; + public float orbitYSpeed = 400; + Vector3 targetposition; + float orbitDistance = 10.0f; + private float xDeg = 0.0f; + private float yDeg = 0.0f; + + [Header("Camera Zoom")] + public bool scrollZoom = false; + public int zoomRate = 40; + public float zoomDampening = 5.0f; + public KeyCode zoomExtends = KeyCode.F; // whole cloud + + [Header("Camera Fly")] + public float flySpeed = 20; // default speed + public float accelerationRatio = 5; // when shift is pressed + public float slowDownRatio = 0.5f; // when ctrl is pressed + public KeyCode increaseSpeedKey = KeyCode.LeftShift; + public KeyCode decreaseSpeedKey = KeyCode.LeftControl; + public KeyCode upKey = KeyCode.E; + public KeyCode downKey = KeyCode.Q; + public string horizontalAxisKey = "Horizontal"; + public string verticalAxisKey = "Vertical"; + + public bool moveRoot = false; + Transform moveTransform; + + Camera cam; + + void Awake() + { + cam = Camera.main; + + moveTransform = moveRoot ? transform.root : transform; + } + + private void Start() + { + + } + + private void OnEnable() + { + // Set target direction to the camera's initial orientation, NOTE need to do this if you move camera manually elsewhere + targetDirection = transform.localRotation.eulerAngles; + } + + void Update() + { + Framing(); + if (MouseOrbit() == true) return; + MouseLook(); + CameraFly(); + } + + void Framing() + { + + } + + public void GetOrbitTarget() + { + targetposition = transform.forward * orbitDistance; + //orbitDistance = Vector3.Distance(transform.position, targetposition); + } + + + bool MouseOrbit() + { + // early exit, if not enabled, or no key is pressed, or if mousekey is set, but not pressed + if (enableOrbit == false || Input.GetKey(orbitKey) == false || (orbitMouseKey > -1 == true && Input.GetMouseButton(orbitMouseKey) == false)) + { + return false; + } + + orbitDistance += Input.GetAxis("Mouse ScrollWheel") * zoomRate * Time.deltaTime; + + xDeg = (Input.GetAxis("Mouse X") * orbitXSpeed); + yDeg = -(Input.GetAxis("Mouse Y") * orbitYSpeed); + transform.RotateAround(targetposition, Vector3.up, xDeg * Time.deltaTime); + transform.RotateAround(targetposition, transform.right, yDeg * Time.deltaTime); + + targetDirection = transform.rotation.eulerAngles; + + return true; + } + + static float ClampAngle(float angle, float min, float max) + { + if (angle < -360) + angle += 360; + if (angle > 360) + angle -= 360; + return Mathf.Clamp(angle, min, max); + } + + void MouseLook() + { + // Get raw mouse input for a cleaner reading on more sensitive mice. + //var mouseDelta = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y")); + var mouseDelta = Mouse.current.delta.ReadValue() * Time.deltaTime*3; + + // panning with middle moouse button + if (enablePanning == true) + { + //if (Input.GetMouseButton(2)) + if (Mouse.current.middleButton.isPressed) + { + transform.Translate(-mouseDelta * panSpeed * Time.deltaTime, Space.Self); + GetOrbitTarget(); // update orbit target + } + } + + // Ensure the cursor is always locked when set + if (lockCursor == true) Cursor.lockState = CursorLockMode.Locked; + + //if (rotateWithRightMouse == true && !Input.GetMouseButton(1)) return; + if (rotateWithRightMouse == true && !Mouse.current.rightButton.isPressed) return; + + // Allow the script to clamp based on a desired target value. + Quaternion targetOrientation = Quaternion.Euler(targetDirection); + + // Scale input against the sensitivity setting and multiply that against the smoothing value. + mouseDelta = Vector2.Scale(mouseDelta, new Vector2(sensitivity.x * smoothing.x, sensitivity.y * smoothing.y)); + // Interpolate mouse movement over time to apply smoothing delta. + _smoothMouse.x = Mathf.Lerp(_smoothMouse.x, mouseDelta.x, 1f / smoothing.x); + _smoothMouse.y = Mathf.Lerp(_smoothMouse.y, mouseDelta.y, 1f / smoothing.y); + // Find the absolute mouse movement value from point zero. + _mouseAbsolute += _smoothMouse; + + // Clamp and apply the local x value first, so as not to be affected by world transforms. + if (clampInDegrees.x < 360) _mouseAbsolute.x = Mathf.Clamp(_mouseAbsolute.x, -clampInDegrees.x * 0.5f, clampInDegrees.x * 0.5f); + + var xRotation = Quaternion.AngleAxis(-_mouseAbsolute.y, targetOrientation * Vector3.right); + transform.localRotation = xRotation; + + // Then clamp and apply the global y value. + if (clampInDegrees.y < 360) _mouseAbsolute.y = Mathf.Clamp(_mouseAbsolute.y, -clampInDegrees.y * 0.5f, clampInDegrees.y * 0.5f); + + var yRotation = Quaternion.AngleAxis(_mouseAbsolute.x, transform.InverseTransformDirection(Vector3.up)); + transform.localRotation *= yRotation; + transform.rotation *= targetOrientation; + } + + void CameraFly() + { + bool moved = false; + // shift was pressed down + //if (Input.GetKeyDown(increaseSpeedKey)) + if (Keyboard.current.leftShiftKey.wasPressedThisFrame) + { + flySpeed *= accelerationRatio; // increase flyspeed + } + + //if (Input.GetKeyUp(increaseSpeedKey)) + if (Keyboard.current.leftShiftKey.wasReleasedThisFrame) + { + flySpeed /= accelerationRatio; // flyspeed back to normal + } + + //if (Input.GetKeyDown(decreaseSpeedKey)) + if (Keyboard.current.leftCtrlKey.wasPressedThisFrame) + { + flySpeed *= slowDownRatio; // decrease flyspeed + } + //if (Input.GetKeyUp(decreaseSpeedKey)) + if (Keyboard.current.leftCtrlKey.wasReleasedThisFrame) + { + flySpeed /= slowDownRatio; // flyspeed back to normal + } + + var vert = -Keyboard.current.sKey.ReadValue() + Keyboard.current.wKey.ReadValue(); + if (vert != 0) + { + moveTransform.Translate(transform.forward * flySpeed * vert * Time.deltaTime, Space.World); + moved = true; + } + + var horz = -Keyboard.current.aKey.ReadValue() + Keyboard.current.dKey.ReadValue(); + if (horz != 0) + { + moveTransform.Translate(transform.right * flySpeed * horz * Time.deltaTime, Space.World); + moved = true; + } + + // annoying in editor as it works from any window +#if !UNITY_EDITOR + if (scrollZoom == true) + { + //if (Input.GetAxis("Mouse ScrollWheel") != 0) + var scrollY = Mouse.current.scroll.ReadValue().y; + if (scrollY != 0) + { + transform.Translate(transform.forward * zoomRate * scrollY * Time.deltaTime, Space.World); + moved = true; + } + } +#endif + + //if (Input.GetKey(upKey)) + if (Keyboard.current.qKey.isPressed) + { + moveTransform.Translate(moveTransform.up * flySpeed * 0.5f * Time.deltaTime, Space.World); + moved = true; + } + //else if (Input.GetKey(downKey)) + else if (Keyboard.current.eKey.isPressed) + { + moveTransform.Translate(-moveTransform.up * flySpeed * 0.5f * Time.deltaTime, Space.World); + moved = true; + } + if (moved == true) GetOrbitTarget(); + } // CameraFly() + + // https://stackoverflow.com/questions/25368259/making-an-object-fit-exactly-inside-the-camera-frustum-in-three-js + public void FocusCameraOnGameObject(Bounds b) + { + var height = b.max.y; + var width = b.max.x - b.min.x; + var vertical_FOV = cam.fieldOfView * (Mathf.PI / 180f); + var aspectRatio = Screen.width / Screen.height; + var horizontal_FOV = 2 * Mathf.Atan(Mathf.Tan(vertical_FOV / 2) * aspectRatio); + var distance_vertical = height / (2 * Mathf.Tan(vertical_FOV / 2)); + var distance_horizontal = width / (2 * Mathf.Tan(horizontal_FOV / 2)); + var z_distance = distance_vertical >= distance_horizontal ? distance_vertical : distance_horizontal; + // current viewdir + var viewDir = transform.forward; + // move camera so that object is in front + cam.transform.position = b.center - viewDir * z_distance * 2; + } + + } // class +} // namespace diff --git a/Assets/Scripts/Docs/Graphics/Graphics_Blit.cs b/Assets/Scripts/Docs/Graphics/Graphics_Blit.cs index 6760898..805f4ba 100644 --- a/Assets/Scripts/Docs/Graphics/Graphics_Blit.cs +++ b/Assets/Scripts/Docs/Graphics/Graphics_Blit.cs @@ -26,7 +26,7 @@ void OnPostRender() { // Copies source texture into destination render texture with a shader // Destination RenderTexture is null to blit directly to screen - Graphics.Blit(displayTexture, null, mat); + Graphics.Blit(displayTexture, null as RenderTexture, mat); } } } \ No newline at end of file diff --git a/Assets/Editor.meta b/Assets/Scripts/Docs/Mesh.meta similarity index 67% rename from Assets/Editor.meta rename to Assets/Scripts/Docs/Mesh.meta index 9c21977..836722b 100644 --- a/Assets/Editor.meta +++ b/Assets/Scripts/Docs/Mesh.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 -guid: 710e084e33216854daedf076c029ff31 +guid: dfe48b7debc5f9c4a9ab9b77ddb37e1a folderAsset: yes -timeCreated: 1501225949 +timeCreated: 1594958191 licenseType: Free DefaultImporter: userData: diff --git a/Assets/Scripts/Docs/Mesh/MeshExample.cs.meta b/Assets/Scripts/Docs/Mesh/MeshExample.cs.meta new file mode 100644 index 0000000..24cd5ab --- /dev/null +++ b/Assets/Scripts/Docs/Mesh/MeshExample.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 3292a9e48eeb600418703ec125a8ef82 +timeCreated: 1594958193 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Docs/UnityEngine/CullingGroupExample.cs.meta b/Assets/Scripts/Docs/UnityEngine/CullingGroupExample.cs.meta new file mode 100644 index 0000000..3f8f80c --- /dev/null +++ b/Assets/Scripts/Docs/UnityEngine/CullingGroupExample.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a0f7bca99a1816244927583c60c3e022 +timeCreated: 1594958194 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Docs/UnityEngine/NavMeshAgentExample.cs b/Assets/Scripts/Docs/UnityEngine/NavMeshAgentExample.cs new file mode 100644 index 0000000..bae418b --- /dev/null +++ b/Assets/Scripts/Docs/UnityEngine/NavMeshAgentExample.cs @@ -0,0 +1,23 @@ +// made with chatGPT + +using UnityEngine; +using UnityEngine.AI; + +namespace UnityLibrary +{ + public class NavMeshAgentExample : MonoBehaviour + { + public Transform target; + + private NavMeshAgent agent; + + void Start() + { + // Get the NavMeshAgent component on this game object + agent = GetComponent(); + + // Set the destination of the NavMeshAgent to the target Transform + agent.destination = target.position; + } + } +} diff --git a/Assets/Scripts/Drawing/DrawLine.cs b/Assets/Scripts/Drawing/DrawLine.cs index 44d6c53..6ffbfbd 100644 --- a/Assets/Scripts/Drawing/DrawLine.cs +++ b/Assets/Scripts/Drawing/DrawLine.cs @@ -53,8 +53,9 @@ protected virtual void Update() } if (Input.GetMouseButton(0)) { - Vector3 mousePosition = m_Camera.ScreenToWorldPoint(Input.mousePosition); - mousePosition.z = m_LineRenderer.transform.position.z; + var pos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, lineRenderer.transform.position.z - m_Camera.transform.position.z); + Vector3 mousePosition = m_Camera.ScreenToWorldPoint(pos); + mousePosition.z = 0; if (!m_Points.Contains(mousePosition)) { m_Points.Add(mousePosition); diff --git a/Assets/Scripts/Editor/Animation.meta b/Assets/Scripts/Editor/Animation.meta new file mode 100644 index 0000000..a9393f4 --- /dev/null +++ b/Assets/Scripts/Editor/Animation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 344c9e72d34584041b61214522912d71 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Animation/LegacyAnimationCreator.meta b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator.meta new file mode 100644 index 0000000..9fc6b69 --- /dev/null +++ b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e78d070eb9ba01749a33425b62b0fe5c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs new file mode 100644 index 0000000..35f7ff2 --- /dev/null +++ b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs @@ -0,0 +1,28 @@ +using System.IO; +using UnityEditor; +using UnityEngine; + +public class LegacyAnimationCreator +{ + [MenuItem("Assets/Create/Legacy Animation", priority = 402)] + public static void CompressSelectedAnimationClips() + { + var clip = new AnimationClip(); + clip.legacy = true; + clip.name = "New Legacy Animation"; + + string path; + var selection = Selection.activeObject; + if (selection == null) + path = "Assets"; + else + path = AssetDatabase.GetAssetPath(selection.GetInstanceID()); + + path = Path.GetDirectoryName(path); + path += $"/{clip.name}.anim"; + + ProjectWindowUtil.CreateAsset(clip, path); + Selection.activeObject = clip; + EditorUtility.SetDirty(clip); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs.meta b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs.meta new file mode 100644 index 0000000..5bd716f --- /dev/null +++ b/Assets/Scripts/Editor/Animation/LegacyAnimationCreator/LegacyAnimationCreator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d536889e20faa9d47b7def4f46203b81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs b/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs new file mode 100644 index 0000000..40bf0d0 --- /dev/null +++ b/Assets/Scripts/Editor/BatchTools/CopyGameObjectNames.cs @@ -0,0 +1,59 @@ +// editor tool to copy names of selected GameObjects to clipboard as a list (so you can paste them in Excel or others..) + +using UnityEngine; +using UnityEditor; +using System.Text; +using System.Linq; +namespace UnityLibrary.Tools +{ + public class CopyGameObjectNames : EditorWindow + { + private string gameObjectNames = string.Empty; + + [MenuItem("Tools/Copy GameObject Names")] + public static void ShowWindow() + { + GetWindow("Copy GameObject Names"); + } + + private void OnGUI() + { + GUILayout.Label("Copy Names of Selected GameObjects", EditorStyles.boldLabel); + + if (GUILayout.Button("Fetch Names")) + { + FetchNames(); + } + + GUILayout.Label("GameObject Names:", EditorStyles.label); + gameObjectNames = EditorGUILayout.TextArea(gameObjectNames, GUILayout.Height(200)); + + if (GUILayout.Button("Copy to Clipboard")) + { + CopyToClipboard(); + } + } + + private void FetchNames() + { + StringBuilder sb = new StringBuilder(); + GameObject[] selectedObjects = Selection.gameObjects; + + // Sort the selected objects by their sibling index + var sortedObjects = selectedObjects.OrderBy(go => go.transform.GetSiblingIndex()).ToArray(); + + foreach (GameObject obj in sortedObjects) + { + sb.AppendLine(obj.name); + } + + gameObjectNames = sb.ToString(); + } + + private void CopyToClipboard() + { + EditorGUIUtility.systemCopyBuffer = gameObjectNames; + Debug.Log("GameObject names copied to clipboard."); + } + } +} diff --git a/Assets/Scripts/Editor/BatchTools/ReplaceCharacterInGameObjectNames.cs b/Assets/Scripts/Editor/BatchTools/ReplaceCharacterInGameObjectNames.cs new file mode 100644 index 0000000..105bc74 --- /dev/null +++ b/Assets/Scripts/Editor/BatchTools/ReplaceCharacterInGameObjectNames.cs @@ -0,0 +1,69 @@ +// editor tool to replace string from selected GameObject names + +using UnityEngine; +using UnityEditor; +using UnityEditor.SceneManagement; + +namespace UnityLibrary.Tools +{ + public class ReplaceCharacterInGameObjectNames : EditorWindow + { + private string searchString = "|"; + private string replaceString = "@"; + + [MenuItem("Tools/Replace Characters in GameObject Names")] + public static void ShowWindow() + { + GetWindow("Replace Characters"); + } + + private void OnGUI() + { + GUILayout.Label("Replace Characters in Selected GameObject Names", EditorStyles.boldLabel); + + searchString = EditorGUILayout.TextField("Search String", searchString); + replaceString = EditorGUILayout.TextField("Replace String", replaceString); + + int selectedObjectCount = Selection.gameObjects.Length; + GUILayout.Label($"Selected GameObjects: {selectedObjectCount}", EditorStyles.label); + + if (GUILayout.Button("Replace")) + { + ReplaceCharacters(); + } + } + + private void ReplaceCharacters() + { + GameObject[] selectedObjects = Selection.gameObjects; + + if (selectedObjects.Length == 0) + { + Debug.LogWarning("No GameObjects selected."); + return; + } + + // Start a new undo group + Undo.IncrementCurrentGroup(); + Undo.SetCurrentGroupName("Replace Character in GameObject Names"); + int undoGroup = Undo.GetCurrentGroup(); + + foreach (GameObject obj in selectedObjects) + { + if (obj.name.Contains(searchString)) + { + Undo.RecordObject(obj, "Replace Character in GameObject Name"); + obj.name = obj.name.Replace(searchString, replaceString); + EditorUtility.SetDirty(obj); + } + } + + // End the undo group + Undo.CollapseUndoOperations(undoGroup); + + EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); + + Debug.Log($"Replaced '{searchString}' with '{replaceString}' in the names of selected GameObjects."); + } + } +} diff --git a/Assets/Scripts/Editor/BuildProcess/PostBuildCopyEmptyFolders.cs b/Assets/Scripts/Editor/BuildProcess/PostBuildCopyEmptyFolders.cs new file mode 100644 index 0000000..186265b --- /dev/null +++ b/Assets/Scripts/Editor/BuildProcess/PostBuildCopyEmptyFolders.cs @@ -0,0 +1,54 @@ +using System.IO; +using UnityEditor; +using UnityEditor.Callbacks; +using UnityEngine; + +// copies empty StreamingAssets/ folders into build, as they are not automatically included + +namespace UnityLibrary +{ + public class PostBuildCopyEmptyFolders : MonoBehaviour + { + [PostProcessBuildAttribute(1)] + public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject) + { + // only for windows + if (target != BuildTarget.StandaloneWindows) return; + + Debug.Log("### POSTBUILD : COPY EMPTY STREAMINGASSETS-FOLDERS ###"); + Debug.Log("Build done: " + pathToBuiltProject); + + // get output root + var root = Path.GetDirectoryName(pathToBuiltProject); + var appName = Path.GetFileNameWithoutExtension(pathToBuiltProject); + + // copy empty streaming asset folders to build + var sourcePath = Application.streamingAssetsPath; + var targetPath = Path.Combine(root, appName + "_Data", "StreamingAssets"); + //Debug.Log("sourcePath= "+ sourcePath); + //Debug.Log("targetPath= " + targetPath); + CopyFolderStructure(sourcePath, targetPath); + } + + // recursive folder copier + static public void CopyFolderStructure(string sourceFolder, string destFolder) + { + if (Directory.Exists(destFolder)) + { + + } + else + { + Directory.CreateDirectory(destFolder); + } + + string[] folders = Directory.GetDirectories(sourceFolder); + foreach (string folder in folders) + { + string name = Path.GetFileName(folder); + string dest = Path.Combine(destFolder, name); + CopyFolderStructure(folder, dest); + } + } + } +} diff --git a/Assets/Scripts/Editor/ContextMenu.meta b/Assets/Scripts/Editor/ContextMenu.meta new file mode 100644 index 0000000..bbbf7ac --- /dev/null +++ b/Assets/Scripts/Editor/ContextMenu.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 07f01cf58a1140349bfac4f393266db1 +folderAsset: yes +timeCreated: 1594958190 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs new file mode 100644 index 0000000..ce3f298 --- /dev/null +++ b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs @@ -0,0 +1,71 @@ +using UnityEngine; +using UnityEditor; + +namespace UnityLibrary +{ + public class BoxColliderFitChildren : MonoBehaviour + { + [MenuItem("CONTEXT/BoxCollider/Fit to Children")] + static void FitColliderToChildren(MenuCommand command) + { + BoxCollider col = (BoxCollider)command.context; + + // Record undo + Undo.RecordObject(col.transform, "Fit Box Collider To Children"); + + // Get world-space bounds of all child meshes + var worldBounds = GetRecursiveMeshBounds(col.gameObject); + + if (worldBounds.size == Vector3.zero) + { + Debug.LogWarning("No valid meshes found to fit the BoxCollider."); + return; + } + + // Convert world-space center to local space + Vector3 localCenter = col.transform.InverseTransformPoint(worldBounds.center); + + // Convert world-space size to local space + Vector3 localSize = col.transform.InverseTransformVector(worldBounds.size); + + // Ensure size is positive + localSize = new Vector3(Mathf.Abs(localSize.x), Mathf.Abs(localSize.y), Mathf.Abs(localSize.z)); + + // Fix potential center flipping + if (Vector3.Dot(col.transform.right, Vector3.right) < 0) + { + localCenter.x = -localCenter.x; + } + if (Vector3.Dot(col.transform.up, Vector3.up) < 0) + { + localCenter.y = -localCenter.y; + } + if (Vector3.Dot(col.transform.forward, Vector3.forward) < 0) + { + localCenter.z = -localCenter.z; + } + + // Apply to collider + col.center = localCenter; + col.size = localSize; + } + + public static Bounds GetRecursiveMeshBounds(GameObject go) + { + Renderer[] renderers = go.GetComponentsInChildren(); + + if (renderers.Length == 0) + return new Bounds(); + + // Start with the first renderer’s bounds in world space + Bounds worldBounds = renderers[0].bounds; + + for (int i = 1; i < renderers.Length; i++) + { + worldBounds.Encapsulate(renderers[i].bounds); + } + + return worldBounds; + } + } +} diff --git a/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs.meta b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs.meta new file mode 100644 index 0000000..08e2df8 --- /dev/null +++ b/Assets/Scripts/Editor/ContextMenu/BoxColliderFitChildren.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 687fde9d16dc6b04e934c421806d4abb +timeCreated: 1594958194 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/ContextMenu/CreateOutlineForPanelEditor.cs.meta b/Assets/Scripts/Editor/ContextMenu/CreateOutlineForPanelEditor.cs.meta new file mode 100644 index 0000000..e5d8f63 --- /dev/null +++ b/Assets/Scripts/Editor/ContextMenu/CreateOutlineForPanelEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ea3d3337d55b5bc42a40dff5480adca0 +timeCreated: 1594958194 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/ContextMenu/GetVideoAspectRatioEditor.cs b/Assets/Scripts/Editor/ContextMenu/GetVideoAspectRatioEditor.cs index 23c6600..b15992c 100644 --- a/Assets/Scripts/Editor/ContextMenu/GetVideoAspectRatioEditor.cs +++ b/Assets/Scripts/Editor/ContextMenu/GetVideoAspectRatioEditor.cs @@ -19,7 +19,12 @@ static void DoubleMass(MenuCommand command) Debug.LogError("No videoclip assigned.."); return; } +#if UNITY_2017 + float aspectRatioY = v.texture.height / (float)v.texture.width; +#else float aspectRatioY = v.height / (float)v.width; +#endif + // record undo Undo.RecordObject(v.transform, "Set scale"); diff --git a/Assets/Scripts/Editor/ContextMenu/GetVideoAspectRatioEditor.cs.meta b/Assets/Scripts/Editor/ContextMenu/GetVideoAspectRatioEditor.cs.meta new file mode 100644 index 0000000..4aa5605 --- /dev/null +++ b/Assets/Scripts/Editor/ContextMenu/GetVideoAspectRatioEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: acde12b67d212ae42ab65f73b96ffd59 +timeCreated: 1594958194 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/ContextMenu/LineRendererToLocalSpace.cs b/Assets/Scripts/Editor/ContextMenu/LineRendererToLocalSpace.cs new file mode 100644 index 0000000..6cc6cc7 --- /dev/null +++ b/Assets/Scripts/Editor/ContextMenu/LineRendererToLocalSpace.cs @@ -0,0 +1,60 @@ +// converts line renderer points from world space to local space + +using UnityEngine; +using UnityEditor; +using UnityEditor.SceneManagement; + +namespace UnityLibrary.ContextMenu +{ + public static class LineRendererToLocalSpace + { + private const string MenuPath = "CONTEXT/LineRenderer/Convert Points To Local Space"; + + [MenuItem(MenuPath, true)] + private static bool Validate(MenuCommand command) + { + return command != null && command.context is LineRenderer; + } + + [MenuItem(MenuPath)] + private static void Convert(MenuCommand command) + { + var lr = (LineRenderer)command.context; + if (lr == null) return; + + int count = lr.positionCount; + if (count == 0) return; + + Transform t = lr.transform; + + Undo.RecordObject(lr, "Convert LineRenderer To Local Space"); + + // Get current positions in world space no matter what mode it's in. + Vector3[] world = new Vector3[count]; + if (lr.useWorldSpace) + { + lr.GetPositions(world); + } + else + { + Vector3[] local = new Vector3[count]; + lr.GetPositions(local); + for (int i = 0; i < count; i++) + world[i] = t.TransformPoint(local[i]); + } + + // Convert world -> local, switch mode, write back. + Vector3[] newLocal = new Vector3[count]; + for (int i = 0; i < count; i++) + newLocal[i] = t.InverseTransformPoint(world[i]); + + lr.useWorldSpace = false; + lr.SetPositions(newLocal); + + EditorUtility.SetDirty(lr); + + if (!Application.isPlaying) + EditorSceneManager.MarkSceneDirty(lr.gameObject.scene); + } + } +} diff --git a/Assets/Scripts/Editor/ContextMenu/LineRendererToWorldSpace.cs b/Assets/Scripts/Editor/ContextMenu/LineRendererToWorldSpace.cs new file mode 100644 index 0000000..90cc2d2 --- /dev/null +++ b/Assets/Scripts/Editor/ContextMenu/LineRendererToWorldSpace.cs @@ -0,0 +1,54 @@ +// converts LineRenderer points from local space to world space via context menu in Unity Editor + +using UnityEngine; +using UnityEditor; +using UnityEditor.SceneManagement; + +namespace UnityLibrary.ContextMenu +{ + public static class LineRendererToWorldSpace + { + private const string MenuPath = "CONTEXT/LineRenderer/Convert Points To World Space"; + + [MenuItem(MenuPath, true)] + private static bool Validate(MenuCommand command) + { + return command != null && command.context is LineRenderer; + } + + [MenuItem(MenuPath)] + private static void Convert(MenuCommand command) + { + var lr = (LineRenderer)command.context; + if (lr == null) return; + + if (lr.useWorldSpace) + { + Debug.Log("LineRenderer is already using World Space."); + return; + } + + int count = lr.positionCount; + if (count == 0) return; + + Transform t = lr.transform; + + Undo.RecordObject(lr, "Convert LineRenderer To World Space"); + + Vector3[] local = new Vector3[count]; + lr.GetPositions(local); + + Vector3[] world = new Vector3[count]; + for (int i = 0; i < count; i++) + world[i] = t.TransformPoint(local[i]); + + lr.useWorldSpace = true; + lr.SetPositions(world); + + EditorUtility.SetDirty(lr); + + if (!Application.isPlaying) + EditorSceneManager.MarkSceneDirty(lr.gameObject.scene); + } + } +} diff --git a/Assets/Scripts/Editor/ContextMenu/SetBoxColliderToUI.cs b/Assets/Scripts/Editor/ContextMenu/SetBoxColliderToUI.cs new file mode 100644 index 0000000..6b58cf5 --- /dev/null +++ b/Assets/Scripts/Editor/ContextMenu/SetBoxColliderToUI.cs @@ -0,0 +1,32 @@ +// Tries to move BoxCollider(3D)-component to match UI panel/image position, by adjusting collider pivot value + +using UnityEngine; +using UnityEditor; + +namespace UnityLibrary +{ + public class SetBoxColliderToUI : MonoBehaviour + { + [MenuItem("CONTEXT/BoxCollider/Match Position to UI")] + static void FixPosition(MenuCommand command) + { + BoxCollider b = (BoxCollider)command.context; + + // record undo + Undo.RecordObject(b.transform, "Set Box Collider To UI"); + + // fix pos from Pivot + var r = b.gameObject.GetComponent(); + if (r == null) return; + + //Debug.Log("pivot "+r.pivot); + + var center = b.center; + + center.x = 0.5f - r.pivot.x; + center.y = 0.5f - r.pivot.y; + + b.center = center; + } + } +} diff --git a/Assets/Scripts/Editor/ContextMenu/SetBoxColliderToUI.cs.meta b/Assets/Scripts/Editor/ContextMenu/SetBoxColliderToUI.cs.meta new file mode 100644 index 0000000..bbc5ca4 --- /dev/null +++ b/Assets/Scripts/Editor/ContextMenu/SetBoxColliderToUI.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9581bb1267ccd9e48b11127704331d49 +timeCreated: 1594958194 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/CustomInspector/CustomRectTransformCopyInspector.cs b/Assets/Scripts/Editor/CustomInspector/CustomRectTransformCopyInspector.cs new file mode 100644 index 0000000..0a5de2b --- /dev/null +++ b/Assets/Scripts/Editor/CustomInspector/CustomRectTransformCopyInspector.cs @@ -0,0 +1,114 @@ +#if UNITY_EDITOR +using System; +using System.Reflection; +using UnityEngine; + +namespace UnityEditor +{ + [CustomEditor(typeof(RectTransform), true)] + [CanEditMultipleObjects] + public class CustomRectTransformCopyInspector : Editor + { + // Unity's built-in editor + Editor defaultEditor = null; + RectTransform rectTransform; + + private static RectTransformData copiedData; + + void OnEnable() + { + // Use reflection to get the default Unity RectTransform editor + defaultEditor = Editor.CreateEditor(targets, Type.GetType("UnityEditor.RectTransformEditor, UnityEditor")); + rectTransform = target as RectTransform; + } + + void OnDisable() + { + // Destroy the default editor to avoid memory leaks + if (defaultEditor != null) + { + MethodInfo disableMethod = defaultEditor.GetType().GetMethod("OnDisable", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + if (disableMethod != null) + disableMethod.Invoke(defaultEditor, null); + + DestroyImmediate(defaultEditor); + } + } + + public override void OnInspectorGUI() + { + // Draw Unity's default RectTransform Inspector + defaultEditor.OnInspectorGUI(); + + // Add Copy and Paste buttons + EditorGUILayout.Space(); + GUILayout.BeginHorizontal(); + + if (GUILayout.Button("C", GUILayout.Width(30))) // Copy + { + CopyRectTransform(rectTransform); + } + + if (GUILayout.Button("P", GUILayout.Width(30))) // Paste + { + PasteRectTransform(rectTransform); + } + + GUILayout.EndHorizontal(); + } + + private void CopyRectTransform(RectTransform rectTransform) + { + copiedData = new RectTransformData(rectTransform); + Debug.Log("RectTransform copied!"); + } + + private void PasteRectTransform(RectTransform rectTransform) + { + if (copiedData == null) + { + Debug.LogWarning("No RectTransform data to paste!"); + return; + } + + Undo.RecordObject(rectTransform, "Paste RectTransform"); + + copiedData.ApplyTo(rectTransform); + Debug.Log("RectTransform pasted!"); + + EditorUtility.SetDirty(rectTransform); + } + + private class RectTransformData + { + public Vector2 anchorMin; + public Vector2 anchorMax; + public Vector2 anchoredPosition; + public Vector2 sizeDelta; + public Vector2 pivot; + public Quaternion rotation; + + public RectTransformData(RectTransform rectTransform) + { + anchorMin = rectTransform.anchorMin; + anchorMax = rectTransform.anchorMax; + anchoredPosition = rectTransform.anchoredPosition; + sizeDelta = rectTransform.sizeDelta; + pivot = rectTransform.pivot; + rotation = rectTransform.rotation; + } + + public void ApplyTo(RectTransform rectTransform) + { + rectTransform.anchorMin = anchorMin; + rectTransform.anchorMax = anchorMax; + rectTransform.anchoredPosition = anchoredPosition; + rectTransform.sizeDelta = sizeDelta; + rectTransform.pivot = pivot; + rectTransform.rotation = rotation; + } + } + } +} +#endif diff --git a/Assets/Scripts/Editor/CustomInspector/CustomRectTransformInspector.cs b/Assets/Scripts/Editor/CustomInspector/CustomRectTransformInspector.cs new file mode 100644 index 0000000..02c51af --- /dev/null +++ b/Assets/Scripts/Editor/CustomInspector/CustomRectTransformInspector.cs @@ -0,0 +1,294 @@ +// source https://gist.github.com/GieziJo/f80bcb24c4caa68ebfb204148ccd4b18 +// =============================== +// AUTHOR : J. Giezendanner +// CREATE DATE : 12.03.2020 +// MODIFIED DATE : +// PURPOSE : Adds helper functions to the RectTransform to align the rect to the anchors and vise-versa +// SPECIAL NOTES : Sources for certain informations: +// Display anchors gizmos: +// https://forum.unity.com/threads/recttransform-custom-editor-ontop-of-unity-recttransform-custom-editor.455925/ +// Draw default inspector: +// https://forum.unity.com/threads/extending-instead-of-replacing-built-in-inspectors.407612/ +// =============================== +// Change History: +//================================== + +#if UNITY_EDITOR +using System; +using System.Reflection; +using UnityEditor.SceneManagement; +using UnityEngine; + + +namespace UnityEditor +{ + [CustomEditor(typeof(RectTransform), true)] + [CanEditMultipleObjects] + public class CustomRectTransformInspector : Editor + { + //Unity's built-in editor + Editor defaultEditor = null; + RectTransform rectTransform; + + bool rect2Anchors_foldout = false; + bool anchors2Rect_foldout = false; + bool rect2Anchors__previousState = false; + bool anchors2Rect_previousState = false; + + private bool playerPrefsChecked = false; + + void OnEnable() + { + //When this inspector is created, also create the built-in inspector + defaultEditor = Editor.CreateEditor(targets, Type.GetType("UnityEditor.RectTransformEditor, UnityEditor")); + rectTransform = target as RectTransform; + } + + void OnDisable() + { + //When OnDisable is called, the default editor we created should be destroyed to avoid memory leakage. + //Also, make sure to call any required methods like OnDisable + + if (defaultEditor != null) + { + MethodInfo disableMethod = defaultEditor.GetType().GetMethod("OnDisable", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + if (disableMethod != null) + disableMethod.Invoke(defaultEditor, null); + DestroyImmediate(defaultEditor); + } + } + + void checkPlayerPrefs() + { + rect2Anchors_foldout = PlayerPrefs.GetInt("giezi_tools_rect2Anchors_foldout_bool", 0) != 0; + anchors2Rect_foldout = PlayerPrefs.GetInt("giezi_tools_anchors2Rect_foldout_bool", 0) != 0; + + rect2Anchors__previousState = rect2Anchors_foldout; + anchors2Rect_previousState = anchors2Rect_foldout; + } + + + public override void OnInspectorGUI() + { + if (!playerPrefsChecked) + { + checkPlayerPrefs(); + playerPrefsChecked = true; + } + + defaultEditor.OnInspectorGUI(); + + + if (rectTransform.parent != null) + { + var centerButtonStyle = new GUIStyle(GUI.skin.button); + centerButtonStyle.fontStyle = FontStyle.Bold; + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Helper Functions", EditorStyles.boldLabel); + + rect2Anchors_foldout = EditorGUILayout.Foldout(rect2Anchors_foldout, "Set Rect to Anchors"); + + if (rect2Anchors_foldout) + { + GUILayout.BeginHorizontal(); + GUILayout.BeginVertical(); + if (GUILayout.Button("Top Left")) + setRectValue("topLeft"); + if (GUILayout.Button("Left")) + setRectValue("left"); + if (GUILayout.Button("Bottom Left")) + setRectValue("bottomLeft"); + GUILayout.EndVertical(); + GUILayout.BeginVertical(); + if (GUILayout.Button("Top")) + setRectValue("top"); + if (GUILayout.Button("All", centerButtonStyle)) + setRectValue("all"); + if (GUILayout.Button("Bottom")) + setRectValue("bottom"); + GUILayout.EndVertical(); + GUILayout.BeginVertical(); + if (GUILayout.Button("Top Right")) + setRectValue("topRight"); + if (GUILayout.Button("Right")) + setRectValue("right"); + if (GUILayout.Button("Bottom Right")) + setRectValue("bottomRight"); + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + } + + anchors2Rect_foldout = EditorGUILayout.Foldout(anchors2Rect_foldout, "Set Anchors to Rect"); + + if (anchors2Rect_foldout) + { + GUILayout.BeginHorizontal(); + GUILayout.BeginVertical(); + if (GUILayout.Button("Top Left")) + setAnchorsToRect("topLeft"); + if (GUILayout.Button("Left")) + setAnchorsToRect("left"); + if (GUILayout.Button("Bottom Left")) + setAnchorsToRect("bottomLeft"); + GUILayout.EndVertical(); + GUILayout.BeginVertical(); + if (GUILayout.Button("Top")) + setAnchorsToRect("top"); + if (GUILayout.Button("All", centerButtonStyle)) + setAnchorsToRect("all"); + if (GUILayout.Button("Bottom")) + setAnchorsToRect("bottom"); + GUILayout.EndVertical(); + GUILayout.BeginVertical(); + if (GUILayout.Button("Top Right")) + setAnchorsToRect("topRight"); + if (GUILayout.Button("Right")) + setAnchorsToRect("right"); + if (GUILayout.Button("Bottom Right")) + setAnchorsToRect("bottomRight"); + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + } + + + if (rect2Anchors_foldout != rect2Anchors__previousState) + { + rect2Anchors__previousState = rect2Anchors_foldout; + PlayerPrefs.SetInt("giezi_tools_rect2Anchors_foldout_bool", rect2Anchors_foldout ? 1 : 0); + } + + if (anchors2Rect_foldout != anchors2Rect_previousState) + { + anchors2Rect_previousState = anchors2Rect_foldout; + PlayerPrefs.SetInt("giezi_tools_anchors2Rect_foldout_bool", anchors2Rect_foldout ? 1 : 0); + } + } + } + + + private void OnSceneGUI() + { + MethodInfo onSceneGUI_Method = defaultEditor.GetType() + .GetMethod("OnSceneGUI", BindingFlags.NonPublic | BindingFlags.Instance); + onSceneGUI_Method.Invoke(defaultEditor, null); + } + + + private void setAnchorsToRect(string field) + { + Vector2 anchorMax = new Vector2(); + Vector2 anchorMin = new Vector2(); + var parent = rectTransform.parent; + anchorMin.x = rectTransform.offsetMin.x / parent.GetComponent().rect.size.x; + anchorMin.y = rectTransform.offsetMin.y / parent.GetComponent().rect.size.y; + anchorMax.x = rectTransform.offsetMax.x / parent.GetComponent().rect.size.x; + anchorMax.y = rectTransform.offsetMax.y / parent.GetComponent().rect.size.y; + + + switch (field) + { + case "topLeft": + anchorMax.x = 0; + rectTransform.anchorMax += anchorMax; + rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, 0); + + anchorMin.y = 0; + rectTransform.anchorMin += anchorMin; + rectTransform.offsetMin = new Vector2(0, rectTransform.offsetMin.y); + break; + case "top": + anchorMax.x = 0; + rectTransform.anchorMax += anchorMax; + rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, 0); + break; + case "topRight": + rectTransform.anchorMax += anchorMax; + rectTransform.offsetMax = Vector2.zero; + break; + case "bottomLeft": + rectTransform.anchorMin += anchorMin; + rectTransform.offsetMin = Vector2.zero; + break; + case "bottom": + anchorMin.x = 0; + rectTransform.anchorMin += anchorMin; + rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, 0); + break; + case "bottomRight": + anchorMin.x = 0; + rectTransform.anchorMin += anchorMin; + rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, 0); + anchorMax.y = 0; + rectTransform.anchorMax += anchorMax; + rectTransform.offsetMax = new Vector2(0, rectTransform.offsetMax.y); + break; + case "left": + anchorMin.y = 0; + rectTransform.anchorMin += anchorMin; + rectTransform.offsetMin = new Vector2(0, rectTransform.offsetMin.y); + break; + case "right": + anchorMax.y = 0; + rectTransform.anchorMax += anchorMax; + rectTransform.offsetMax = new Vector2(0, rectTransform.offsetMax.y); + break; + case "all": + rectTransform.anchorMax += anchorMax; + rectTransform.anchorMin += anchorMin; + rectTransform.offsetMin = Vector2.zero; + rectTransform.offsetMax = Vector2.zero; + break; + } + + handleChange(); + } + + + private void setRectValue(string field) + { + switch (field) + { + case "topLeft": + rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, 0); + rectTransform.offsetMin = new Vector2(0, rectTransform.offsetMin.y); + break; + case "top": + rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x, 0); + break; + case "topRight": + rectTransform.offsetMax = Vector2.zero; + break; + case "bottomLeft": + rectTransform.offsetMin = Vector2.zero; + break; + case "bottom": + rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, 0); + break; + case "bottomRight": + rectTransform.offsetMin = new Vector2(rectTransform.offsetMin.x, 0); + rectTransform.offsetMax = new Vector2(0, rectTransform.offsetMax.y); + break; + case "left": + rectTransform.offsetMin = new Vector2(0, rectTransform.offsetMin.y); + break; + case "right": + rectTransform.offsetMax = new Vector2(0, rectTransform.offsetMax.y); + break; + case "all": + rectTransform.offsetMin = new Vector2(0, 0); + rectTransform.offsetMax = new Vector2(0, 0); + break; + } + + handleChange(); + } + + private void handleChange() + { + EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); + } + } +} +#endif diff --git a/Assets/Scripts/Editor/CustomInspector/TransformEditor.cs b/Assets/Scripts/Editor/CustomInspector/TransformEditor.cs new file mode 100644 index 0000000..152989e --- /dev/null +++ b/Assets/Scripts/Editor/CustomInspector/TransformEditor.cs @@ -0,0 +1,436 @@ +// source https://gist.github.com/unitycoder/e5e6384f087639c0d9edc93aa3820468 + +using UnityEngine; +using UnityEditor; + +namespace OddTales.Framework.Core.EditorExtension +{ + /// + /// Custom inspector for Transform component. Using only DrawDefaultInspector would give different display. + /// Script based on Unity wiki implementation : https://wiki.unity3d.com/index.php/TransformInspector + /// Buttons to reset, copy, paste Transform values. + /// Context menu to round/truncate values, hide/show tools. + /// + [CanEditMultipleObjects, CustomEditor(typeof(Transform))] + public class TransformEditor : Editor + { + private const float FIELD_WIDTH = 212.0f; + private const bool WIDE_MODE = true; + + private const float POSITION_MAX = 100000.0f; + + private static GUIContent positionGUIContent = new GUIContent(LocalString("Position")); + private static GUIContent rotationGUIContent = new GUIContent(LocalString("Rotation")); + private static GUIContent scaleGUIContent = new GUIContent(LocalString("Scale")); + + private static string positionWarningText = LocalString("Due to floating-point precision limitations, it is recommended to bring the world coordinates of the GameObject within a smaller range."); + + private SerializedProperty positionProperty, rotationProperty, scaleProperty; + + private static Vector3? positionClipboard = null; + private static Quaternion? rotationClipboard = null; + private static Vector3? scaleClipboard = null; + + private const string SHOW_TOOLS_KEY = "TransformEditor_ShowTools"; + private const string SHOW_RESET_TOOLS_KEY = "TransformEditor_ShowResetTools"; + private const string SHOW_PASTE_TOOLS_KEY = "TransformEditor_ShowPasteTools"; + private const string SHOW_ADVANCED_PASTE_TOOLS_KEY = "TransformEditor_ShowAdvancedPasteTools"; + private const string SHOW_CLIPBOARD_INFORMATIONS_KEY = "TransformEditor_ShowClipboardInformations"; + private const string SHOW_SHORTCUTS_KEY = "TransformEditor_ShowHelpbox"; + + +#if UNITY_2017_3_OR_NEWER + private static System.Reflection.MethodInfo getLocalizedStringMethod; +#endif + + + /// Get translated Transform label + private static string LocalString(string text) + { +#if UNITY_2017_3_OR_NEWER + // Since Unity 2017.3, static class LocalizationDatabase is no longer public. Need to use reflection to access it. + if (getLocalizedStringMethod == null) + { + System.Reflection.Assembly assembly = typeof(UnityEditor.EditorWindow).Assembly; + System.Type localizationDatabaseType = assembly.GetType("UnityEditor.LocalizationDatabase"); + + getLocalizedStringMethod = localizationDatabaseType.GetMethod("GetLocalizedString"); + } + + return (string)getLocalizedStringMethod.Invoke(null, new object[] { text }); +#else + return LocalizationDatabase.GetLocalizedString(text); +#endif + } + + public void OnEnable() + { + positionProperty = serializedObject.FindProperty("m_LocalPosition"); + rotationProperty = serializedObject.FindProperty("m_LocalRotation"); + scaleProperty = serializedObject.FindProperty("m_LocalScale"); + + // Init options + if (!EditorPrefs.HasKey(SHOW_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_TOOLS_KEY, true); + if (!EditorPrefs.HasKey(SHOW_RESET_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_RESET_TOOLS_KEY, true); + if (!EditorPrefs.HasKey(SHOW_PASTE_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_PASTE_TOOLS_KEY, true); + if (!EditorPrefs.HasKey(SHOW_ADVANCED_PASTE_TOOLS_KEY)) EditorPrefs.SetBool(SHOW_ADVANCED_PASTE_TOOLS_KEY, true); + if (!EditorPrefs.HasKey(SHOW_CLIPBOARD_INFORMATIONS_KEY)) EditorPrefs.SetBool(SHOW_CLIPBOARD_INFORMATIONS_KEY, true); + if (!EditorPrefs.HasKey(SHOW_SHORTCUTS_KEY)) EditorPrefs.SetBool(SHOW_SHORTCUTS_KEY, true); + } + + + public override void OnInspectorGUI() + { + Rect beginRect = GUILayoutUtility.GetRect(0, 0); + + EditorGUIUtility.wideMode = TransformEditor.WIDE_MODE; + EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth - TransformEditor.FIELD_WIDTH; // align field to right of inspector + + serializedObject.Update(); + + EditorGUIUtility.labelWidth = 60; // To allow float fields to expand when inspector width is increased + + // Position GUI + EditorGUILayout.BeginHorizontal(); + PositionPropertyField(positionProperty, positionGUIContent); // Note : Can't add generic menu if we use EditorGUILayout.PropertyField instead + if (EditorPrefs.GetBool(SHOW_TOOLS_KEY) && EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY)) + { + if (GUILayout.Button("Reset", GUILayout.Width(50))) + { + Undo.RecordObjects(targets, "Reset Positions"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).localPosition = Vector3.zero; + } + GUI.FocusControl(null); + } + } + EditorGUILayout.EndHorizontal(); + + // Rotation GUI + EditorGUILayout.BeginHorizontal(); + RotationPropertyField(rotationProperty, rotationGUIContent); // Note : Can't add generic menu if we use EditorGUILayout.PropertyField instead + if (EditorPrefs.GetBool(SHOW_TOOLS_KEY) && EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY)) + { + if (GUILayout.Button("Reset", GUILayout.Width(50))) + { + Undo.RecordObjects(targets, "Reset Rotations"); + for (int i = 0; i < targets.Length; i++) + { + TransformUtils.SetInspectorRotation(((Transform)targets[i]), Vector3.zero); + } + GUI.FocusControl(null); + } + + } + EditorGUILayout.EndHorizontal(); + + // Scale GUI + EditorGUILayout.BeginHorizontal(); + ScalePropertyField(scaleProperty, scaleGUIContent); // Note : Can't add generic menu if we use EditorGUILayout.PropertyField instead + if (EditorPrefs.GetBool(SHOW_TOOLS_KEY) && EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY)) + { + if (GUILayout.Button("Reset", GUILayout.Width(50))) + { + Undo.RecordObjects(targets, "Reset Scales"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).localScale = Vector3.one; + } + GUI.FocusControl(null); + } + } + EditorGUILayout.EndHorizontal(); + + + if (!ValidatePosition(((Transform)target).position)) EditorGUILayout.HelpBox(positionWarningText, MessageType.Warning); // Display floating-point warning message if values are too high + + if (EditorPrefs.GetBool(SHOW_TOOLS_KEY)) + { + // Paste Tools GUI + if (EditorPrefs.GetBool(SHOW_PASTE_TOOLS_KEY)) + { + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Copy")) + { + positionClipboard = ((Transform)target).localPosition; + rotationClipboard = ((Transform)target).localRotation; + scaleClipboard = ((Transform)target).localScale; + } + + if (!positionClipboard.HasValue) EditorGUI.BeginDisabledGroup(true); + if (GUILayout.Button("Paste")) + { + Undo.RecordObjects(targets, "Paste Clipboard Values"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).localPosition = positionClipboard.Value; + ((Transform)targets[i]).localRotation = rotationClipboard.Value; + ((Transform)targets[i]).localScale = scaleClipboard.Value; + } + GUI.FocusControl(null); + } + if (!positionClipboard.HasValue) EditorGUI.EndDisabledGroup(); + GUILayout.EndHorizontal(); + } + + // Advanced Paste Tools GUI + if (EditorPrefs.GetBool(SHOW_ADVANCED_PASTE_TOOLS_KEY)) + { + GUILayout.BeginHorizontal(); + + if (!positionClipboard.HasValue) EditorGUI.BeginDisabledGroup(true); + if (GUILayout.Button("Paste position")) + { + Undo.RecordObjects(targets, "Paste Position Clipboard Value"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).localPosition = positionClipboard.Value; + } + GUI.FocusControl(null); + } + + if (GUILayout.Button("Paste rotation")) + { + Undo.RecordObjects(targets, "Paste Rotation Clipboard Value"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).rotation = rotationClipboard.Value; + } + GUI.FocusControl(null); + } + + if (GUILayout.Button("Paste scale")) + { + Undo.RecordObjects(targets, "Paste Scale Clipboard Value"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).localScale = scaleClipboard.Value; + } + GUI.FocusControl(null); + } + if (!positionClipboard.HasValue) EditorGUI.EndDisabledGroup(); + + GUILayout.EndHorizontal(); + } + + // Clipboard GUI + if (EditorPrefs.GetBool(SHOW_CLIPBOARD_INFORMATIONS_KEY)) + { + if (positionClipboard.HasValue && rotationClipboard.HasValue && scaleClipboard.HasValue) + { + + GUIStyle helpboxStyle = new GUIStyle(EditorStyles.helpBox); + helpboxStyle.richText = true; + + EditorGUILayout.TextArea("Clipboard values :\n" + + "Position : " + positionClipboard.Value.ToString("f2") + "\n" + + "Rotation : " + rotationClipboard.Value.ToString("f2") + "\n" + + "Scale : " + scaleClipboard.Value.ToString("f2"), helpboxStyle); + } + } + + + // Shortcuts GUI - Related to InspectorShortcuts.cs https://github.com/VoxelBoy/Useful-Unity-Scripts/blob/master/InspectorShortcuts.cs + if (EditorPrefs.GetBool(SHOW_SHORTCUTS_KEY)) + { + EditorGUILayout.HelpBox("Inspector shortcuts :\n" + + "Toggle inspector lock : Ctrl + Shift + L\n" + + "Toggle inspector mode : Ctrl + Shift + D", MessageType.None); + } + } + Rect endRect = GUILayoutUtility.GetLastRect(); + endRect.y += endRect.height; + + + #region Context Menu + Rect componentRect = new Rect(beginRect.x, beginRect.y, beginRect.width, endRect.y - beginRect.y); + //EditorGUI.DrawRect(componentRect, Color.green); // Debug : display GenericMenu zone + + Event currentEvent = Event.current; + + if (currentEvent.type == EventType.ContextClick) + { + if (componentRect.Contains(currentEvent.mousePosition)) + { + GUI.FocusControl(null); + + GenericMenu menu = new GenericMenu(); + + menu.AddItem(new GUIContent("Display/Tools"), EditorPrefs.GetBool(SHOW_TOOLS_KEY), ToggleOption, SHOW_TOOLS_KEY); + menu.AddSeparator("Display/"); + menu.AddItem(new GUIContent("Display/Reset Tools"), EditorPrefs.GetBool(SHOW_RESET_TOOLS_KEY), ToggleOption, SHOW_RESET_TOOLS_KEY); + menu.AddItem(new GUIContent("Display/Paste Tools"), EditorPrefs.GetBool(SHOW_PASTE_TOOLS_KEY), ToggleOption, SHOW_PASTE_TOOLS_KEY); + menu.AddItem(new GUIContent("Display/Advanced Paste Tools"), EditorPrefs.GetBool(SHOW_ADVANCED_PASTE_TOOLS_KEY), ToggleOption, SHOW_ADVANCED_PASTE_TOOLS_KEY); + menu.AddItem(new GUIContent("Display/Clipboard informations"), EditorPrefs.GetBool(SHOW_CLIPBOARD_INFORMATIONS_KEY), ToggleOption, SHOW_CLIPBOARD_INFORMATIONS_KEY); + menu.AddItem(new GUIContent("Display/Shortcuts informations"), EditorPrefs.GetBool(SHOW_SHORTCUTS_KEY), ToggleOption, SHOW_SHORTCUTS_KEY); + + // Round menu + menu.AddItem(new GUIContent("Round/Three Decimals"), false, Round, 3); + menu.AddItem(new GUIContent("Round/Two Decimals"), false, Round, 2); + menu.AddItem(new GUIContent("Round/One Decimal"), false, Round, 1); + menu.AddItem(new GUIContent("Round/Integer"), false, Round, 0); + + // Truncate menu + menu.AddItem(new GUIContent("Truncate/Three Decimals"), false, Truncate, 3); + menu.AddItem(new GUIContent("Truncate/Two Decimals"), false, Truncate, 2); + menu.AddItem(new GUIContent("Truncate/One Decimal"), false, Truncate, 1); + menu.AddItem(new GUIContent("Truncate/Integer"), false, Truncate, 0); + + menu.ShowAsContext(); + currentEvent.Use(); + } + } + #endregion + + serializedObject.ApplyModifiedProperties(); + } + + + private bool ValidatePosition(Vector3 position) + { + if (Mathf.Abs(position.x) > POSITION_MAX) return false; + if (Mathf.Abs(position.y) > POSITION_MAX) return false; + if (Mathf.Abs(position.z) > POSITION_MAX) return false; + return true; + } + + private void PositionPropertyField(SerializedProperty positionProperty, GUIContent content) + { + Transform transform = (Transform)targets[0]; + Vector3 localPosition = transform.localPosition; + for (int i = 0; i < targets.Length; i++) + { + if (!localPosition.Equals(((Transform)targets[i]).localPosition)) + { + EditorGUI.showMixedValue = true; + break; + } + } + + EditorGUI.BeginChangeCheck(); + Vector3 newLocalPosition = EditorGUILayout.Vector3Field(content, localPosition); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObjects(targets, "Position Changed"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).localPosition = newLocalPosition; + } + positionProperty.serializedObject.SetIsDifferentCacheDirty(); + } + EditorGUI.showMixedValue = false; + } + + private void RotationPropertyField(SerializedProperty rotationProperty, GUIContent content) + { + Transform transform = (Transform)targets[0]; + Vector3 localRotation = TransformUtils.GetInspectorRotation(transform); + + + for (int i = 0; i < targets.Length; i++) + { + if (!localRotation.Equals(TransformUtils.GetInspectorRotation((Transform)targets[i]))) + { + EditorGUI.showMixedValue = true; + break; + } + } + + EditorGUI.BeginChangeCheck(); + Vector3 eulerAngles = EditorGUILayout.Vector3Field(content, localRotation); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObjects(targets, "Rotation Changed"); + for (int i = 0; i < targets.Length; i++) + { + //((Transform)targets[i]).localEulerAngles = eulerAngles; + TransformUtils.SetInspectorRotation(((Transform)targets[i]), eulerAngles); + } + rotationProperty.serializedObject.SetIsDifferentCacheDirty(); + } + EditorGUI.showMixedValue = false; + } + + private void ScalePropertyField(SerializedProperty scaleProperty, GUIContent content) + { + Transform transform = (Transform)targets[0]; + Vector3 localScale = transform.localScale; + for (int i = 0; i < targets.Length; i++) + { + if (!localScale.Equals(((Transform)targets[i]).localScale)) + { + EditorGUI.showMixedValue = true; + break; + } + } + + EditorGUI.BeginChangeCheck(); + Vector3 newLocalScale = EditorGUILayout.Vector3Field(content, localScale); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObjects(targets, "Scale Changed"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).localScale = newLocalScale; + } + scaleProperty.serializedObject.SetIsDifferentCacheDirty(); + } + EditorGUI.showMixedValue = false; + } + + + #region Generic Menu Callbacks + private void ToggleOption(object obj) + { + EditorPrefs.SetBool(obj.ToString(), !EditorPrefs.GetBool(obj.ToString())); + } + + /// Round all values of the Transform to a given number of decimals + private void Round(object objNumberOfDecimals) + { + int numberOfDecimals = (int)objNumberOfDecimals; + + Undo.RecordObjects(targets, "Round to " + numberOfDecimals + " decimals"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).localPosition = RoundVector(((Transform)targets[i]).localPosition, numberOfDecimals); + ((Transform)targets[i]).localEulerAngles = RoundVector(((Transform)targets[i]).localEulerAngles, numberOfDecimals); + ((Transform)targets[i]).localScale = RoundVector(((Transform)targets[i]).localScale, numberOfDecimals); + } + } + + /// Round all components of a Vector3 + private Vector3 RoundVector(Vector3 vector, int numberOfDecimals) + { + vector.x = Mathf.Round(vector.x * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals); + vector.y = Mathf.Round(vector.y * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals); + vector.z = Mathf.Round(vector.z * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals); + return vector; + } + + /// Truncate all values of the Transform to a given number of decimals + private void Truncate(object objNumberOfDecimals) + { + int numberOfDecimals = (int)objNumberOfDecimals; + + Undo.RecordObjects(targets, "Truncate to " + numberOfDecimals + " decimals"); + for (int i = 0; i < targets.Length; i++) + { + ((Transform)targets[i]).localPosition = TruncateVector(((Transform)targets[i]).localPosition, numberOfDecimals); + ((Transform)targets[i]).localEulerAngles = TruncateVector(((Transform)targets[i]).localEulerAngles, numberOfDecimals); + ((Transform)targets[i]).localScale = TruncateVector(((Transform)targets[i]).localScale, numberOfDecimals); + } + } + + /// Truncate all components of a Vector3 + private Vector3 TruncateVector(Vector3 vector, int numberOfDecimals) + { + vector.x = Mathf.Floor(vector.x * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals); + vector.y = Mathf.Floor(vector.y * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals); + vector.z = Mathf.Floor(vector.z * Mathf.Pow(10.0f, (float)numberOfDecimals)) / Mathf.Pow(10.0f, (float)numberOfDecimals); + return vector; + } + #endregion + } +} diff --git a/Assets/Scripts/Editor/GameObject.meta b/Assets/Scripts/Editor/GameObject.meta new file mode 100644 index 0000000..ea83061 --- /dev/null +++ b/Assets/Scripts/Editor/GameObject.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: dda0f0ad5057e2244b9a17ccd5031e99 +folderAsset: yes +timeCreated: 1594958191 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/GameObject/ResetTransform.cs.meta b/Assets/Scripts/Editor/GameObject/ResetTransform.cs.meta new file mode 100644 index 0000000..0cc89a0 --- /dev/null +++ b/Assets/Scripts/Editor/GameObject/ResetTransform.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 51c038ebd5a3e0d4b92a12f03a73b6ea +timeCreated: 1594958193 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Gizmos/RendererBoundsGizmo.cs.meta b/Assets/Scripts/Editor/Gizmos/RendererBoundsGizmo.cs.meta new file mode 100644 index 0000000..d097268 --- /dev/null +++ b/Assets/Scripts/Editor/Gizmos/RendererBoundsGizmo.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f1bf2afb27df5364dab899f318af7f69 +timeCreated: 1594958195 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Hierarchy.meta b/Assets/Scripts/Editor/Hierarchy.meta new file mode 100644 index 0000000..d0052a8 --- /dev/null +++ b/Assets/Scripts/Editor/Hierarchy.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 4ad868ac71006a0418c8df13616a4c3e +folderAsset: yes +timeCreated: 1594958190 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Hierarchy/MoveAround.cs b/Assets/Scripts/Editor/Hierarchy/MoveAround.cs new file mode 100644 index 0000000..1ac886c --- /dev/null +++ b/Assets/Scripts/Editor/Hierarchy/MoveAround.cs @@ -0,0 +1,77 @@ +// Simple shortcuts for moving selected GameObject around in hierarchy by Factuall +// SHIFT + CTRL + ALT + +// W - Move it up in hierarchy +// S - Move it down in hierarchy +// A - Unparent it down +// D - Parent it to next object in hierarchy + +using UnityEditor; + +namespace UnityLibrary +{ + public class UnparentMe + { + [MenuItem("GameObject/Unparent %#&a")] + static void Detach() + { + + if (Selection.activeGameObject != null && Selection.activeGameObject.transform.parent != null) + { + int newIndex = Selection.activeGameObject.transform.parent.GetSiblingIndex(); + Undo.SetTransformParent(Selection.activeGameObject.transform, Selection.activeGameObject.transform.parent.parent, "unparent selection"); + Selection.activeGameObject.transform.SetSiblingIndex(newIndex + 1); + } + } + [MenuItem("GameObject/ParentDown %#&d")] + static void ParentDown() + { + if (Selection.activeGameObject != null) + { + int parentChildCount = Selection.activeGameObject.transform.parent == null ? + Selection.activeGameObject.scene.rootCount : Selection.activeGameObject.transform.parent.childCount; + if (Selection.activeGameObject.transform.GetSiblingIndex() == parentChildCount - 1) return; + if (Selection.activeGameObject.transform.parent != null) + { + if (Selection.activeGameObject.transform.parent.childCount + 1 > Selection.activeGameObject.transform.GetSiblingIndex()) + Undo.SetTransformParent(Selection.activeGameObject.transform, Selection.activeGameObject.transform.parent.GetChild(Selection.activeGameObject.transform.GetSiblingIndex() + 1), "parent selection down in hierarchy"); + + } + else + { + if (Selection.activeGameObject.scene.rootCount + 1 > Selection.activeGameObject.transform.GetSiblingIndex()) + Undo.SetTransformParent(Selection.activeGameObject.transform, Selection.activeGameObject.scene.GetRootGameObjects()[Selection.activeGameObject.transform.GetSiblingIndex() + 1].transform, "parent selection down in hierarchy"); + } + } + + } + [MenuItem("GameObject/Moveup %#&w")] + static void MoveUp() + { + if (Selection.activeGameObject != null) + { + + if (Selection.activeGameObject.transform.GetSiblingIndex() != 0) + { + Undo.RegisterCompleteObjectUndo(Selection.activeGameObject.transform, "move selction up in hierarchy"); + Selection.activeGameObject.transform.SetSiblingIndex(Selection.activeGameObject.transform.GetSiblingIndex() - 1); + } + + } + } + [MenuItem("GameObject/Movedown %#&s")] + static void MoveDown() + { + if (Selection.activeGameObject != null) + { + int parentChildCount = Selection.activeGameObject.transform.parent == null ? + Selection.activeGameObject.scene.rootCount : Selection.activeGameObject.transform.parent.childCount; + if (Selection.activeGameObject.transform.GetSiblingIndex() != parentChildCount - 1) + { + Undo.RegisterCompleteObjectUndo(Selection.activeGameObject.transform, "move selction down in hierarchy"); + Selection.activeGameObject.transform.SetSiblingIndex(Selection.activeGameObject.transform.GetSiblingIndex() + 1); + } + + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Editor/Hierarchy/MoveAround.cs.meta b/Assets/Scripts/Editor/Hierarchy/MoveAround.cs.meta new file mode 100644 index 0000000..41e518a --- /dev/null +++ b/Assets/Scripts/Editor/Hierarchy/MoveAround.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 18fe530f4fbd8b74096f07f377a4b9ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Hierarchy/UnparentMe.cs.meta b/Assets/Scripts/Editor/Hierarchy/UnparentMe.cs.meta new file mode 100644 index 0000000..f0a2192 --- /dev/null +++ b/Assets/Scripts/Editor/Hierarchy/UnparentMe.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 859aeadcffa51fe4f9cc875774621ef9 +timeCreated: 1594958194 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Importers/AnimationClipListImporter.cs b/Assets/Scripts/Editor/Importers/AnimationClipListImporter.cs new file mode 100644 index 0000000..733ac03 --- /dev/null +++ b/Assets/Scripts/Editor/Importers/AnimationClipListImporter.cs @@ -0,0 +1,65 @@ +// Checks for a .txt file with the same name as an imported .fbx file (in Assets/Models/ folder), containing a list of animation clips to add to the ModelImporter. +// .txt file should be tab-delimited with the following columns: "title", "start frame", "end frame" (and optional description, not used). +// example: +// Take0 10 40 asdf +// Take1 50 80 wasdf.. + +using System; +using System.IO; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +namespace UnityLibrary.Importers +{ + public class AnimationClipListImporter : AssetPostprocessor + { + void OnPreprocessModel() + { + ModelImporter modelImporter = assetImporter as ModelImporter; + if (modelImporter == null) return; + + string assetPath = assetImporter.assetPath; + if (!assetPath.StartsWith("Assets/Models") || !assetPath.EndsWith(".fbx", StringComparison.OrdinalIgnoreCase)) return; + + string txtPath = Path.ChangeExtension(assetPath, ".txt"); + if (!File.Exists(txtPath)) return; + + try + { + List clips = new List(); + string[] lines = File.ReadAllLines(txtPath); + + foreach (string line in lines) + { + string[] parts = line.Split('\t'); + if (parts.Length < 3) continue; // Ensure we have at least "title, start, end" + + string title = parts[0].Trim(); + if (!int.TryParse(parts[1], out int startFrame) || !int.TryParse(parts[2], out int endFrame)) + continue; + + ModelImporterClipAnimation clip = new ModelImporterClipAnimation + { + name = title, + firstFrame = startFrame, + lastFrame = endFrame, + loopTime = false + }; + + clips.Add(clip); + } + + if (clips.Count > 0) + { + modelImporter.clipAnimations = clips.ToArray(); + Debug.Log($"Added {clips.Count} animation clips to {assetPath}"); + } + } + catch (Exception ex) + { + Debug.LogError($"Failed to process animation data for {assetPath}: {ex.Message}"); + } + } + } +} diff --git a/Assets/Scripts/Editor/Mesh.meta b/Assets/Scripts/Editor/Mesh.meta new file mode 100644 index 0000000..bf539ea --- /dev/null +++ b/Assets/Scripts/Editor/Mesh.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: f45eaf3f880e5c04785b1cdca6b98021 +folderAsset: yes +timeCreated: 1594958191 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Mesh/CreatePlane.cs b/Assets/Scripts/Editor/Mesh/CreatePlane.cs new file mode 100644 index 0000000..f2633c1 --- /dev/null +++ b/Assets/Scripts/Editor/Mesh/CreatePlane.cs @@ -0,0 +1,227 @@ +// editor tools to create mesh plane with adjustable resolution +// original : http://wiki.unity3d.com/index.php?title=CreatePlane#C.23_-_CreatePlane.cs + +using UnityEngine; +using UnityEditor; +using System.Collections; + +namespace UnityLibrary +{ + public class CreatePlane : ScriptableWizard + { + + public enum Orientation + { + Horizontal, + Vertical + } + + public enum AnchorPoint + { + TopLeft, + TopHalf, + TopRight, + RightHalf, + BottomRight, + BottomHalf, + BottomLeft, + LeftHalf, + Center + } + + public int widthSegments = 1; + public int lengthSegments = 1; + public float width = 1.0f; + public float length = 1.0f; + public Orientation orientation = Orientation.Horizontal; + public AnchorPoint anchor = AnchorPoint.Center; + public bool addCollider = false; + public bool createAtOrigin = true; + public bool flipYZ = false; + public bool twoSided = false; + public string optionalName; + + static Camera cam; + static Camera lastUsedCam; + + + [MenuItem("GameObject/Create Other/Custom Plane...")] + static void CreateWizard() + { + cam = Camera.current; + // Hack because camera.current doesn't return editor camera if scene view doesn't have focus + if (!cam) + cam = lastUsedCam; + else + lastUsedCam = cam; + ScriptableWizard.DisplayWizard("Create Plane",typeof(CreatePlane)); + } + + + void OnWizardUpdate() + { + widthSegments = Mathf.Clamp(widthSegments, 1, 254); + lengthSegments = Mathf.Clamp(lengthSegments, 1, 254); + } + + + void OnWizardCreate() + { + GameObject plane = new GameObject(); + + if (!string.IsNullOrEmpty(optionalName)) + plane.name = optionalName; + else + plane.name = "Plane"; + + if (!createAtOrigin && cam) + plane.transform.position = cam.transform.position + cam.transform.forward*5.0f; + else + plane.transform.position = Vector3.zero; + + Vector2 anchorOffset; + string anchorId; + switch (anchor) + { + case AnchorPoint.TopLeft: + anchorOffset = new Vector2(-width/2.0f,length/2.0f); + anchorId = "TL"; + break; + case AnchorPoint.TopHalf: + anchorOffset = new Vector2(0.0f,length/2.0f); + anchorId = "TH"; + break; + case AnchorPoint.TopRight: + anchorOffset = new Vector2(width/2.0f,length/2.0f); + anchorId = "TR"; + break; + case AnchorPoint.RightHalf: + anchorOffset = new Vector2(width/2.0f,0.0f); + anchorId = "RH"; + break; + case AnchorPoint.BottomRight: + anchorOffset = new Vector2(width/2.0f,-length/2.0f); + anchorId = "BR"; + break; + case AnchorPoint.BottomHalf: + anchorOffset = new Vector2(0.0f,-length/2.0f); + anchorId = "BH"; + break; + case AnchorPoint.BottomLeft: + anchorOffset = new Vector2(-width/2.0f,-length/2.0f); + anchorId = "BL"; + break; + case AnchorPoint.LeftHalf: + anchorOffset = new Vector2(-width/2.0f,0.0f); + anchorId = "LH"; + break; + case AnchorPoint.Center: + default: + anchorOffset = Vector2.zero; + anchorId = "C"; + break; + } + + MeshFilter meshFilter = (MeshFilter)plane.AddComponent(typeof(MeshFilter)); + plane.AddComponent(typeof(MeshRenderer)); + + string planeAssetName = plane.name + widthSegments + "x" + lengthSegments + "W" + width + "L" + length + (orientation == Orientation.Horizontal? "H" : "V") + anchorId + ".asset"; + Mesh m = (Mesh)AssetDatabase.LoadAssetAtPath("Assets/Editor/" + planeAssetName,typeof(Mesh)); + + if (m == null) + { + m = new Mesh(); + m.name = plane.name; + + int hCount2 = widthSegments+1; + int vCount2 = lengthSegments+1; + int numTriangles = widthSegments * lengthSegments * 6; + if (twoSided) { + numTriangles *= 2; + } + int numVertices = hCount2 * vCount2; + + Vector3[] vertices = new Vector3[numVertices]; + Vector2[] uvs = new Vector2[numVertices]; + int[] triangles = new int[numTriangles]; + + int index = 0; + float uvFactorX = 1.0f/widthSegments; + float uvFactorY = 1.0f/lengthSegments; + float scaleX = width/widthSegments; + float scaleY = length/lengthSegments; + for (float y = 0.0f; y < vCount2; y++) + { + for (float x = 0.0f; x < hCount2; x++) + { + if (orientation == Orientation.Horizontal) + { + if (flipYZ) + { + vertices[index] = new Vector3(x*scaleX - width/2f - anchorOffset.x, y*scaleY - length/2f - anchorOffset.y, 0.0f); + }else{ + vertices[index] = new Vector3(x*scaleX - width/2f - anchorOffset.x, 0.0f, y*scaleY - length/2f - anchorOffset.y); + } + } + else + { + if (flipYZ) + { + vertices[index] = new Vector3(x*scaleX - width/2f - anchorOffset.x, 0.0f, y*scaleY - length/2f - anchorOffset.y); + }else{ + vertices[index] = new Vector3(x*scaleX - width/2f - anchorOffset.x, y*scaleY - length/2f - anchorOffset.y, 0.0f); + } + } + uvs[index++] = new Vector2(x*uvFactorX, y*uvFactorY); + } + } + + index = 0; + for (int y = 0; y < lengthSegments; y++) + { + for (int x = 0; x < widthSegments; x++) + { + triangles[index] = (y * hCount2) + x; + triangles[index+1] = ((y+1) * hCount2) + x; + triangles[index+2] = (y * hCount2) + x + 1; + + triangles[index+3] = ((y+1) * hCount2) + x; + triangles[index+4] = ((y+1) * hCount2) + x + 1; + triangles[index+5] = (y * hCount2) + x + 1; + index += 6; + } + if (twoSided) { + // Same tri vertices with order reversed, so normals point in the opposite direction + for (int x = 0; x < widthSegments; x++) + { + triangles[index] = (y * hCount2) + x; + triangles[index+1] = (y * hCount2) + x + 1; + triangles[index+2] = ((y+1) * hCount2) + x; + + triangles[index+3] = ((y+1) * hCount2) + x; + triangles[index+4] = (y * hCount2) + x + 1; + triangles[index+5] = ((y+1) * hCount2) + x + 1; + index += 6; + } + } + } + + m.vertices = vertices; + m.uv = uvs; + m.triangles = triangles; + m.RecalculateNormals(); + + AssetDatabase.CreateAsset(m, "Assets/Editor/" + planeAssetName); + AssetDatabase.SaveAssets(); + } + + meshFilter.sharedMesh = m; + m.RecalculateBounds(); + + if (addCollider) + plane.AddComponent(typeof(BoxCollider)); + + Selection.activeObject = plane; + } + } + } diff --git a/Assets/Scripts/Editor/Mesh/CreatePlane.cs.meta b/Assets/Scripts/Editor/Mesh/CreatePlane.cs.meta new file mode 100644 index 0000000..e2ec4a4 --- /dev/null +++ b/Assets/Scripts/Editor/Mesh/CreatePlane.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d1cea26cb1deacf48b0dd190be08a597 +timeCreated: 1594958194 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Mesh/MeshCombineWizard.cs b/Assets/Scripts/Editor/Mesh/MeshCombineWizard.cs new file mode 100644 index 0000000..5406c35 --- /dev/null +++ b/Assets/Scripts/Editor/Mesh/MeshCombineWizard.cs @@ -0,0 +1,127 @@ +// from https://forum.unity.com/threads/mesh-combine-wizard-free-unity-tool-source-code.444483/#post-5575042 + +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using System.Linq; + +namespace UnityLibrary +{ + public class MeshCombineWizard : ScriptableWizard + { + public GameObject parentOfObjectsToCombine; + + [MenuItem("E.S. Tools/Mesh Combine Wizard")] + static void CreateWizard() + { + ScriptableWizard.DisplayWizard("Mesh Combine Wizard"); + } + + void OnWizardCreate() + { + if (parentOfObjectsToCombine == null) return; + + Vector3 originalPosition = parentOfObjectsToCombine.transform.position; + parentOfObjectsToCombine.transform.position = Vector3.zero; + + MeshFilter[] meshFilters = parentOfObjectsToCombine.GetComponentsInChildren(); + Dictionary> materialToMeshFilterList = new Dictionary>(); + List combinedObjects = new List(); + + for (int i = 0; i < meshFilters.Length; i++) + { + var materials = meshFilters[i].GetComponent().sharedMaterials; + if (materials == null) continue; + if (materials.Length > 1) + { + parentOfObjectsToCombine.transform.position = originalPosition; + Debug.LogError("Objects with multiple materials on the same mesh are not supported. Create multiple meshes from this object's sub-meshes in an external 3D tool and assign separate materials to each."); + return; + } + var material = materials[0]; + if (materialToMeshFilterList.ContainsKey(material)) materialToMeshFilterList[material].Add(meshFilters[i]); + else materialToMeshFilterList.Add(material, new List() { meshFilters[i] }); + } + + + // NC 03/2020 changes the loop to create meshes smaller than 695536 vertices + foreach (var entry in materialToMeshFilterList) + { + // get list of each meshes order by number of vertices + List meshesWithSameMaterial = entry.Value.OrderByDescending(mf => mf.sharedMesh.vertexCount).ToList(); + // split into bins of 65536 vertices max + int nrMeshes = meshesWithSameMaterial.Count; + while (nrMeshes > 0) + { + string materialName = entry.Key.ToString().Split(' ')[0]; + List meshBin = new List(); + meshBin.Add(meshesWithSameMaterial[0]); + meshesWithSameMaterial.RemoveAt(0); + nrMeshes--; + // add meshes in bin until 65536 vertices is reached + for (int i = 0; i < meshesWithSameMaterial.Count; i++) + { + if (meshBin.Sum(mf => mf.sharedMesh.vertexCount) + meshesWithSameMaterial[i].sharedMesh.vertexCount < 65536) + { + meshBin.Add(meshesWithSameMaterial[i]); + meshesWithSameMaterial.RemoveAt(i); + i--; + nrMeshes--; + } + } + + // merge this bin + CombineInstance[] combine = new CombineInstance[meshBin.Count]; + for (int i = 0; i < meshBin.Count; i++) + { + combine[i].mesh = meshBin[i].sharedMesh; + combine[i].transform = meshBin[i].transform.localToWorldMatrix; + } + Mesh combinedMesh = new Mesh(); + combinedMesh.CombineMeshes(combine); + + // save the mesh + materialName += "_" + combinedMesh.GetInstanceID(); + if (meshBin.Count > 1) + { + AssetDatabase.CreateAsset(combinedMesh, "Assets/CombinedMeshes_" + materialName + ".asset"); ; + } + + // assign the mesh to a new go + string goName = (materialToMeshFilterList.Count > 1) ? "CombinedMeshes_" + materialName : "CombinedMeshes_" + parentOfObjectsToCombine.name; + GameObject combinedObject = new GameObject(goName); + var filter = combinedObject.AddComponent(); + if (meshBin.Count > 1) + { + filter.sharedMesh = combinedMesh; + } + else + { + filter.sharedMesh = meshBin[0].sharedMesh; // the original mesh + } + var renderer = combinedObject.AddComponent(); + renderer.sharedMaterial = entry.Key; + combinedObjects.Add(combinedObject); + } + } + + GameObject resultGO = null; + if (combinedObjects.Count > 1) + { + resultGO = new GameObject("CombinedMeshes_" + parentOfObjectsToCombine.name); + foreach (var combinedObject in combinedObjects) combinedObject.transform.parent = resultGO.transform; + } + else + { + resultGO = combinedObjects[0]; + } + + Object prefab = PrefabUtility.CreateEmptyPrefab("Assets/" + resultGO.name + ".prefab"); + PrefabUtility.ReplacePrefab(resultGO, prefab, ReplacePrefabOptions.ConnectToPrefab); + + parentOfObjectsToCombine.SetActive(false); + parentOfObjectsToCombine.transform.position = originalPosition; + resultGO.transform.position = originalPosition; + } + } +} diff --git a/Assets/Scripts/Editor/Mesh/MeshCombineWizard.cs.meta b/Assets/Scripts/Editor/Mesh/MeshCombineWizard.cs.meta new file mode 100644 index 0000000..69ac2e2 --- /dev/null +++ b/Assets/Scripts/Editor/Mesh/MeshCombineWizard.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 72fbe9dd2dbf0f845ab4d30da0d8b27c +timeCreated: 1594958194 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Tools/AndroidStoreCaptureTool.cs b/Assets/Scripts/Editor/Tools/AndroidStoreCaptureTool.cs new file mode 100644 index 0000000..8a73f3a --- /dev/null +++ b/Assets/Scripts/Editor/Tools/AndroidStoreCaptureTool.cs @@ -0,0 +1,513 @@ +// AndroidStoreCaptureTool.cs +// Put this file anywhere under an "Editor" folder. +// Usage: +// 1) Enter Play Mode. +// 2) Open: Tools/Android Store Capture +// 3) Pick output folder and click "Capture All Presets" + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace UnityLibrary.Tools +{ + public class AndroidStoreCaptureTool : EditorWindow + { + [Serializable] + private class Preset + { + public string name; // base file name (without _WxH) + public int width; + public int height; + public CropMode cropMode; + + public Preset(string name, int w, int h, CropMode cropMode) + { + this.name = name; + width = w; + height = h; + this.cropMode = cropMode; + } + } + + private enum CropMode + { + Stretch, // no crop, just scale to target (may distort) + CropToFit // center-crop to target aspect, then scale (no distortion) + } + + private string _outputFolder = "StoreCaptures"; + private int _phoneCount = 2; // Play Console: 2-8 phone screenshots + + // Jobs + private class CaptureJob + { + public Preset preset; + public string filename; + } + + private readonly Queue _queue = new Queue(); + private bool _isRunning; + + // Hidden helper MonoBehaviour that runs coroutines in Play Mode + private CaptureHelper _helper; + + // Presets based on Play Console rules in your message. + // Phone/tablet sizes are common choices within allowed ranges. + private List BuildPresets() + { + var list = new List(); + + // App icon and feature graphic + list.Add(new Preset("appicon", 512, 512, CropMode.CropToFit)); + list.Add(new Preset("featuregraphic", 1024, 500, CropMode.CropToFit)); + + // Phone screenshots (2-8). 9:16 or 16:9. Each side 320..3840. + // We capture portrait by default; toggle to landscape if you want. + for (int i = 1; i <= Mathf.Clamp(_phoneCount, 2, 8); i++) + list.Add(new Preset("phone_" + i.ToString("00"), 1080, 1920, CropMode.CropToFit)); + + // 7-inch tablet screenshots (allowed: 320..3840 each side) + list.Add(new Preset("tablet7_01", 1920, 1200, CropMode.CropToFit)); // landscape 16:10 + list.Add(new Preset("tablet7_02", 1200, 1920, CropMode.CropToFit)); // portrait 10:16 + + // 10-inch tablet screenshots (each side 1080..7680) + list.Add(new Preset("tablet10_01", 2560, 1600, CropMode.CropToFit)); // landscape 16:10 + list.Add(new Preset("tablet10_02", 1600, 2560, CropMode.CropToFit)); // portrait 10:16 + + return list; + } + + [MenuItem("Tools/Android Store Capture")] + public static void Open() + { + var w = GetWindow("Android Store Capture"); + w.minSize = new Vector2(420, 340); + w.Show(); + } + + private void OnDisable() + { + StopRunner(); + } + + private void OnGUI() + { + EditorGUILayout.LabelField("Capture from Game View (Play Mode)", EditorStyles.boldLabel); + + using (new EditorGUILayout.VerticalScope("box")) + { + EditorGUILayout.LabelField("Output", EditorStyles.boldLabel); + + EditorGUILayout.BeginHorizontal(); + _outputFolder = EditorGUILayout.TextField("Folder", _outputFolder); + if (GUILayout.Button("Browse", GUILayout.Width(80))) + { + string picked = EditorUtility.OpenFolderPanel("Pick output folder", Application.dataPath, ""); + if (!string.IsNullOrEmpty(picked)) + { + // Make it project-relative when possible + string proj = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + string full = Path.GetFullPath(picked); + if (full.StartsWith(proj, StringComparison.OrdinalIgnoreCase)) + { + _outputFolder = full.Substring(proj.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + } + else + { + _outputFolder = full; + } + } + } + EditorGUILayout.EndHorizontal(); + + _phoneCount = EditorGUILayout.IntSlider("Phone screenshots (2-8)", _phoneCount, 2, 8); + } + + using (new EditorGUILayout.VerticalScope("box")) + { + EditorGUILayout.LabelField("Actions", EditorStyles.boldLabel); + + if (!EditorApplication.isPlaying) + { + EditorGUILayout.HelpBox("Enter Play Mode first. This tool captures the rendered Game View.", MessageType.Warning); + } + + GUI.enabled = EditorApplication.isPlaying && !_isRunning; + if (GUILayout.Button("Capture All Presets")) + { + EnqueueAll(); + StartRunner(); + } + + if (GUILayout.Button("Capture Only Icon + Feature Graphic")) + { + EnqueueIconAndFeatureOnly(); + StartRunner(); + } + GUI.enabled = true; + + GUI.enabled = _isRunning; + if (GUILayout.Button("Stop")) + { + StopRunner(); + } + GUI.enabled = true; + + if (_isRunning) + { + EditorGUILayout.Space(6); + EditorGUILayout.LabelField("Running...", EditorStyles.boldLabel); + EditorGUILayout.LabelField("Remaining", _queue.Count.ToString()); + } + } + + using (new EditorGUILayout.VerticalScope("box")) + { + EditorGUILayout.LabelField("Notes", EditorStyles.boldLabel); + EditorGUILayout.LabelField("- Files are named like: appicon_512x512.png, featuregraphic_1024x500.png"); + EditorGUILayout.LabelField("- Phone screenshots are named like: phone_01_1080x1920.png"); + EditorGUILayout.LabelField("- Captures center-crop to match target aspect (no stretching)."); + } + } + + private void EnqueueAll() + { + _queue.Clear(); + + string folder = ResolveOutputFolder(); + Directory.CreateDirectory(folder); + + foreach (var p in BuildPresets()) + { + string fn = $"{p.name}_{p.width}x{p.height}.png"; + _queue.Enqueue(new CaptureJob { preset = p, filename = Path.Combine(folder, fn) }); + } + } + + private void EnqueueIconAndFeatureOnly() + { + _queue.Clear(); + + string folder = ResolveOutputFolder(); + Directory.CreateDirectory(folder); + + var icon = new Preset("appicon", 512, 512, CropMode.CropToFit); + var feature = new Preset("featuregraphic", 1024, 500, CropMode.CropToFit); + + _queue.Enqueue(new CaptureJob { preset = icon, filename = Path.Combine(folder, $"appicon_512x512.png") }); + _queue.Enqueue(new CaptureJob { preset = feature, filename = Path.Combine(folder, $"featuregraphic_1024x500.png") }); + } + + private string ResolveOutputFolder() + { + // If user gave absolute path, use it. Otherwise, place under project root. + if (Path.IsPathRooted(_outputFolder)) + return _outputFolder; + + string projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + return Path.Combine(projectRoot, _outputFolder); + } + + private CaptureHelper EnsureHelper() + { + if (_helper != null) return _helper; + + var go = new GameObject("[AndroidStoreCaptureHelper]") + { + hideFlags = HideFlags.HideAndDontSave + }; + _helper = go.AddComponent(); + return _helper; + } + + private void StartRunner() + { + if (_isRunning) return; + if (!EditorApplication.isPlaying) return; + + _isRunning = true; + + GetMainGameView(); + + var helper = EnsureHelper(); + helper.StartCoroutine(RunCaptures()); + } + + private void StopRunner() + { + _isRunning = false; + _queue.Clear(); + + if (_helper != null) + { + _helper.StopAllCoroutines(); + DestroyImmediate(_helper.gameObject); + _helper = null; + } + } + + private IEnumerator RunCaptures() + { + while (_queue.Count > 0) + { + if (!EditorApplication.isPlaying) + { + StopRunner(); + yield break; + } + + var job = _queue.Dequeue(); + + SetGameViewSize(job.preset.width, job.preset.height); + + // Wait for the GameView to resize and re-render + for (int i = 0; i < 6; i++) + yield return null; + + // Wait for end of frame — this is required for ScreenCapture to work + yield return new WaitForEndOfFrame(); + + try + { + ProcessCaptureJob(job); + } + catch (Exception ex) + { + Debug.LogError("Capture failed: " + ex); + } + + Repaint(); + } + + _isRunning = false; + AssetDatabase.Refresh(); + Debug.Log("Android Store Capture: All captures finished."); + Repaint(); + + if (_helper != null) + { + DestroyImmediate(_helper.gameObject); + _helper = null; + } + } + + private void ProcessCaptureJob(CaptureJob job) + { + int targetW = job.preset.width; + int targetH = job.preset.height; + + Texture2D src = ScreenCapture.CaptureScreenshotAsTexture(); + if (src == null) + { + Debug.LogError($"CaptureScreenshotAsTexture returned null for {job.filename}. Skipping."); + return; + } + + Texture2D processed; + if (job.preset.cropMode == CropMode.CropToFit) + processed = CropToAspectThenScale(src, targetW, targetH); + else + processed = ScaleTexture(src, targetW, targetH); + + byte[] png = processed.EncodeToPNG(); + File.WriteAllBytes(job.filename, png); + + DestroyImmediate(src); + if (processed != src) + DestroyImmediate(processed); + + Debug.Log("Saved: " + job.filename); + } + + private static Texture2D CropToAspectThenScale(Texture2D src, int targetW, int targetH) + { + float srcAspect = (float)src.width / src.height; + float dstAspect = (float)targetW / targetH; + + int cropW = src.width; + int cropH = src.height; + + if (srcAspect > dstAspect) + { + // too wide -> crop width + cropW = Mathf.RoundToInt(src.height * dstAspect); + cropH = src.height; + } + else + { + // too tall -> crop height + cropW = src.width; + cropH = Mathf.RoundToInt(src.width / dstAspect); + } + + // Crop from top-left: x starts at 0, y starts from top + int x0 = 0; + int y0 = src.height - cropH; + + Color[] pixels = src.GetPixels(x0, y0, cropW, cropH); + Texture2D cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, false); + cropped.SetPixels(pixels); + cropped.Apply(false, false); + + Texture2D scaled = ScaleTexture(cropped, targetW, targetH); + DestroyImmediate(cropped); + return scaled; + } + + private static Texture2D ScaleTexture(Texture2D src, int targetW, int targetH) + { + Texture2D dst = new Texture2D(targetW, targetH, TextureFormat.RGBA32, false); + + for (int y = 0; y < targetH; y++) + { + float v = (targetH == 1) ? 0f : (float)y / (targetH - 1); + for (int x = 0; x < targetW; x++) + { + float u = (targetW == 1) ? 0f : (float)x / (targetW - 1); + Color c = SampleBilinear(src, u, v); + dst.SetPixel(x, y, c); + } + } + + dst.Apply(false, false); + return dst; + } + + private static Color SampleBilinear(Texture2D tex, float u, float v) + { + float x = u * (tex.width - 1); + float y = v * (tex.height - 1); + + int x0 = Mathf.Clamp((int)Mathf.Floor(x), 0, tex.width - 1); + int y0 = Mathf.Clamp((int)Mathf.Floor(y), 0, tex.height - 1); + int x1 = Mathf.Clamp(x0 + 1, 0, tex.width - 1); + int y1 = Mathf.Clamp(y0 + 1, 0, tex.height - 1); + + float tx = x - x0; + float ty = y - y0; + + Color c00 = tex.GetPixel(x0, y0); + Color c10 = tex.GetPixel(x1, y0); + Color c01 = tex.GetPixel(x0, y1); + Color c11 = tex.GetPixel(x1, y1); + + Color a = Color.Lerp(c00, c10, tx); + Color b = Color.Lerp(c01, c11, tx); + return Color.Lerp(a, b, ty); + } + + // --------------------------- + // GameView sizing (internal) + // --------------------------- + + private static EditorWindow GetMainGameView() + { + Type t = Type.GetType("UnityEditor.GameView,UnityEditor"); + if (t == null) return null; + + // Try "GetMainGameView" first (older Unity versions) + MethodInfo getMain = t.GetMethod("GetMainGameView", BindingFlags.NonPublic | BindingFlags.Static); + if (getMain != null) + { + var result = getMain.Invoke(null, null) as EditorWindow; + if (result != null) return result; + } + + // Fallback: try "GetMainGameViewRenderRect" or just find an open GameView window + var gameView = GetWindow(t, false, null, false); + return gameView; + } + + private static void SetGameViewSize(int width, int height) + { + // Creates/uses a fixed resolution entry in the current platform group, then selects it. + // Unity does not expose this publicly; reflection is used. + + Type sizesType = Type.GetType("UnityEditor.GameViewSizes,UnityEditor"); + Type sizeType = Type.GetType("UnityEditor.GameViewSize,UnityEditor"); + Type groupType = Type.GetType("UnityEditor.GameViewSizeGroupType,UnityEditor"); + + if (sizesType == null || sizeType == null || groupType == null) + return; + + var instanceProp = sizesType.GetProperty("instance", BindingFlags.Public | BindingFlags.Static); + if (instanceProp == null) return; + object sizesInstance = instanceProp.GetValue(null, null); + if (sizesInstance == null) return; + + MethodInfo getGroup = sizesType.GetMethod("GetGroup"); + if (getGroup == null) return; + object group = getGroup.Invoke(sizesInstance, new object[] { (int)Enum.Parse(groupType, "Standalone") }); + if (group == null) return; + + // Find existing + MethodInfo getBuiltinCount = group.GetType().GetMethod("GetBuiltinCount"); + MethodInfo getCustomCount = group.GetType().GetMethod("GetCustomCount"); + MethodInfo getGameViewSize = group.GetType().GetMethod("GetGameViewSize"); + + if (getBuiltinCount == null || getCustomCount == null || getGameViewSize == null) return; + + int builtin = (int)getBuiltinCount.Invoke(group, null); + int custom = (int)getCustomCount.Invoke(group, null); + + int total = builtin + custom; + int foundIndex = -1; + + for (int i = 0; i < total; i++) + { + object gvSize = getGameViewSize.Invoke(group, new object[] { i }); + if (gvSize == null) continue; + var widthProp = gvSize.GetType().GetProperty("width"); + var heightProp = gvSize.GetType().GetProperty("height"); + if (widthProp == null || heightProp == null) continue; + + int w = (int)widthProp.GetValue(gvSize, null); + int h = (int)heightProp.GetValue(gvSize, null); + + if (w == width && h == height) + { + foundIndex = i; + break; + } + } + + if (foundIndex < 0) + { + // Add custom size + Type gvSizeType = Type.GetType("UnityEditor.GameViewSizeType,UnityEditor"); + if (gvSizeType == null) return; + object fixedRes = Enum.Parse(gvSizeType, "FixedResolution"); + + ConstructorInfo ctor = sizeType.GetConstructor(new[] { gvSizeType, typeof(int), typeof(int), typeof(string) }); + if (ctor == null) return; + object newSize = ctor.Invoke(new object[] { fixedRes, width, height, width + "x" + height }); + + MethodInfo addCustom = group.GetType().GetMethod("AddCustomSize"); + if (addCustom == null) return; + addCustom.Invoke(group, new object[] { newSize }); + + custom = (int)getCustomCount.Invoke(group, null); + foundIndex = builtin + (custom - 1); + } + + // Select size in GameView + EditorWindow gv = GetMainGameView(); + if (gv == null) return; + + Type gvType = gv.GetType(); + PropertyInfo selectedSizeIndex = gvType.GetProperty("selectedSizeIndex", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (selectedSizeIndex != null) + selectedSizeIndex.SetValue(gv, foundIndex, null); + + gv.Repaint(); + } + + // Hidden MonoBehaviour to run coroutines from the editor tool + private class CaptureHelper : MonoBehaviour { } + } +} diff --git a/Assets/Scripts/Editor/Tools/ColorPickerWindow.cs.meta b/Assets/Scripts/Editor/Tools/ColorPickerWindow.cs.meta new file mode 100644 index 0000000..d8f24d1 --- /dev/null +++ b/Assets/Scripts/Editor/Tools/ColorPickerWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 333163797deb57f4a8d7373ac970d9bf +timeCreated: 1594958193 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Tools/CompileTime.cs b/Assets/Scripts/Editor/Tools/CompileTime.cs new file mode 100644 index 0000000..89721bc --- /dev/null +++ b/Assets/Scripts/Editor/Tools/CompileTime.cs @@ -0,0 +1,201 @@ +// compile time tracking tool (and quickly enable disable settings, and scene autosave option) +// original source: https://gist.github.com/spajus/72a74146b1bbeddd44a66e1b8a3c829c +// created by https://github.com/spajus twitch https://www.twitch.tv/dev_spajus +// 02.07.2020 added unity_2018_4 check and set autosave off by default - unitycoder.com + +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.Compilation; +using UnityEditor.SceneManagement; +using UnityEngine; + +namespace UnityLibrary +{ + class CompileTime : EditorWindow + { + bool allowProfiler = false; + bool isTrackingTime; + bool isLockReload; + bool isAutoSave = false; // autosave default off + bool isLocked; + bool restartAfterCompile; + bool memoryWarned; + string lastReloadTime = ""; + string lastCompileTime = ""; + string lastAssCompileTime = ""; + double startTime, finishTime, compileTime, reloadTime; + double assStartTime, assFinishTime, assCompileTime; + Dictionary startTimes = new Dictionary(); + List lastAssCompile; + + [MenuItem("Tools/UnityLibrary/Compile Time")] + public static void Init() + { + EditorWindow.GetWindow(typeof(CompileTime)); + } + + void Update() + { + if (isLockReload) { return; } + if (EditorApplication.isCompiling && !isTrackingTime) + { + if (EditorApplication.isPlaying) + { + restartAfterCompile = true; + EditorApplication.isPlaying = false; + } + startTime = EditorApplication.timeSinceStartup; + lastReloadTime = "Reloading now"; + lastCompileTime = "Compiling now"; + lastAssCompile = new List(); + Debug.Log("Started compiling scripts"); + isTrackingTime = true; + } + else if (!EditorApplication.isCompiling && isTrackingTime) + { + finishTime = EditorApplication.timeSinceStartup; + isTrackingTime = false; + EditorApplication.Beep(); + reloadTime = finishTime - startTime; + lastReloadTime = reloadTime.ToString("0.000") + "s"; + compileTime = reloadTime - assCompileTime; + lastCompileTime = compileTime.ToString("0.000") + "s"; + if (isAutoSave && !EditorApplication.isPlaying) + { + Debug.Log("Auto Saving Scene"); + EditorSceneManager.SaveOpenScenes(); + } + if (restartAfterCompile) + { + restartAfterCompile = false; + EditorApplication.isPlaying = true; + } + } + } + + void OnGUI() + { +#if UNITY_2018_4_OR_NEWER + // Toggle domain reload + var playModeOptsEnabled = EditorSettings.enterPlayModeOptionsEnabled; + playModeOptsEnabled = EditorGUILayout.Toggle("Disable Domain Reload", playModeOptsEnabled); + EditorSettings.enterPlayModeOptionsEnabled = playModeOptsEnabled; +#endif + + if (UnityEngine.Profiling.Profiler.enabled) + { + EditorGUILayout.LabelField("PROFILER ENABLED"); + } + allowProfiler = EditorGUILayout.Toggle("Allow profiler", allowProfiler); + if (!allowProfiler && UnityEngine.Profiling.Profiler.enabled) + { + UnityEngine.Profiling.Profiler.enabled = false; + } + EditorGUILayout.LabelField("Time", Time.realtimeSinceStartup.ToString()); + float m1 = (UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong() / 1024f / 1024f); + float m2 = (UnityEngine.Profiling.Profiler.GetAllocatedMemoryForGraphicsDriver() / 1024f / 1024f); + float m3 = (UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong() / 1024f / 1024f); + float m = m1 + m2 + m3; + if (m > 10000 && !memoryWarned) + { + memoryWarned = true; + EditorApplication.Beep(); + Debug.LogError("Memory over 10000MB!"); + allowProfiler = false; + UnityEngine.Profiling.Profiler.enabled = false; + } + if (m < 8000 && memoryWarned) + { + memoryWarned = false; + } + EditorGUILayout.LabelField("Memory used:", m.ToString("0.00") + " MB"); + + isLockReload = EditorGUILayout.Toggle("Lock assembly reload", isLockReload); + isAutoSave = EditorGUILayout.Toggle("Auto Save", isAutoSave); + EditorGUILayout.LabelField("Full reload", lastReloadTime); + EditorGUILayout.LabelField("Compile", lastCompileTime); + if (lastAssCompileTime != null) + { + EditorGUILayout.LabelField("Assembly reload", lastAssCompileTime); + } + // For mysterious reason, iterating over a dictionary doesn't work, it gets empty! + if (lastAssCompile != null) + { + foreach (string s in lastAssCompile) + { + var ss = s.Split(':'); + EditorGUILayout.LabelField(ss[0], ss[1]); + } + } + + if (isLockReload) + { + if (!isLocked) + { + Debug.Log("Locking reload of assemblies"); + EditorApplication.LockReloadAssemblies(); + isLocked = true; + } + } + else + { + if (isLocked) + { + Debug.Log("Unlocking reload of assemblies"); + EditorApplication.UnlockReloadAssemblies(); + isLocked = false; + } + } + } + + void OnBeforeAssemblyReload() + { + assStartTime = EditorApplication.timeSinceStartup; + this.ShowNotification(new GUIContent("Started assembly reload")); + } + + void OnAfterAssemblyReload() + { + assFinishTime = EditorApplication.timeSinceStartup; + assCompileTime = assFinishTime - assStartTime; + lastAssCompileTime = assCompileTime.ToString("0.000") + "s"; + } + + void CompilationPipelineOnAssemblyCompilationStarted(string assembly) + { + Debug.Log("Assembly compile started: " + assembly); + startTimes[assembly] = DateTime.UtcNow; + } + + void CompilationPipelineOnAssemblyCompilationFinished(string assembly, CompilerMessage[] arg2) + { + var time = startTimes[assembly]; + var timeSpan = DateTime.UtcNow - startTimes[assembly]; + var bt = string.Format("{0:0.00}s", timeSpan.TotalMilliseconds / 1000f); + var cleanAss = assembly.Replace("Library/ScriptAssemblies/", ""); + + if (lastAssCompile != null) + { + lastAssCompile.Add(cleanAss + ":" + bt); + } + Debug.Log("Assembly compile ended: " + assembly + " in " + bt); + } + + void OnEnable() + { + AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; + AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; + CompilationPipeline.assemblyCompilationStarted += CompilationPipelineOnAssemblyCompilationStarted; + CompilationPipeline.assemblyCompilationFinished += CompilationPipelineOnAssemblyCompilationFinished; + } + + void OnDisable() + { + AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; + AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; + CompilationPipeline.assemblyCompilationStarted -= CompilationPipelineOnAssemblyCompilationStarted; + CompilationPipeline.assemblyCompilationFinished -= CompilationPipelineOnAssemblyCompilationFinished; + } + } +} diff --git a/Assets/Scripts/Editor/Tools/CompileTime.cs.meta b/Assets/Scripts/Editor/Tools/CompileTime.cs.meta new file mode 100644 index 0000000..30a9f44 --- /dev/null +++ b/Assets/Scripts/Editor/Tools/CompileTime.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a15be7d1f359a614fb4f298da7a5fa9e +timeCreated: 1594958194 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/Tools/FindWhatButtonCallsMyMethod.cs b/Assets/Scripts/Editor/Tools/FindWhatButtonCallsMyMethod.cs new file mode 100644 index 0000000..e67f45b --- /dev/null +++ b/Assets/Scripts/Editor/Tools/FindWhatButtonCallsMyMethod.cs @@ -0,0 +1,69 @@ +// prints out which buttons in current scene are referencing your given method + +using UnityEngine; +using UnityEngine.UI; +using UnityEditor; +using UnityEngine.Events; +using System.Reflection; + +namespace UnityLibrary +{ + public class FindWhatButtonCallsMyMethod : EditorWindow + { + private string methodName = "MyMethodHere"; + + [MenuItem("Tools/Find Buttons with Method")] + public static void ShowWindow() + { + GetWindow("FindWhatButtonCallsMyMethod"); + } + + private void OnGUI() + { + GUILayout.Label("Find Buttons that call Method", EditorStyles.boldLabel); + methodName = EditorGUILayout.TextField("Method Name:", methodName); + + if (GUILayout.Button("Find Buttons")) + { + FindButtonsWithMethod(); + } + } + + private void FindButtonsWithMethod() + { + Button[] allButtons = FindObjectsOfType