chore: Project Restructuring & Theatre
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Core.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets third-party static state that doesn't handle Domain Reload being disabled.
|
||||
/// Runs automatically at the start of every Play Mode session before the scene loads.
|
||||
/// </summary>
|
||||
internal static class GameSessionResetter
|
||||
{
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void OnSessionStart()
|
||||
{
|
||||
ResetLeanTween();
|
||||
}
|
||||
|
||||
private static void ResetLeanTween()
|
||||
{
|
||||
// LeanTween holds a static tween array that persists when Domain Reload is disabled.
|
||||
// Stale tween IDs from the previous session cause callbacks to fire on wrong objects,
|
||||
// which breaks animations like organic shake on floating words.
|
||||
LeanTween.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34edfe05577b2a24892a90d9901cef39
|
||||
@@ -0,0 +1,128 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Core.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility functions for portal camera rendering and visibility calculations.
|
||||
/// Used to optimize portal rendering by detecting visibility and overlaps.
|
||||
/// </summary>
|
||||
public static class PortalCameraUtility
|
||||
{
|
||||
private static readonly Vector3[] _cubeCornerOffsets = {
|
||||
new Vector3(1, 1, 1),
|
||||
new Vector3(-1, 1, 1),
|
||||
new Vector3(-1, -1, 1),
|
||||
new Vector3(-1, -1, -1),
|
||||
new Vector3(-1, 1, -1),
|
||||
new Vector3(1, -1, -1),
|
||||
new Vector3(1, 1, -1),
|
||||
new Vector3(1, -1, 1),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Tests if a renderer is visible from the camera's perspective.
|
||||
/// Uses frustum culling to determine visibility.
|
||||
/// </summary>
|
||||
public static bool VisibleFromCamera(Renderer renderer, Camera camera)
|
||||
{
|
||||
Plane[] frustumPlanes = GeometryUtility.CalculateFrustumPlanes(camera);
|
||||
return GeometryUtility.TestPlanesAABB(frustumPlanes, renderer.bounds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if two objects' screen-space bounding boxes overlap when viewed from a camera.
|
||||
/// Used to determine if one portal is being viewed through another portal (recursion).
|
||||
/// Returns true if the far object is visible through/behind the near object.
|
||||
/// </summary>
|
||||
public static bool BoundsOverlap(MeshFilter nearObject, MeshFilter farObject, Camera camera)
|
||||
{
|
||||
var near = GetScreenRectFromBounds(nearObject, camera);
|
||||
var far = GetScreenRectFromBounds(farObject, camera);
|
||||
|
||||
if (far.zMax > near.zMin)
|
||||
{
|
||||
if (far.xMax < near.xMin || far.xMin > near.xMax)
|
||||
return false;
|
||||
|
||||
if (far.yMax < near.yMin || far.yMin > near.yMax)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a mesh's 3D bounding box to screen-space coordinates (viewport space).
|
||||
/// Handles corner cases where parts of the mesh are behind the camera.
|
||||
/// Returns a MinMax3D struct containing the min/max viewport coordinates (x, y) and depth (z).
|
||||
/// </summary>
|
||||
public static MinMax3D GetScreenRectFromBounds(MeshFilter renderer, Camera mainCamera)
|
||||
{
|
||||
MinMax3D minMax = new MinMax3D(float.MaxValue, float.MinValue);
|
||||
var localBounds = renderer.sharedMesh.bounds;
|
||||
bool anyPointIsInFrontOfCamera = false;
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
Vector3 localSpaceCorner = localBounds.center + Vector3.Scale(localBounds.extents, _cubeCornerOffsets[i]);
|
||||
Vector3 worldSpaceCorner = renderer.transform.TransformPoint(localSpaceCorner);
|
||||
Vector3 viewportSpaceCorner = mainCamera.WorldToViewportPoint(worldSpaceCorner);
|
||||
|
||||
if (viewportSpaceCorner.z > 0)
|
||||
{
|
||||
anyPointIsInFrontOfCamera = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
viewportSpaceCorner.x = (viewportSpaceCorner.x <= 0.5f) ? 1 : 0;
|
||||
viewportSpaceCorner.y = (viewportSpaceCorner.y <= 0.5f) ? 1 : 0;
|
||||
}
|
||||
|
||||
minMax.AddPoint(viewportSpaceCorner);
|
||||
}
|
||||
|
||||
if (!anyPointIsInFrontOfCamera)
|
||||
{
|
||||
return new MinMax3D();
|
||||
}
|
||||
|
||||
return minMax;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 3D axis-aligned bounding box in viewport/screen space.
|
||||
/// X and Y are in viewport coordinates [0,1], Z is depth from camera.
|
||||
/// </summary>
|
||||
public struct MinMax3D
|
||||
{
|
||||
public float xMin;
|
||||
public float xMax;
|
||||
public float yMin;
|
||||
public float yMax;
|
||||
public float zMin;
|
||||
public float zMax;
|
||||
|
||||
public MinMax3D(float min, float max)
|
||||
{
|
||||
this.xMin = min;
|
||||
this.xMax = max;
|
||||
this.yMin = min;
|
||||
this.yMax = max;
|
||||
this.zMin = min;
|
||||
this.zMax = max;
|
||||
}
|
||||
|
||||
public void AddPoint(Vector3 point)
|
||||
{
|
||||
xMin = Mathf.Min(xMin, point.x);
|
||||
xMax = Mathf.Max(xMax, point.x);
|
||||
yMin = Mathf.Min(yMin, point.y);
|
||||
yMax = Mathf.Max(yMax, point.y);
|
||||
zMin = Mathf.Min(zMin, point.z);
|
||||
zMax = Mathf.Max(zMax, point.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d0beb143dec2a948b0c8348fc80e02b
|
||||
@@ -0,0 +1,26 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Core.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Shared constants for the portal system.
|
||||
/// Contains only hardcoded values used in multiple places (shader properties, object names, thresholds).
|
||||
/// Does NOT include default values for serialized fields (those are configured per-instance in Inspector).
|
||||
/// </summary>
|
||||
public static class PortalConstants
|
||||
{
|
||||
public const string PORTAL_CAMERA_NAME = "PortalCamera";
|
||||
|
||||
public const string SHADER_PROPERTY_MAIN_TEXTURE = "_MainTex";
|
||||
public const string SHADER_PROPERTY_DISPLAY_MASK = "_DisplayMask";
|
||||
public const string SHADER_PROPERTY_SLICE_NORMAL = "_sliceNormal";
|
||||
public const string SHADER_PROPERTY_SLICE_OFFSET = "_sliceOffsetDst";
|
||||
public const string SHADER_PROPERTY_SLICE_CENTER = "_SliceCenter";
|
||||
|
||||
public const float MIN_VOLUME_THRESHOLD = 0.001f;
|
||||
public const float GAMEPAD_DEADZONE = 0.01f;
|
||||
|
||||
public const int RENDER_TEXTURE_DEPTH = 24;
|
||||
public const RenderTextureFormat RENDER_TEXTURE_FORMAT = RenderTextureFormat.DefaultHDR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37282c5300e816f498b180139042682b
|
||||
@@ -0,0 +1,66 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Core.Utils
|
||||
{
|
||||
public static class RaycastHelper
|
||||
{
|
||||
private static readonly Vector3 ViewportCenter = new Vector3(0.5f, 0.5f, 0f);
|
||||
|
||||
public static bool RaycastFromCameraCenter(Camera camera, float maxDistance,
|
||||
LayerMask layerMask, out RaycastHit hit)
|
||||
{
|
||||
if (camera == null)
|
||||
{
|
||||
hit = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
Ray ray = camera.ViewportPointToRay(ViewportCenter);
|
||||
return Physics.Raycast(ray, out hit, maxDistance, layerMask);
|
||||
}
|
||||
|
||||
public static bool RaycastFromCameraCenter<T>(Camera camera, float maxDistance,
|
||||
LayerMask layerMask, out T component) where T : Component
|
||||
{
|
||||
component = null;
|
||||
|
||||
if (RaycastFromCameraCenter(camera, maxDistance, layerMask, out RaycastHit hit))
|
||||
{
|
||||
component = hit.collider.GetComponent<T>();
|
||||
return component != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ThrottledRaycast(ref float timer, float interval,
|
||||
Camera camera, float maxDistance, LayerMask layerMask, out RaycastHit hit)
|
||||
{
|
||||
timer += Time.deltaTime;
|
||||
|
||||
if (timer >= interval)
|
||||
{
|
||||
timer = 0f;
|
||||
return RaycastFromCameraCenter(camera, maxDistance, layerMask, out hit);
|
||||
}
|
||||
|
||||
hit = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ThrottledRaycastComponent<T>(ref float timer, float interval,
|
||||
Camera camera, float maxDistance, LayerMask layerMask, out T component) where T : Component
|
||||
{
|
||||
timer += Time.deltaTime;
|
||||
|
||||
if (timer >= interval)
|
||||
{
|
||||
timer = 0f;
|
||||
return RaycastFromCameraCenter(camera, maxDistance, layerMask, out component);
|
||||
}
|
||||
|
||||
component = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f37b9866154109148b3f7d2d3c121c36
|
||||
@@ -0,0 +1,224 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
|
||||
namespace Unheimlich.Core.Utils
|
||||
{
|
||||
public static class TweenHelper
|
||||
{
|
||||
public static int FadeCanvasGroup(CanvasGroup canvasGroup, float targetAlpha,
|
||||
float duration, System.Action onComplete = null,
|
||||
LeanTweenType easeType = LeanTweenType.easeInQuad)
|
||||
{
|
||||
if (canvasGroup == null) return -1;
|
||||
|
||||
LeanTween.cancel(canvasGroup.gameObject);
|
||||
return LeanTween.alphaCanvas(canvasGroup, targetAlpha, duration)
|
||||
.setEase(easeType)
|
||||
.setOnComplete(onComplete)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int FadeText(TextMeshProUGUI text, float targetAlpha,
|
||||
float duration, System.Action onComplete = null,
|
||||
LeanTweenType easeType = LeanTweenType.easeInQuad)
|
||||
{
|
||||
return FadeText(text?.gameObject, text, null, targetAlpha, duration, 1f, onComplete, easeType);
|
||||
}
|
||||
|
||||
public static int FadeText(GameObject targetObject, TextMeshProUGUI mainText,
|
||||
TextMeshProUGUI ghostText, float targetAlpha, float duration,
|
||||
float ghostAlphaRatio, System.Action onComplete = null,
|
||||
LeanTweenType easeType = LeanTweenType.easeInQuad)
|
||||
{
|
||||
if (mainText == null || targetObject == null) return -1;
|
||||
|
||||
float startAlpha = mainText.color.a;
|
||||
LeanTween.cancel(targetObject);
|
||||
|
||||
return LeanTween.value(targetObject, startAlpha, targetAlpha, duration)
|
||||
.setEase(easeType)
|
||||
.setOnUpdate((float val) => {
|
||||
if (mainText != null)
|
||||
{
|
||||
Color c = mainText.color;
|
||||
c.a = val;
|
||||
mainText.color = c;
|
||||
}
|
||||
|
||||
if (ghostText != null)
|
||||
{
|
||||
Color gc = ghostText.color;
|
||||
gc.a = val * ghostAlphaRatio;
|
||||
ghostText.color = gc;
|
||||
}
|
||||
})
|
||||
.setOnComplete(onComplete)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int ScaleLocal(GameObject target, Vector3 targetScale,
|
||||
float duration, System.Action onComplete = null,
|
||||
LeanTweenType easeType = LeanTweenType.easeInOutQuad)
|
||||
{
|
||||
if (target == null) return -1;
|
||||
|
||||
LeanTween.cancel(target);
|
||||
return LeanTween.scale(target, targetScale, duration)
|
||||
.setEase(easeType)
|
||||
.setOnComplete(onComplete)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int MoveLocal(GameObject target, Vector3 targetPosition,
|
||||
float duration, System.Action onComplete = null,
|
||||
LeanTweenType easeType = LeanTweenType.easeInOutQuad)
|
||||
{
|
||||
if (target == null) return -1;
|
||||
|
||||
LeanTween.cancel(target);
|
||||
return LeanTween.moveLocal(target, targetPosition, duration)
|
||||
.setEase(easeType)
|
||||
.setOnComplete(onComplete)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int RotateLocal(GameObject target, Vector3 targetRotation,
|
||||
float duration, System.Action onComplete = null,
|
||||
LeanTweenType easeType = LeanTweenType.easeInOutQuad)
|
||||
{
|
||||
if (target == null) return -1;
|
||||
|
||||
LeanTween.cancel(target);
|
||||
return LeanTween.rotate(target, targetRotation, duration)
|
||||
.setEase(easeType)
|
||||
.setOnComplete(onComplete)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int MoveLocalWithCurve(GameObject target, Vector3 targetPosition,
|
||||
float duration, AnimationCurve curve, System.Action onComplete = null)
|
||||
{
|
||||
if (target == null) return -1;
|
||||
|
||||
LeanTween.cancel(target);
|
||||
return LeanTween.moveLocal(target, targetPosition, duration)
|
||||
.setEase(curve)
|
||||
.setOnComplete(onComplete)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int FadeVolume(AudioSource source, float targetVolume,
|
||||
float duration, System.Action onComplete = null,
|
||||
LeanTweenType easeType = LeanTweenType.easeInOutQuad)
|
||||
{
|
||||
if (source == null) return -1;
|
||||
|
||||
GameObject sourceObject = source.gameObject;
|
||||
LeanTween.cancel(sourceObject);
|
||||
|
||||
return LeanTween.value(sourceObject, source.volume, targetVolume, duration)
|
||||
.setEase(easeType)
|
||||
.setOnUpdate((float val) => {
|
||||
if (source != null)
|
||||
source.volume = val;
|
||||
})
|
||||
.setOnComplete(onComplete)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int FadeVolume(GameObject target, float targetVolume,
|
||||
float duration, System.Action onComplete = null,
|
||||
LeanTweenType easeType = LeanTweenType.linear)
|
||||
{
|
||||
if (target == null) return -1;
|
||||
|
||||
float startVolume = AudioListener.volume;
|
||||
LeanTween.cancel(target);
|
||||
|
||||
return LeanTween.value(target, startVolume, targetVolume, duration)
|
||||
.setEase(easeType)
|
||||
.setOnUpdate((float val) => {
|
||||
AudioListener.volume = val;
|
||||
})
|
||||
.setOnComplete(onComplete)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int FloatAnimation(GameObject target, Vector3 basePosition,
|
||||
float amplitude, float duration, float delay = 0f, Axis axis = Axis.Y)
|
||||
{
|
||||
if (target == null) return -1;
|
||||
|
||||
LeanTween.cancel(target);
|
||||
|
||||
return LeanTween.value(target, 0f, Mathf.PI * 2f, duration)
|
||||
.setDelay(delay)
|
||||
.setOnUpdate((float val) => {
|
||||
if (target != null)
|
||||
{
|
||||
Vector3 newPos = basePosition;
|
||||
float offset = Mathf.Sin(val) * amplitude;
|
||||
|
||||
switch (axis)
|
||||
{
|
||||
case Axis.X: newPos.x += offset; break;
|
||||
case Axis.Y: newPos.y += offset; break;
|
||||
case Axis.Z: newPos.z += offset; break;
|
||||
}
|
||||
|
||||
target.transform.localPosition = newPos;
|
||||
}
|
||||
})
|
||||
.setLoopClamp()
|
||||
.setEase(LeanTweenType.linear)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int RotateAround(GameObject target, Vector3 axis, float degreesPerSecond)
|
||||
{
|
||||
if (target == null || degreesPerSecond <= 0f) return -1;
|
||||
|
||||
float duration = 360f / degreesPerSecond;
|
||||
LeanTween.cancel(target);
|
||||
|
||||
return LeanTween.rotateAroundLocal(target, axis, 360f, duration)
|
||||
.setLoopClamp()
|
||||
.setEase(LeanTweenType.linear)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static int AnimateMaterialFloat(GameObject target, Material material,
|
||||
int propertyID, float fromValue, float toValue, float duration,
|
||||
System.Action onComplete = null,
|
||||
LeanTweenType easeType = LeanTweenType.easeInOutQuad)
|
||||
{
|
||||
if (target == null || material == null) return -1;
|
||||
|
||||
LeanTween.cancel(target);
|
||||
|
||||
return LeanTween.value(target, fromValue, toValue, duration)
|
||||
.setEase(easeType)
|
||||
.setOnUpdate((float val) => {
|
||||
if (material != null)
|
||||
material.SetFloat(propertyID, val);
|
||||
})
|
||||
.setOnComplete(onComplete)
|
||||
.id;
|
||||
}
|
||||
|
||||
public static void CancelTween(ref int tweenId)
|
||||
{
|
||||
if (tweenId != -1)
|
||||
{
|
||||
LeanTween.cancel(tweenId);
|
||||
tweenId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum Axis
|
||||
{
|
||||
X, Y, Z
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f70df97fd94f86a4aa2cb00353624ba4
|
||||
@@ -0,0 +1,47 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Core.Utils
|
||||
{
|
||||
public static class UnityExtensions
|
||||
{
|
||||
public static void SafeInvoke(this System.Action action)
|
||||
{
|
||||
action?.Invoke();
|
||||
}
|
||||
|
||||
public static void SafeInvoke<T>(this System.Action<T> action, T arg)
|
||||
{
|
||||
action?.Invoke(arg);
|
||||
}
|
||||
|
||||
public static Color WithAlpha(this Color color, float alpha)
|
||||
{
|
||||
color.a = alpha;
|
||||
return color;
|
||||
}
|
||||
|
||||
public static Vector3 WithY(this Vector3 vector, float y)
|
||||
{
|
||||
vector.y = y;
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static Vector3 WithX(this Vector3 vector, float x)
|
||||
{
|
||||
vector.x = x;
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static Vector3 WithZ(this Vector3 vector, float z)
|
||||
{
|
||||
vector.z = z;
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static void CancelAllTweens(this GameObject gameObject)
|
||||
{
|
||||
if (gameObject != null)
|
||||
LeanTween.cancel(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5c8821940a7678458f26015ee167fdf
|
||||
Reference in New Issue
Block a user