feat: establish mech extraction prototype with bilingual UI

Track Unity source, scenes, editable assets, licenses, tools and QA evidence. Exclude generated caches, Windows builds and raw runtime logs.
This commit is contained in:
2026-11-12 12:28:40 +08:00
commit 92623a1bc8
256 changed files with 11842 additions and 0 deletions
+140
View File
@@ -0,0 +1,140 @@
using UnityEngine;
namespace Ironspore
{
public sealed class EnemyUnit : MonoBehaviour
{
GameSession game;
float health, cooldown, age;
int type;
public void Initialize(GameSession session, int enemyType, GameObject mech, Transform world)
{
game = session;
type = enemyType;
health = type == 3 ? 650 : type == 2 ? 140 : 60;
var model = Instantiate(mech, transform);
model.transform.localScale = Vector3.one * (type == 3 ? 1.7f : type == 0 ? .65f : 1);
foreach (var renderer in model.GetComponentsInChildren<Renderer>())
renderer.sharedMaterial = WorldFactory.Material(new Color(.5f, .2f, .12f));
foreach (var node in model.GetComponentsInChildren<Transform>())
node.gameObject.layer = 9;
var capsule = gameObject.AddComponent<CapsuleCollider>();
capsule.height = type == 3 ? 5.2f : 3;
capsule.radius = type == 3 ? 1.5f : .8f;
capsule.center = Vector3.up * capsule.height * .5f;
WorldFactory.Label(type == 3 ? "world.overseer" : type == 2 ? "world.bulwark" : type == 0 ? "world.skimmer" : "world.sentinel", transform.position + Vector3.up * (capsule.height + 1), new Color(1, .5f, .25f), transform);
cooldown = 1 + Random.value;
}
void Update()
{
if (!game.InField || game.Paused || game.Trial || !game.World.Player)
return;
age += Time.deltaTime;
Vector3 target = game.World.Player.transform.position + Vector3.up * 1.5f;
if (game.World.Escort && game.World.Escort.Health > 0 && Vector3.Distance(transform.position, game.World.Escort.transform.position) < 15)
target = game.World.Escort.transform.position;
Vector3 flat = target - transform.position;
flat.y = 0;
if (flat.sqrMagnitude > .1f)
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(flat), Time.deltaTime * 2);
float distance = flat.magnitude;
if (type != 1 && distance > 10)
{
Vector3 step = flat.normalized * Time.deltaTime * (type == 3 ? 1.4f : type == 0 ? 2.5f : 1.2f);
if (!Physics.Raycast(transform.position + Vector3.up, step.normalized, 1.1f, 1, QueryTriggerInteraction.Ignore))
transform.position += step;
}
if (type == 0)
{
Vector3 p = transform.position;
p.y = 1.8f + Mathf.Sin(age) * .4f;
transform.position = p;
}
cooldown -= Time.deltaTime;
if (cooldown <= 0 && distance < 42)
{
Vector3 origin = transform.position + Vector3.up * (type == 3 ? 3.5f : 2);
if (!Physics.Linecast(origin, target, 1, QueryTriggerInteraction.Ignore))
{
var bolt = WorldFactory.Primitive(PrimitiveType.Sphere, "EnemyBolt", origin, Vector3.one * .3f, new Color(1, .35f, .08f), null, false, true);
bolt.AddComponent<EnemyBolt>().Initialize((target - origin).normalized, type == 3 ? 23 : 12);
}
cooldown = type == 3 ? .7f : type == 1 ? 1.7f : 2.5f;
}
}
public void Damage(float damage)
{
health -= damage;
if (health > 0)
return;
if (type == 3)
game.CompleteTerminal();
GameAudio.Cue(95, .15f);
Destroy(gameObject);
}
}
public sealed class EnemyBolt : MonoBehaviour
{
Vector3 direction;
float damage, life;
public void Initialize(Vector3 travel, float amount)
{
direction = travel;
damage = amount;
}
void Update()
{
if (!GameSession.Active || !GameSession.Active.InField)
{
Destroy(gameObject);
return;
}
float distance = Time.deltaTime * 17;
if (Physics.SphereCast(transform.position, .12f, direction, out var hit, distance, ~(1 << 9), QueryTriggerInteraction.Ignore))
{
var pilot = hit.collider.GetComponentInParent<MechPilot>();
if (pilot)
pilot.Damage(damage);
var escort = hit.collider.GetComponentInParent<EscortUnit>();
if (escort)
escort.Damage(damage);
Destroy(gameObject);
return;
}
transform.position += direction * distance;
life += Time.deltaTime;
if (life > 5)
Destroy(gameObject);
}
}
public sealed class EscortUnit : MonoBehaviour
{
GameSession game;
public float Health { get; private set; } = 250;
public void Initialize(GameSession session)
{
game = session;
Health = game.Run.escortArmor;
WorldFactory.Label("world.escort", transform.position + Vector3.up * 3, Color.cyan, transform);
}
void Update()
{
if (!game.InField || game.Paused || Health <= 0 || game.Run.routeCompleted)
return;
if (Vector3.Distance(game.World.Player.transform.position, transform.position) < 14)
transform.position += Vector3.forward * Time.deltaTime * 1.5f;
if (transform.position.z >= 33)
game.CompleteTerminal();
}
public void Damage(float amount)
{
Health = Mathf.Max(0, Health - amount);
game.Run.escortArmor = Health;
if (Health <= 0)
{
game.Store.Commit(s => true);
game.Notify("notice.escortLost");
}
}
}
}