Hello all and thanks for following this post this has been fun , and thanks for brian for all the help
So to explain how everything was done :
First Create a Character Selection Scene
What does it mean Create a scene build another world or maybe you already have from another scene
with terrain and all put a player Prefab there with a camera Facing the player Prefab so when you will be switching the models thats what the Player playing will game will see
Inside this scene have a Save Menu and Character Menu UI , Start Button and Back Button
So you will need to Create a Canvas with a Save Menu Panel and a Character Menu panel
The save menu will control which slot index you have given by input by each button for make it easy i did it by 1 2 3 ( see Photos below the Section )
He will also Be in charge of Closing the panel of the Character menu and Save Menu
So I made it simple 3 buttons in each panel the character and the save menu
The buttons in the Save Menu call a method in the SaveMenu script that sits on the Save panel
Which does a simple thing
Updating the slotIndex by the button input 1 - 3
disabling the save panel
enabling the Character panel
enabling the back button and start button which are for the Character panel
Back is for go back to the save menu panel
start is for loading a new game and save a new save file from the slot index we got before
we will talk later what happen after the start.
public void ChooseCharacter(int index)
{
if (savingWrapper.FileExists(index)) // Dont mind this for now , we will get back to that later
{
savingWrapper.SetSlotIndex(index);
savingWrapper.LoadLastSave();
gameObject.SetActive(false);
return;
}
slotIndex = index;
gameObject.SetActive(false);
characterPanel.SetActive(true);
backButton.SetActive(true);
startButton.SetActive(true);
}
In the Character Menu
You will build it quite Similar with 3 buttons For switching the models
Of course here now you will see the Back button and the start button
So first will do it simple and explain about the back button
The back button what it does is similar to the Saveslot button we pressed before
it is enabling the save panel to be alive again
disabling the character panel
disabling the back and start button
public void OpenSaveMenu()
{
gameObject.SetActive(true);
characterPanel.SetActive(false);
backButton.SetActive(false);
startButton.SetActive(false);
}
Now up until here everything is quite simple playing with UI opening closing
Now starts the fun
So we will need to Create on the player prefab a new Class Called CharacterSelector
After trying many different types of stuff and help with brian
We saw many problems with switching models that are on the player already which i think i have a fix now
but i dont want to go there again this solution is much more simple
So we create a class called CharacterSelector
This class will hold a list of all the prefabs (Models) you want in my case it was 3 later i will put more
it will have a index for current character
a GameObject cached var for the currentModel
a Cached for the animator that sits on the player
What we will do here is in Awake we will Bring first the Animator Component for not having null errors.
we will already Apply the current character
Which in my case is the default model because this is how i want the people see the game
What this Method will do is , If a current model exist it will destroy it first
and then Create a new one and store it in the currentModel var
Then for fix the Animation will start a Corutine that what it does is basically reset the Animator off and on
Then what we will Save in Capture state will be currentCharacter Index which Get Set by the Character Panel Buttons .
Then We will Restore State the currentcharacter Index and apply the current Character which will do what we talk above . Destroy and make a new model reset animator
I hope everyone still understand if not , dont be shy to reply :]
i had a long few days about this
Btw from what you understand from this In the player prefab that is it you wont need a model anymore ! :]
it will be created from the list we put in this class so fun
public class CharacterSelector : MonoBehaviour, ISaveable
{
[SerializeField] List<GameObject> models = new List<GameObject>();
int currentCharacter = 0;
GameObject currentModel;
Animator animator;
void Awake()
{
animator = GetComponent<Animator>();
ApplyCurrentCharacter(); //default to character 0 until set or restoreState
}
public void SetCurrentCharacter(int character)
{
currentCharacter = character;
ApplyCurrentCharacter();
}
void ApplyCurrentCharacter()
{
if (currentModel) Destroy(currentModel);
currentModel = Instantiate(models[currentCharacter],transform);
StartCoroutine(ResetAnimator());
}
IEnumerator ResetAnimator()
{
animator.enabled = false;
currentModel.SetActive(false);
yield return new WaitForSeconds(0.3f);
animator.enabled = true;
currentModel.SetActive(true);
animator.Rebind();
}
public object CaptureState()
{
return currentCharacter;
}
public void RestoreState(object state)
{
currentCharacter = (int)state;
ApplyCurrentCharacter();
}
}
So Back to the Character panel
We have there the Buttons they will call the CharacterSelector.SetCurrentCharacter and set the number between 1 - 3 in my case
Now Remember we had that Code that i did there A comment and said we will get back to it for Continue a new Game before that we need to explain what happen when you press Start on the button
i promise we will get there it is also very simple
So to make the Start and Load a new Game and save for the first time
First we will need to change that from now on from this scene On the PersistentObjectSpawner will be here and not on the Core Prefab as a different object
i believe everyone know what i talk about let me know in reply if need explain about this
we will need also to Create an Object called SceneLoader
in this object will be another class called SceneLoader
It will have inside ref for saveMenu
Fade times in out and wait time
Basically we are doing here same like we did with the portal
just a few minor changes
so if you understand the portal class you will understand this for sure
so this object is DontDestroyOnLoad then when it finish everything it gets destroy same like the portal
it calls the playerController disable it
then fade out
Set the slot Index which we will need to add in the Saving Wrapper ( Later i will share this too )
And after we set the slot Index for the saving wrapper we save a new file under the slot index which we get from the ref in the Save Menu !
Then we load the first Scene of the game Sandbox for RPG courses
We load the save from the slotIndex
We disable the new PlayerController from the scene of the sandbox
so we have the player controller from the scene CharacterSelection and the Sandbox
then we get the Starting position from the player Controller
Just add in the player Controller a new var of Vector3 startPos and in awake save the transfrom.position
Because the player was saved in the scene before and not spawn from the portal
we need to move the player
so we use the navmesh to warp to the start position dont forgot all of this the Player playing game cant see because we fade Out
then we Fade back in bring back Control
Save again and destroy the Object
public class SceneLoader : MonoBehaviour
{
[SerializeField] SaveMenu saveMenu;
[SerializeField] float fadeOutTime = 1f;
[SerializeField] float fadeInTime = 2f;
[SerializeField] float fadeWaitTime = 0.5f;
[SerializeField] int sceneToLoad = 2;
SavingWrapper savingWrapper;
Fader fader;
// Start is called before the first frame update
void Awake()
{
savingWrapper = FindObjectOfType<SavingWrapper>();
fader = FindObjectOfType<Fader>();
saveMenu = saveMenu.GetComponent<SaveMenu>();
}
public void StartNewGame()
{
StartCoroutine(LoadNewGame());
}
IEnumerator LoadNewGame()
{
DontDestroyOnLoad(transform.root.gameObject);
PlayerController playerController = GameObject.FindWithTag("Player").GetComponent<PlayerController>();
playerController.enabled = false;
//Remove control player
yield return fader.FadeOut(fadeOutTime);
savingWrapper.SetSlotIndex(saveMenu.GetSlotIndex());
savingWrapper.NewSaveFile(saveMenu.GetSlotIndex());
yield return SceneManager.LoadSceneAsync(sceneToLoad);
savingWrapper.LoadFromMenu(saveMenu.GetSlotIndex());
PlayerController newPlayerController = GameObject.FindWithTag("Player").GetComponent<PlayerController>();
newPlayerController.enabled = false;
//Change Player Character
Vector3 startPos = newPlayerController.GetStartPos();
newPlayerController.GetComponent<NavMeshAgent>().Warp(startPos);
yield return new WaitForSeconds(fadeWaitTime);
fader.FadeIn(fadeInTime);
newPlayerController.enabled = true;
savingWrapper.SetSlotIndex(saveMenu.GetSlotIndex());
savingWrapper.NewSaveFile(saveMenu.GetSlotIndex());
Destroy(gameObject);
}
}
This need to be added to the SavingSystem Script
public bool FileExists(string saveFile)
{
string path = GetPathFromSaveFile(saveFile);
return File.Exists(path);
}
Updated Saving Wrapper for the SlotIndex
Have some Debug probably some places feel free to delete Add etc
public class SavingWrapper : MonoBehaviour
{
const string defaultSaveFile = "Autosave";
const string newSaveFile = "SaveSlot";
[SerializeField] float fadeInTime = 0.2f;
[SerializeField] float fadeOutTime = 1f;
int saveSlotIndex = 0;
IEnumerator LoadLastScene()
{
Debug.Log("LoadingLastScene");
Debug.Log("Save Slot index is : " + saveSlotIndex);
Fader fader = FindObjectOfType<Fader>();
yield return fader.FadeOut(fadeOutTime);
yield return GetComponent<SavingSystem>().LoadLastScene(newSaveFile + saveSlotIndex);
yield return fader.FadeIn(fadeInTime);
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.L))
{
Load();
}
if (Input.GetKeyDown(KeyCode.S))
{
Save();
}
if (Input.GetKeyDown(KeyCode.D))
{
Delete();
}
}
public void SetSlotIndex(int index)
{
saveSlotIndex = index;
}
public void Delete()
{
GetComponent<SavingSystem>().Delete(newSaveFile + saveSlotIndex);
}
public void Load()
{
//Call to saving system to load
GetComponent<SavingSystem>().Load(newSaveFile + saveSlotIndex);
}
public void Save()
{
//Call to saving system to save
GetComponent<SavingSystem>().Save(newSaveFile + saveSlotIndex);
}
public void NewSaveFile(int index)
{
GetComponent<SavingSystem>().Save(newSaveFile + index);
}
public void LoadFromMenu(int index)
{
GetComponent<SavingSystem>().Load(newSaveFile + index);
}
public void LoadLastSave()
{
StartCoroutine(LoadLastScene());
}
public bool FileExists(int index)
{
return GetComponent<SavingSystem>().FileExists(newSaveFile + index);
}
}
And i know in the end i promised you all the Continue/Resume the save
So …
In the save Menu Script i told you all that this code will resume your LastSave
as you all see we Set the slot Index then use the new Method in Saving Wrapper of LoadLastSave
Its all the same like LoadLastScene just upgraded with fade out fade in properly
if (savingWrapper.FileExists(index))
{
savingWrapper.SetSlotIndex(index);
savingWrapper.LoadLastSave();
gameObject.SetActive(false);
return;
}
So to Make the text appear in the Continue instead of SaveSlot 1 -3 i did it like this
very simple have an array of textmesh just go on them check if the file exist if it does write continue if not write the saveslot(1-3)
public class SaveMenu : MonoBehaviour
{
[SerializeField] GameObject characterPanel;
[SerializeField] GameObject backButton;
[SerializeField] GameObject startButton;
[SerializeField] Transform player;
[SerializeField] TextMeshProUGUI [] savefilesNames;
int slotIndex = 1;
const string savefileName = "SaveSlot";
SavingWrapper savingWrapper;
private void Awake()
{
savingWrapper = FindObjectOfType<SavingWrapper>();
}
private void Start()
{
for (int i = 0; i < 3; i++)
{
if (savingWrapper.FileExists(i+1))
{
savefilesNames[i].text = "Continue";
}
else
{
savefilesNames[i].text = savefileName + (i+ 1);
}
}
}
Some Photos can help with building the UI
Character Button
SaveSlot Button
SavePanel
Heirchy of the Scene of CharacterSelection