Spawn at random spawn point

trueh

New member
Hello,

I wonder what is the best way of spawning my player at a randomly chosen spawn point when the game starts.

I am doing it in a behavior attached to the player in the Start() method. I get the reference to the CharacterRespawner component and invoke its Respawn() method. I also subclassed CharacterRespawner so it can choose a random spawn point from SpawnPointManager. It seems to work but CharacterIK is throwing NullReference exceptions, so I am supposing that this is not the correct way of doing it.

Any clue on what is the correct way of spawning the player at a given spawn point?
 
Looking at the source code I have seen that I do not need to subclass CharacterRespawner. Assigning the spawn points to the same group should do the trick. However, SpawnPointManager.ShuffleSpawnPoints() is not shuffling the spawn point list as expected and the same spawn point is returned every time. The problem seems to be that I have only two spawn points and the code for ShuffleSpawnPoints() is the following:

C#:
        private void ShuffleSpawnPoints(List<SpawnPoint> spawnPoints)
        {
            var n = spawnPoints.Count - 1;
            while (n > 1) {
                var k = Random.Range(0, n);
                var temp = spawnPoints[n];
                spawnPoints[n] = spawnPoints[k];
                spawnPoints[k] = temp;
                n--;
            }
        }

With only two spawn points, that code does nothing. Is this the expected behavior for ShuffleSpawnPoints()?

The NullReference exceptions still persist when calling Respawn() from the Start() method. They disappear if I call Respawn() from Update() but it is bad for performance.
 
What is the null reference that you are getting? It does look like the shuffle spawn points should have while n > 0 instead of 1.
 
Changing while (n > 0) is still not working. It is always choosing the same spawn point. There must be something else. It is late her. I will debug it tomorrow.

The NullReference is this:

Code:
[Exception] NullReferenceException: Object reference not set to an instance of an object
CharacterIK.LookAtTarget() at /Opsive/UltimateCharacterController/Scripts/Character/CharacterIK.cs:711
709:   private void LookAtTarget()
710:   {
-->711:       var lookDirection = m_LookSource.LookDirection(m_Head.position, false, 0, true);
712:       // Multiply the local offset by the distance so the same relative offset will be applied for both the upper body and head.
713:       var localOffset = m_LookAtOffset * m_LookSource.LookDirectionDistance;

CharacterIK.OnAnimatorIK() at /Opsive/UltimateCharacterController/Scripts/Character/CharacterIK.cs:545
543:       PositionLowerBody();
544:       // The upper body should look in the direction of the LookSource.
-->545:       LookAtTarget();
546:   } else if (layerIndex == m_UpperBodyLayerIndex) { // Upper body.
547:       // If the character is aiming the hands should be rotated towards the target.

AnimatorMonitor.SnapAnimator() at /Opsive/UltimateCharacterController/Scripts/Character/AnimatorMonitor.cs:261
259:       m_Animator.Play(m_Animator.GetCurrentAnimatorStateInfo(i).fullPathHash, i, 0);
260:   }
-->261:   m_Animator.Update(Time.fixedDeltaTime);
262:   // Prevent the change parameters from staying triggered when the animator is on the idle state.
263:   SetAbilityChangeParameter(false);

UltimateCharacterLocomotion.OnImmediateTransformChange() at /Opsive/UltimateCharacterController/Scripts/Character/UltimateCharacterLocomotion.cs:2287
2285:       // Snap the animator after the abilities have updated.
2286:       if (snapAnimator) {
-->2287:           EventHandler.ExecuteEvent(m_GameObject, "OnCharacterSnapAnimator");
2288:       }
2289:   }

CharacterRespawner.Respawn() at /Opsive/UltimateCharacterController/Scripts/Traits/CharacterRespawner.cs:43
41:       // Execute OnCharacterImmediateTransformChange after OnRespawn to ensure all of the interested components are using the new position/rotation.
42:       if (transformChange) {
-->43:           EventHandler.ExecuteEvent(m_GameObject, "OnCharacterImmediateTransformChange", true);
44:       }
45:   }

Respawner.Respawn() at /Opsive/UltimateCharacterController/Scripts/Traits/Respawner.cs:164
162:       }
-->164:       Respawn(position, rotation, true);
165:   } else {
166:       Respawn(m_Transform.position, m_Transform.rotation, false);

NolanController.Start() at /Bytecode Bits/Game/Nolan/Scripts/NolanController.cs:27
25:   private void Start()
26:   {
-->27:       _respawner.Respawn();
28:   }
 
The problem with the current code and only two spawn points is that Random.Range() is always returning 0. It should be Random.Range(0, n + 1) as the upper limit for random integer generation is excluded.

It is annoying that Random.Range() works different when using integers instead of floats.

This code in SpawnPointManager should work properly:


private void ShuffleSpawnPoints(List<SpawnPoint> spawnPoints) { var n = spawnPoints.Count - 1; while (n > 0) { var k = Random.Range(0, n + 1); var temp = spawnPoints[n]; spawnPoints[n] = spawnPoints[k]; spawnPoints[k] = temp; n--; } }


But in my case it does not because Random.Range() is always returning the same sequence of numbers. It seems that something is setting a fixed seed somewhere which is the same for each execution. The only way I made it work is calling Random.InitState(System.Environment.TickCount) just before calling Random.Range(). I tried other places, but it did not work.


About the exceptions, it seems that the IK is trying to access the LookSource before it has been set. There is some kind of race conditions when calling Respawn() from Start().

Regards.
 
I can see Vegetation Studio Pro manipulating the random number seed but it is hard to know if that is the issue.

For the moment, I fixed it in my code calling Random.InitState(System.Environment.TickCount); before calling CharacterRespawner.Respawn(). But the issue with SpawnPointManager.ShuffleSpawnPoints() seems to be a bug in UCC for your records.

I do not know if the problem with the NullReference exceptions can be qualified as a bug. Calling CharacterRespawner.Respawn() from a Start() method does not seem to be supported by UCC. It would be nice to have a feature in UCC to enable invoking Respawn() when the Scene starts. This way it would possible to determine the initial position for characters and other objects based on the logic of defined in the specific Respawner implementation.
 
I will change the ShuffleSpawnPoints while loop to be greater than 0. With CharacterIK it does look like it is trying to update before the character has been assigned to the look source. You should be able to call respawn after the character has completely initialized - try changing your script execution order so your respawn script executes after all of the character controller scripts.
 
I will try the solution you are suggesting.

What I finally did was stop calling Respawn() from Start() and start calling SetEnabled(false) on the player GameObject. It is working fine as far as Schedule Respawn on Disable is activated.

In order to make it work the way I wanted I had to define a State in the Character Respawner so I can detect whether the respawn is ocurring at the beginning of the Scene. Once the initial respawn is done, I deactivate the special state so the player is respawned in the normal way when dies.

In other words, this:

C#:
private void Start()

{

    // Respawn the player at a random SpawnPoint. This is the initial spawn which has

    // a special state in the spawner

    StateManager.SetState(_playerGO, "InitGame", true);

    _playerGO.SetActive(false);

    StateManager.SetState(_playerGO, "InitGame", false);

}
 
What I finally did was stop calling Respawn() from Start() and start calling SetEnabled(false) on the player GameObject. It is working fine as long as Schedule Respawn on Disable is activated in the respawner.

In order to make it work the way I wanted I had to define a State in the Character Respawner so I can detect whether the respawn is happening at the beginning of the Scene. Once the initial respawn is scheduled, I deactivate the special state so the player is respawned in the normal way when dies.

In other words, this:

C#:
private void Start()

{

    // Respawns the player at a random SpawnPoint. This is the initial spawn which has

    // a special state in the spawner

    StateManager.SetState(_playerGO, "InitGame", true);

    _playerGO.SetActive(false);

    StateManager.SetState(_playerGO, "InitGame", false);

}

I need to respawn my character immediately after the game starts without waiting any time. The InitGame state does that. It did not work with MinRespawnTime=MaxRespawnTime=0, so I defined the state in this way:

1597757005709.png

Good enough for me. I will try the solution you mention though. It seems cleaner.
 
Changing the script execution order worked like charm to solve the NullReferenceExceptions. Thank you Justin.
 
Top