PreviewBlockControl.decompiled.cs
源文件:
code/PreviewBlockControl.decompiled.cs
csharp
// =============================================================================
// PreviewBlockControl / TouchBlockBehavior —— 由 APK libil2cpp.so(ARM64) 反汇编还原的等价 C#
//
// 依据:libil2cpp.so + dump.cs(Il2CppDumper 6.7.46),capstone 5.0.7。
// 每个方法标注 VA(取自 dump.cs,APK 实测);泛型实参、静态单例等由 ELF 重定位/元数据槽反查确定。
//
// 字段偏移(dump.cs):
// 0x20 destroyInterval | 0x28 progressControl | 0x30 levelControl | 0x38 blockInfo(GameInformation.BlockArea)
// 0x40 screenWidth | 0x44 screenHeight | 0x48 index | 0x50 renderer(SpriteRenderer)
// 0x58 disabledBlockShowDuration | 0x5C disabledBlockReadyDuration
// 0x60 isDragging | 0x68 enabledLayer | 0x70 disabledLayer | 0x78 readyLayer | 0x80 touchLayer
// 0x88 lastPhase | 0x8C isDisabled | 0x8D wasVisible | 0x8E wasReady
// BlockArea:topRightPercentage 0x10 | bottomLeftPercentage 0x18 | appearTime 0x20 | enableTime 0x24
// disableTime 0x28 | disappearTime 0x2C | isSubtract 0x30 | rotateEvents 0x38 | moveEvents 0x40 | scaleEvents 0x48
// RotateEvent:anchor 0x10 | time 0x18 | easeType 0x1C | rotation 0x20
// MoveEvent:endPosition 0x10 | time 0x18 | easeTypeX 0x1C | easeTypeY 0x20
// ScaleEvent:anchor 0x10 | time 0x18 | easeTypeX 0x1C | easeTypeY 0x20 | scale 0x24
// =============================================================================
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ProjectEditor.PreviewScripts
{
public class PreviewBlockControl : MonoBehaviour
{
[SerializeField] private float destroyInterval; // 0x20
public ProgressControl progressControl; // 0x28
public LevelControl levelControl; // 0x30
public GameInformation.BlockArea blockInfo; // 0x38
public float screenWidth; // 0x40
public float screenHeight; // 0x44
public int index; // 0x48
[SerializeField] private SpriteRenderer renderer; // 0x50
[SerializeField] private float disabledBlockShowDuration; // 0x58
[SerializeField] private float disabledBlockReadyDuration; // 0x5C
[HideInInspector] public bool isDragging; // 0x60
private string enabledLayer; // 0x68
private string disabledLayer; // 0x70
private string readyLayer; // 0x78
private string touchLayer; // 0x80
private BlockPhase lastPhase; // 0x88
private bool isDisabled; // 0x8C
private bool wasVisible; // 0x8D
private bool wasReady; // 0x8E
// dump.cs TypeDefIndex 4924:HiddenBefore=0, Disabled=1, Ready=2, Active=3, HiddenAfter=4
public enum BlockPhase { HiddenBefore = 0, Disabled = 1, Ready = 2, Active = 3, HiddenAfter = 4 }
private readonly struct Geometry
{
public readonly Vector2 size; // Item1
public readonly Vector2 center; // Item2
public readonly Vector2 anchorWorld; // Item3
public Geometry(Vector2 s, Vector2 c, Vector2 a) { size = s; center = c; anchorWorld = a; }
}
// ---- VA 0x1D7060C ---------------------------------------------------
// 仅当 blockInfo.isSubtract 为真时,才把三个 layer 名字段设成静态字符串常量。
// 普通块(isSubtract=false)**不设置** enabledLayer/disabledLayer/readyLayer
// (保持 null → NameToLayer(null) 行为由后续协程承担)。反汇编实测。
private void Start()
{
if (blockInfo == null) throw new NullReferenceException();
if (blockInfo.isSubtract) // 0x30
{
enabledLayer = KEnabledLayerName; // 0x68 ← .rodata/元数据字符串常量
disabledLayer = KDisabledLayerName; // 0x70
readyLayer = KReadyLayerName; // 0x78
}
}
// 三个 layer 名常量取自静态字符串槽 0x4144aa0 / 0xa98 / 0xa90(名字字面量未在本环境解出)。
private const string KEnabledLayerName = null;
private const string KDisabledLayerName = null;
private const string KReadyLayerName = null;
// ---- VA 0x1D7069C ---------------------------------------------------
private void Update()
{
UpdateBlocksTransform();
UpdateBlockActivation();
UpdateBlockAnimations();
EnsureTouchHover(); // g__DestroyAfterInterval|16_0,见下方同名方法
}
// ---- VA 0x1D706C4 ---------------------------------------------------
public void UpdateBlocksTransform()
{
if (isDragging) return;
if (blockInfo == null || progressControl == null) throw new NullReferenceException();
float now = progressControl.nowTime; // ProgressControl +0x88
if (blockInfo.appearTime > now) // 未出现 → 藏到远处
{
transform.localPosition = new Vector3(1000f, 0f, 0f);
return;
}
if (now >= blockInfo.disappearTime) // 已消失 → 藏
{
transform.localPosition = new Vector3(1000f, 0f, 0f);
return;
}
Vector2 tr = blockInfo.topRightPercentage; // 0x10
Vector2 bl = blockInfo.bottomLeftPercentage; // 0x18
Vector2 screen = new Vector2(screenWidth, screenHeight);
Vector2 trW = (tr - new Vector2(0.5f, 0.5f)) * screen;
Vector2 blW = (bl - new Vector2(0.5f, 0.5f)) * screen;
Vector2 center = (trW + blW) * 0.5f;
transform.localPosition = new Vector3(center.x, center.y, 0f);
Vector2 size = new Vector2(Mathf.Abs(trW.x - blW.x), Mathf.Abs(trW.y - blW.y));
transform.localScale = new Vector3(size.x, size.y, 1f);
}
// ---- VA 0x1D707CC ---------------------------------------------------
private void UpdateBlockActivation()
{
if (progressControl == null || blockInfo == null) throw new NullReferenceException();
float now = progressControl.nowTime; // 0x88
float enable = blockInfo.enableTime; // 0x24
float disable = blockInfo.disableTime; // 0x28
float appear = blockInfo.appearTime; // 0x20
float disappear = blockInfo.disappearTime; // 0x2C
bool inWindow = enable <= now && now < disable;
bool notInWindow = !inWindow; // 反汇编 w20
bool visible = appear <= now && now < disappear; // w21
bool ready = (enable - disabledBlockReadyDuration) <= now && now < enable; // w22,用 0x5C
// DisabledBlockShow:进入窗口且本帧刚“出现”
if (visible && !wasVisible && !notInWindow)
StartCoroutine(DisabledBlockShow());
// DisabledBlockReady:进入预备窗口
if (ready && !wasReady)
StartCoroutine(DisabledBlockReady());
// 层级切换(仅切换到 disabledLayer;回到窗口时只清 isDisabled,层由协程改)
if (notInWindow)
{
if (!isDisabled) { isDisabled = true; gameObject.layer = LayerMask.NameToLayer(disabledLayer); }
}
else if (isDisabled) isDisabled = false;
// 减块用半透明
Color c = renderer.color;
c.a = blockInfo.isSubtract ? SubtractAlpha : 1f; // SubtractAlpha ≈ 0.1(.rodata 0xC261F0)
renderer.color = c;
wasVisible = visible;
wasReady = ready;
}
private const float SubtractAlpha = 0.1f; // .rodata 0xC261F0 实测
// ---- VA 0x1D7098C ---------------------------------------------------
private void UpdateBlockAnimations()
{
if (isDragging) return;
if (!IsTimeValid()) return;
// anchor = Vector2.one × 0.5 = (0.5, 0.5)
// 反汇编:静态 Vec2.one(static_fields+8) × 0.5(Vector2_TypeInfo 经重定位反查)
Vector2 anchor = Vector2.one * 0.5f;
Geometry geo = GetBlockGeometry(anchor);
var dc = new AnimState
{
currentSize = geo.size,
originalSize = geo.size,
currentCenter = geo.center,
currentRotation = 0f,
};
(Vector2 size, Vector2 center) sc = UpdateScale(ref dc);
dc.currentSize = sc.size;
dc.currentCenter = sc.center;
(float rot, Vector2 rotCenter) rot = UpdateRotation(ref dc);
Vector2 pos = UpdateMovement(geo.center, rotCenter);
transform.localPosition = new Vector3(pos.x, pos.y, 0f);
transform.localScale = new Vector3(Mathf.Abs(sc.size.x), Mathf.Abs(sc.size.y), 1f);
transform.eulerAngles = new Vector3(0f, 0f, rot);
}
private struct AnimState
{
public Vector2 currentSize; // +0x8
public Vector2 currentCenter; // +0x10
public Vector2 originalSize; // +0x18
public float currentRotation; // +0x20
}
// ---- VA 0x1D70C7C ---------------------------------------------------
private bool IsTimeValid()
{
if (blockInfo == null || progressControl == null) throw new NullReferenceException();
float now = progressControl.nowTime;
if (blockInfo.appearTime > now) return false;
return now < blockInfo.disappearTime;
}
// ---- VA 0x1D70CC0 ---------------------------------------------------
// 返回 (size, center, anchorWorld)
private Geometry GetBlockGeometry(Vector2 anchor)
{
if (blockInfo == null) throw new NullReferenceException();
Vector2 tr = blockInfo.topRightPercentage;
Vector2 bl = blockInfo.bottomLeftPercentage;
Vector2 screen = new Vector2(screenWidth, screenHeight);
Vector2 half = new Vector2(0.5f, 0.5f);
Vector2 trW = (tr - half) * screen;
Vector2 blW = (bl - half) * screen;
Vector2 size = trW - blW;
Vector2 center = (blW + trW) * 0.5f;
Vector2 anchorWorld = (anchor - half) * screen;
return new Geometry(size, center, anchorWorld);
}
// ---- VA 0x1D71404 ---------------------------------------------------
// currentCenter += (InterpolateMoveEvent(index) - originalCenter)
private Vector2 UpdateMovement(Vector2 originalCenter, Vector2 currentCenter)
{
if (blockInfo == null || blockInfo.moveEvents == null) throw new NullReferenceException();
if (blockInfo.moveEvents.Count >= 1)
{
int i = FindCurrentEventIndex(blockInfo.moveEvents, e => e.time);
if (i != -1)
{
Vector2 e = InterpolateMoveEvent(i);
currentCenter += e - originalCenter;
}
}
return currentCenter;
}
// ---- VA 0x1D7157C ---------------------------------------------------
private Vector2 AnchorToWorld(Vector2 anchor)
=> new Vector2((anchor.x - 0.5f) * screenWidth, (anchor.y - 0.5f) * screenHeight);
// ---- VA 0x1D71598 ---------------------------------------------------
private static Vector2 ScaleAroundAnchor(Vector2 point, Vector2 anchor, float stepX, float stepY)
=> new Vector2(anchor.x + (point.x - anchor.x) * stepX,
anchor.y + (point.y - anchor.y) * stepY);
// ---- VA 0x1D715B4 ---------------------------------------------------
private static Vector2 RotateAroundAnchor(Vector2 point, Vector2 anchor, float deltaDeg)
{
// 阈值 = max(|deltaDeg| * 1e-6, 8 * Mathf.Epsilon);Epsilon = 1.401298E-45(Mathf 唯一静态字段)
// 即 ~1e-44,对正常角度该守卫恒不触发。
float eps = Mathf.Max(Mathf.Abs(deltaDeg) * 1e-6f, 8f * Mathf.Epsilon);
if (Mathf.Abs(deltaDeg) < eps) return point;
float rad = deltaDeg * 0.017453292f; // Deg2Rad(.rodata 0xC34510)
float sin = Mathf.Sin(rad), cos = Mathf.Cos(rad); // 反汇编为单次 sincosf
float dx = point.x - anchor.x;
float dy = point.y - anchor.y;
return new Vector2(anchor.x + (dx * cos - dy * sin),
anchor.y + (dx * sin + dy * cos));
}
// ---- VA 0x1D716A0 ---------------------------------------------------
private static float SafeDiv(float numerator, float denominator)
{
float eps = Mathf.Max(Mathf.Abs(denominator) * 1e-6f, 8f * Mathf.Epsilon);
return Mathf.Abs(denominator) < eps ? 1f : numerator / denominator;
}
// ---- VA 0x1D719D8 ---------------------------------------------------
// GetEaseWithIndex(easeType, (nowTime - currentTime) / (nextTime - currentTime))
private float CalculateEasedProgress(float currentTime, float nextTime, int easeType)
{
if (progressControl == null) throw new NullReferenceException();
float progress = (progressControl.nowTime - currentTime) / (nextTime - currentTime);
return GetEase.GetEaseWithIndex(easeType, progress);
}
// ---- VA 0x1D7172C ---------------------------------------------------
private Vector2 InterpolateMoveEvent(int index)
{
var ev = blockInfo.moveEvents;
if (ev == null) throw new NullReferenceException();
int last = ev.Count - 1;
if (last <= index)
return AnchorToWorld(ev[index].endPosition);
MoveEvent cur = ev[index], next = ev[index + 1];
float tx = CalculateEasedProgress(cur.time, next.time, cur.easeTypeX);
float ty = CalculateEasedProgress(cur.time, next.time, cur.easeTypeY);
tx = Mathf.Clamp01(tx);
ty = Mathf.Clamp01(ty);
Vector2 v = new Vector2(
Mathf.Lerp(cur.endPosition.x, next.endPosition.x, tx),
Mathf.Lerp(cur.endPosition.y, next.endPosition.y, ty));
return AnchorToWorld(v);
}
// ---- VA 0x1D71868 ---------------------------------------------------
// 返回 (scale, anchor)
private (Vector2 scale, Vector2 anchor) InterpolateScaleEvent(int index)
{
var ev = blockInfo.scaleEvents;
if (ev == null) throw new NullReferenceException();
int last = ev.Count - 1;
if (last <= index)
return (ev[index].scale, ev[index].anchor);
ScaleEvent cur = ev[index], next = ev[index + 1];
float tx = Mathf.Clamp01(CalculateEasedProgress(cur.time, next.time, cur.easeTypeX));
float ty = Mathf.Clamp01(CalculateEasedProgress(cur.time, next.time, cur.easeTypeY));
Vector2 scale = new Vector2(
Mathf.Lerp(cur.scale.x, next.scale.x, tx),
Mathf.Lerp(cur.scale.y, next.scale.y, ty));
return (scale, cur.anchor);
}
// ---- VA 0x1D71A08 ---------------------------------------------------
// 返回 (rotation, anchor)
private (float rotation, Vector2 anchor) InterpolateRotateEvent(int index)
{
var ev = blockInfo.rotateEvents;
if (ev == null) throw new NullReferenceException();
int last = ev.Count - 1;
if (last <= index)
return (ev[index].rotation, ev[index].anchor);
RotateEvent cur = ev[index], next = ev[index + 1];
float t = Mathf.Clamp01(CalculateEasedProgress(cur.time, next.time, cur.easeType));
return (Mathf.Lerp(cur.rotation, next.rotation, t), cur.anchor);
}
// ---- VA 0x1D71C40 ---------------------------------------------------
public bool IsActive(float t)
{
if (blockInfo == null) throw new NullReferenceException();
return blockInfo.enableTime <= t && blockInfo.disableTime > t;
}
// ---- VA 0x1D71B40(编辑器回写:transform → blockInfo 百分比 + 写回列表)----
// 读 localPosition / localScale(世界→百分比反算),改写 topRightPercentage /
// bottomLeftPercentage,再把 blockInfo 写回 levelControl 列表的第 index 项。
private void UpdateBlockInfo()
{
if (blockInfo == null) throw new NullReferenceException();
Vector3 lp = transform.localPosition;
Vector3 ls = transform.localScale;
float hx = ls.x * 0.5f, hy = ls.y * 0.5f; // half scale
// 中心 + 半尺寸 → 右上百分比;中心 − 半尺寸 → 左下百分比
float cx = lp.x / screenWidth, cy = lp.y / screenHeight; // 0x40/0x44
float ex = hx / screenWidth, ey = hy / screenHeight;
blockInfo.bottomLeftPercentage = new Vector2((cx - ex) + 0.5f, (cy - ey) + 0.5f); // 0x18
blockInfo.topRightPercentage = new Vector2((cx + ex) + 0.5f, (cy + ey) + 0.5f); // 0x10
// 写回:levelControl(0x30) → +0x140 = chart(Chart) → +0x20 = chart.blockAreaList,
// 尾调用 `List<BlockArea>.set_Item(index, blockInfo)`(VA 0x2E0F2F8),index = 本对象 0x48。
levelControl.chart.blockAreaList[index] = blockInfo;
}
// ---- VA 0x1D70B10(<Update>g__DestroyAfterInterval|16_0)------------
private void EnsureTouchHover()
{
if (blockInfo == null || progressControl == null) throw new NullReferenceException();
float now = progressControl.nowTime;
float end = Mathf.Max(blockInfo.disableTime, blockInfo.disappearTime) + destroyInterval;
if (now > end) Destroy(gameObject);
}
// ---- VA 0x1F9A2DC(FindCurrentEventIndex<object>)-------------------
// 从头线性扫描(无二分):返回「最后一个 time ≤ now 的事件」的下标,
// 范围 [-1, Count-2]。Count==0 或 now < events[0].time 时返回 -1。
// 用严格比较 `t > now` 判停 ⇒ 等时刻取最靠后的那个(见 behavior.md §3.3)。
private int FindCurrentEventIndex<T>(List<T> events, Func<T, float> getTime)
{
if (events == null) throw new NullReferenceException();
if (progressControl == null) throw new NullReferenceException();
float now = progressControl.nowTime; // 0x28 → +0x88
int result = -1;
for (int i = 1; i < events.Count; i++)
{
if (getTime(events[i]) > now) return result; // fcmp/b.hi:首个严格大于 now 即返回 i-1
result = i;
}
return result; // 全 ≤ now ⇒ Count-2(或 -1 若 Count<2)
}
// ---- VA 0x1D70D78(<UpdateBlockAnimations>g__UpdateScale|24_0)--------
// 返回 (size = scaleFactor * originalSize, center)。
// 对 index 之前的每个事件把 center 按 SafeDiv(下一事件 scale / 本事件 scale) 绕锚点缩放,
// 再叠加当前事件的插值比例;size 用当前(或插值)scale 乘 originalSize。
private (Vector2 size, Vector2 center) UpdateScale(ref AnimState dc)
{
if (blockInfo == null || blockInfo.scaleEvents == null) throw new NullReferenceException();
var ev = blockInfo.scaleEvents;
if (ev.Count == 0) return (dc.currentSize, dc.currentCenter);
int index = FindCurrentEventIndex(ev, e => e.time);
if (index == -1) return (dc.currentSize, dc.currentCenter);
Vector2 screen = new Vector2(screenWidth, screenHeight);
Vector2 half = new Vector2(0.5f, 0.5f);
Vector2 p = dc.currentCenter;
for (int i = 0; i < index; i++)
{
ScaleEvent e0 = ev[i], e1 = ev[i + 1];
Vector2 a = (e0.anchor - half) * screen;
p = ScaleAroundAnchor(p, a,
SafeDiv(e1.scale.x, e0.scale.x), SafeDiv(e1.scale.y, e0.scale.y));
}
Vector2 size;
if (index >= ev.Count - 1)
{
size = ev[index].scale * dc.originalSize;
}
else
{
ScaleEvent cur = ev[index], next = ev[index + 1];
float tx = Mathf.Clamp01(CalculateEasedProgress(cur.time, next.time, cur.easeTypeX));
float ty = Mathf.Clamp01(CalculateEasedProgress(cur.time, next.time, cur.easeTypeY));
Vector2 interp = new Vector2(
Mathf.Lerp(cur.scale.x, next.scale.x, tx),
Mathf.Lerp(cur.scale.y, next.scale.y, ty));
Vector2 a = (cur.anchor - half) * screen;
p = ScaleAroundAnchor(p, a,
SafeDiv(interp.x, cur.scale.x), SafeDiv(interp.y, cur.scale.y));
size = interp * dc.originalSize;
}
return (size, p);
}
// ---- VA 0x1D710F4(<UpdateBlockAnimations>g__UpdateRotation|24_1)-------
// 返回 (rotation, center)。累积 index 之前每个事件的 Δrotation(绕锚点旋转 center),
// 再叠加当前事件(插值)的 Δrotation。
private (float rotation, Vector2 center) UpdateRotation(ref AnimState dc)
{
if (blockInfo == null || blockInfo.rotateEvents == null) throw new NullReferenceException();
var ev = blockInfo.rotateEvents;
if (ev.Count == 0) return (dc.currentRotation, dc.currentCenter);
int index = FindCurrentEventIndex(ev, e => e.time);
if (index == -1) return (dc.currentRotation, dc.currentCenter);
Vector2 screen = new Vector2(screenWidth, screenHeight);
Vector2 half = new Vector2(0.5f, 0.5f);
Vector2 p = dc.currentCenter;
for (int i = 0; i < index; i++)
{
RotateEvent e0 = ev[i], e1 = ev[i + 1];
Vector2 a = (e0.anchor - half) * screen;
p = RotateAroundAnchor(p, a, e1.rotation - e0.rotation);
}
float rotation;
if (index >= ev.Count - 1)
{
rotation = ev[index].rotation;
}
else
{
RotateEvent cur = ev[index], next = ev[index + 1];
float t = Mathf.Clamp01(CalculateEasedProgress(cur.time, next.time, cur.easeType));
rotation = Mathf.Lerp(cur.rotation, next.rotation, t);
Vector2 a = (cur.anchor - half) * screen;
p = RotateAroundAnchor(p, a, rotation - cur.rotation);
}
return (rotation, p);
}
// ---- VA 0x1D71E34(<DisabledBlockReady>d__39。)------------------------
// 预备窗口开始时先切 readyLayer,等待 disabledBlockReadyDuration 后再切 enabledLayer。
private IEnumerator DisabledBlockReady()
{
gameObject.layer = LayerMask.NameToLayer(readyLayer);
yield return new WaitForSeconds(disabledBlockReadyDuration);
gameObject.layer = LayerMask.NameToLayer(enabledLayer);
}
// ---- VA 0x1D71F90(<DisabledBlockShow>d__40。)------------------------
// 颜色按 disabledBlockShowDuration 线性淡入。起始/目标色取自 .rodata:
// 普通:(1,1,1,0) → (1,1,1,1);减块:(1,0,1,0.1) → (1,1,1,0.1)
private IEnumerator DisabledBlockShow()
{
Color startColor = blockInfo.isSubtract ? new Color(1f, 0f, 1f, 0.1f) : new Color(1f, 1f, 1f, 0f);
Color targetColor = blockInfo.isSubtract ? new Color(1f, 1f, 1f, 0.1f) : new Color(1f, 1f, 1f, 1f);
renderer.color = startColor;
float t = 0f;
while (t <= disabledBlockShowDuration)
{
t += Time.deltaTime;
renderer.color = Color.Lerp(startColor, targetColor, Mathf.Clamp01(t / disabledBlockShowDuration));
yield return null;
}
renderer.color = targetColor;
}
}
// =========================================================================
public class TouchBlockBehavior : MonoBehaviour
{
public float size; // 0x20
public float animationDuration; // 0x24
public Vector3 idlePosition; // 0x28
private Coroutine animationRoutine; // 0x38
// ---- VA 0x1D1CE04 ---------------------------------------------------
public void Initialize()
{
if (animationRoutine != null) { StopCoroutine(animationRoutine); animationRoutine = null; }
transform.localScale = Vector3.zero; // 静态单例 = Vector3.zeroVector
transform.localPosition = idlePosition;
}
// ---- VA 0x1D1D904 ---------------------------------------------------
public void UpdatePosition(Vector2 position)
{
Vector3 p = transform.position;
transform.position = new Vector3(position.x, position.y, p.z);
}
// ---- VA 0x1D1D968 ---------------------------------------------------
public void Show()
{
if (animationRoutine != null) { StopCoroutine(animationRoutine); animationRoutine = null; }
animationRoutine = StartCoroutine(Animation(Vector3.zero, new Vector3(size, size, size), true));
}
// ---- VA 0x1D1DA88 ---------------------------------------------------
public void Hide()
{
if (animationRoutine != null) { StopCoroutine(animationRoutine); animationRoutine = null; }
animationRoutine = StartCoroutine(Animation(transform.localScale, Vector3.zero, false));
}
// ---- VA 0x1D1E900(<Animation>d__8。)----------------------------------
// 线性插值(帧率无关):start→end,t 超过 animationDuration 后收尾;
// 若是 Hide(isShowing=false),结束时把 transform.position 归到 idlePosition。
private IEnumerator Animation(Vector3 startScale, Vector3 endScale, bool isShowing)
{
transform.localScale = startScale;
float t = 0f;
while (t <= animationDuration)
{
transform.localScale = Vector3.Lerp(startScale, endScale, Mathf.Clamp01(t / animationDuration));
t += Time.deltaTime;
yield return null;
}
transform.localScale = endScale;
animationRoutine = null;
if (!isShowing) transform.position = idlePosition;
}
}
}