173 lines
11 KiB
C#
173 lines
11 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using BlockOutReplica.Presentation;
|
|
using UnityEngine;
|
|
|
|
namespace BlockOutReplica.Gameplay
|
|
{
|
|
public sealed partial class BlockOutGameController
|
|
{
|
|
public string BoosterPhase { get; private set; } = "";
|
|
public float BoosterPlaybackTime { get; private set; }
|
|
public float FreezeAnimationTime { get; private set; }
|
|
public int ActiveBoosterParticleCount { get; private set; }
|
|
public int ActiveFreezeParticleCount { get; private set; }
|
|
public bool HasNativeFreezeScreen { get; private set; }
|
|
private Transform CreateAnimationSpace(string name,bool atTop=false)
|
|
{
|
|
var space=new GameObject(name).transform; space.SetParent(GetBoardRoot(),false); generated.Add(space.gameObject);
|
|
float sourceScale=gameCamera.orthographicSize*2/20.48f;
|
|
space.position=gameCamera.transform.position+gameCamera.transform.forward*3
|
|
+gameCamera.transform.up*(atTop?gameCamera.orthographicSize:-gameCamera.orthographicSize);
|
|
// FreezeGun's animated Root already enters from below. Original
|
|
// playtest places the gun in the lower half, not over the timer.
|
|
if(atTop)space.position-=gameCamera.transform.up*gameCamera.orthographicSize;
|
|
space.rotation=gameCamera.transform.rotation; space.localScale=Vector3.one*sourceScale;
|
|
return space;
|
|
}
|
|
private IEnumerator PlayFreezeBooster()
|
|
{
|
|
var data=Resources.Load<OriginalBoosterAnimationData>("OriginalBoosters/Freeze");
|
|
if(data==null){Debug.LogError("Missing recovered Freeze animation. Run Recover Original Visual Layers.");yield break;}
|
|
var space=CreateAnimationSpace("Original Freeze Gun",true);
|
|
var actor=data.CreateInstance(space,200);
|
|
var clockData=Resources.Load<OriginalBoosterAnimationData>("OriginalBoosters/FreezeTimerStart");
|
|
var endData=Resources.Load<OriginalBoosterAnimationData>("OriginalBoosters/FreezeTimerEnd");
|
|
var screenData=Resources.Load<OriginalBoosterAnimationData>("OriginalBoosters/FreezeScreen");
|
|
Transform clockSpace=clockData==null?null:CreateAnimationSpace("Original Ice Timer");
|
|
var clock=clockData==null?null:clockData.CreateInstance(clockSpace,205);
|
|
Transform screenSpace=screenData==null?null:CreateAnimationSpace("Original Freeze Screen",true);
|
|
var screen=screenData==null?null:screenData.CreateInstance(screenSpace,190);
|
|
HasNativeFreezeScreen=screen!=null;
|
|
if(screen!=null&&screen.Renderers.TryGetValue("freezefront",out var front))
|
|
front.size=new Vector2(gameCamera.aspect*20.48f,20.48f);
|
|
float clockTime=0,ending=0;
|
|
float endDuration=endData==null?.8333333f:endData.Duration;
|
|
for(float t=0;ScreenState==BlockOutScreenState.Playing && (freezeRemaining>0 || ending<endDuration);)
|
|
{
|
|
if(IsPaused){yield return null;continue;}
|
|
FreezeAnimationTime=t;
|
|
if(actor.Root!=null)
|
|
{
|
|
data.Sample(actor,Mathf.Min(t,data.Duration));
|
|
if(t>=data.Duration)Destroy(actor.Root);
|
|
}
|
|
if(clock!=null)
|
|
{
|
|
clockTime=Mathf.Max(0,t-.55f);
|
|
if(clockTime<=clockData.Duration)clockData.Sample(clock,clockTime);
|
|
else clockData.AdvanceParticles(clock,Time.deltaTime);
|
|
}
|
|
if(freezeRemaining<=0)ending+=Time.deltaTime;
|
|
// Original end alpha targets are stripped hashes. Retain the source end
|
|
// duration, but explicitly reconstruct opacity rather than invent bindings.
|
|
float fade=1-Mathf.Clamp01(ending/endDuration);
|
|
if(screen!=null)screenData.SetOverlayOpacity(screen,Mathf.Clamp01(t/.28f)*fade);
|
|
if(clock!=null&&ending>0)clockData.SetOverlayOpacity(clock,fade);
|
|
ActiveFreezeParticleCount=(actor.Root==null?0:actor.LiveParticleCount)+(clock==null?0:clock.LiveParticleCount);
|
|
t+=Time.deltaTime; yield return null;
|
|
}
|
|
Destroy(space.gameObject);if(clockSpace!=null)Destroy(clockSpace.gameObject);if(screenSpace!=null)Destroy(screenSpace.gameObject);
|
|
ActiveFreezeParticleCount=0;HasNativeFreezeScreen=false;
|
|
}
|
|
private IEnumerator PlayRecoveredTargetBooster(BoosterType type,List<BlockPieceView> targets)
|
|
{
|
|
var data=Resources.Load<OriginalBoosterAnimationData>("OriginalBoosters/"+type);
|
|
if(data==null)
|
|
{
|
|
Debug.LogError("Recovered booster animation missing: "+type);
|
|
foreach(var item in targets) if(item!=null){SpecialCompleteRemoval(item);Destroy(item.gameObject);activeExits--;}
|
|
InputLocked=false; CloseUnusedDoors(); yield return FinishBoardIfCleared(); yield break;
|
|
}
|
|
var space=CreateAnimationSpace("Original "+type+" FX");
|
|
var actor=data.CreateInstance(space,160);
|
|
var origins=new Vector3[targets.Count]; var scales=new Vector3[targets.Count];
|
|
for(int i=0;i<targets.Count;i++){origins[i]=targets[i].transform.position; scales[i]=targets[i].transform.localScale;}
|
|
PlayUiSound(type==BoosterType.Rocket?"Rocket_Launch":"UFO");
|
|
float hit=type==BoosterType.Rocket?1.28f:1.32f;
|
|
bool landed=false; float trailAt=0;
|
|
for(float time=0;time<data.Duration;)
|
|
{
|
|
if(IsPaused){yield return null;continue;}
|
|
data.Sample(actor,time);
|
|
ActiveBoosterParticleCount=actor.LiveParticleCount;
|
|
BoosterPlaybackTime=time;
|
|
BoosterPhase=time<hit?"Entry":time<2.2f?"Impact":"Departure";
|
|
Vector3 collector=space.TransformPoint(new Vector3(0,10.09f,0));
|
|
if(type==BoosterType.Rocket && actor.Transforms.TryGetValue("RocketParent",out var rocket))
|
|
{
|
|
// The source clip supplies actual rocket frames. The Bezier
|
|
// flight was script-driven; this path is reconstructed from playtest.
|
|
Vector3 target=space.InverseTransformPoint(origins[0]);
|
|
Vector3 point;
|
|
if(time<.62f)point=Vector3.Lerp(new Vector3(2.6f,1.6f,0),new Vector3(8,25,0),Mathf.Pow(time/.62f,1.5f));
|
|
else point=Vector3.Lerp(new Vector3(target.x-7,25,0),target,Mathf.Clamp01((time-.72f)/(.56f)));
|
|
rocket.localPosition=point;
|
|
rocket.localRotation=Quaternion.Euler(0,0,time<.62f?-22:155);
|
|
if(time>=hit&&actor.Transforms.TryGetValue("RocketParent/Rocket",out var rocketImage))rocketImage.gameObject.SetActive(false);
|
|
if(data.RecoveredParticleSystems==0&&time>=trailAt+.05f && time<.62f){SpawnRocketTrail(space,point);trailAt=time;}
|
|
}
|
|
if(time>=hit && !landed)
|
|
{
|
|
landed=true; PlayUiSound(type==BoosterType.Rocket?"Rocket_Land":"UFO");
|
|
if(type==BoosterType.Rocket)
|
|
{
|
|
if(data.RecoveredParticleSystems>0)data.TriggerParticles(actor,"RocketParent/rocket_explosion1");
|
|
else SpawnBoosterImpact(origins[0]);
|
|
}
|
|
}
|
|
for(int i=0;i<targets.Count;i++)
|
|
{
|
|
var item=targets[i]; if(item==null)continue;
|
|
float delay=type==BoosterType.Ufo?Mathf.Min(.38f,i*.065f):0;
|
|
float t=Mathf.Clamp01((time-hit-delay)/(type==BoosterType.Ufo?.48f:.24f));
|
|
item.transform.localScale=scales[i]*(1-t);
|
|
if(type==BoosterType.Ufo)
|
|
{
|
|
Vector3 destination=collector+Vector3.up*.2f;
|
|
item.transform.position=Vector3.Lerp(origins[i],destination,t*t)+gameCamera.transform.right*(Mathf.Sin(t*8+i)*.13f*t);
|
|
item.transform.rotation=Quaternion.AngleAxis(t*160,Vector3.up);
|
|
}
|
|
if(t>=1){SpecialCompleteRemoval(item);Destroy(item.gameObject);activeExits--;}
|
|
}
|
|
time+=Time.deltaTime; yield return null;
|
|
}
|
|
// Extremely large target batches are still retired exactly once.
|
|
foreach(var item in targets)if(item!=null&&item.transform.localScale.sqrMagnitude>.00001f){SpecialCompleteRemoval(item);Destroy(item.gameObject);activeExits--;}
|
|
Destroy(space.gameObject); InputLocked=false; BoosterPhase=""; BoosterPlaybackTime=0; Status="";
|
|
ActiveBoosterParticleCount=0;
|
|
CloseUnusedDoors(); yield return FinishBoardIfCleared();
|
|
}
|
|
private void SpawnRocketTrail(Transform space,Vector3 point)
|
|
{
|
|
// Reconstruction fallback: original particle modules are not yet restored.
|
|
var cube=GameObject.CreatePrimitive(PrimitiveType.Cube); Destroy(cube.GetComponent<Collider>());
|
|
cube.name="Rocket Exhaust Cube"; cube.transform.SetParent(space,false);
|
|
cube.transform.localPosition=point-new Vector3(.2f,.7f,0); cube.transform.localScale=Vector3.one*.32f;
|
|
cube.transform.localRotation=Random.rotation;
|
|
var m=new Material(Shader.Find("Sprites/Default"));m.color=new Color(1,.65f,.12f);
|
|
cube.GetComponent<Renderer>().material=m;
|
|
cube.AddComponent<BoosterTransient>().Initialize(new Vector3(-.7f,0,-1),.4f,this,m);
|
|
}
|
|
private void SpawnBoosterImpact(Vector3 position)
|
|
{
|
|
for(int i=0;i<12;i++)
|
|
{
|
|
var cube=GameObject.CreatePrimitive(PrimitiveType.Cube);Destroy(cube.GetComponent<Collider>());
|
|
cube.name="Rocket Impact Cube";cube.transform.SetParent(GetBoardRoot(),false); cube.transform.position=position+Vector3.up*.4f;
|
|
cube.transform.localScale=Vector3.one*.15f;generated.Add(cube);
|
|
var m=new Material(Shader.Find("Sprites/Default"));m.color=i%2==0?Color.white:new Color(1,.65f,.1f);
|
|
cube.GetComponent<Renderer>().material=m;
|
|
cube.AddComponent<BoosterTransient>().Initialize(new Vector3(Mathf.Cos(i*2.4f),.6f,Mathf.Sin(i*2.4f))*2,.45f,this,m);
|
|
}
|
|
}
|
|
}
|
|
internal sealed class BoosterTransient:MonoBehaviour
|
|
{
|
|
private Vector3 velocity,initialScale; private float remaining,duration;private BlockOutGameController owner;private Material material;
|
|
public void Initialize(Vector3 v,float life,BlockOutGameController game,Material m){velocity=v;duration=remaining=life;owner=game;material=m;initialScale=transform.localScale;}
|
|
private void Update(){if(owner!=null&&owner.IsPaused)return;remaining-=Time.deltaTime;transform.position+=velocity*Time.deltaTime;transform.localScale=initialScale*Mathf.Clamp01(remaining/duration);if(remaining<=0)Destroy(gameObject);}
|
|
private void OnDestroy(){if(material!=null)Destroy(material);}
|
|
}
|
|
}
|