Add AntMan scaling, ghost trail and test prefab
Introduce a set of test tools and demo assets for an AntMan-style scaling effect: - Add AntManScaleController: handles smooth scale transitions (absolute/relative modes), pivot preservation, queued toggles and events. - Add GhostTrailEmitter and GhostTrailGhost: spawn fading ghost meshes (supports skinned mesh baking, material fallback, lifetime and pooling limit). - Add GKTInventoryScaleToggle: inventory-gated input wrapper to toggle scale with optional Ctrl bypass and auto reference resolution. - Add AntManTestSceneBootstrap and TestBootstrap prefab to quickly construct a test scene (cube, light, camera) with components configured. - Add AntManScaleTest scene and related prefab/meta files, plus .vsconfig for Unity workload. - Update SampleScene LFS pointer (scene file checksum/size). These changes provide a reusable demo and components to test scaling visuals and interactions during development.
This commit is contained in:
6
.vsconfig
Normal file
6
.vsconfig
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"components": [
|
||||
"Microsoft.VisualStudio.Workload.ManagedGame"
|
||||
]
|
||||
}
|
||||
8
Assets/Game/Test.meta
Normal file
8
Assets/Game/Test.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9aed44429792668429343756e69073b5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Game/Test/Materials.meta
Normal file
8
Assets/Game/Test/Materials.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 570a115f5fb663e479ec22a4ffbe73bd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Game/Test/Scripts.meta
Normal file
8
Assets/Game/Test/Scripts.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4863615a0de796040b44022d21ba5147
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
194
Assets/Game/Test/Scripts/AntManScaleController.cs
Normal file
194
Assets/Game/Test/Scripts/AntManScaleController.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class AntManScaleController : MonoBehaviour
|
||||
{
|
||||
private enum ScaleMode
|
||||
{
|
||||
Absolute,
|
||||
RelativeToInitial
|
||||
}
|
||||
|
||||
[Header("Input")]
|
||||
[SerializeField] private bool readToggleInput = true;
|
||||
[SerializeField] private KeyCode toggleKey = KeyCode.Space;
|
||||
|
||||
[Header("Objetivo de Escala")]
|
||||
[SerializeField] private Transform scaleTarget;
|
||||
[SerializeField] private bool keepPivotWorldPosition = true;
|
||||
[SerializeField] private Transform pivotReference;
|
||||
|
||||
[Header("Escalas")]
|
||||
[SerializeField] private ScaleMode scaleMode = ScaleMode.RelativeToInitial;
|
||||
[SerializeField] private Vector3 smallScale = Vector3.one;
|
||||
[SerializeField] private Vector3 largeScale = new Vector3(20f, 20f, 20f);
|
||||
[SerializeField] [Min(0.001f)] private float smallScaleMultiplier = 0.2f;
|
||||
[SerializeField] [Min(0.001f)] private float largeScaleMultiplier = 1f;
|
||||
[SerializeField] private bool startLarge;
|
||||
|
||||
[Header("Transicion")]
|
||||
[SerializeField] [Min(0.01f)] private float transitionDuration = 0.45f;
|
||||
[SerializeField] private AnimationCurve transitionCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showStateLogs;
|
||||
|
||||
public event Action<Vector3, Vector3> TransitionStarted;
|
||||
public event Action<Vector3> TransitionCompleted;
|
||||
|
||||
public bool IsTransitioning => _transitionRoutine != null;
|
||||
public bool IsLarge => _isLarge;
|
||||
public Transform ScaleTarget => scaleTarget;
|
||||
|
||||
private bool _isLarge;
|
||||
private bool _hasQueuedToggle;
|
||||
private Coroutine _transitionRoutine;
|
||||
private Vector3 _initialScale;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (scaleTarget == null)
|
||||
{
|
||||
scaleTarget = transform;
|
||||
}
|
||||
|
||||
if (pivotReference == null)
|
||||
{
|
||||
pivotReference = scaleTarget;
|
||||
}
|
||||
|
||||
_initialScale = scaleTarget.localScale;
|
||||
|
||||
_isLarge = startLarge;
|
||||
ApplyScale(_isLarge ? GetLargeScale() : GetSmallScale());
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!readToggleInput)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(toggleKey))
|
||||
{
|
||||
RequestToggle();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetReadToggleInputState(bool state)
|
||||
{
|
||||
readToggleInput = state;
|
||||
}
|
||||
|
||||
public Vector3 GetCurrentScale()
|
||||
{
|
||||
if (scaleTarget == null)
|
||||
{
|
||||
return transform.localScale;
|
||||
}
|
||||
|
||||
return scaleTarget.localScale;
|
||||
}
|
||||
|
||||
public void RequestToggle()
|
||||
{
|
||||
if (IsTransitioning)
|
||||
{
|
||||
_hasQueuedToggle = !_hasQueuedToggle;
|
||||
if (showStateLogs)
|
||||
{
|
||||
Debug.Log($"[AntManScaleController] Toggle en cola: {_hasQueuedToggle}", this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
StartScaleTransition();
|
||||
}
|
||||
|
||||
private void StartScaleTransition()
|
||||
{
|
||||
Vector3 from = scaleTarget.localScale;
|
||||
_isLarge = !_isLarge;
|
||||
Vector3 to = _isLarge ? GetLargeScale() : GetSmallScale();
|
||||
|
||||
_transitionRoutine = StartCoroutine(TransitionRoutine(from, to));
|
||||
}
|
||||
|
||||
private IEnumerator TransitionRoutine(Vector3 from, Vector3 to)
|
||||
{
|
||||
if (showStateLogs)
|
||||
{
|
||||
Debug.Log($"[AntManScaleController] Transicion {from} -> {to}", this);
|
||||
}
|
||||
|
||||
TransitionStarted?.Invoke(from, to);
|
||||
|
||||
float elapsed = 0f;
|
||||
while (elapsed < transitionDuration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / transitionDuration);
|
||||
float eased = transitionCurve.Evaluate(t);
|
||||
ApplyScale(Vector3.LerpUnclamped(from, to, eased));
|
||||
yield return null;
|
||||
}
|
||||
|
||||
ApplyScale(to);
|
||||
TransitionCompleted?.Invoke(to);
|
||||
|
||||
if (showStateLogs)
|
||||
{
|
||||
Debug.Log($"[AntManScaleController] Escala final: {to}", this);
|
||||
}
|
||||
|
||||
_transitionRoutine = null;
|
||||
|
||||
if (_hasQueuedToggle)
|
||||
{
|
||||
_hasQueuedToggle = false;
|
||||
StartScaleTransition();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyScale(Vector3 newScale)
|
||||
{
|
||||
if (scaleTarget == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keepPivotWorldPosition || pivotReference == null)
|
||||
{
|
||||
scaleTarget.localScale = newScale;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 pivotPositionBefore = pivotReference.position;
|
||||
scaleTarget.localScale = newScale;
|
||||
Vector3 pivotPositionAfter = pivotReference.position;
|
||||
Vector3 delta = pivotPositionBefore - pivotPositionAfter;
|
||||
scaleTarget.position += delta;
|
||||
}
|
||||
|
||||
private Vector3 GetSmallScale()
|
||||
{
|
||||
if (scaleMode == ScaleMode.RelativeToInitial)
|
||||
{
|
||||
return _initialScale * smallScaleMultiplier;
|
||||
}
|
||||
|
||||
return smallScale;
|
||||
}
|
||||
|
||||
private Vector3 GetLargeScale()
|
||||
{
|
||||
if (scaleMode == ScaleMode.RelativeToInitial)
|
||||
{
|
||||
return _initialScale * largeScaleMultiplier;
|
||||
}
|
||||
|
||||
return largeScale;
|
||||
}
|
||||
}
|
||||
2
Assets/Game/Test/Scripts/AntManScaleController.cs.meta
Normal file
2
Assets/Game/Test/Scripts/AntManScaleController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd4a72fbc89e96841b4c2b9bdb1b1868
|
||||
70
Assets/Game/Test/Scripts/AntManTestSceneBootstrap.cs
Normal file
70
Assets/Game/Test/Scripts/AntManTestSceneBootstrap.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class AntManTestSceneBootstrap : MonoBehaviour
|
||||
{
|
||||
[Header("Escena")]
|
||||
[SerializeField] private string cubeName = "AntManCube";
|
||||
[SerializeField] private string lightName = "Directional Light";
|
||||
[SerializeField] private string cameraName = "Main Camera";
|
||||
|
||||
[ContextMenu("Build Test Scene")]
|
||||
public void BuildTestScene()
|
||||
{
|
||||
GameObject cube = FindOrCreateCube();
|
||||
FindOrCreateLight();
|
||||
FindOrCreateCamera(cube.transform.position);
|
||||
}
|
||||
|
||||
private GameObject FindOrCreateCube()
|
||||
{
|
||||
GameObject cube = GameObject.Find(cubeName);
|
||||
if (cube == null)
|
||||
{
|
||||
cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
cube.name = cubeName;
|
||||
cube.transform.position = Vector3.zero;
|
||||
}
|
||||
|
||||
if (cube.GetComponent<AntManScaleController>() == null)
|
||||
{
|
||||
cube.AddComponent<AntManScaleController>();
|
||||
}
|
||||
|
||||
if (cube.GetComponent<GhostTrailEmitter>() == null)
|
||||
{
|
||||
cube.AddComponent<GhostTrailEmitter>();
|
||||
}
|
||||
|
||||
return cube;
|
||||
}
|
||||
|
||||
private void FindOrCreateLight()
|
||||
{
|
||||
if (GameObject.Find(lightName) != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject lightObject = new GameObject(lightName);
|
||||
Light lightComponent = lightObject.AddComponent<Light>();
|
||||
lightComponent.type = LightType.Directional;
|
||||
lightComponent.intensity = 1.1f;
|
||||
|
||||
lightObject.transform.rotation = Quaternion.Euler(50f, -30f, 0f);
|
||||
}
|
||||
|
||||
private void FindOrCreateCamera(Vector3 targetPosition)
|
||||
{
|
||||
GameObject cameraObject = GameObject.Find(cameraName);
|
||||
if (cameraObject == null)
|
||||
{
|
||||
cameraObject = new GameObject(cameraName);
|
||||
cameraObject.tag = "MainCamera";
|
||||
cameraObject.AddComponent<Camera>();
|
||||
cameraObject.AddComponent<AudioListener>();
|
||||
}
|
||||
|
||||
cameraObject.transform.position = targetPosition + new Vector3(0f, 7f, -18f);
|
||||
cameraObject.transform.LookAt(targetPosition);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4163624d80de5a348970d71a64d6ccac
|
||||
177
Assets/Game/Test/Scripts/GKTInventoryScaleToggle.cs
Normal file
177
Assets/Game/Test/Scripts/GKTInventoryScaleToggle.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class GKTInventoryScaleToggle : MonoBehaviour
|
||||
{
|
||||
[Header("Input")]
|
||||
[SerializeField] private KeyCode toggleKey = KeyCode.L;
|
||||
[SerializeField] private bool disableDirectInputOnScaleController = true;
|
||||
[SerializeField] private bool allowCtrlLBypass = true;
|
||||
|
||||
[Header("Objeto Requerido (X)")]
|
||||
[SerializeField] private string requiredInventoryObjectName = "";
|
||||
[SerializeField] private bool requireObjectConfiguredInInventoryList = true;
|
||||
|
||||
[Header("Referencias")]
|
||||
[SerializeField] private AntManScaleController scaleController;
|
||||
[SerializeField] private playerComponentsManager mainPlayerComponentsManager;
|
||||
[SerializeField] private inventoryManager mainInventoryManager;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showLogs = true;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (scaleController != null && disableDirectInputOnScaleController)
|
||||
{
|
||||
scaleController.SetReadToggleInputState(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(toggleKey))
|
||||
{
|
||||
bool bypassInventoryCheck = allowCtrlLBypass && IsControlPressed();
|
||||
TryToggleScale(bypassInventoryCheck);
|
||||
}
|
||||
}
|
||||
|
||||
public void TryToggleScale(bool bypassInventoryCheck = false)
|
||||
{
|
||||
ResolveReferences();
|
||||
|
||||
if (scaleController == null)
|
||||
{
|
||||
if (showLogs)
|
||||
{
|
||||
Debug.LogWarning("[GKTInventoryScaleToggle] Falta AntManScaleController en este personaje.", this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (bypassInventoryCheck)
|
||||
{
|
||||
scaleController.RequestToggle();
|
||||
|
||||
if (showLogs)
|
||||
{
|
||||
Debug.Log("[GKTInventoryScaleToggle] Ctrl+L bypass activo. Se omite verificacion de objeto X.", this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mainInventoryManager == null)
|
||||
{
|
||||
if (showLogs)
|
||||
{
|
||||
Debug.LogWarning("[GKTInventoryScaleToggle] No se encontro inventoryManager en el personaje.", this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(requiredInventoryObjectName))
|
||||
{
|
||||
if (showLogs)
|
||||
{
|
||||
Debug.LogWarning("[GKTInventoryScaleToggle] El objeto X aun no esta asignado. Completa Required Inventory Object Name.", this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (requireObjectConfiguredInInventoryList && !mainInventoryManager.existInventoryInfoFromName(requiredInventoryObjectName))
|
||||
{
|
||||
if (showLogs)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
"[GKTInventoryScaleToggle] El objeto X no existe en el Inventory List Manager: " + requiredInventoryObjectName,
|
||||
this
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int amount = mainInventoryManager.getInventoryObjectAmountByName(requiredInventoryObjectName);
|
||||
bool hasObject = amount > 0;
|
||||
|
||||
if (!hasObject)
|
||||
{
|
||||
if (showLogs)
|
||||
{
|
||||
Debug.Log("[GKTInventoryScaleToggle] No puedes cambiar escala. Falta objeto X en inventario: " + requiredInventoryObjectName, this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
scaleController.RequestToggle();
|
||||
|
||||
if (showLogs)
|
||||
{
|
||||
Debug.Log("[GKTInventoryScaleToggle] Escala alternada. Objeto X verificado: " + requiredInventoryObjectName + " x" + amount, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRequiredInventoryObjectName(string newObjectName)
|
||||
{
|
||||
requiredInventoryObjectName = newObjectName;
|
||||
}
|
||||
|
||||
private static bool IsControlPressed()
|
||||
{
|
||||
return Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
|
||||
}
|
||||
|
||||
private void ResolveReferences()
|
||||
{
|
||||
if (scaleController == null)
|
||||
{
|
||||
scaleController = GetComponent<AntManScaleController>();
|
||||
|
||||
if (scaleController == null)
|
||||
{
|
||||
scaleController = GetComponentInParent<AntManScaleController>(true);
|
||||
}
|
||||
|
||||
if (scaleController == null)
|
||||
{
|
||||
scaleController = GetComponentInChildren<AntManScaleController>(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (mainPlayerComponentsManager == null)
|
||||
{
|
||||
mainPlayerComponentsManager = GetComponent<playerComponentsManager>();
|
||||
|
||||
if (mainPlayerComponentsManager == null)
|
||||
{
|
||||
mainPlayerComponentsManager = GetComponentInParent<playerComponentsManager>(true);
|
||||
}
|
||||
|
||||
if (mainPlayerComponentsManager == null)
|
||||
{
|
||||
mainPlayerComponentsManager = GetComponentInChildren<playerComponentsManager>(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (mainInventoryManager == null && mainPlayerComponentsManager != null)
|
||||
{
|
||||
mainInventoryManager = mainPlayerComponentsManager.getInventoryManager();
|
||||
}
|
||||
|
||||
if (mainInventoryManager == null)
|
||||
{
|
||||
mainInventoryManager = GetComponent<inventoryManager>();
|
||||
|
||||
if (mainInventoryManager == null)
|
||||
{
|
||||
mainInventoryManager = GetComponentInParent<inventoryManager>(true);
|
||||
}
|
||||
|
||||
if (mainInventoryManager == null)
|
||||
{
|
||||
mainInventoryManager = GetComponentInChildren<inventoryManager>(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Game/Test/Scripts/GKTInventoryScaleToggle.cs.meta
Normal file
2
Assets/Game/Test/Scripts/GKTInventoryScaleToggle.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5104cfed39c5e904ea25c70040dd6de2
|
||||
238
Assets/Game/Test/Scripts/GhostTrailEmitter.cs
Normal file
238
Assets/Game/Test/Scripts/GhostTrailEmitter.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class GhostTrailEmitter : MonoBehaviour
|
||||
{
|
||||
[Header("Referencias")]
|
||||
[SerializeField] private AntManScaleController scaleController;
|
||||
[SerializeField] private Material ghostMaterial;
|
||||
[SerializeField] private Transform ghostSourceTransform;
|
||||
[SerializeField] private MeshFilter sourceMeshFilter;
|
||||
[SerializeField] private MeshRenderer sourceMeshRenderer;
|
||||
[SerializeField] private SkinnedMeshRenderer sourceSkinnedMeshRenderer;
|
||||
[SerializeField] private bool useSkinnedMeshBake = true;
|
||||
[SerializeField] private bool useScaleControllerScaleOnSkinnedGhost = true;
|
||||
|
||||
[Header("Spawn")]
|
||||
[SerializeField] [Min(0.01f)] private float spawnInterval = 0.05f;
|
||||
[SerializeField] [Min(1)] private int maxActiveGhosts = 32;
|
||||
|
||||
[Header("Fade")]
|
||||
[SerializeField] [Min(0.01f)] private float ghostLifetime = 0.35f;
|
||||
[SerializeField] [Range(0f, 1f)] private float startAlpha = 0.45f;
|
||||
[SerializeField] [Range(0f, 1f)] private float endAlpha = 0f;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showGhostCountLogs;
|
||||
|
||||
private readonly List<GhostTrailGhost> _activeGhosts = new List<GhostTrailGhost>();
|
||||
|
||||
private Material _fallbackMaterial;
|
||||
private bool _isEmitting;
|
||||
private float _spawnTimer;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (scaleController == null)
|
||||
{
|
||||
scaleController = GetComponent<AntManScaleController>();
|
||||
}
|
||||
|
||||
ResolveRenderSources();
|
||||
|
||||
if (ghostMaterial == null)
|
||||
{
|
||||
_fallbackMaterial = GetFallbackMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (scaleController != null)
|
||||
{
|
||||
scaleController.TransitionStarted += HandleTransitionStarted;
|
||||
scaleController.TransitionCompleted += HandleTransitionCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (scaleController != null)
|
||||
{
|
||||
scaleController.TransitionStarted -= HandleTransitionStarted;
|
||||
scaleController.TransitionCompleted -= HandleTransitionCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!_isEmitting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_spawnTimer += Time.deltaTime;
|
||||
while (_spawnTimer >= spawnInterval)
|
||||
{
|
||||
_spawnTimer -= spawnInterval;
|
||||
SpawnGhost();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTransitionStarted(Vector3 from, Vector3 to)
|
||||
{
|
||||
_isEmitting = true;
|
||||
_spawnTimer = 0f;
|
||||
SpawnGhost();
|
||||
}
|
||||
|
||||
private void HandleTransitionCompleted(Vector3 scale)
|
||||
{
|
||||
_isEmitting = false;
|
||||
}
|
||||
|
||||
private void SpawnGhost()
|
||||
{
|
||||
CleanupNullGhosts();
|
||||
EnforceGhostLimit();
|
||||
|
||||
if (!TryGetGhostRenderData(out Mesh meshToUse, out Material materialToUse, out Transform sourceTransform, out bool ownsMesh, out bool fromSkinnedMesh))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject ghostObject = new GameObject("GhostTrail");
|
||||
ghostObject.transform.SetPositionAndRotation(sourceTransform.position, sourceTransform.rotation);
|
||||
ghostObject.transform.localScale = GetGhostScale(sourceTransform, fromSkinnedMesh);
|
||||
|
||||
GhostTrailGhost ghost = ghostObject.AddComponent<GhostTrailGhost>();
|
||||
ghost.Initialize(
|
||||
meshToUse,
|
||||
materialToUse,
|
||||
ghostLifetime,
|
||||
startAlpha,
|
||||
endAlpha,
|
||||
this,
|
||||
ownsMesh
|
||||
);
|
||||
|
||||
_activeGhosts.Add(ghost);
|
||||
|
||||
if (showGhostCountLogs)
|
||||
{
|
||||
Debug.Log($"[GhostTrailEmitter] Ghosts activos: {_activeGhosts.Count}", this);
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyGhostDestroyed(GhostTrailGhost ghost)
|
||||
{
|
||||
_activeGhosts.Remove(ghost);
|
||||
}
|
||||
|
||||
private void CleanupNullGhosts()
|
||||
{
|
||||
for (int i = _activeGhosts.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_activeGhosts[i] == null)
|
||||
{
|
||||
_activeGhosts.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnforceGhostLimit()
|
||||
{
|
||||
while (_activeGhosts.Count >= maxActiveGhosts)
|
||||
{
|
||||
GhostTrailGhost oldestGhost = _activeGhosts[0];
|
||||
_activeGhosts.RemoveAt(0);
|
||||
|
||||
if (oldestGhost != null)
|
||||
{
|
||||
Destroy(oldestGhost.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveRenderSources()
|
||||
{
|
||||
if (ghostSourceTransform == null)
|
||||
{
|
||||
ghostSourceTransform = transform;
|
||||
}
|
||||
|
||||
if (sourceSkinnedMeshRenderer == null)
|
||||
{
|
||||
sourceSkinnedMeshRenderer = ghostSourceTransform.GetComponentInChildren<SkinnedMeshRenderer>(true);
|
||||
}
|
||||
|
||||
if (sourceMeshFilter == null)
|
||||
{
|
||||
sourceMeshFilter = ghostSourceTransform.GetComponentInChildren<MeshFilter>(true);
|
||||
}
|
||||
|
||||
if (sourceMeshRenderer == null)
|
||||
{
|
||||
sourceMeshRenderer = ghostSourceTransform.GetComponentInChildren<MeshRenderer>(true);
|
||||
}
|
||||
}
|
||||
|
||||
private Material GetFallbackMaterial()
|
||||
{
|
||||
if (sourceSkinnedMeshRenderer != null)
|
||||
{
|
||||
return sourceSkinnedMeshRenderer.sharedMaterial;
|
||||
}
|
||||
|
||||
if (sourceMeshRenderer != null)
|
||||
{
|
||||
return sourceMeshRenderer.sharedMaterial;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryGetGhostRenderData(out Mesh mesh, out Material material, out Transform sourceTransform, out bool ownsMesh, out bool fromSkinnedMesh)
|
||||
{
|
||||
ResolveRenderSources();
|
||||
|
||||
sourceTransform = ghostSourceTransform != null ? ghostSourceTransform : transform;
|
||||
material = ghostMaterial != null ? ghostMaterial : (_fallbackMaterial != null ? _fallbackMaterial : GetFallbackMaterial());
|
||||
|
||||
mesh = null;
|
||||
ownsMesh = false;
|
||||
fromSkinnedMesh = false;
|
||||
|
||||
if (useSkinnedMeshBake && sourceSkinnedMeshRenderer != null)
|
||||
{
|
||||
Mesh bakedMesh = new Mesh();
|
||||
sourceSkinnedMeshRenderer.BakeMesh(bakedMesh);
|
||||
|
||||
mesh = bakedMesh;
|
||||
ownsMesh = true;
|
||||
fromSkinnedMesh = true;
|
||||
sourceTransform = sourceSkinnedMeshRenderer.transform;
|
||||
}
|
||||
else if (sourceMeshFilter != null)
|
||||
{
|
||||
mesh = sourceMeshFilter.sharedMesh;
|
||||
|
||||
if (sourceMeshFilter.transform != null)
|
||||
{
|
||||
sourceTransform = sourceMeshFilter.transform;
|
||||
}
|
||||
}
|
||||
|
||||
return mesh != null && material != null;
|
||||
}
|
||||
|
||||
private Vector3 GetGhostScale(Transform sourceTransform, bool fromSkinnedMesh)
|
||||
{
|
||||
if (fromSkinnedMesh && useScaleControllerScaleOnSkinnedGhost && scaleController != null)
|
||||
{
|
||||
return scaleController.GetCurrentScale();
|
||||
}
|
||||
|
||||
return sourceTransform.lossyScale;
|
||||
}
|
||||
}
|
||||
2
Assets/Game/Test/Scripts/GhostTrailEmitter.cs.meta
Normal file
2
Assets/Game/Test/Scripts/GhostTrailEmitter.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cee3ef18b23f7654f8d9cfc52fb4c7b6
|
||||
129
Assets/Game/Test/Scripts/GhostTrailGhost.cs
Normal file
129
Assets/Game/Test/Scripts/GhostTrailGhost.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
[RequireComponent(typeof(MeshFilter))]
|
||||
[RequireComponent(typeof(MeshRenderer))]
|
||||
public class GhostTrailGhost : MonoBehaviour
|
||||
{
|
||||
private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor");
|
||||
private static readonly int ColorId = Shader.PropertyToID("_Color");
|
||||
|
||||
private MeshRenderer _meshRenderer;
|
||||
private GhostTrailEmitter _owner;
|
||||
private Material _runtimeMaterial;
|
||||
private Mesh _runtimeMesh;
|
||||
private bool _ownsRuntimeMesh;
|
||||
|
||||
private float _lifeElapsed;
|
||||
private float _lifetime;
|
||||
private float _startAlpha;
|
||||
private float _endAlpha;
|
||||
private Color _baseColor;
|
||||
private bool _usesBaseColor;
|
||||
|
||||
public void Initialize(
|
||||
Mesh mesh,
|
||||
Material templateMaterial,
|
||||
float lifetime,
|
||||
float startAlpha,
|
||||
float endAlpha,
|
||||
GhostTrailEmitter owner,
|
||||
bool ownsRuntimeMesh = false)
|
||||
{
|
||||
_owner = owner;
|
||||
_lifetime = Mathf.Max(0.01f, lifetime);
|
||||
_startAlpha = Mathf.Clamp01(startAlpha);
|
||||
_endAlpha = Mathf.Clamp01(endAlpha);
|
||||
_runtimeMesh = mesh;
|
||||
_ownsRuntimeMesh = ownsRuntimeMesh;
|
||||
|
||||
MeshFilter meshFilter = GetComponent<MeshFilter>();
|
||||
_meshRenderer = GetComponent<MeshRenderer>();
|
||||
|
||||
meshFilter.sharedMesh = _runtimeMesh;
|
||||
|
||||
_runtimeMaterial = new Material(templateMaterial);
|
||||
ConfigureTransparentMaterial(_runtimeMaterial);
|
||||
|
||||
_usesBaseColor = _runtimeMaterial.HasProperty(BaseColorId);
|
||||
if (_usesBaseColor)
|
||||
{
|
||||
_baseColor = _runtimeMaterial.GetColor(BaseColorId);
|
||||
}
|
||||
else if (_runtimeMaterial.HasProperty(ColorId))
|
||||
{
|
||||
_baseColor = _runtimeMaterial.GetColor(ColorId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_baseColor = Color.white;
|
||||
}
|
||||
|
||||
_meshRenderer.sharedMaterial = _runtimeMaterial;
|
||||
ApplyAlpha(_startAlpha);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_lifeElapsed += Time.deltaTime;
|
||||
float t = Mathf.Clamp01(_lifeElapsed / _lifetime);
|
||||
float alpha = Mathf.Lerp(_startAlpha, _endAlpha, t);
|
||||
|
||||
ApplyAlpha(alpha);
|
||||
|
||||
if (_lifeElapsed >= _lifetime)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_owner != null)
|
||||
{
|
||||
_owner.NotifyGhostDestroyed(this);
|
||||
}
|
||||
|
||||
if (_runtimeMaterial != null)
|
||||
{
|
||||
Destroy(_runtimeMaterial);
|
||||
}
|
||||
|
||||
if (_ownsRuntimeMesh && _runtimeMesh != null)
|
||||
{
|
||||
Destroy(_runtimeMesh);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyAlpha(float alpha)
|
||||
{
|
||||
Color color = _baseColor;
|
||||
color.a = alpha;
|
||||
|
||||
if (_usesBaseColor)
|
||||
{
|
||||
_runtimeMaterial.SetColor(BaseColorId, color);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_runtimeMaterial.HasProperty(ColorId))
|
||||
{
|
||||
_runtimeMaterial.SetColor(ColorId, color);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureTransparentMaterial(Material material)
|
||||
{
|
||||
if (material.HasProperty("_Surface")) material.SetFloat("_Surface", 1f);
|
||||
if (material.HasProperty("_Blend")) material.SetFloat("_Blend", 0f);
|
||||
if (material.HasProperty("_AlphaClip")) material.SetFloat("_AlphaClip", 0f);
|
||||
if (material.HasProperty("_SrcBlend")) material.SetFloat("_SrcBlend", (float)BlendMode.SrcAlpha);
|
||||
if (material.HasProperty("_DstBlend")) material.SetFloat("_DstBlend", (float)BlendMode.OneMinusSrcAlpha);
|
||||
if (material.HasProperty("_ZWrite")) material.SetFloat("_ZWrite", 0f);
|
||||
|
||||
material.DisableKeyword("_ALPHATEST_ON");
|
||||
material.EnableKeyword("_ALPHABLEND_ON");
|
||||
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
|
||||
material.renderQueue = (int)RenderQueue.Transparent;
|
||||
}
|
||||
}
|
||||
2
Assets/Game/Test/Scripts/GhostTrailGhost.cs.meta
Normal file
2
Assets/Game/Test/Scripts/GhostTrailGhost.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b153d4f8b1c912040b2b8846f2297bef
|
||||
8
Assets/Game/Test/prefabs.meta
Normal file
8
Assets/Game/Test/prefabs.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d1e7c4c92a0e6f4093087ae4a1f242c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Game/Test/prefabs/TestBootstrap.prefab
LFS
Normal file
BIN
Assets/Game/Test/prefabs/TestBootstrap.prefab
LFS
Normal file
Binary file not shown.
7
Assets/Game/Test/prefabs/TestBootstrap.prefab.meta
Normal file
7
Assets/Game/Test/prefabs/TestBootstrap.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: efec9115045e1914da81e3ece87a5361
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Scenes/AntManScaleTest.unity
LFS
Normal file
BIN
Assets/Scenes/AntManScaleTest.unity
LFS
Normal file
Binary file not shown.
7
Assets/Scenes/AntManScaleTest.unity.meta
Normal file
7
Assets/Scenes/AntManScaleTest.unity.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ba5b445a9d39dc418c0570957d961d1
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e2e2ba4c3e81e5a0614568e37712497dff43ad0c82ea50aa0ffd84a962796768
|
||||
size 43402298
|
||||
oid sha256:6c78b30f04395b661117cda55d07f644824651c319ce0262450d07db870f4ac4
|
||||
size 43409826
|
||||
|
||||
Reference in New Issue
Block a user