I am working on this 3d game were a player has to press E to interact with objects.
the player has a collider that when touching a type of trigger that has this type of code, makes an object pop up showing that the player's is "selecting" something.
when the player is in the trigger I made it were when they press E, the objects animation plays. When adding the objecting selecting thing it made it now that I cant make the animation play when pressing E
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fixedpress : MonoBehaviour
{
public Animator Tributton;
public GameObject GM;
private Coroutine routine;
private void Start()
{
GM.SetActive(false);
}
private void OnTriggerStay(Collider other)
{
// 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("LookTrig")) {
GM.SetActive(true);
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("LookTrig")) {
GM.SetActive(false);
return;
}
// when exiting the trigger stop the routine so later button press is not handled
if (routine != null)
{
StopCoroutine(routine);
GM.SetActive(false);
}
}
}
OnTrigger methods are called when another object with a Collider enters in your object Collider.
First, make sure everything was well set up well, and maybe you want to use OnCollision instead of OnTrigger methods, if you want to detect a collision whether than a "penetration" if i may say
Related
I have a button that is supposed to switch beetween light and dark mode in my game by running the method "ToggleTheme" inside the ObjectTheme script, which all the objects that I want to be affected by light/dark mode have. ToggleTheme just changes the boolean "DarkMode", since all the objects' transitions use this DarkMode boolean. It all works fine if I just assign the objects and select ObjectTheme.ToggleTheme, but if I assign the objects' prefabs and select ObjectTheme.ToggleTheme I get the warning "Animation is not playing an AnimatorController" on button press. Is there any way around this, because assigning every object in every scene would just be to impractical and one of the objects has up to 30 copies in every level of the game?
P.S. I know It probably would have been easier if I just used a toggle instead of a button, but I'm new to Unity and I just couldn't get the toggle to work how I wanted it, so I'm using a button instead.
Here is the ObjectTheme script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectTheme : MonoBehaviour
{
public Animator animator;
// Start is called before the first frame update
void Start()
{
animator = GetComponent<Animator>();
}
// Update is called once per frame
public void ToggleTheme()
{
if(animator.GetBool("DarkMode") == true)
{
animator.SetBool("DarkMode", false);
}
else
{
animator.SetBool("DarkMode", true);
}
}
}
**Edited after Jonatan's comments below.
I understand the desire to just assign the prefab as the button's event target. But the prefab itself is in some sense also just an instance that is just not living in the scene. While in edit mode, all changes in the prefab itself will reflect in the scene instances. But when you are in play mode (runtime) the prefab instances in the scene will no longer automatically update themselves with changes in the prefab file.
In this case, we are trying to set a bool value on an Animator component, but the Animator on the prefab is not really playing - only the Animators on the scene instances are playing. That is why you get the 'not playing' warning.
One option to solve the issue could be something like the following.
First add a script to the button that has a function that can be hooked up with your button's OnClick() UnityEvent. The script will look for instances of another script, which is present on all the objects that should react to dark mode state. This other script could be your ObjectTheme script but here I call it DarkModeReceiver. When the button triggers the function, the script will simply call a function on all the script instances stored in its array.
//Put this script on the Button,
//and hook up the Button's OnClick event with the OnButtonClicked() function
using UnityEngine;
public class DarkModeHandler : MonoBehaviour
{
static bool isDarkMode;
public static bool IsDarkMode => isDarkmode;//Public get property
//Make your Button call this function in its OnClick() event
public void OnButtonClicked()
{
isDarkMode = !isDarkMode;//Toggle bool
SendIsDarkMode();
}
//Alternatively, if you choose to use a Toggle instead
//you could hook this function up with the Toggle's OnValueChanged(Boolean) event
//with the dynamic bool of that event.
public void OnToggleValueChanged(bool isToggledOn)
{
isDarkMode = isToggledOn;
SendIsDarkMode();
}
void SendIsDarkMode()
{
var darkModeReceivers = FindObjectsOfType<DarkModeReceiver>(true);
foreach (var receiver in darkModeReceivers)
{
receiver.SetIsDarkMode(isDarkMode);
}
}
}
And then the receiving script (attached on all the game objects / prefabs that should react to dark mode state) could be something like this (or a modified version of your ObjectTheme script).
using UnityEngine;
public class DarkModeReceiver : MonoBehaviour
{
Animator myAnimator;
void Awake()
{
myAnimator = GetComponent<Animator>();
}
void Start()
{
//Ensure that our state is in sync with the DarkModeHandler
SetIsDarkMode(DarkModeHandler.IsDarkMode);
}
public void SetIsDarkMode(bool isDarkMode)
{
myAnimator.SetBool("DarkMode", isDarkMode);
}
}
Alternatively, you could do something where the DarkModeReceivers/ObjectThemes register themselves on the DarkModeHandler on their Start() and unregister themselves again on their OnDestroy() - for example by subscribing to an event. Then the DarkModeHandler wouldn't have to look for receivers every time the button is clicked.
I am in the middle of creating a simple 3d platformer in unity and i'm trying to make it so if you are in a certain radius and you hit the "f" key the enemy will disappear. the way i'm checking to make sure the enemy is close enough to be attacked is to have trigger sphere and when its triggered and you press "f" the enemy will be removed. i'm currently trying to see what collided with the trigger but i cant figure it out. here is my current code
using UnityEngine;
public class PlayerCombat : MonoBehaviour
{
public GameObject Enemy1;
public GameObject Enemy2;
public GameObject Enemy3;
public GameObject Enemy4;
// Update is called once per frame
void OnTriggerEnter (Trigger triggerInfo)
{
if (triggerInfo.collider.tag == "Enemy" & Input.GetKey("f"))
{
if (triggerInfo.collider.name == Enemy)
{
Enemy1.SetActive(false);
}
}
}
}
The signature is and has always been OnTriggerEnter(Collider) otherwise that message method will not be recognized by Unity and not get called at all!
And then you already have the according Collider ... what else do you need?
public class PlayerCombat : MonoBehaviour
{
// There is no need to know the enemy references beforehand at all
// you will get all required references from the Collider parameter of OnTriggerEnter itself
// I personally would however expose these two settings to the Inspector to be more flexible
// this way you can adjust these two settings within Unity without having to touch your code
public string listenToTag = "Enemy";
public KeyCode listenToKey = KeyCode.F;
// The signature has to be this otherwise the message method is never invoked at all
private void OnTriggerEnter (Collider other)
{
// Rather use "CompareTag" instead of `==` since the latter will silently fail
// for typos and nonexistent tags while "CompareTag" shows an error which is good for your debugging
// And in general you want to use the logical "&&" instead of the bitwise operator "&" for bools
if (other.CompareTag(listenToTag) && Input.GetKey(listenToKey))
{
// simply set the object you collide with inactive
other.gameObject.SetActive(false);
}
}
}
Finally just make sure that all your enemy instances actually have the tag Enemy
First, I'm pretty sure OnTriggerEnter takes a Collider as its parameter, no? https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html
You can get the name of the collided object like so
//Upon collision with another GameObject,
private void OnTriggerEnter(Collider other)
{
Debug.Log($"collided with {other.gameObject.name}");
//you can check for specific components too
MyComponent myComponent = other.GetComponent<MyComponent>();
if (myComponent != null) {
// do something if it has that component on it!
}
}
I am trying to make fire evacuation simulation by using Unity. I need to count the number of agent passes through the exit door during the evacuation. Is there any way to do it ?
You could set up a simple trigger collider system.
First, you would place a box collider at the exit door, and set it to trigger so it isn’t a solid object (objects can path through it, rather than walk into it). Now, add a Rigidbody to this box collider, and set first drop-down menu that says ‘Dynamic’ to ‘Kinematic’. Now, a way to count them. We will ad the following script to the box collider object:
using UnityEngine;
public class ExitDoor : MonoBehavour
{
void OnTriggerEnter(Collider obj)
{
if (obj.gameObject.tag == “agent”)
{
}
}
}
This doesn’t work yet, because we don’t have anything other than an OnTriggerEnter statement. OnTriggerEnter is called every time either a game object passes through a trigger collider or this game object passes through a trigger collider. We set up an if statement to detect if the game object that passed through it has a certain tag. We are searching for a tag called “agent”. Set each agent’s tag to “agent”. Now we should start a counting system.
using UnityEngine;
public class ExitDoor : MonoBehavour
{
public int agents;
void OnTriggerEnter(Collider obj)
{
if (obj.gameObject.tag == “agent”)
{
agents += 1;
}
}
}
Now, we add 1 to a variable each time an agent enters the collider. The only problem with this is that if the agent goes through the collider twice, it will count it twice.
This system is done, but now you might want to access this from different scripts. We will use GetComponent<>() to access the script. Add this to your game management script, or whatever you want to be accessing this:
using UnityEngine;
public class GameManagement : MonoBehavour
{
public GameObject ExitDoor;
public int agents;
void Update()
{
agents = ExitDoor.GetComponent<ExitDoor>().agents;
if (agents == 10)
{
Debug.Log(“10 agents have exited.”);
}
}
}
Now, we access the script ExitDoor, and get agents from it. Make sure you set the ExitDoor variable from the game management script to the box collider game object used for counting.
This was untested and if you get any bugs or this wasn’t what you wanted, comment on this post to ask me.
When loading a new Scene I run into the trouble of having my mouse drag not carry over to the next scene and having to re-click when the new scene is loaded.
I would like the mouse click to carry over seamlessly to the next scene without the player noticing and more generally I would like to know how best to preserve certain game objects and make them carry over to the next scene.
In essence, what I'm trying to do is have the entire game act like one big scene that the player can play trough but still be broken down into smaller scenes that could be accessed or transformed into levels at a later stage.
Thanks in advance.
This is the code I'm currently using
using UnityEngine;
using System.Collections;
using UnityEngine.Profiling;
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
private void OnMouseDrag()
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = mousePos;
DontDestroyOnLoad(this.gameObject);
}
}
Bellow is the script that is responsible for the loading of the scene:
public class StarCollision : MonoBehaviour
{
private bool alreadyScored = false;
private void OnEnable()
{
alreadyScored = false;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("White Ball"))
{
if (!alreadyScored)
{
ScoreScript.scoreValue += 1;
StartCoroutine(ChangeColor());
alreadyScored = true;
}
}
if (ScoreScript.scoreValue > 4)
{
SceneManager.LoadScene(1);
}
}
private IEnumerator ChangeColor()
{
ScoreScript.score.color = Color.yellow;
yield return new WaitForSeconds(0.1f);
ScoreScript.score.color = Color.white;
gameObject.SetActive(false);
}
}
I think the main reason why it doesn't work is that you probably also have another Camera in the new Scene.
The OnMouseDrag rely on the Physics system internally using the objects Collider and raycasts from the Camera. Now if you switch Scene I'ld guess the one Camera gets disabled so your drag gets interrupted.
Also using LoadScene instead of LoadSceneAsync causes a visible lag and might also be related to the issue.
I have a maybe a bit more complex solution but that is what I usually do:
1. Have one Global Scene "MainScene"
This Scene contains stuff like e.g. the MainCamera, global ligthning, global manager components that should never be destroyed anyway.
2. Use additive async Scene loading
You said you do not want your user to not note when the scene switches so I would recommend using SceneManager.LoadSceneAsync anyway.
Then in order to not unload the before mentioned MainScene you pass the optional parameter LoadSceneMode.Additive. This makes the new Scene be loaded additional to the already present one. Then later you only have to exchange those by unloading the previously additive loaded scene.
I created a very simple static manager for this:
public static class MySceneManager
{
// store build index of last loaded scene
// in order to unload it later
private static int lastLoadedScene = -1;
public static void LoadScene(int index, MonoBehaviour caller)
{
caller.StartCoroutine(loadNextScene(index));
}
// we need this to be a Coroutine (see link below)
// in order to correctly set the SceneManager.SetActiveScene(newScene);
// after the scene has finished loading. So the Coroutine is required
// in order to wait with it until the reight moment
private static IEnumerator loadNextScene(int index)
{
// start loading the new scene async and additive
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
// optionally prevent the scene from being loaded instantly but e.g.
// display a loading progress
// (in your case not but for general purpose I added it)
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
// e.g. show progress of loading
// yield in a Coroutine means
// "pause" the execution here, render this frame
// and continue from here in the next frame
yield return null;
}
_async.allowSceneActivation = true;
// loads the remaining 10%
// (meaning it runs all the Awake and OnEnable etc methods)
while (!_async.isDone)
{
yield return null;
}
// at this moment the new Scene is supposed to be fully loaded
// Get the new scene
var newScene = SceneManager.GetSceneByBuildIndex(index);
// would return false if something went wrong during loading the scene
if (!newScene.IsValid()) yield break;
// Set the new scene active
// we need this later in order to place objects back into the correct scene
// if we do not want them to be DontDestroyOnLoad anymore
// (see explanation in SetDontDestroyOnLoad)
SceneManager.SetActiveScene(newScene);
// Unload the last loaded scene
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
// update the stored index
lastLoadedScene = index;
}
}
This MySceneManager is a static class so it is not attached to any GameObject or Scene but simply "lives" in the Assets. You can now call it from anywhere using
MySceneManager.LoadScene(someIndex, theMonoBehaviourCallingIt);
The second parameter of type MonoBehaviour (so basically your scripts) is required because someone has to be responsible for running the IEnumerator Coroutine which can't be done by the static class itself.
3. DontDestroyOnLoad
Currently you are adding any GameObject you dragged at any time to DontDestroyOnLoad. But you never undo this so anything you touched meanwhile will be carried on from that moment ... forever.
I would rather use e.g. something like
public static class GameObjectExtensions
{
public static void SetDontDestroyOnLoad(this GameObject gameObject, bool value)
{
if (value)
{
// Note in general if DontDestroyOnLoad is called on a child object
// the call basically bubbles up until the root object in the Scene
// and makes this entire root tree DontDestroyOnLoad
// so you might consider if you call this on a child object to first do
//gameObject.transform.SetParent(null);
UnityEngine.Object.DontDestroyOnLoad(gameObject);
}
else
{
// add a new temporal GameObject to the active scene
// therefore we needed to make sure before to set the
// SceneManager.activeScene correctly
var newGO = new GameObject();
// This moves the gameObject out of the DontdestroyOnLoad Scene
// back into the currently active scene
gameObject.transform.SetParent(newGO.transform, true);
// remove its parent and set it back to the root in the
// scene hierachy
gameObject.transform.SetParent(null, true);
// remove the temporal newGO GameObject
UnityEngine.Object.Destroy(newGO);
}
}
}
This is an Extension Method which allows you to simply call
someGameObject.SetDontDestroyOnLoad(boolvalue);
on any GameObject reference.
Then I changed your script to
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
// On mouse down enable DontDestroyOnLoad
private void OnMouseDown()
{
gameObject.SetDontDestroyOnLoad(true);
}
// Do your dragging part here
private void OnMouseDrag()
{
// NOTE: Your script didn't work for me
// in ScreenToWorldPoint you have to pass in a Vector3
// where the Z value equals the distance to the
// camera/display plane
mousePos = Camera.main.ScreenToWorldPoint(new Vector3(
Input.mousePosition.x,
Input.mousePosition.y,
transform.position.z)));
transform.position = mousePos;
}
// On mouse up disable DontDestroyOnLoad
private void OnMouseUp()
{
gameObject.SetDontDestroyOnLoad(false);
}
}
And in your StarCollision script you only have to exchange
SceneManager.LoadScene(1);
with
MySceneManager.LoadScene(2, this);
Demo
For a little demonstration I "faked" it using two simple scripts
This one in the Main scene
public class LoadFirstscene : MonoBehaviour
{
// Start is called before the first frame update
private void Start()
{
MySceneManager.LoadScene(1, this);
}
}
And this one in the other scenes
public class LoadNextScene : MonoBehaviour
{
[SerializeField] private int nexSceneIndex;
private void Update()
{
if (!Input.GetKeyDown(KeyCode.Space)) return;
MySceneManager.LoadScene(nexSceneIndex, this);
}
}
And have 3 Scenes:
Main: As mentioned contains
the MainCamera
a DirectionalLight
the LoadFirstScene
test: contains
a MoveBall "Sphere"
the LoadNextScene
test2: contains
a MoveBall "Cube"
the LoadNextScene
With the indexes matching the build settings (make sure Main is always at 0 ;) )
I can now switch between test and test2 using the Space key.
If I drag one of the objects meanwhile I can carry it on into the next scene (but only 1 at a time). I can even take it on again back to the first scene in order to have e.g. two sphere objects I can play with ;)
I am using Unity for my project. What I am trying to do is when a button is pressed it moves a game object to a new position. Then when the same button is press again, the object returns to its original position.
My problem is with the while loop I am using; it just crashes Unity when I press this button. Am I using the while loop wrong or is there a better way of going about this?
Here is what I have so far:
public Gameobject TroubleMove;
public Gameobject TroubleAnchor;
public Gameobject TroubleMain;
public Gameobject CauseMain;
public Gameobject Cause;
public int OnOff = 1;
public void Change ()
{
switch (OnOff)
{
Case 1:
CauseMain.setActive (true);
Cause.setActive (true);
While (CauseMain == true)
{
CauseMove.transform.position = CauseAnchor.tranform.position;
}
OnOff += 1;
break;
Case 2:
CauseMain.setActive (false);
Cause.setActive (false);
OnOff _=1;
break;
}
}
I assume CauseMain is never being set to false and your while loop never exits.
Why do you need the while loop? Don't you want to change the cursor once? I would change while to if()
I will assume that the Change() is called in the OnClick() method.
Firsly, you should not use a while(true) loop when waiting for a user input or response. The while(true) loop will become a never ending loop and crash Unity / Device.
I am confuse with what your GameObjects really are but instead of while(true) loop, you should be using the Update() or FixedUpdate() method to transform the position (can be in relation to Time.DeltaTime). If I am not mistaken, this would be the skeleton of what could be done:
void Update () {
if(CauseMain) {
//TODO: Apply Transformation Changes
CauseMove.transform.position = CauseAnchor.tranform.position;
CauseMain = false;
}
}
This code snippet will only apply the transformation change whenever the player presses a button and once the position is set to "CauseMove", the check is disabled preventing an endless loop which will crash Unity.