Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

[SOLVED] Prefab transform.position Confusion

Discussion in 'Scripting' started by RyogaH, Apr 5, 2020.

  1. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    I'm using Unity 2019.3.1f1 and C# on Windows 10. The issue I've encountered is happening at run-time during my development session.

    I have a Ship prefab. I have a separate Turret prefab; I will have several because the Turrets can be swapped/upgraded. That Turret prefab has a single child, an empty GameObject which sits exactly where Bullets are to be emitted from at run-time.

    At run-time, the Ship gets Instantiated. During Start(), it self-Instantiates the Turret:

    from `Ship.cs`:
    Code (CSharp):
    1.  
    2.     [Header("Weapon Anchors")]
    3.     [Tooltip("Point (location, rotation, and scale) at which Turrets are mounted.")]
    4.     public Transform turretMount;
    5.     [Tooltip("The finite set of Ship Turrets which this Ship can support.")]
    6.     public ShipTurret[] turretModels;
    7.  
    8.     public Rigidbody RigidBodyRef { get; private set; }
    9.     public Collider ColliderRef { get; private set; }
    10.  
    11.     protected void Awake() {
    12.         RigidBodyRef = GetComponent<Rigidbody>();
    13.         ColliderRef = GetComponent<Collider>();
    14.     }
    15.  
    16.     protected IEnumerator Start() {
    17.         AttachTurret();
    18.         // Other setup code which has nothing to do with the Turret (and needs to Wait*() for other setups)...
    19.     }
    20.  
    21.     protected void AttachTurret() {
    22.         // Equip the selected turret
    23.         // TODO:  Make this a player option; hard-code to turret 0 for now
    24.         m_activeTurret = turretModels[0];
    25.         Instantiate(m_activeTurret.gameObject, turretMount);
    26.         m_activeTurret.AttachTo(this);
    27.     }
    from `Turret.cs`:
    Code (CSharp):
    1.     [Header("Weapon Anchors")]
    2.     [Tooltip("The business-end of the weapon; the point from which projectiles emit.")]
    3.     public Transform mountEmitter;
    4.  
    5.     protected Ship m_attachedShip;
    6.  
    7.     public void AttachTo(Ship owner) {
    8.         m_attachedShip = owner;
    9.         cooldownUntilTime = 0f;
    10.     }
    11.  
    12.     public ProjectileWeapon Fire() {
    13.         ProjectileWeapon newProjectile = GetProjectile();
    14.         newProjectile.Fire(
    15.             m_attachedShip,
    16.             mountEmitter,
    17.             newProjectile.intrinsicMuzzleVelocity * weaponMuzzleVelocityMod,
    18.             newProjectile.intrinsicLifespan * weaponLifespanMod
    19.         );
    20.  
    21.         // OnFire presently does nothing except `return newProjectile)`; it will later run a muzzle-flash and recoil animation
    22.         return OnFire(newProjectile);
    23.     }
    24.  
    from `ProjectileWeapon.cs`:
    Code (CSharp):
    1.  
    2.     protected Ship m_owner;
    3.  
    4.     public void Fire(Ship owner, Transform fireFrom, float muzzleVelocity, float lifeSpan) {
    5.         m_owner = owner;
    6.  
    7.         // Other setup which has no effect on position; just clears animation effects since this comes from a Pool
    8.  
    9.         // Actually moves the projectile to fireFrom, which is expected to be the Turret emitter
    10.         StartCoroutine(FireOnNextFrame(fireFrom, muzzleVelocity, lifeSpan));
    11.     }
    12.  
    13.     public IEnumerator FireOnNextFrame(Transform fireFrom, float muzzleVelocity, float selfDestructAfter) {
    14.         StopParticleEffects();
    15.  
    16.         StopPhysics();
    17.         yield return new WaitForFixedUpdate();
    18.  
    19.         Vector3 firingFromPos = fireFrom.position;
    20.         Debug.Log(string.Format(
    21.             "ProjectileWeapon.FireOnNextFrame: Firing from {0} mk {1}."
    22.             , firingFromPos
    23.             , fireFrom.rotation
    24.             ), this);
    25.         transform.position = firingFromPos;
    26.         transform.rotation = fireFrom.rotation;
    27.  
    28.         StartPhysics();
    29.         yield return new WaitForFixedUpdate();
    30.  
    31.         Vector3 ownerVelocity = m_owner.RigidBodyRef.velocity;
    32.         Vector3 bulletDirection = fireFrom.forward;
    33.         Vector3 firingForce = ownerVelocity + bulletDirection * muzzleVelocity;
    34.         ColliderRef.enabled = true;
    35.         RendererRef.enabled = true;
    36.         RigidbodyRef.AddForce(firingForce, ForceMode.VelocityChange);
    37.         PlayLaunchSound();
    38.         StartParticleEffects();
    39.         StartSelfdestructSequence(selfDestructAfter);
    40.     }
    41.  
    42.  
    Obvious code omitted for brevity and obviously, all `public` refs are set in the Editor.

    If I put the Turret assets right inside the Ship prefab (not using a Turret prefab, at all) and disable the self-Turret-instantiation code, this all *works*. However, as long as the Turret is a separate prefab, this does *not* work. There is no error message. The issue is in run-time object behavior.

    The issue is that when the Turret is Instantiated and parented to the Ship at runtime, the Turret does attach perfectly to the Ship. All controls for the Turret are flawless; it rotates, moves with the Ship, everything is happy. However, if the player attempts to Fire the Turret, the Projectile emits from very unexpected coordinates. Instead of emitting from the `mountEmitter` of the newly-instantiated Turret, which is parented correctly to the Ship, all of the Projectiles instead emit from the center of the Scene plus the Turret's internal `localPosition`. This oddball position even rotates with the turret remains fixed at the Scene origin *even when the Turret model moves around with the Ship*. If I `Debug.Log` the `mountEmitter` and compare its `position` and `localPosition`, I can see that they don't jive with what I can plainly see.

    The `position` coordinates of the `mountEmitter` are stuck at scene origin plus the `mountEmitter`s `localPosition`. I can see this in the Debug Log, BUT if I freeze the game, drill into what the `mountEmitter` is linked to, the Gizmo correctly shows it positioned at the tip of the Ship's Turret! These two reports do not jive.

    This is making me crazy. Thanks for taking a look. Any and all help MUCH appreciated!

    PS: This may or may not be related, but a variable on the Turret that is *NOT* static is behaving as if it were. There is a `protected float cooldownUntilTime` variable which mysteriously retains its value between runs. Before I decided to just hard-set it back to `0f` in the `AttachTo()` method, I had to close Unity and re-open it entirely to reset the value between runs. Insane!
     
    Last edited: Apr 5, 2020
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    Hm, your last paragraph sounds like an important clue. Why don't you temporarily change the name of that variable and then resave your prefabs? I theorize that you once had that variable public and Unity saved a value for it, then you changed it to protected and the value persists in the prefab or scene.
     
    koan09 likes this.
  3. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    Thinking the same, I've already tried that (I removed the customary `m_` from the variable for this very test) *and* I checked with Git Blame. The variable has *never* been `public`. Changing the name of the variable *did* constitute a reset of the value but only for the next run. On the second run after the rename, I found the value had mysteriously carried forward again. I don't yet have any state saving mechanism in this game, so I'm certainly not doing any save/load on these objects.

    Unity is behaving as if any changes to the variable are being somehow saved to the Prefab. Doesn't make any sense, otherwise. That somewhat coincides with the weird `.location` behavior, as if at run-time, Unity is running everything against the Prefab rather than an Instance of it. The Projectiles are emitting from exactly where the Turret's tip is in the Prefab editor instead of where it "now" is at run-time from its Instance. I know it's nonsense, but here we are.
     
    Last edited: Apr 5, 2020
  4. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    Here's a slice of this insanity, copied right from the Debug.Log. At run-time, we can see that the `.location` is impossible. It *should* be the Ship's `.location` + the Turret's `.localPosition` + the turret emitter's `.localPosition`. It is not. Unless I'm just going cross-eyed from staring at this for too long (certainly possible), it looks to me like the emitter's `.location` is actually just `.localPostion`, oriented to the scene origin (as if stuck in the Prefab editor).

    The Ship's global `.location` is at (120, -80). The Turret's `.location` (*global* position) should be at the Ship's location + the Turret's `.localPosition`. It is not. It's insane. See for yourself:

    FireMainWeapon: Firing from global (0.0, 0.5, 3.1)mk(0.0, 0.0, 0.0, 1.0), local (0.0, 0.6, 3.8)mk(0.0, 0.0, 0.0, 1.0), GO global (0.0, 0.0, 0.0)mk(0.0, 0.0, 0.0, 1.0), GO local (0.0, 0.0, 0.0)mk(0.0, 0.0, 0.0, 1.0), Ship global (120.0, -80.0, 0.0)mk(-0.5, 0.5, -0.5, 0.5), Ship local (120.0, -80.0, 0.0)mk(-0.5, 0.5, -0.5, 0.5),

    This is from the following Debug.Log line added to the Turret's Fire() method:
    Code (CSharp):
    1.         Debug.Log(string.Format(
    2.             "Fire: Firing from "
    3.             + "global {0}mk{1}, "
    4.             + "local {2}mk{3}, "
    5.             + "GO global {4}mk{5}, "
    6.             + "GO local {6}mk{7}, "
    7.             + "Ship global {8}mk{9}, "
    8.             + "Ship local {10}mk{11}, "
    9.             , mountEmitter.position
    10.             , mountEmitter.rotation
    11.             , mountEmitter.localPosition
    12.             , mountEmitter.localRotation
    13.             , gameObject.transform.position
    14.             , gameObject.transform.rotation
    15.             , gameObject.transform.localPosition
    16.             , gameObject.transform.localRotation
    17.             , m_attachedShip.transform.position
    18.             , m_attachedShip.transform.rotation
    19.             , m_attachedShip.transform.localPosition
    20.             , m_attachedShip.transform.localRotation
    21.             ), this);
    22.  
     
    Last edited: Apr 5, 2020
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    I guess the next thing I would try is to make a fresh copy of the prefab, just a poorman's copy, like with the hierarchy correct and perhaps one or two primitive GameObjects (cubes, spheres) parented below the important bits. Reattach the script, fill out the properties, see what that does. If it has the same problem it eliminates the serialization as being a possible problem.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    One other thing I notice: your firing coroutine has two yields in it before you actually use the fireFrom Transform. Because Transforms are references, your projectile is going to come out wherever that fireFrom has gone to, and if you're using an object pooling system instead of truly Destroying it, sometimes object poolers move objects back to (0,0,0). Try taking ALL the values you care about out of the fireFrom transform at the start of the coroutine, then use those local values. Same thing goes for the owner Rigidbody because that could change too.
     
    RyogaH likes this.
  7. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    Sharp eye! This is deliberate. Because there's a frame or two between "begin firing" and "actually firing" I deliberately let the object position update before actually firing. Otherwise, the projectile might appear to come from a position the Ship is no longer at. Again, this *works* perfectly well when the Turret is *not* a Prefab; when only the Ship is. Everything gets screwy only when the Ship instance Instantiates the Turret from a separate Prefab.
     
  8. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    I will try this and report back. I agree this Turret prefab seems to have gone completely insane.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    That sentence makes me think maybe you are instantiating the prefab into the scene, but then using the reference to the prefab itself to fire the gun. Are you keeping a reference to the newly-instantiated copy and using that?
     
    RyogaH likes this.
  10. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    Not directly; I didn't think it necessary. To my knowledge, you can't actually put a Prefab into a Scene; it becomes an Instance of said Prefab. No? Here's what I'm doing:

    1. The Ship has an array of possible Turrets by way of their ShipTurret[] script refs (click-drag Turret Prefab into the array, exposed in the Editor): `public ShipTurret[] turretModels;`
    2. The Ship keeps a reference to the selected ShipTurret, `protected ShipTurret m_activeTurret;`, that is populated in it's Start() {} via a helper method, AttachTurret(), like: `m_activeTurret = turretModels[0];`, `Instantiate(m_activeTurret.gameObject, turretMount);`, `m_activeTurret.AttachTo(this);` As you can see, I'm discarding what would be the reference to the newly Instantiated Turret because I have its script reference, instead.
    3. Everything happening beyond this point is completely against that script reference, which I expect to be properly attached to the Instance of the Turret which spawned it (`Instantiate(**m_activeTurret.gameObject**, turretMount);`).
    I'll try to hot-swap the m_activeTurret reference to what comes out of the Instantiate() call and see what happens.
     
  11. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,890
    AttachTurret seems weird. I haven't looked much, but line 24 of Ship.cs assigns itself as the first turret (ShipTurret) in the list, then you clone (instantiate) the gameobject it's on, but then call AttachTo on the first turret in the list script, not the thing your instantiated. Is that what you wanted?
     
  12. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    I created an entirely new Turret prefab with nothing but a Cylinder and an empty emitter GameObject properly positioned. I attached the Turret script to it, click-dragged the emitter into its required field, updated the Ship to use the new Turret Prefab... and got exactly the same result. The Projectiles now emit from where the new Prefab's tip would be, were it coming from the Prefab editor rather than its Instance.
     
  13. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,926
    Where is a turret's
    mountEmiter
    set? I feel like it should be like: transform.Find("gun/tip"); in the attach-to-ship command. This sort of thing often happens when a reference to an instance is accidentally going to a prefab.
     
  14. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    You misread a little. :) That line just points the active Turret ref to the first element of the Turrets array, thereby emulating a player selection of the first one. That code will flesh out later when players can actually customize their Turrets.
     
  15. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    This is set in the Prefab editor with a simple click-drag. This bakes the reference in so I don't have to seek it at run-time. This normally works; I use it quite a lot and haven't had any issues with this baking technique.
     
  16. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,890
    That's what I said. I meant you assign the m_activeTurret variable.

    Your point 2 isn't true. You are discarding referencing the instance, but you have its script reference? How? That's the reference to the one in the array, not the new instance.

    You would have to do:
    Code (csharp):
    1. var turretInstance = Instantiate(m_activeTurret.gameObject, turretMount).GetComponent<ShipTurret>();
    2. turretInstance.AttachTo...
    .
     
    RyogaH and Kurt-Dekker like this.
  17. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    Your problem actually is the first script, top of this post, line 24 and line 25.

    You are instantiating the prefab but not keeping a reference to what you instantiated. That selected turret still points to the prefab. You gotta keep a copy of what you instantiate!
     
    RyogaH likes this.
  18. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    My expectation was wrong and @Kurt-Dekker got me on the right track. I'm honestly very surprised that Unity is behaving as if the Prefab were just placed into the scene as a raw, still-in-the-editor Prefab! When I change AttachTurret() to this, everything starts working again:

    Code (CSharp):
    1.     protected void AttachTurret() {
    2.         // Equip the selected turret
    3.         // TODO:  Make this a player option; hard-code to turret 0 for now
    4.         ShipTurret selectedTurret = turretModels[0];
    5.         GameObject selectedPrefabRef = selectedTurret.gameObject;
    6.         GameObject newTurret = Instantiate(selectedPrefabRef, turretMount);
    7.         m_activeTurret = newTurret.GetComponent<ShipTurret>();
    8.         m_activeTurret.AttachTo(this);
    9.     }
    10.  
    Notice that I'm now grabbing the result of Instantiate and storing/using the **additional** reference to the "newly instantiated" ShipTurret script.

    So... here's the weird thing. Unity evidently permits us to edit Prefabs at run-time. Recall that Unity is automatically saving and recalling the state of all `protected` variables in the Prefab between Scene runs (reported above with the variable that wouldn't reset between runs)! Does this seem odd to anyone else, or am I just behind-the-curve and Unity has always behaved like this? :)

    Anyway, I'm all set; this is now SOLVED. THANK YOU ALL SO VERY MUCH for helping me through this brain-bender! I had no idea Unity permitted run-time Prefab editing.
     
    Kurt-Dekker likes this.
  19. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    The key takeaway is if you have something that references a Prefab, and you instantiate that thing, it does NOT in any way change the original thing. It's still the Prefab on disk.
     
  20. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    It's more subtle than that. Of course if you had an actual build, it would not allow you to change it, because the built data is read-only.

    What is happening is that you are in the editor, and by design Unity allows you to modify assets on disk from code running in editor, because of how you write Editor scripts in Unity. Unity can't tell your code running in the Editor isn't some editor script, which is how you write Editor scripts that modify assets!

    It lets you write AMAZINGLY powerful editor script that can do just about anything, but the tradeoff is you can get weird stuff like this.

    Don't worry, everybody gets bitten by this one. I'm a sucker for punishment and do it to myself about once or twice a year on average.
     
    RyogaH likes this.
  21. RyogaH

    RyogaH

    Joined:
    Mar 28, 2020
    Posts:
    14
    You sir, are my hero for the day. I normally track the references from everything I do. For the Turrets, I just tried something different because they're hot-swappable and I thought, "Hey, I already have a reference to the Script, which gives me a reference to the GameObject it's attached to. Why would I also need a reference to the GameObject?"

    I was wrong; you were right. Thanks!! :)
     
    Kurt-Dekker likes this.
  22. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,926
    You don't. Your original idea is correct. Unity is now smart about returning the matching part of a spawned prefab. This works fine:
    Code (CSharp):
    1. ShipTurret turretPrefab = shipModels[0];
    2. ShipTurret activeTurret = Instantiate(turretPrefab, thisShip);
    activeTurret
    will be the script from the instance you just made. It's a very nice shortcut.
     
    Kurt-Dekker likes this.
  23. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,926
    But you don't run editor scripts in Play mode. Changing a prefab during Play probably should give an error, but it's probably a pain to make that happen, and low on their list (and most people probably try to give it a parent, which creates an error anyway).
     
  24. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,971
    Actually you can do so all day long, and we do at our company for crazy-live level editors. You can make a custom level editor that lets you play the game and tweak it while playing, then at the press of a button update prefabs and/or ScriptableObjects with the updated data so the next time you can start from that. That's what I mean by "powerful" and it will bend your brain.

    There really isn't such a thing as an "Editor" script. It's all just code and the assets are all just data. Merely putting a using UnityEditor in the top changes nothing about a script, just gives you access to that namespace. Putting a script in an Editor folder likewise only causes it to be emitted in a separate assembly, but it's all Just Code(tm).

    Code, code, code, turtles all the way down. :)
     
  25. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,926
    It's probably important to note that an Editor Script that you'd find in the manual is a way to put a button or something else in Edit mode.

    But I see the point. While in Play mode it's perfectly legitimate to hand-tweak a prefab. Maybe you're shooting a gun and want to adjust the muzzle flare particle effect, without leaving Play. Code to tweak prefabs in Play mode is the next logical step. It wouldn't technically be an Editor Script, but logically you'd call it a lower-case e edit-script.
     
    Kurt-Dekker likes this.