From 239f3599904c0478b4ba35c82d8629705e788f30 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 3 Oct 2026 15:39:14 +0200 Subject: [PATCH] Ship exhaust: wiring an exhaust to an emitter turns Use Mesh Colors off The emitter carries the invisible material, whose alpha is 0, so an exhaust prefab left at Unity's default (Use Mesh Colors on) emitted nothing. The shipped thruster prefabs only worked because theirs is off. ThrusterHardpoint .WireExhaust now owns the wiring and turns it off; ShipInstance calls it. The preview smoke wires a default particle system to each emitter on the clone and requires every particle emitted and none transparent. Co-Authored-By: Claude Opus 5.5 --- Assets/Scripts/Editor/ShipModPreview.cs | 40 +++++++++++++++++++- Assets/Scripts/Gameplay/ShipInstance.cs | 3 +- Assets/Scripts/Gameplay/ThrusterHardpoint.cs | 14 +++++-- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Assets/Scripts/Editor/ShipModPreview.cs b/Assets/Scripts/Editor/ShipModPreview.cs index 677253561..9ed4fcec4 100644 --- a/Assets/Scripts/Editor/ShipModPreview.cs +++ b/Assets/Scripts/Editor/ShipModPreview.cs @@ -51,9 +51,9 @@ public static class ShipModPreview // Instantiate clones the assembled root, and Unity must remap every array reference into the clone. var clone = UnityEngine.Object.Instantiate(ship.gameObject).GetComponent(); - try { CheckShip(clone, plan, "clone"); } + try { CheckShip(clone, plan, "clone"); CheckExhaust(clone); } finally { UnityEngine.Object.DestroyImmediate(clone.gameObject); } - Debug.Log($"SHIP_MOD_ASSEMBLY_SMOKE thrusters={plan.Thrusters.Length} " + + Debug.Log($"SHIP_MOD_ASSEMBLY_SMOKE thrusters={plan.Thrusters.Length} exhaust=ok collider=hidden " + $"weapons={plan.Weapons.Length} radiators={plan.Radiators.Length}"); } @@ -79,6 +79,42 @@ public static class ShipModPreview node.FiringPoint.All(Inside)).All(ok => ok), "weapon hardpoints with their muzzles in order"); } + // Exhaust from each hidden emitter, wired as ShipInstance wires it, from a particle system left at Unity's defaults + // (Use Mesh Colors on). The emitter's invisible material has alpha 0, so mesh colours would make every particle + // transparent; the wiring must turn them off. + private static void CheckExhaust(ShipInstance ship) + { + const int count = 200; + foreach (var hardpoint in ship.ThrusterHardpoints) + { + var exhaust = new GameObject("Smoke Exhaust").AddComponent(); + try + { + exhaust.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); + var main = exhaust.main; + main.playOnAwake = false; + main.startSpeed = 0; + main.startLifetime = 10; + main.maxParticles = count * 2; + var emission = exhaust.emission; + emission.enabled = false; + var shape = exhaust.shape; + shape.enabled = true; + shape.shapeType = ParticleSystemShapeType.MeshRenderer; + if (!shape.useMeshColors) throw new InvalidOperationException("Unity's default Use Mesh Colors is off; the exhaust check proves nothing."); + ThrusterHardpoint.WireExhaust(exhaust, hardpoint); + exhaust.Emit(count); + if (exhaust.particleCount != count) + throw new InvalidOperationException($"clone: exhaust from {hardpoint.name} emitted {exhaust.particleCount} of {count} particles."); + var alive = new ParticleSystem.Particle[count]; + exhaust.GetParticles(alive); + if (alive.Any(particle => particle.GetCurrentColor(exhaust).a == 0)) + throw new InvalidOperationException($"clone: exhaust from {hardpoint.name} has transparent particles."); + } + finally { UnityEngine.Object.DestroyImmediate(exhaust.gameObject); } + } + } + [MenuItem("Aetheria/Preview Mod Ship Package")] private static async void Open() { diff --git a/Assets/Scripts/Gameplay/ShipInstance.cs b/Assets/Scripts/Gameplay/ShipInstance.cs index 8a0b22b6e..8b2540974 100644 --- a/Assets/Scripts/Gameplay/ShipInstance.cs +++ b/Assets/Scripts/Gameplay/ShipInstance.cs @@ -65,10 +65,9 @@ public class ShipInstance : EntityInstance { var effectData = (ThrusterData) thruster.Data; var particles = Instantiate(EngineAssets.Load(effectData.ParticlesPrefab), transform, false); - var particlesShape = particles.shape; var thrusterHardpoint = ThrusterHardpoints .FirstOrDefault(t => t.name == ship.Hardpoints[thruster.Item.Position.x, thruster.Item.Position.y].Transform); - particlesShape.meshRenderer = thrusterHardpoint?.Emitter; + ThrusterHardpoint.WireExhaust(particles, thrusterHardpoint); // if (!string.IsNullOrEmpty(thruster.Item.Data.SoundEffectTrigger) && thrusterHardpoint != null) // { // AkSoundEngine.RegisterGameObj(thrusterHardpoint.gameObject); diff --git a/Assets/Scripts/Gameplay/ThrusterHardpoint.cs b/Assets/Scripts/Gameplay/ThrusterHardpoint.cs index d3c3dbb98..b0b1d0f26 100644 --- a/Assets/Scripts/Gameplay/ThrusterHardpoint.cs +++ b/Assets/Scripts/Gameplay/ThrusterHardpoint.cs @@ -1,8 +1,16 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; +using UnityEngine; public class ThrusterHardpoint : MonoBehaviour { public MeshRenderer Emitter; + + // Points an exhaust's shape at a hardpoint's emitter, or at nothing when the ship has no such hardpoint. The emitter + // carries the invisible material (alpha 0), so the particles never take their colour from its mesh: with Use Mesh + // Colors on, Unity's default, they would all be born transparent. + public static void WireExhaust(ParticleSystem exhaust, ThrusterHardpoint hardpoint) + { + var shape = exhaust.shape; + shape.useMeshColors = false; + shape.meshRenderer = hardpoint != null ? hardpoint.Emitter : null; + } }