How can I add the locksystem state to my SaveState class? - c#

The Lock System script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LockSystem : MonoBehaviour
{
public bool mouseCursorLockState = true;
public PlayerCameraController playerCameraController;
public PlayerController playerController;
// Start is called before the first frame update
void Start()
{
MouseCursorLockState(mouseCursorLockState);
}
public void MouseCursorLockState(bool lockState)
{
if (lockState == false)
{
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
}
else
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
}
public void PlayerLockState(bool LockPlayer, bool LockPlayerCamera)
{
if (LockPlayer == true)
{
playerController.enabled = false;
}
else
{
playerController.enabled = true;
}
if (LockPlayerCamera == true)
{
playerCameraController.enabled = false;
}
else
{
playerCameraController.enabled = true;
}
}
}
I'm using it in some places in my game for example :
public LockSystem playerLockMode;
playerLockMode.PlayerLockState(true, false);
The playerLockMode is at the top and the using it in some function it's just exmaple of how I'm using it.
The Save State class. So far I can save any object position rotation scaling. Now I want to save also the locking system state :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public class SaveState
{
[Serializable]
public struct SerializableVector3
{
public float X;
public float Y;
public float Z;
public SerializableVector3(Vector3 v)
{
X = v.x;
Y = v.y;
Z = v.z;
}
// And now some magic
public static implicit operator SerializableVector3(Vector3 v)
{
return new SerializableVector3(v);
}
public static implicit operator Vector3(SerializableVector3 sv)
{
return new Vector3(sv.X, sv.Y, sv.Z);
}
}
[Serializable]
public struct SerializableQuaternion
{
public float X;
public float Y;
public float Z;
public float W;
public SerializableQuaternion(Quaternion q)
{
X = q.x;
Y = q.y;
Z = q.z;
W = q.w;
}
public static implicit operator SerializableQuaternion(Quaternion q)
{
return new SerializableQuaternion(q);
}
public static implicit operator Quaternion(SerializableQuaternion sq)
{
return new Quaternion(sq.X, sq.Y, sq.Z, sq.W);
}
}
public SerializableVector3 position;
public SerializableQuaternion rotation;
public SerializableVector3 scale;
public SaveState(Vector3 pos, Quaternion rot, Vector3 sca)
{
position = pos;
rotation = rot;
scale = sca;
}
public void ApplyToPlayer(Transform player)
{
player.localPosition = position;
player.localRotation = rotation;
player.localScale = scale;
}
public bool LockState(bool lockState)
{
return lockState;
}
}
I tried to add in the bottom the new function LockState but that's not the right way to handle it.
The save manager script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System;
using System.IO;
public class SaveManager : MonoBehaviour
{
public static void Save(SaveState player)
{
BinaryFormatter formatter = new BinaryFormatter();
string path = Application.persistentDataPath + "/player.bin";
FileStream stream = new FileStream(path, FileMode.Create);
formatter.Serialize(stream, player);
stream.Close();
}
public static SaveState Load()
{
string path = Application.persistentDataPath + "/player.bin";
if (File.Exists(path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(path, FileMode.Open);
SaveState data = formatter.Deserialize(stream) as SaveState;
stream.Close();
return data;
}
else
{
Debug.LogError("Save file not found in " + path);
return null;
}
}
}
The Player State script for saving the player state but I can also save here the lock system state just not sure yet how :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerState : MonoBehaviour
{
private void Awake()
{
Save();
}
public void Save()
{
SaveState saveState = new SaveState(transform.localPosition,
transform.localRotation, transform.localScale);
SaveManager.Save(saveState);
}
public void Load()
{
var playerInfo = SaveManager.Load();
playerInfo.ApplyToPlayer(transform);
}
}
Once the game start I'm saving in the Awake once.
Then calling the Load function to my Main Menu ui button :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour
{
public void StartNewGame()
{
ScenesManager.StartNewGame();
}
public void LoadGame()
{
var playerstate = GameObject.Find("Player").GetComponent<PlayerState>();
playerstate.Load();
}
public void ResumeGame()
{
ScenesManager.ResumeGame();
}
public void QuitGame()
{
Application.Quit();
}
}
My idea is to save one the game start and then using a timer and save each 5 minutes for example.
So I'm calling only the Load function in the Main Menu. The save will be automatic during the game.
In the SaveState class I want to add more and more stuff to save it's state like the locking system and also a coroutine state. The game start with a running coroutine that make some effect. I want also to save the coroutine state if the game save at the start or a bit later while the coroutine is running so save the current coroutine state too.
This is when the game start :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.PostProcessing;
public class DepthOfField : MonoBehaviour
{
public UnityEngine.GameObject player;
public PostProcessingProfile postProcessingProfile;
public bool dephOfFieldFinished = false;
public LockSystem playerLockMode;
private Animator playerAnimator;
private float clipLength;
private Coroutine depthOfFieldRoutineRef;
// Start is called before the first frame update
void Start()
{
if (depthOfFieldRoutineRef != null)
{
StopCoroutine(depthOfFieldRoutineRef);
}
playerAnimator = player.GetComponent<Animator>();
AnimationClip[] clips = playerAnimator.runtimeAnimatorController.animationClips;
foreach (AnimationClip clip in clips)
{
clipLength = clip.length;
}
DepthOfFieldInit(clipLength);
// Don't forget to set depthOfFieldRoutineRef to null again at the end of routine!
}
public void DepthOfFieldInit(float duration)
{
var depthOfField = postProcessingProfile.depthOfField.settings;
depthOfField.focalLength = 300;
StartCoroutine(changeValueOverTime(depthOfField.focalLength, 1, duration));
postProcessingProfile.depthOfField.settings = depthOfField;
}
public IEnumerator changeValueOverTime(float fromVal, float toVal, float duration)
{
playerLockMode.PlayerLockState(true, true);
float counter = 0f;
while (counter < duration)
{
var dof = postProcessingProfile.depthOfField.settings;
counter += Time.deltaTime;
float val = Mathf.Lerp(fromVal, toVal, counter / duration);
dof.focalLength = val;
postProcessingProfile.depthOfField.settings = dof;
yield return null;
}
playerAnimator.enabled = false;
dephOfFieldFinished = true;
depthOfFieldRoutineRef = null;
}
}
This is a screenshot of the game start and the effect while the coroutine is running :
This blur effect is made by the PostProcessing and the coroutine.
I want also to save the state if that.
Saving objects info is easy like position rotation and scaling but how can I save the state of other stuff like the locking system and the DepthOfField ? How should I extend the SaveState class and then how to use it to save it in the PlayerState script ?

Related

Event invokes in one place but doesn't want to in the other

I am using UnityEvent I called ScoreEvent and I feed it a float. All works fine, except when I need it to. I tried invoking the event when an enemy dies. Nada. I tried putting it in an If statement before, and for some reason it works.
This is the main code (ignore useless variables, still seeing what I need and what I don't)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
[RequireComponent(typeof(TriggerEnter))]
public class Health : MonoBehaviour
{
private ScriptableObjectLoader _sol;
private TriggerEnter _te;
public HealthbarEvent HE;
public ScoreEvent SE;
public float ObjectHealth;
public float EnemyScore;
private float _score;
private bool _isDead;
private void Awake() {
if(_te == null)
{
_te = GetComponent<TriggerEnter>();
}
if(HE == null)
{
HE = new HealthbarEvent();
}
if(SE == null)
{
SE = new ScoreEvent();
}
}
void Start()
{
this._sol = GetComponent<ScriptableObjectLoader>();
_te.DE.AddListener(onChange);
this.ObjectHealth = _sol.Health;
this._score = _sol.Score;
this._isDead = false;
HE.Invoke(ObjectHealth);
}
void onChange(Damage damage){
ObjectHealth -= damage.damage;
if(gameObject.tag == "Player")
{
HE.Invoke(ObjectHealth);
//Works if i put SE.Invoke here
}
if(ObjectHealth <= 0)
{
SE.Invoke(EnemyScore); //doesn't work here (I WANT IT TO BE HERE, NOT UP THERE) :/
if(gameObject.tag == "Enemy")
{
EnemyScore = gameObject.GetComponent<Health>()._score;
}
Destroy(gameObject);
}
}
}
[System.Serializable]
public class ScoreEvent : UnityEvent<float>{}
public class HealthbarEvent : UnityEvent<float>{}
And this is the script that listenes:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class ScoreLoader : MonoBehaviour
{
private TMP_Text _tekst;
private GameObject _player;
private Health _h;
void Awake() {
_tekst = GetComponent<TMP_Text>();
}
void Start()
{
_player = GameObject.Find("Player");
_h = _player.GetComponent<Health>();
_h.SE.AddListener(updateScore);
_tekst.text = "0";
}
void Update()
{
}
void updateScore(float score)
{
Debug.Log("Primio sam ga. Jako");
}
}

My if statement is On constant use and doesn't do the statement

So I have Ctr C Ctr V an Interaction System from YT channel.
I have perfectly copied line by line But the only issue was that he was using the old input system and used KeyPressedDown and keyPressedUp to make 2 boolean's which change according what state is the keypressed, I tried to mimic it with new Input system by making it from a button to a Axis value which follows the logic of if its ClickedAxis>0.4 its true and ClockedAxis==0 false, as a quick press it worked so I was happy BUT here comes the bug
My if statement is Being Spammed Constantly like none stop. My statement basically say's that if the Interacting is true Do :
bool interacting;
If(interacting)
{
float HoldingDownTimer+=Time.DeltaTime;
if(HoldingDownTimer>=5)
{
//Do your task;
}
}
but When I run the statement For some reason it prints 0.0110823 values which are stuck at that range of number's no lower no higher, My theory is that its been spam called.
Here's the Script
If your interested for all the components the YT channel is this guy's VeryHotShark
using UnityEngine;
using NaughtyAttributes;
public class LocomotionScript : MonoBehaviour
{
#region Data
[BoxGroup("Animation Handling Data")]
Animator animator;
int isWalkingHash;
[SerializeField] bool movementPressed;
[BoxGroup("Input Handling Data")]
private CharacterController controller;
public MovementInput input;
Vector2 currentMovement;
[SerializeField] float speed = 1;
private Vector3 velocity;
private float gravity = -9.81f;
public Transform ground;
public float distanceToGround = 0.4f;
public LayerMask groundMask;
private Vector2 smoothinMovement;
private float smoothInputSpeed;
private Vector2 tempCurrentMovement;
private bool isGrounded;
public InteractionInputData interactionInput;
#endregion
private void Awake()
{
animator = GetComponent<Animator>();
input = new MovementInput();
controller = GetComponent<CharacterController>();
input.KeyBoard.ASWD.performed += ctx =>
{
currentMovement = ctx.ReadValue<Vector2>();
movementPressed = currentMovement.x != 0 || currentMovement.y != 0;
};
}
private void Start()
{
interactionInput.Reset();
}
void GetInteractionInputData()
{
interactionInput.InteractedClicked = input.KeyBoard.Interact.ReadValue<float>() > 0.1f;
interactionInput.InteractedReleased = input.KeyBoard.Interact.ReadValue<float>() == 0f;
}
private void Update()
{
LocoMotion();
Grav();
Interact();
GetInteractionInputData();
}
void Grav()
{
isGrounded = Physics.CheckSphere(ground.position, distanceToGround, groundMask);
if (isGrounded && velocity.y<0)
{
velocity.y = -2f;
}
velocity.y += gravity * Time.deltaTime;
controller.Move(velocity * Time.deltaTime);
}
void Interact()
{
}
void LocoMotion()
{
tempCurrentMovement=Vector2.SmoothDamp(tempCurrentMovement, currentMovement, ref smoothinMovement, smoothInputSpeed);
Vector3 movement = (tempCurrentMovement.y * transform.forward) + (tempCurrentMovement.x * transform.right);
WalkAnimation();
controller.Move(movement * speed * Time.deltaTime);
}
void WalkAnimation()
{
animator.SetBool("WalkingHush", movementPressed);
}
private void OnEnable()
{
input.KeyBoard.ASWD.Enable();
input.KeyBoard.Interact.Enable();
}
private void OnDisable()
{
input.KeyBoard.ASWD.Enable();
input.KeyBoard.Interact.Enable();
}
}
//
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace th
{
public class InteractionController : MonoBehaviour
{
#region Variables
[Header("Data")]
public InteractionData interactionData;
public InteractionInputData interactionInputData;
[Space]
[Header("RaySetting's")]
public float rayDistance;
public float raySphereRadius;
public LayerMask interactableLayer;
#endregion
#region Private
private Camera m_cam;
private bool m_interacting;
private float m_holderTimer = 0.0f;
#endregion
#region BuildIn
private void Awake()
{
m_cam = FindObjectOfType<Camera>();
}
private void Update()
{
CheckForInteractable();
CheckForInteractableInput();
}
#endregion
#region Crafted Methodds
void CheckForInteractable()
{
Ray _ray = new Ray(m_cam.transform.position, m_cam.transform.forward);
RaycastHit _hitInfo;
bool _hitSomething = Physics.SphereCast(_ray, raySphereRadius, out _hitInfo,
rayDistance,interactableLayer);
if (_hitSomething)
{
InteractableBase _interactable = _hitInfo.transform.GetComponent<InteractableBase>();
if (_interactable != null)
{
if (interactionData.isEmpty())
{
interactionData.Interactable = _interactable;
}
else
{
if (!interactionData.IsSameInteractible(_interactable))
interactionData.Interactable = _interactable;
}
}
}
else
{
interactionData.ResetData();
}
Debug.DrawRay(_ray.origin, _ray.direction * rayDistance, _hitSomething ? Color.green : Color.red);
}
void CheckForInteractableInput()
{
if (interactionData.isEmpty())
{
return;
}
if (interactionInputData.InteractedClicked)
{
m_interacting = true;
m_holderTimer = 0f;
}
if (interactionInputData.InteractedReleased)
{
m_interacting = false;
m_holderTimer = 0f;
}
if (m_interacting)
{
if (!interactionData.Interactable.IsInteractible)
return;
if (interactionData.Interactable.HoldInteract)
{
m_holderTimer += Time.deltaTime;
Debug.Log(m_holderTimer);
if (m_holderTimer >= interactionData.Interactable.holdDuration)
{
interactionData.Interact();
m_interacting = false;
}
}
else
{
interactionData.Interact();
m_interacting = false;
}
}
}
#endregion
}
}
///
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace th
{
[CreateAssetMenu(fileName ="Interaction Data",menuName = "InteractionSystem/InteractionData")]
public class InteractionData : ScriptableObject
{
private InteractableBase m_interactible;
public InteractableBase Interactable
{
get => m_interactible;
set => m_interactible = value;
}
public void Interact()
{
m_interactible.OnInteract();
ResetData();
}
public bool IsSameInteractible(InteractableBase _newInteractible) => m_interactible == _newInteractible;
public bool isEmpty() => m_interactible == null;
public void ResetData()=> m_interactible = null;
}
}
//
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "InteractionInputData", menuName = "InteractionSystem/InputData")]
public class InteractionInputData : ScriptableObject
{
private bool m_interactedClicked;
private bool m_interactRelease;
public bool InteractedClicked
{
get => m_interactedClicked;
set => m_interactedClicked = value;
}
public bool InteractedReleased
{
get => m_interactRelease;
set => m_interactRelease = value;
}
public void Reset()
{
m_interactedClicked = false;
m_interactRelease = false;
}
}
//
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace th
{
public interface IInteractible
{
float HoldDurration { get; }
bool HoldInteract { get; }
bool MultipleUse { get;}
bool IsInteractible { get; }
void OnInteract();
}
}
//
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace th{
public class InteractableBase : MonoBehaviour,IInteractible
{
#region Variables
[Header("Interactible Settings")]
public float holdDuration;
[Space]
public bool holdInteract;
public bool multipleUse;
public bool isInteractible;
#endregion
#region Properties
public float HoldDurration => holdDuration;
public bool HoldInteract => holdInteract;
public bool MultipleUse => multipleUse;
public bool IsInteractible => isInteractible;
#endregion
#region Methods
public void OnInteract()
{
Debug.Log("Interacted: " + gameObject.name);
}
#endregion
}
}
Well without parsing through your entire code:
once
HoldingDownTimer >= 5
is true you still keep adding more time in the next frame so the condition is still true in the next frame => you will keep calling it all the following frames as well.
You could introduce a flag somewhat like e.g.
private bool alreadyCalled;
and then
if(interacting)
{
if(!alreadyCalled)
{
HoldingDownTimer += Time.DeltaTime;
if(HoldingDownTimer >= 5)
{
alreadyCalled = true;
//Do your task;
}
}
}
else
{
alreadyCalled = false;
}
As far as I understood your code I think:
I found the issue in it actually you are checking if the 'm_holdTimer' >= 'holdDuration' then perform interaction and you are making it equal to 0 on click start and click end.
So it means if the player has continuously held the button then it will not reset 'm_holdTimer'... so it will continue to call the function recursively because player hasn't actually ended the click so there is not any single function which is reseting 'm_holdTimer' to 0. still if you didn't get my point just copy this function and paste it instead of your own and see if it works, if it do work then you can check the difference between line of code then you'll understand it for sure.
void CheckForInteractableInput()
{
if (interactionData.isEmpty())
{
return;
}
if (interactionInputData.InteractedClicked)
{
m_interacting = true;
m_holderTimer = 0f;
}
if (interactionInputData.InteractedReleased)
{
m_interacting = false;
m_holderTimer = 0f;
}
if (m_interacting)
{
if (!interactionData.Interactable.IsInteractible)
return;
if (interactionData.Interactable.HoldInteract)
{
m_holderTimer += Time.deltaTime;
Debug.Log(m_holderTimer);
if (m_holderTimer >= interactionData.Interactable.holdDuration)
{
m_holderTimer = 0f;
interactionData.Interact();
m_interacting = false;
}
}
else
{
interactionData.Interact();
m_interacting = false;
}
}
}
Hope it helps... Happy coding :)

How can I fade out the ui text when enable it false?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SaveGameMessage : MonoBehaviour
{
public Text text;
public float speed;
public bool startFading = false;
public bool enableText = false;
private Color textColor;
private void Start()
{
text = GetComponent<Text>();
textColor = text.color;
}
private void Update()
{
if (startFading == true)
{
if (enableText == true)
{
text.enabled = true;
}
textColor.a = (Mathf.Sin(Time.time * speed) + 1.0f) / 2.0f;
text.color = textColor;
}
else
{
text.enabled = false;
}
}
}
And using it :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cinemachine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class PlayingInGameScenesController : MonoBehaviour
{
public CinemachineFreeLook[] freeLookcameras;
public LockController lockController;
public GameObject uiSceneText;
public GameObject player;
public float transitionSpeed = 5f;
public float thresHold = 0.1f;
public bool talkingProcess = true;
public SaveGameMessage saveGameMessage;
private bool newGame = true;
private void Start()
{
}
public void PlayingSceneInGame()
{
PlayingSceneStatesControls(true);
StartCoroutine(ScenePlayingTime());
}
private void Update()
{
if (SceneManager.GetActiveScene().name != "Main Menu" && newGame == true)
{
PlayingSceneInGame();
newGame = false;
}
}
private void LateUpdate()
{
}
private void PlayingSceneStatesControls(bool LockState)
{
lockController.LockControl(LockState);
if (LockState == true)
{
uiSceneText.SetActive(true);
}
else
{
talkingProcess = false;
uiSceneText.SetActive(false);
}
}
IEnumerator ScenePlayingTime()
{
yield return new WaitForSeconds(10);
PlayingSceneStatesControls(false);
freeLookcameras[0].enabled = false;
freeLookcameras[1].enabled = true;
var brain = Camera.main.GetComponent<CinemachineBrain>().m_DefaultBlend.m_Time;
saveGameMessage.enableText = true;
saveGameMessage.startFading = true;
player.GetComponent<Player>().SavePlayer();
StartCoroutine(FakeSave());
}
IEnumerator FakeSave()
{
yield return new WaitForSeconds(7);
saveGameMessage.startFading = false;
}
}
At the bottom I'm making fake for 7 seconds the problem is when the fake finish the ui text enable false at once. In the SaveGameMessage script at the bottom I'm doing :
text.enabled = false;
but that is ugly it's just stop the fade in out effect at once. I want somehow to enable it false but before that to finish the current fading so the text enabled false will be smooth fading out and ot just suddenly stopping the fading effect.
Before you disable it run an animation to fade it out. this animation should first be made or maybe you can find one online.
then just run the animation before setting it to false.

How do I use the Load function?

For saving I created structures in the state class :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public class SaveState
{
public struct Position
{
public float x;
public float y;
public float z;
public void Fill(Vector3 v3)
{
x = v3.x;
y = v3.y;
z = v3.z;
}
public Vector3 V3 { get { return new Vector3(x, y, z); } set { Fill(value); } }
}
public struct Rotation
{
public float x;
public float y;
public float z;
public float w;
public void Fill(Quaternion Quat)
{
x = Quat.x;
y = Quat.y;
z = Quat.z;
w = Quat.w;
}
public Quaternion Qua { get { return new Quaternion(x, y, z, w); } set { Fill(value); } }
}
public struct Scale
{
public float x;
public float y;
public float z;
public float w;
public void Fill(Vector3 v3)
{
x = v3.x;
y = v3.y;
z = v3.z;
}
public Vector3 V3 { get { return new Vector3(x, y, z); } set { Fill(value); } }
}
public SaveState(Vector3 pos, Quaternion rot, Vector3 sca)
{
Position position = new Position();
position.V3 = pos;
Rotation qua = new Rotation();
qua.Qua = rot;
Scale scale = new Scale();
scale.V3 = sca;
}
}
Then in the manager script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System;
using System.IO;
public class SaveManager : MonoBehaviour
{
public static void Save(SaveState player)
{
BinaryFormatter formatter = new BinaryFormatter();
string path = Application.persistentDataPath + "/player.bin";
FileStream stream = new FileStream(path, FileMode.Create);
formatter.Serialize(stream, player);
stream.Close();
}
public static SaveState Load()
{
string path = Application.persistentDataPath + "/player.bin";
if (File.Exists(path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(path, FileMode.Open);
SaveState data = formatter.Deserialize(stream) as SaveState;
stream.Close();
return data;
}
else
{
Debug.LogError("Save file not found in " + path);
return null;
}
}
}
And test script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SaveTest : MonoBehaviour
{
private void Update()
{
if(Input.GetKeyDown(KeyCode.T))
{
var player = GameObject.Find("Player");
SaveState saveState = new SaveState(player.transform.position, player.transform.rotation,player.transform.localScale);
SaveManager.Save(saveState);
}
if(Input.GetKeyDown(KeyCode.L))
{
var player = GameObject.Find("Player");
}
}
}
The save part seems to be ok but the load part I'm not sure.
I'm not sure if the Load function is written good and not sure how to use it in the L key input.
Edit :
My classes and scripts after updates :
Save State :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public class SaveState
{
public struct SerializableVector3
{
public float X;
public float Y;
public float Z;
public SerializableVector3(Vector3 v)
{
X = v.x;
Y = v.y;
Z = v.z;
}
// And now some magic
public static implicit operator SerializableVector3(Vector3 v)
{
return new SerializableVector3(v);
}
public static implicit operator Vector3(SerializableVector3 sv)
{
return new Vector3(sv.X, sv.Y, sv.Z);
}
}
public struct SerializableQuaternion
{
public float X;
public float Y;
public float Z;
public float W;
public SerializableQuaternion(Quaternion q)
{
X = q.x;
Y = q.y;
Z = q.z;
W = q.w;
}
public static implicit operator SerializableQuaternion(Quaternion q)
{
return new SerializableQuaternion(q);
}
public static implicit operator Quaternion(SerializableQuaternion sq)
{
return new Quaternion(sq.X, sq.Y, sq.Z, sq.W);
}
}
public SerializableVector3 position;
public SerializableQuaternion rotation;
public SerializableVector3 scale;
public SaveState(Vector3 pos, Quaternion rot, Vector3 sca)
{
position = pos;
rotation = rot;
scale = sca;
}
public void ApplyToPlayer(Transform player)
{
player.position = position;
player.rotation = rotation;
player.localScale = scale;
}
}
Manager :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization.Formatters.Binary;
using System;
using System.IO;
public class SaveManager : MonoBehaviour
{
public static void Save(SaveState player)
{
BinaryFormatter formatter = new BinaryFormatter();
string path = Application.persistentDataPath + "/player.bin";
FileStream stream = new FileStream(path, FileMode.Create);
formatter.Serialize(stream, player);
stream.Close();
}
public static SaveState Load()
{
string path = Application.persistentDataPath + "/player.bin";
if (File.Exists(path))
{
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(path, FileMode.Open);
SaveState data = formatter.Deserialize(stream) as SaveState;
stream.Close();
return data;
}
else
{
Debug.LogError("Save file not found in " + path);
return null;
}
}
}
Test script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SaveTest : MonoBehaviour
{
private void Update()
{
if (Input.GetKeyDown(KeyCode.T))
{
var player = GameObject.Find("Player");
SaveState saveState = new SaveState(player.transform.position, player.transform.rotation, player.transform.localScale);
SaveManager.Save(saveState);
}
if (Input.GetKeyDown(KeyCode.L))
{
var player = GameObject.Find("Player");
var playerInfo = SaveManager.Load();
playerInfo.ApplyToPlayer(player.transform);
}
}
}
You currently don't have any fields in your class!
Only type definitions. You will need some like
public Position position;
public Rotation rotation;
public Scale scale;
The ones you used are just local method variables that won't be stored anywhere!
And in the constructor assign these
public SaveState(Vector3 pos, Quaternion rot, Vector3 sca)
{
// Since structs are never null
// you don't need to create new ones
position.V3 = pos;
rotation.Qua = rot;
scale.V3 = sca;
}
Then I usually go for a method within your class itself like e.g.
public void ApplyToPlayer(Transform player)
{
player.position = position.V3;
player.rotation = rotation.Qua;
player.localScale = scale.V3;
}
and then in your L block
var player = GameObject.Find("Player").transform;
var playerInfo = SaveManager.Load();
playerInfo.ApplyToPlayer(player);
As a last step:
For your structs I would even rather recommend to write implicit operator conversions as one example:
[Serializable]
public struct SerializableVector3
{
public float X;
public float Y;
public float Z;
public SerializableVector3(Vector3 v)
{
X = v.x;
Y = v.y;
Z = v.z;
}
// And now some magic
public static implicit operator SerializableVector3 (Vector3 v)
{
return new SerializableVector3 (v);
}
public static implicit operator Vector3 (SerializableVector3 sv)
{
return new Vector3 (sv.X, sv.Y, sv.Z);
}
}
This allows you now to simply use both types exchangeably like you could rather use
public SerializableVector3 position;
// This type is your homework
public SerializableQuaternion rotation;
public SerializableVector3 scale;
and then you can directly assign them like
player.position = someSaveStat.position;
and also
someSaveStat.position = player.position;
So you could change the constructor to e.g.
public SaveState(Vector3 pos, Quaternion rot, Vector3 sca)
{
position = pos;
rotation = rot;
scale = sca;
}
and the apply method would become
public void ApplyToPlayer (Transform player)
{
player.position = position;
player.rotation = rotation;
player.localScale = scale;
}
Note: Typed on smartphone but I hope the idea gets clear.
Load() seems to be OK.
By the way you can also use FileStream stream = File.Open(path, FileMode.Open);
Nothing really special with using it, just: SaveState save = SaveManager.Load();

How can I start a conversation only once even if it's starting inside Update?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ItemAction : MonoBehaviour
{
public camMouseLook mouselook;
public GameObject lockedRoomCamera;
public Camera playerCamera;
public GameObject navi;
public ConversationTrigger trigger;
public GameObject player;
private Vector3 playerposition;
private Quaternion playerrotation;
private bool torotate = false;
public void Init()
{
navi.transform.parent = null;
navi.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
navi.transform.LookAt(lockedRoomCamera.transform);
PlayerController.disablePlayerController = true;
mouselook.enabled = false;
playerCamera.enabled = false;
lockedRoomCamera.SetActive(true);
torotate = true;
playerposition = player.transform.position;
playerrotation = player.transform.rotation;
}
private void Update()
{
if(torotate == true)
{
RotateRandom.RandomRotation(navi.transform);
PlayConversations.ConversationToPlay(3);
}
}
}
The problem is that it's calling this line many times :
PlayConversations.ConversationToPlay(3);
I don't mind it will be called many times but I want it to play for exmaple index 3 only once. If it started playing the index remember it started playing and played the index in this case 3 and don't play it again even if it will call this line in the Update over again.
This is the PlayConversations script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayConversations : MonoBehaviour
{
private static ConversationTrigger conversationTrigger;
private static PlayConversations instance;
private void Awake()
{
conversationTrigger = GetComponent<ConversationTrigger>();
instance = this;
}
public static void ConversationToPlay(int index)
{
ConversationTrigger.conversationsToPlay.Add(index);
instance.StartCoroutine(conversationTrigger.PlayConversations());
}
}
And the ConversationTrigger script :
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEditorInternal;
public class ConversationTrigger : MonoBehaviour
{
public List<Conversation> conversations = new List<Conversation>();
public static List<int> conversationsToPlay = new List<int>();
[HideInInspector]
public GameObject canvas;
[HideInInspector]
public bool conversationEnd;
[HideInInspector]
public static int conversationIndex;
private DialogueManager dialoguemanager;
private void Start()
{
conversationIndex = 0;
dialoguemanager = FindObjectOfType<DialogueManager>();
}
public IEnumerator PlayConversations()
{
conversationEnd = false;
var conversations = conversationsToPlay.ToArray(); // Copy the list
conversationsToPlay.Clear(); // Immediately clear the original list
for (int i = 0; i < conversations.Length; i++) // iterate over the array
{
// Now you also don't need to remove items anymore,
// since you already cleared the list
yield return StartCoroutine(PlayConversation(conversations[i]));
}
}
public IEnumerator PlayConversation(int index)
{
if (conversations.Count > 0 &&
conversations[index].Dialogues.Count > 0)
{
for (int i = 0; i < conversations[index].Dialogues.Count; i++)
{
if (dialoguemanager != null)
{
dialoguemanager.StartDialogue(conversations[index].Dialogues[i]);
}
while (DialogueManager.dialogueEnded == false)
{
yield return null;
}
}
conversationIndex = index;
conversationEnd = true;
canvas.SetActive(false);
Debug.Log("Conversation Ended");
}
}
public void SaveConversations()
{
string jsonTransform = JsonHelper.ToJson(conversations.ToArray(), true);
File.WriteAllText(#"d:\json.txt", jsonTransform);
}
public void LoadConversations()
{
string jsonTransform = File.ReadAllText(#"d:\json.txt");
conversations.Clear();
conversations.AddRange(JsonHelper.FromJson<Conversation>(jsonTransform));
}
}
Either to make inside the PlayConversations script or inside the ConversationTrigger that when it started playing a conversation don't play this conversation over again.
The problem is that each place in the game I'm playing a conversation like in this case I need to use flags to make it stop after start playing but inthis case for example while the conversation is playing I'm also rotating an object and that should be in the Update and be called many times :
RotateRandom.RandomRotation(navi.transform);
But the conversation should start only once.
Since I don't see a reason why to play the same conversation twice in the game then I want it to be playing only once without the need to use flags each place.

Categories

Resources