greyBow
greyBow

Reputation: 1348

Serialization error in class

Just started getting this error, The same field name is serialized multiple times in the class or its parent class. This is not supported: Base(ICEWorldPathfindingAdapter) m_Transform from the code below.

I went to the variable m_Transform and changed it from protected Transform m_Transform; to protected new Transform m_Transform; but that ended up throwing more errors. I'm unsure how to track down and the solve this issue. Any help here would be greatly appreciated. Thank you.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

using ICE;
using ICE.World;

#if ICE_CC
using ICE.Creatures;
using ICE.Creatures.Objects;
using ICE.Creatures.EnumTypes;
#endif

#if ICE_ASTAR
using Pathfinding;
using Pathfinding.RVO;
#elif ICE_APEX
using Apex;
using Apex.Services;
using Apex.Steering;
using Apex.Messages;
#endif

namespace ICE.Integration.Adapter
{
#if ICE_APEX && ICE_CC
    public class ICEWorldPathfindingAdapter : ExtendedMonoBehaviour, IHandleMessage<UnitNavigationEventMessage>
    {

        private ICECreatureControl m_Controller = null;
        protected ICECreatureControl AttachedCreatureController{
        get{ return m_Controller = ( m_Controller == null ? GetComponent<ICECreatureControl>() : m_Controller ); }
        }

        private CharacterController m_CharacterController = null;
        protected CharacterController AttachedCharacterController{
            get{ return m_CharacterController = ( m_CharacterController == null ? GetComponent<CharacterController>() : m_CharacterController ); }
        }

        private Rigidbody m_Rigidbody = null;
        protected Rigidbody AttachedRigidbody{
            get{ return m_Rigidbody = ( m_Rigidbody == null ? GetComponent<Rigidbody>() : m_Rigidbody ); }
        }

        private IMovable m_Mover = null;
        protected IMovable m_AttachedMover{
            get{ return m_Mover = ( m_Mover == null ? this.As<IMovable>() : m_Mover ); }
        }

        private Vector3 m_DesiredMovePosition = Vector3.zero;


        /*
        void OnMoveComplete( GameObject _sender, TargetObject _target  )
        {

        }

        void OnMoveUpdatePosition(  GameObject _sender, Vector3 _origin_position, ref Vector3 _new_position )
        {

        }

        void OnTargetMovePositionReached( GameObject _sender, TargetObject _target )
        {
        }*/

        void OnCustomMove( GameObject _sender, ref Vector3 _new_position, ref Quaternion _new_rotation )
        {
            if( m_AttachedMover == null )
                return;

            if( m_AttachedMover.finalDestination == null || m_AttachedMover.finalDestination != _new_position )
            {
                m_DesiredMovePosition = _new_position;
                m_AttachedMover.MoveTo( m_DesiredMovePosition, false );
            }
        }

        void IHandleMessage<UnitNavigationEventMessage>.Handle( UnitNavigationEventMessage _message )
        {
            if( _message.entity != this.gameObject )
                return;

            switch( _message.eventCode )
            {
                case UnitNavigationEventMessage.Event.StoppedUnitOutsideGrid:
                case UnitNavigationEventMessage.Event.StoppedNoRouteExists:
                case UnitNavigationEventMessage.Event.StoppedDestinationBlocked:
                    AttachedCreatureController.Creature.ActiveTarget.UpdateTargetMovePositionOffset( true );
                    break;
            }
        }


        /// <summary>
        /// Raises the enable event and starts RepeatTrySearchPath.
        /// </summary>
        /// <description>
        /// Starts RepeatTrySearchPath.
        /// </description>
        protected override void OnEnable () {

            base.OnEnable();

            if( AttachedCreatureController != null )
            {
                //AttachedCreatureController.Creature.Move.OnTargetMovePositionReached += OnTargetMovePositionReached;
                //AttachedCreatureController.Creature.Move.OnMoveComplete += OnMoveComplete;
                //AttachedCreatureController.Creature.Move.OnUpdateMovePosition += OnMoveUpdatePosition;
                AttachedCreatureController.Creature.Move.OnCustomMove += OnCustomMove;
            }


        }

        /// <summary>
        /// Called on Start and OnEnable, but only one of the two, i.e. at startup it is only called once.
        /// </summary>
        protected override void OnStartAndEnable()
        {
            GameServices.messageBus.Subscribe(this);
        }

        /// <summary>
        /// Raises the disable event.
        /// </summary>
        public void OnDisable () 
        {       
            if( AttachedCreatureController != null )
            {
                //AttachedCreatureController.Creature.Move.OnTargetMovePositionReached -= OnTargetMovePositionReached;
                //AttachedCreatureController.Creature.Move.OnMoveComplete -= OnMoveComplete;
                //AttachedCreatureController.Creature.Move.OnUpdateMovePosition -= OnMoveUpdatePosition;
                AttachedCreatureController.Creature.Move.OnCustomMove -= OnCustomMove;
            }

            GameServices.messageBus.Unsubscribe(this);
        }
    }
#elif ICE_ASTAR && ICE_CC
    [RequireComponent(typeof(ICECreatureControl))]
    [RequireComponent(typeof(Seeker))]
    public class ICEWorldPathfindingAdapter : ICEWorldBehaviour {

        private ICECreatureControl m_Controller = null;
        protected ICECreatureControl AttachedCreatureController{
            get{ return m_Controller = ( m_Controller == null ? GetComponent<ICECreatureControl>() : m_Controller ); }
        }

        private RVOController m_RVOController = null;
        protected RVOController AttachedRVOController{
            get{ return m_RVOController = ( m_RVOController == null ? GetComponent<RVOController>() : m_RVOController ); }
        }

        private CharacterController m_CharacterController = null;
        protected CharacterController AttachedCharacterController{
            get{ return m_CharacterController = ( m_CharacterController == null ? GetComponent<CharacterController>() : m_CharacterController ); }
        }

        private Rigidbody m_Rigidbody = null;
        protected Rigidbody AttachedRigidbody{
            get{ return m_Rigidbody = ( m_Rigidbody == null ? GetComponent<Rigidbody>() : m_Rigidbody ); }
        }

        private Seeker m_Seeker = null;
        protected Seeker AttachedSeeker{
            get{ return m_Seeker = ( m_Seeker == null ? GetComponent<Seeker>() : m_Seeker ); }
        }


        void OnMoveComplete( GameObject _sender, TargetObject _target  )
        {

        }

        void OnMoveUpdatePosition(  GameObject _sender, Vector3 _origin_position, ref Vector3 _new_position )
        {

        }

        void OnTargetMovePositionReached( GameObject _sender, TargetObject _target )
        {
        }

        void OnCustomMove( GameObject _sender, ref Vector3 _new_position, ref Quaternion _new_rotation )
        {
            if( _new_position != m_CurrentMovePosition )
            {
                m_CurrentMovePosition = _new_position;
                SearchPath();
            }
            else
                m_CurrentMovePosition = _new_position;
        }

        void OnPathDelegate( Path _path )
        {
            if( _path == null )
                return;

            Vector3 _end_point = _path.vectorPath[_path.vectorPath.Count-1];
            float _distance = ( _end_point-m_CurrentMovePosition ).sqrMagnitude;

            if( _distance > AttachedCreatureController.Creature.Move.DesiredStoppingDistance )
                Debug.Log( "Distance to EndPoint too far : " + _distance );
        }

        /// <summary>
        /// Enables or disables searching for paths.
        /// </summary>
        /// <description>Setting this to false does not stop any active path requests from being calculated
        /// or stop it from continuing to follow the current path.
        /// </description>
        public bool AutoRepath = false;

        /// <summary>
        /// Determines how often it will search for new paths.
        /// </summary>
        /// <description>
        /// If you have fast moving targets or AIs, you might want to set it to a lower value.
        /// The value is in seconds between path requests.
        /// </description>
        public float AutoRepathRate = 0.5F;


        /// <summary>
        /// Enables or disables movement.
        /// </summary>
        public bool CanMove = true;

        /// <summary>
        /// Distance from the target point where the AI will start to slow down.
        /// </summary>
        /// <description>
        /// Note that this doesn't only affect the end point of the path but also any intermediate points, 
        /// so be sure to set #forwardLook and #pickNextWaypointDist to a higher value than this.
        /// </description>
        public float SlowdownDistance = 0.6F;

        /// <summary>
        /// Determines within what range it will switch to target the next waypoint in the path 
        /// </summary>
        public float PickNextWaypointDist = 2;

        /** Target point is Interpolated on the current segment in the path so that it has a distance of #forwardLook from the AI.
      * See the detailed description of AIPath for an illustrative image */
        public float ForwardLook = 1;

        /** Distance to the end point to consider the end of path to be reached.
     * When this has been reached, the AI will not move anymore until the target changes and OnTargetReached will be called.
     */
        public float EndReachedDistance = 0.2F;

        /// <summary>
        /// Do a closest point on path check when receiving path callback.
        /// </summary>
        /// <description>Usually the AI has moved a bit between requesting the path, and getting it back, 
        /// and there is usually a small gap between the AI and the closest node. If this option is enabled, 
        /// it will simulate, when the path callback is received, movement between the closest node and the 
        /// current AI position. This helps to reduce the moments when the AI just get a new path back, and 
        /// thinks it ought to move backwards to the start of the new path even though it really should just 
        /// proceed forward.</description>
        public bool ClosestOnPathCheck = true;

        protected float MinMoveScale = 0.05F;

        /// <summary>
        /// Time when the last path request was sent
        /// </summary>
        protected float m_LastRepath = -9999;

        /// <summary>
        /// Current path which is followed
        /// </summary>
        protected Path m_Path;

        protected Transform m_Transform;



        /// <summary>
        /// Current index in the path which is current target       
        /// </summary>
        protected int m_CurrentWaypointIndex = 0;

        /// <summary>
        /// Holds if the end-of-path is reached
        /// </summary>
        protected bool m_TargetReached = false;

        /// <summary>
        /// Only when the previous path has been returned should be search for a new path
        /// </summary>
        protected bool m_CanSearchAgain = true;

        protected Vector3 m_LastFoundWaypointPosition;
        protected float m_LastFoundWaypointTime = -9999;

        /// <summary>
        /// Gets a value indicating whether this <see cref="ICE.Creatures.Adapter.ICECreatureAstarAdapter"/> target reached.
        /// </summary>
        /// <value><c>true</c> if target reached; otherwise, <c>false</c>.</value>
        public bool TargetReached {
            get { return m_TargetReached; }
        }

        /// <summary>
        /// Holds if the Start function has been run.
        /// </summary>
        /// <description>
        /// Used to test if coroutines should be started in OnEnable to prevent calculating paths
        /// in the awake stage (or rather before start on frame 0).
        /// </description>
        private bool m_StartHasRun = false;

        /// <summary>
        /// Awake this instance and initializes reference variables.
        /// </summary>
        /// <description>
        /// If you override this function you should in most cases call base.Awake () at the start of it.
        /// </description>
        protected virtual void Awake () 
        {           
            if( AttachedRVOController != null ) 
                AttachedRVOController.enableRotation = false;

            //just to optimize the transform component lookup
            m_Transform = transform;        
        }

        /// <summary>
        /// Start this instance.
        /// </summary>
        /// <description>
        /// If you override this function you should in most cases call base.Start () at the start of it.
        /// </description>
        protected virtual void Start () 
        {
            m_StartHasRun = true;
            OnEnable ();
        }

        /// <summary>
        /// Raises the enable event and starts RepeatTrySearchPath.
        /// </summary>
        /// <description>
        /// Starts RepeatTrySearchPath.
        /// </description>
        protected virtual void OnEnable () {

            if( AttachedCreatureController != null )
            {
                AttachedCreatureController.Creature.Move.OnTargetMovePositionReached += OnTargetMovePositionReached;
                AttachedCreatureController.Creature.Move.OnMoveComplete += OnMoveComplete;
                AttachedCreatureController.Creature.Move.OnUpdateMovePosition += OnMoveUpdatePosition;
                AttachedCreatureController.Creature.Move.OnCustomMove += OnCustomMove;
            }

            m_LastRepath = -9999;
            m_CanSearchAgain = true;

            m_LastFoundWaypointPosition = GetFeetPosition ();

            if( m_StartHasRun ) 
            {
                AttachedSeeker.pathCallback += OnPathComplete;

                //StartCoroutine( RepeatTrySearchPath() );
            }


        }

        /// <summary>
        /// Raises the disable event.
        /// </summary>
        public void OnDisable () 
        {
            if( AttachedRVOController != null )
                AttachedRVOController.Move( Vector3.zero );

            AttachedCreatureController.Creature.Move.OnTargetMovePositionReached -= OnTargetMovePositionReached;
            AttachedCreatureController.Creature.Move.OnMoveComplete -= OnMoveComplete;
            AttachedCreatureController.Creature.Move.OnUpdateMovePosition -= OnMoveUpdatePosition;
            AttachedCreatureController.Creature.Move.OnCustomMove -= OnCustomMove;

            // Abort calculation of path
            if( AttachedSeeker != null && ! AttachedSeeker.IsDone() ) 
                AttachedSeeker.GetCurrentPath().Error();

            // Release current path
            if( m_Path != null ) 
                m_Path.Release( this );

            m_Path = null;
            AttachedSeeker.pathCallback -= OnPathComplete;


        }


        /// <summary>
        /// Tries to search for a path every #repathRate seconds.
        /// </summary>
        protected IEnumerator RepeatTrySearchPath () 
        {
            while(true) 
            {
                float _v = TrySearchPath();
                yield return new WaitForSeconds(_v);
            }
        }

        /// <summary>
        /// Tries the search path.
        /// </summary>
        /// <returns>returns The time to wait until calling this function again (based on #repathRate)</returns>
        /// <description>
        /// Will search for a new path if there was a sufficient time since the last repath and both
        /// #canSearchAgain and #canSearch are true and there is a target.
        /// </description>
        public float TrySearchPath () 
        {
            if( Time.time - m_LastRepath >= AutoRepathRate && m_CanSearchAgain && AutoRepath ) 
            {
                SearchPath ();
                return AutoRepathRate;
            } 
            else 
            {
                //StartCoroutine (WaitForRepath ());
                float v = AutoRepathRate - (Time.time-m_LastRepath);
                return v < 0 ? 0 : v;
            }
        }

        private Vector3 m_CurrentMovePosition = Vector3.zero;

        /// <summary>
        /// Requests a path to the given MovePosition.
        /// </summary>
        public virtual void SearchPath() 
        {
            m_LastRepath = Time.time;
            //This is where we should search to
            m_CurrentMovePosition = AttachedCreatureController.Creature.Move.MovePosition;

            m_CanSearchAgain = false;

            //Alternative way of requesting the path
            //ABPath p = ABPath.Construct (GetFeetPosition(),targetPoint,null);
            //seeker.StartPath (p);

            //We should search from the current position
            AttachedSeeker.StartPath( GetFeetPosition(), m_CurrentMovePosition, OnPathDelegate );
        }

        /// <summary>
        /// Raises the target reached event.
        /// </summary>
        public virtual void OnTargetReached () 
        {
            //End of path has been reached
            //If you want custom logic for when the AI has reached it's destination
            //add it here
            //You can also create a new script which inherits from this one
            //and override the function in that script
        }

        /// <summary>
        /// Called when a requested path has finished calculation.
        /// </summary>
        /// <param name="_p">_p.</param>
        /// <description>Called when a requested path has finished calculation.
        /// A path is first requested by #SearchPath, it is then calculated, probably in the same or the next frame.
        /// Finally it is returned to the seeker which forwards it to this function.
        /// </description>
        public virtual void OnPathComplete( Path _p ) 
        {
            ABPath _path = _p as ABPath;
            if( _path == null ) throw new System.Exception ("This function only handles ABPaths, do not use special path types");

            m_CanSearchAgain = true;

            //Claim the new path
            _path.Claim(this);

            // Path couldn't be calculated of some reason.
            // More info in p.errorLog (debug string)
            if( _path.error ) 
            {
                _path.Release(this);
                return;
            }

            //Release the previous path
            if (m_Path != null) 
                m_Path.Release(this);

            //Replace the old path
            m_Path = _path;

            //Reset some variables
            m_CurrentWaypointIndex = 0;
            m_TargetReached = false;

            //The next row can be used to find out if the path could be found or not
            //If it couldn't (error == true), then a message has probably been logged to the console
            //however it can also be got using p.errorLog
            //if (p.error)

            if( ClosestOnPathCheck ) 
            {
                Vector3 _pos_1 = Time.time - m_LastFoundWaypointTime < 0.3f ? m_LastFoundWaypointPosition : _path.originalStartPoint;
                Vector3 _pos_2 = GetFeetPosition();
                Vector3 _direction = _pos_2 - _pos_1;
                float _magnitude = _direction.magnitude;
                _direction /= _magnitude;
                int _steps = (int)(_magnitude/PickNextWaypointDist);                

                for( int i = 0; i <= _steps; i++ ) 
                {
                    CalculateVelocity( _pos_1 );
                    _pos_1 += _direction;
                }
            }
        }

        /// <summary>
        /// Gets the feet position.
        /// </summary>
        /// <returns>The feet position.</returns>
        public virtual Vector3 GetFeetPosition() 
        {
            if( AttachedCharacterController != null && AttachedCharacterController.enabled ) 
                return m_Transform.position - Vector3.up * AttachedCharacterController.height * 0.5f;
            else
                return m_Transform.position;
        }

        public virtual void Update () {

            if( ! CanMove || AttachedCreatureController == null || ! AttachedCreatureController.enabled )
            {
                if( AttachedRVOController != null )
                    AttachedRVOController.Move( Vector3.zero );

                return;
            }

            if( m_CurrentMovePosition != AttachedCreatureController.Creature.Move.MovePosition  )
                TrySearchPath();

            Vector3 _velocity = CalculateVelocity( GetFeetPosition() );

            //Rotate towards targetDirection (filled in by CalculateVelocity)
            RotateTowards( m_TargetDirection );

            if( AttachedRVOController != null && AttachedRVOController.enabled )
            {
                AttachedRVOController.maxSpeed = AttachedCreatureController.Creature.Move.MoveSpeed;
                AttachedRVOController.rotationSpeed = AttachedCreatureController.Creature.Move.MoveAngularSpeed;
                AttachedRVOController.Move( _velocity );
            } 
            else if( AttachedCharacterController != null && AttachedCharacterController.enabled ) 
            {
                AttachedCharacterController.SimpleMove( _velocity );
            } 
            else if( AttachedRigidbody != null && ! AttachedRigidbody.isKinematic ) 
            {
                AttachedRigidbody.AddForce( _velocity );
            } 
            else 
            {
                m_Transform.Translate( _velocity * Time.deltaTime, Space.World );
            }
        }

        /** Point to where the AI is heading.
      * Filled in by #CalculateVelocity */
        protected Vector3 m_TargetPoint;
        /** Relative direction to where the AI is heading.
     * Filled in by #CalculateVelocity */
        protected Vector3 m_TargetDirection;

        /// <summary>
        /// XZs the sqr magnitude.
        /// </summary>
        /// <returns>The sqr magnitude.</returns>
        /// <param name="_a">_a.</param>
        /// <param name="_b">_b.</param>
        protected float XZSqrMagnitude( Vector3 _a, Vector3 _b ) 
        {
            float _dx = _b.x-_a.x;
            float _dz = _b.z-_a.z;
            return _dx*_dx + _dz*_dz;
        }

        /// <summary>
        /// Calculates the desired velocity.
        /// </summary>
        /// <returns>The velocity.</returns>
        /// <param name="_position">_position.</param>
        /// <description>
        /// Finds the target path segment and returns the forward direction, scaled with speed.
        /// A whole bunch of restrictions on the velocity is applied to make sure it doesn't 
        /// overshoot, does not look too far ahead, and slows down when close to the target.
        /// </description>
        protected Vector3 CalculateVelocity( Vector3 _position ) 
        {
            if( m_Path == null || m_Path.vectorPath == null || m_Path.vectorPath.Count == 0 ) 
                return Vector3.zero;

            List<Vector3> _vector_path = m_Path.vectorPath;

            if( _vector_path.Count == 1 ) 
                _vector_path.Insert( 0,_position );

            if( m_CurrentWaypointIndex >= _vector_path.Count )
                m_CurrentWaypointIndex = _vector_path.Count-1;

            if( m_CurrentWaypointIndex <= 1 ) 
                m_CurrentWaypointIndex = 1;

            while( true ) 
            {
                if( m_CurrentWaypointIndex < _vector_path.Count - 1 ) 
                {
                    //There is a "next path segment"
                    float _dist = XZSqrMagnitude( _vector_path[ m_CurrentWaypointIndex ], _position );

                    //Mathfx.DistancePointSegmentStrict (vPath[currentWaypointIndex+1],vPath[currentWaypointIndex+2],currentPosition);
                    if( _dist < PickNextWaypointDist*PickNextWaypointDist ) 
                    {
                        m_LastFoundWaypointPosition = _position;
                        m_LastFoundWaypointTime = Time.time;
                        m_CurrentWaypointIndex++;
                    } 
                    else
                        break;
                } 
                else
                    break;
            }

            Vector3 _direction = _vector_path[ m_CurrentWaypointIndex ] - _vector_path[ m_CurrentWaypointIndex - 1 ];
            Vector3 _target_position = CalculateTargetPoint( _position, _vector_path[ m_CurrentWaypointIndex - 1 ], _vector_path[m_CurrentWaypointIndex]);


            _direction = _target_position - _position;
            _direction.y = 0;
            float _target_distance = _direction.magnitude;

            float _slowdown = Mathf.Clamp01( _target_distance / SlowdownDistance );

            this.m_TargetDirection = _direction;
            this.m_TargetPoint = _target_position;

            if( m_CurrentWaypointIndex == _vector_path.Count-1 && _target_distance <= EndReachedDistance ) 
            {
                if( ! m_TargetReached ) 
                {
                    m_TargetReached = true; 
                    OnTargetReached(); 
                }
                return Vector3.zero;
            }

            Vector3 _forward = m_Transform.forward;
            float _dot = Vector3.Dot( _direction.normalized, _forward );

            float _forward_speed = AttachedCreatureController.Creature.Move.MoveSpeed;
            float _speed = _forward_speed * Mathf.Max( _dot, MinMoveScale ) * _slowdown;


            if( Time.deltaTime  > 0 )
                _speed = Mathf.Clamp( _speed ,0, _target_distance/( Time.deltaTime*2  ));

            return _forward*_speed;
        }

        /// <summary>
        /// Rotates in the specified direction.
        /// </summary>
        /// <param name="_direction">_direction.</param>
        protected virtual void RotateTowards( Vector3 _direction ) {

            if( _direction == Vector3.zero ) 
                return;

            Quaternion _rotation = m_Transform.rotation;
            Quaternion _to_target = Quaternion.LookRotation( _direction );

            float _turning_speed = AttachedCreatureController.Creature.Move.DesiredAngularVelocity.y;

            _rotation = Quaternion.Slerp( _rotation,_to_target,_turning_speed * Time.deltaTime );
            _rotation = Quaternion.Euler( new Vector3( 0, _rotation.eulerAngles.y, 0 ) );

            m_Transform.rotation = _rotation;
        }

        /// <summary>
        /// Calculates target point from the current line segment.      
        /// </summary>
        /// <returns>The target point.</returns>
        /// <param name="_p">_p.</param>
        /// <param name="_a">_a.</param>
        /// <param name="_b">_b.</param>
        protected Vector3 CalculateTargetPoint( Vector3 _position, Vector3 _segment_start, Vector3 _segment_end ) 
        {
            _segment_start.y = _position.y;
            _segment_end.y = _position.y;

            float _magnitude = (_segment_start-_segment_end).magnitude;
            if( _magnitude == 0 ) 
                return _segment_start;


            float _closest = Mathf.Clamp01 (VectorMath.ClosestPointOnLineFactor(_segment_start, _segment_end, _position));
            Vector3 _point = (_segment_end-_segment_start)*_closest + _segment_start;
            float _distance = (_point-_position).magnitude;

            float _look_ahead = Mathf.Clamp( ForwardLook - _distance, 0.0F, ForwardLook );

            float _offset = _look_ahead / _magnitude;
            _offset = Mathf.Clamp( _offset + _closest, 0.0F , 1.0F );
            return (_segment_end-_segment_start)*_offset + _segment_start;
        }
    }
#else
    public class ICEWorldPathfindingAdapter : ICEWorldBehaviour {}
#endif
}

Upvotes: 1

Views: 1092

Answers (1)

Supert
Supert

Reputation: 88

Helpful answer: Don't bother with optimizing transform access. It's not simple GetComponent() since Unity 5.x.

in Unity5 we also cache the transform component on the c# side, so there should no longer be a performance reason to cache the transform component yourself.

Source.

Lazy answer: Rename your m_transform variable.

Some speculation on why this is happening since Unity team did not provide us with it's serialization system source code:

Apparently, Unity stores cached transform in the field with the name "m_transform" (it fits code style of Unity team). Apparently, it compares fields simply by name to prevent collisions and does not take into account if it can be serialized in a first place. Another example of that problem could be found here.

By the way, Unity serialization is done with reflection. Further read.

Upvotes: 1

Related Questions