Ship render: the hull collider carries the invisible material, so clones draw nothing

forceRenderingOff is not serialized, so Instantiate and SaveAsPrefabAsset dropped
it and every cloned mod ship (and every FBX-built prefab) drew its collision hull.
The collider renderer now takes the template's invisible material, as the thruster
emitter does. The preview smoke checks the collider on the clone and the prefab
smoke checks it on the configured prefab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-03 15:37:33 +02:00
commit fc73d7abcd
4 changed files with 15 additions and 3 deletions

View file

@ -63,6 +63,9 @@ public static class ShipModPreview
bool Inside(Component part) => part != null && part.transform.IsChildOf(ship.transform);
Require(ship.HullColliders.Length == 1 && Inside(ship.HullColliders[0]) &&
ship.HullColliders[0].GetComponent<MeshCollider>() != null, "one hull collider with a MeshCollider");
Require(ship.HullColliders[0].GetComponentsInChildren<Renderer>(true).All(renderer =>
renderer.sharedMaterials.All(material => material == ship.InvisibleMaterial)),
"a hull collider that draws nothing");
Require(Inside(ship.MapIcon) && Inside(ship.Shield) && Inside(ship.TractorBeam), "map icon, shield and tractor inside the ship");
Require(ship.ThrusterHardpoints.Select(node => node.name).SequenceEqual(plan.Thrusters) &&
ship.ThrusterHardpoints.All(node => Inside(node) && Inside(node.Emitter) &&

View file

@ -160,7 +160,8 @@ public static class ShipPrefabAuthoring
mapIcon.gameObject.layer = minimapLayer;
hull.gameObject.layer = combatLayer;
var hullRenderer = hull.GetComponent<MeshRenderer>();
if (hullRenderer != null) hullRenderer.forceRenderingOff = true;
// Serialized, so it survives SaveAsPrefabAsset and every clone; forceRenderingOff survives neither.
if (hullRenderer != null) ShipModShips.Hide(hullRenderer, invisible);
var collider = AddOrGet<MeshCollider>(hull.gameObject);
collider.sharedMesh = hullMesh;
collider.convex = true;

View file

@ -33,6 +33,8 @@ public static class ShipPrefabAuthoringSmoke
Require(ship.RadiatorHardpoints.Length == 1 && ship.RadiatorHardpoints[0].Mesh != null, "radiator mesh");
Require(ship.ArticulationPoints.Length == 1 && ship.ArticulationPoints[0].Group == 2, "articulation");
Require(ship.HullColliders.Length == 1 && ship.HullColliders[0].GetComponent<MeshCollider>().convex, "hull collision");
Require(Array.TrueForAll(ship.HullColliders[0].GetComponent<MeshRenderer>().sharedMaterials,
material => material == ship.InvisibleMaterial), "hull collider drawn with the invisible material only");
Require(ship.Shield != null && ship.Shield.GetComponent<ShieldEnvelope>() != null, "shield envelope");
Require(ship.TractorBeam != null && ship.PingPrefab != null && ship.DestroyEffect != null, "shared assets");
Require(ship.MapIcon != null && ship.MapIcon.gameObject.layer == LayerMask.NameToLayer("Minimap"), "map icon");

View file

@ -103,8 +103,10 @@ public static class ShipModShips
if (!hullMesh.isReadable)
throw new InvalidOperationException($"{ship.Id}: the hull-collider mesh imported unreadable, so it cannot back a MeshCollider.");
hullNode.gameObject.layer = combat;
// The collision hull is never drawn. Its renderer takes the invisible material, which is serialized and so survives
// the prototype's clone; forceRenderingOff would not survive Instantiate.
var hullRenderer = hullNode.GetComponent<MeshRenderer>();
if (hullRenderer != null) hullRenderer.forceRenderingOff = true;
if (hullRenderer != null) Hide(hullRenderer, template.Invisible);
var collider = AddOrGet<MeshCollider>(hullNode.gameObject);
collider.sharedMesh = hullMesh;
collider.convex = true;
@ -127,7 +129,7 @@ public static class ShipModShips
// this renderer's visible material; forceRenderingOff would not survive Instantiate.
var hardpoint = AddOrGet<ThrusterHardpoint>(Node(id).gameObject);
hardpoint.Emitter = Renderer(id);
hardpoint.Emitter.sharedMaterials = Enumerable.Repeat(template.Invisible, hardpoint.Emitter.sharedMaterials.Length).ToArray();
Hide(hardpoint.Emitter, template.Invisible);
return hardpoint;
}).ToArray();
var radiators = plan.Radiators.Select(id =>
@ -162,6 +164,10 @@ public static class ShipModShips
return instance;
}
// Every material slot takes the invisible material, so the renderer draws nothing on the prototype and on every clone.
public static void Hide(Renderer renderer, Material invisible) =>
renderer.sharedMaterials = Enumerable.Repeat(invisible, renderer.sharedMaterials.Length).ToArray();
private static void Reset(Transform transform)
{
transform.localPosition = Vector3.zero;