Switching characters with InVector controller

Another post to remind myself how I did something.

A game uses InVector’s 3rd person controller, the basic motions version (not the shooter, etc.). I wanted to give the player a menu like this:

Menu
Menu

Click a character to use that one.

I had a hard time getting it to work, much messing around. Here’s what I did. There are probably better ways.

First, I set up this:

Hierarchy
Hierarchy

Player is the game object created by the InVector wizard. There’s something missing, though. 3D Model usually has the 3D model of the character as a child, you know, bones, skinned meshed renderers, like that. It’s missing.

When the player chooses a character, code reparents a model for the chosen character to 3D Model, and restarts the animator.

This class defines data for an entry in the menu and the associated model.

[Serializable]
public class CharacterModelDeets
{
	public string characterName;
	public bool isFemale;
	public Texture2D picture;
	public GameObject model; 
}

[Tooltip("The models available for use by the player")]
public CharacterModelDeets[] characterModelDeetses;

model is Dude3, Woman2, whatevs.

This one…

[SerializeField]
[Tooltip("Where character models should be placed.")]
private GameObject characterModelParent;

… points to the 3D Model GO, the place where the models are reparented to.

This…

[SerializeField]
private Transform modelCache;

… is where models live when not in use. Models in the hierarchy above.

When the player chooses a character in the menu, code like this runs.

private int _currentCharacterModelNumber;

/// <summary>
/// Player choose a character.
/// </summary>
/// <param name="characterModelNumber">Character number, starts at 1.</param>
/// <param name="force">Force a change, even if char is the same as what is recorded as current.
/// Used in init.</param>
public void PlayerChoseCharacterModel(int characterModelNumber, bool force = false)
{
	// Exit if the model hasn't changed, unless forced.
	if (! force && characterModelNumber == _currentCharacterModelNumber)
	{
		return;
	}
	// Turn off the old model.
	characterModelDeetses[_currentCharacterModelNumber - 1].model.SetActive(false);
	_currentCharacterModelNumber = characterModelNumber;
	// Move the old model back to the place models not in use live.
	// They like it there. Air con, snacks, games...
	if (characterModelParent.transform.childCount > 0)
	{
		Transform firstKid = characterModelParent.transform.GetChild(0);
		firstKid.parent = modelCache;
	}
	// Parent the new model to InVector's controller.
	characterModelDeetses[characterModelNumber - 1].model.transform.parent = characterModelParent.transform;
	// Init location and rotation.
	characterModelDeetses[characterModelNumber - 1].model.transform.localPosition = Vector3.zero;
	characterModelDeetses[characterModelNumber - 1].model.transform.localRotation = Quaternion.identity;
	// Turn it on, baby.
	characterModelDeetses[characterModelNumber - 1].model.SetActive(true);
	// Restart the animator.
	PlayerController playerController = Utilities.GetKnowerOfThings().playerController;
	Animator animator = playerController.GetComponent<Animator>();
	animator.enabled = false;
	StartCoroutine(nameof(RestartAnimator));
}

/// <summary>
/// Restart the animator to get the character to work.
/// </summary>
/// <returns></returns>
private IEnumerator RestartAnimator()
{
	yield return new WaitForEndOfFrame();
	PlayerController playerController = Utilities.GetKnowerOfThings().playerController;
	Animator animator = playerController.GetComponent<Animator>();
	animator.enabled = true;
	animator.Rebind();
}

It may not be necessary to do all of these things, but it works. I tried variants, like calling Rebind without waiting for the end of frame, but no joy.

NB: I don’t know why this works with InVector’s controller, but it does. For me. At the moment. Until it breaks. Again.

Leave a Comment

Your email address will not be published. Required fields are marked *

Auto
Scroll to Top