Ben Traje
← Back to unity

Mastering Unity Prefabs: Instantiation, Transform Offsets, and Shader Graph Integration

14 Aug 26 (2mo ago)

Solving the "Stuck at Scale 1" Unity Prefab Issue

A common frustration when instantiating prefabs via C# in Unity is discovering that your newly spawned objects completely ignore the scale, position, or rotation adjustments you made in the Scene view. This usually happens for two reasons: you are trying to apply overrides directly to an imported 3D model, or your instantiation script is forcefully overwriting the prefab's base transform data.

Here is how to properly manage your assets and scripts for a flawless workflow.

1. FBX Models vs. Native Unity Prefabs

If you drag an .fbx or .obj file directly into your scene, scale it up, and attempt to click "Apply All" in the Overrides menu, Unity will throw an error: "Applying to a Model Prefab is not possible."

Unity treats source 3D files as read-only. To save scene-specific changes, you must convert the model into a native Unity Prefab:

  1. Drag the 3D model into your Scene.
  2. Drag it from the Scene down into your Project window.
  3. Select Prefab Variant (this maintains a link to the original 3D mesh while letting you save custom Unity settings).
  4. Apply your scale, position, or rotation changes to this new .prefab file.

Best Practice: If a model consistently needs to be scaled by a massive amount (e.g., 100x), avoid changing the Transform scale. Instead, select the original .fbx in the Project window, go to the Model tab in the Inspector, and adjust the Scale Factor. This optimizes physics and performance while keeping your root transforms at a clean 1, 1, 1.

2. Combining Script Transforms with Prefab Transforms

When spawning objects from external data (like JSON point data from SideFX Houdini), your script might overwrite your prefab's carefully crafted offsets.

If you use the equals operator (=) on a transform property, Unity completely discards the Prefab's original settings. Instead, you must combine them:

  • Scale: Use Vector3.Scale() to multiply two vectors together, or use the *= operator if multiplying by a single float.
  • Position: Use += to add the script's coordinates to the prefab's base offset.
  • Rotation: Multiply Quaternions together to combine rotations (e.g., spawnRotation * prefabRotation).

3. The "Child Offset" Pattern (The Ultimate Solution)

The cleanest way to handle instanced art assets is to completely separate script authority from art authority using a parent-child hierarchy.

  • The Root (Controlled by Code): Create an Empty GameObject as the Prefab Root. Leave its transform completely defaulted (Position 0,0,0, Rotation 0,0,0, Scale 1,1,1). Your instantiation script should only apply data to this root object.
  • The Child (Controlled by the Artist): Place your 3D mesh inside the root. Apply all your visual offsets, base rotations, and scale multipliers here.

Because the script never touches the child object, you can open the Prefab at any time, tweak the child's rotation or size, and every instance in your scene will update in real-time.

Modifying Shader Graph Colors at Runtime

Once your objects are properly instanced and scaled, you may want them to interact dynamically with the player—such as a collectible changing the player's color upon collision.

Step 1: Expose the Shader Property

Inside your Shader Graph, hardcoded color nodes cannot be accessed by scripts.

  1. Open the Blackboard window in Shader Graph.
  2. Add a new Color property (e.g., EggColor).
  3. Select it and look at the Graph Inspector to find the Reference string (it will look like _EggColor). Copy this exact string.
  4. Drag the property into your graph and connect it to your output. Save the asset.

Step 2: The C# Trigger Script

Attach a script to your collectible trigger to access the player's renderer and apply the new color. Using .material (instead of .sharedMaterial) ensures that only the specific player instance changes color, leaving the rest of the game's materials unaffected.

using UnityEngine;

public class ModColor : MonoBehaviour
{
    [Header("Rotation Settings")]
    public float rotationSpeed = 90f;
    public Vector3 rotationAxis = Vector3.up;

    [Header("Color Settings")]
    public Color colorToApply = Color.blue;
    [Tooltip("The EXACT Reference string from Shader Graph")]
    public string shaderReferenceName = "_EggColor";

    void Update()
    {
        // Perpetually rotate the object
        transform.Rotate(rotationAxis * rotationSpeed * Time.deltaTime, Space.Self);
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            Renderer playerRenderer = other.GetComponentInChildren<Renderer>();

            if (playerRenderer != null)
            {
                // Accesses the instantiated material and modifies the exposed Shader Graph property
                playerRenderer.material.SetColor(shaderReferenceName, colorToApply);
            }
            
            // Cleanly destroy the collectible
            Destroy(gameObject);
        }
    }
}

By utilizing Prefab Variants, adhering to the Child Offset pattern, and correctly exposing Shader Graph references, you can build highly scalable, artist-friendly pipelines in Unity.