How to save a disabled Game object? - c#

So I have a button in my game and when I click it, it disables from script
MyGameobject.SetActive(false)
But the problem is that when I restart the game the disabled gameobject is enabled again. So my question is how do I save the disabled gameobject ?
I tried using PlayerPrefs, but I realised that it makes no sense. Or does it?

Using PlayerPrefs:
public GameObject MyGameObject;
void Start()
{
// Check on start if gameobject should be enabled or disabled
if (PlayerPrefs.GetInt("MyGoActiveState") == 0)
{
// Should be disabled
MyGameObject.SetActive(false);
}
else
{
// Should be enabled
MyGameObject.SetActive(true);
}
}
// Assuming this is your callback for disabling the object
public void DisableGameObject()
{
// Disable gameobject
MyGameObject.SetActive(false);
// Store in PlayerPrefs that gameobject is disabled
PlayerPrefs.SetInt("MyGoActiveState", 0);
}
// Assuming this is your callback for enabling the object
public void EnableGameObject()
{
// Enable gameobject
MyGameObject.SetActive(true);
// Store in PlayerPrefs that gameobject is enabled
PlayerPrefs.SetInt("MyGoActiveState", 1);
}
For one-line fans one could also set the active state like this, assuming 0 is used to indicate disabled and anything else means enabled:
MyGameObject.SetActive(PlayerPrefs.GetInt("MyGoActiveState") == 0);

Related

I'm trying to make an animation that can always happen when clicking a button after entering a trigger

I'm making a game with unity 2022 and was struggling to make this animator [bool turn true] after entering a trigger and pressing "E". and then go false after still holding "E" still or not pressing it. also am struggling with Get Key stuff. it isn't working mate and I have been trying for hours at a time with struggles so yeah. this is my code. it's basically someone pressing a button (the object in unity I'm using), making it click until you click it again, please help this is for a school summer project!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
class Fixedpress : MonoBehaviour
{
public Animator Tributton;
bool YUIp = false;
// Start is called before the first frame update
void OnTriggerEnter(Collider other)
{
if(other.tag == "player")
{
YUIp = true;
}
}
void OnTriggerStay(Collider other)
{
if (other.tag == "Player" && YUIp == true)
{
if (Input.GetKeyDown(KeyCode.E))
{
Tributton.SetBool("Fiveyon", true);
Debug.Log("THY");
}
else if (Input.GetKey(KeyCode.E))
{
Tributton.SetBool("Fiveyon", false);
Debug.Log("THe");
}
else if (Input.GetKeyUp(KeyCode.Return))
{
Tributton.SetBool("Fiveyon", false);
Debug.Log("THane");
}
}
}
void OnTriggerExit(Collider other)
{
if(other.tag == "Player")
{
YUIp = false;
}
}
}
I mostly need help with the press e code stuff to make work so yeah :D
thank you
sorry for the bad grammar its 3 am rn
I think I got it now.
You want
Player has to be in the trigger in order to start the animation
Pressing E should trigger the animation only ONCE
I would no use a Bool parameter in that case but rather a Trigger and then Animator.SetTrigger.
Your transitions should be somewhat like e.g.
Idle State --condition-Fiveyon--> Your Animation
Your Animation --exit-time-no-further-condition--> Idle State
Then you could probably do something like e.g.
public class Fixedpress : MonoBehaviour
{
public Animator Tributton;
private Coroutine routine;
private void OnTriggerEnter(Collider other)
{
// not sure if needed right now, some of the events are triggered even on disabled components
if(!enabled) return;
// in general rather use CompareTag instead of ==
// it is slightly faster and also shows an error if the tag doesn't exist instead of failing silent
if(!other.CompareTag("player")) return;
// just in case to prevent concurrent routines
if(routine != null) StopCoroutine(routine);
// start a new Coroutine
routine = StartCoroutine(WaitForKeyPress());
}
private IEnumerator WaitForKeyPress()
{
// check each FRAME if the key goes down
// This is way more reliable as OnTriggerStay which is called
// in the physics loop and might skip some frames
// This also prevents from holding E while entering the trigger, it needs to go newly down
yield return new WaitUntil(() => Input.GetKeyDown(KeyCode.E));
// set the trigger once and finish the routine
// There is no way to trigger twice except exit the trigger and enter again now
Tributton.SetTrigger("Fiveyon");
Debug.Log("Fiveyon!");
// If you even want to prevent this from getting triggered ever again simply add
enabled = false;
// Now this can only be triggered ONCE for the entire lifecycle of this component
// (except you enable it from the outside again of course)
}
void OnTriggerExit(Collider other)
{
if(!other.CompareTag("player")) return;
// when exiting the trigger stop the routine so later button press is not handled
if(routine != null) StopCoroutine(routine);
}
}

How to control a bool individual to each instantiated script

So I want this script to check if bool is true and if it is then play an audio once but if its false then don't play the audio but the problem is that this script is in multiple gameobjects and is always constantly turning on and off so the bool may be true on this gameobject but false on the other and so when I tried checking the bool value it returns both true and false and so never really executing the if condition to play the music until both gameobjects meet the same condition...how do I fix this?
ps: the gameobject isn't a prefab
private void Update()
{
if (isChasing && inSightArea)
{
if (!enemyFOV.chaseSound.isPlaying)
{
Debug.Log("music playing");
enemyFOV.chaseSound.Play();
}
Debug.Log(enemyFOV.chaseSound.isPlaying);
}
else
{
Debug.Log("is patrolling");
enemyFOV.chaseSound.Stop();
}
}
the "music playing" debug log seems to be logging at the right time i want but the enemyFOV.chaseSound.Play(); somehow didn't run at all until both bool from both object returns true for some reason..?
It sounds to me like your enemyFOV is the same instance for both your objects.
So if in one instance the isChasing && inSightArea is true, but on another it is not
=> The one object starts the sound, the other object stops the sound immediately.
I would rather store all currently chasing objects and let your EnemyFOV class control the sound like e.g.
public class EnemyFOV : MonoBehaviour
{
// store all currently chasing instances
private readonly HashSet<YourScript> currentlyChasing = HashSet<YourScript>();
// your audiosource as you have it anyway
public AudioSource chaseSound;
// call this one instead
public void SetChasing(YourScript yourScript, bool iAmChasing)
{
if(iAmChasing)
{
// Store this instance
if(!currentlyChasing.Contains(yourScript)) currentlyChasing.Add(yourScript);
}
else
{
// remove this instance
if(currentlyChasing.Contains(yourScript)) currentlyChasing.Remove(yourScript);
}
// If there is at least one instance and no sound is playing
if(currentlyChasing.Count > 0 && !chaseSound.isPlaying)
{
// -> start the sound
Debug.Log("music playing");
chaseSound.Play();
}
// If there is no chasing instance but sound is playing
else if (currentlyChasing.Count == 0 && chaseSound.isPlaying)
{
// -> stop the sound
chaseSound.Stop();
}
}
}
Where YourScript is the script from which you are calling your original code.
There instead you will now do
private void Update()
{
enemyFOV.SetChasing(this, isChasing && inSightArea);
}

how can I make my door open from a specific distance c#

I want to add doors to my Unity project and so far I have gotten the code to work with my door but I can open it from any distance in the scene. I want to be able to only open it from a small distance away but I cannot figure out how.
public class Door : MonoBehaviour
{
public bool doorIsOpen;
public Transform closedPos;
public Transform openPos;
public float openSpeed;
public float openRadius;
void Update()
{
if(Physics.CheckSphere(gameObject.transform.position, openRadius))
{
if(Input.GetKeyDown(KeyCode.E))
{
doorIsOpen = !doorIsOpen;
}
}
if(doorIsOpen == true)
{
OpenDoor();
}
if(doorIsOpen == false)
{
CloseDoor();
}
}
void OpenDoor()
{
doorIsOpen = true;
gameObject.transform.position = openPos.position;
gameObject.GetComponent<Animator>().SetTrigger("OpenDoor");
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", false);
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", true);
}
void CloseDoor()
{
doorIsOpen = false;
gameObject.transform.position = closedPos.position;
gameObject.GetComponent<Animator>().SetTrigger("CloseDoor");
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", false);
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", true);
}
}
Prerequisites
Add a sphere with a collision body around the door instance in Unity. This sphere functions as the radius which will trigger the ChangeDoorState method.
Change the update method
The update will look at any collisions happening in the specified sphere. If there is at least one object in range (the collision sphere) of the door, then it opens or closes the door instance. Source: https://docs.unity3d.com/ScriptReference/Physics.OverlapSphere.html
void Update()
{
Collider[] hitColliders = Physics.OverlapSphere(center, radius);
if (hitColliders.Length > 0)
{
ChangeDoorState();
}
}
Merged the OpenDoor and CloseDoor methods
void ChangeDoorState()
{
doorClosureState = doorClosure ? true : false;
gameObject.transform.position = doorClosureState ? closedPos.position : openPos.postition;
gameObject.GetComponent<Animator>().SetTrigger("DoorClosure");
gameObject.GetComponent<Animator>().SetBool("DoorIsOpen", doorClosureState);
gameObject.GetComponent<Animator>().SetBool("DoorIsClosed", !doorClosureState);
}
You can increase the value of 'openRadius', the game is creating a sphere at gameObject.transform.position with a radius of 'openRadius' and checking if there is any colliders overlapping the sphere.
//...
if(Physics.CheckSphere(gameObject.transform.position, openRadius))
//...
One issue is that you permanently set the triggers in every frame. You would only want to do so when you hit the key.
Then also Physics.CheckSphere checks whether there is any collider within the range. This could be any collider, not only the player. To make sure it only works if the player is the one in range you should definitely use Layers and give the player its own layer e.g. "Player". Then you can pass the layerMask parameter to make sure the doors only check for the player's layer.
so I would simply use
// if possible already reference this in the Inspector
[SerializeField] private Animator _animator;
[Tooltip("Here select only the Layer(s) which you assigned to your Player object(s)")]
[SerializeField] LayerMask _playerLayer;
private void Awake()
{
// as fallback get it ONCE on runtime
if(!_animator) _animator = GetComponent<Animator>();
}
private void Update()
{
// Afaik the Key is way cheaper to check so do it first
// and use physics only if actually necessary
if(Input.GetKeyDown(KeyCode.E))
{
// Only check for the Player's Layer and ignore other colliders
if(Physics.CheckSphere(transform.position, openRadius, _playerLayer.value))
{
ToggleDoorState();
}
}
}
// Since manually changing the flag in the Inspector will not have
// any effect anymore in order to be able to test it you can use
// the context menu of this component in the Inspector
[ContextMenu(nameof(ToggleDoorState))]
private void ToggleDoorState()
{
// invert the flag
doorIsOpen = !doorIsOpen;
// use the flag in ternary expressions
transform.position = doorIsOpen ? openPos.position : closedPos.position;
_animator.SetTrigger(doorIsOpen ? "OpenDoor" : "CloseDoor");
// use the value of the flag diectly
_animator.SetBool("DoorIsClosed", !doorIsOpen);
_animator.SetBool("DoorIsOpen", doorIsOpen);
}
It is seems a bit though as if you have a bit of redundancy in your animator. I would either use the Bools in order to trigger a transition or use the Triggers, to have both seems odd.

Unity: Can't access Childrens SpriteRenderer.enabled

I have problems accessing the enabled variable in children of my Player GameObject.
-Human holds Rigidbody2D, BoxCollider, PlayerController script and Animator
-body and range_attack_body only hold SpriteRenderer
What I want to do:
I want to change the SpriteRenderer of my Player Object when mouse button is on hold. There are SpriteRenderers in body and range_attack_body. Both GameObjects are part of animations.
body.SpriteRenderer is active and range_attack_body.SpriteRenderer inactive during normal motion.
In my PlayerController script I wrote a routine that will trigger an attack animation when mouse button is on hold. In this routine I wanted to change the enabled states of the SpriteRenderers. However nothing is happening, meaning the varibales are not changing during runtime. I already checked if the GameObjects and SpriteRenderers are accessed correctly during Awake() and I can find both renderers in my SpriteRenderer array using debug messages.
On top of that I checked what happens if I add a SpriteRenderer to my Human GameObject. It will appear in my SpriteRenderer array and I have full access to enabled variable meaning I can change them in my routine. So I figured there might be a conflict with body and range_attack_body due to their SpriteRenderers being part of animations. I added Human.SpriteRenderer to an animation and can still change variables.
I have no clue what is going on, please help. Here is some code:
public class PlayerController2D : PhysicsObject {
public float maxSpeed = 7f;
public float jumpTakeOffSpeed = 7f;
public float posOffset = 1;
protected bool flipSprite = false;
protected bool flipState = true;
private Animator animator;
private SpriteRenderer[] spriteRenderers;
void Awake ()
{
animator = GetComponent<Animator> ();
GameObject human = GameObject.Find("Human");
spriteRenderers = human.GetComponentsInChildren<SpriteRenderer> ();
}
protected override void Attack ()
{
if(Input.GetMouseButton(0))
{
spriteRenderers[0].enabled = false;
spriteRenderers[1].enabled = true;
Debug.LogError("Inhalt:" + spriteRenderers[0].ToString());
Debug.LogError("Inhalt:" + spriteRenderers[1].ToString());
animator.SetBool("attack", true); // boolean to start hold animation
}
else if(!Input.GetMouseButton(0))
{
spriteRenderers[0].enabled = true;
spriteRenderers[1].enabled = false;
animator.SetBool("attack", false);
}
}
}
Your script code is ok, the problem should be elsewhere.
First of all, disable the Animator component and run the script: if enabling/disabling the sprite renderers work, than you must look in the animation clips if you have the Sprite Renderer.Enabled property anywhere, most probably you'll have it - remove it so you can enable/disable the renderers only via script.
If the script still doesn't work, then the problem is elsewhere (another script which is accessing the enabled property of the renderers).
But I'd bet that you're changing the enabled property from the animation clips, which supercedes the script due to how the Unity execution order works (animations are always updated after the scripts and before rendering).

Unity - Panel is disabled and re-enabled immediately afterwards (C#)

Just need a way to get around this problem. I know what the issue is, I just need a viable solution.
So basically I have a script called MouseManager, which detects if you're looking at a specific game object and if you press "f" it tells another script, called UIManager, to open the GameObject's panel (I have a game where you can press f on a console and it enables a UI with a puzzle).
Basically, I want the user to be able to press "f" or "esc" and have it exit the panel, however if you press "f", it disables the panel but instantly re-enables it as you're still looking at the gameobject, and both checks are in Update()
I need a workaround, if anyone knows one PLEASE comment as this issue is getting on my nerves :P
Thanks
EDIT: By console I mean there's a GameObject that looks like a console.
EDIT 2: Here's the code, I'll put some comments in it so you can understand what each thing does. I will be refactoring it soon because it's messy...
void Start()
{
uiPuzzles = GameObject.Find("PuzzlesUI").GetComponentInChildren<UIManager>(); // Currently I have the UIManager set to a canvas called PuzzlesUI. I will soon be moving it to an empty GameObject.
}
void Update()
{
if (!uiPuzzles.invActive && !uiPuzzles.puzzleActive) // invActive and puzzleActive are two public booleans that just show if the inventory or any puzzle panel is empty (UIManager handles opening and closing the inventory too)
{
if (Input.GetButtonDown("Use") && !uiPuzzles.invActive && !uiPuzzles.puzzleActive) // Use key is bound to "f". Currently Im just checking if puzzleActive and invActive are still false.
{
ray = GetComponentInChildren<Camera>().ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, 7)) // Raycast where you're looking
{
uiPuzzles.openPuzzleOverlay(hit.collider.name); // function in UIManager which will check if the hit collider is in a list of all the puzzle colliders currently added and if it is, open that panel.
}
}
}
}
As for UIManager:
public void openPuzzleOverlay(string collider) // Get a panel name
{
foreach (Puzzle item in puzzleList)
{
item.panelGameObject.SetActive(false); // Disable all puzzle panels.
if (item.consoleGameObject.name == collider) // unless its the given panel,
{
item.panelGameObject.SetActive(true); // then enable it.
Cursor.lockState = CursorLockMode.None; // Unlock the mouse.
Cursor.visible = true; // Show the mouse.
DisablePlayerUI(); // Disable the UI (e.g. crosshair)
puzzleActive = true; // Disable movement. (Because player movement requires puzzleActive and invActive to also be false
}
}
}
void Update ()
{
if (Input.GetButtonDown("Use") && puzzleActive && !invActive && // If you want to use a console and the puzzle is already active
!puzzleList.Where(p => p.panelGameObject.activeSelf).FirstOrDefault().panelGameObject.GetComponentInChildren<InputField>().isFocused) // Check if the currently active panel's input field is not focused.
{
ClosePuzzleOverlay(); // Close the puzzle.
EnablePlayerUI(); // Enable the UI.
}
}
public void ClosePuzzleOverlay()
{
foreach (Puzzle item in puzzleList)
item.panelGameObject.SetActive(false);
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
puzzleActive = false;
}
The problem is being caused by the two input checks. When F is pressed both are true so the UI is being closed and then opened in the same frame.
Take the Input code out of UIManager and keep it in one place. Then you can rework your input class to be something like this:
//MouseManager Update
if(Input.GetButtonDown("Use")){
if(uiPuzzles.isActive)
uiPuzzles.Hide();
else
uiPuzzles.Show(GetPuzzleName());
}
Now it will only call either open or close on the frame which F is pressed down.

Categories

Resources