Unity: Script attached to child object of DontDestroyOnLoad object being called twice - c#

I am trying to populate various objects in Unity based on some triggers and some data. The data that I am using looks something like this Key === O2 Values are ==== { collected, collected, collected, absent}Key === O3 Values are ==== { collected, collected, present } stored in a Dictionary object Dictionary<string, List<string>> textMap. Following script is attached to these objects:
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class SensePlayerProximity : MonoBehaviour {
bool disableEntry = false;
bool disableExit = false;
public bool isCollected = false;
//List<Collider2D> triggerList = new List<Collider2D>();
// Use this for initialization
static Dictionary<string, List<string>> textMap = new Dictionary<string, List<string>>
{
{ "O2", new List<string>() { "present", "absent", "absent", "absent" }},
{ "O3",new List<string>() { "absent", "absent", "absent" }}
};
private void OnTriggerEnter2D(Collider2D collision)
{
if (disableEntry || isCollected)
return;
StartCoroutine(disableTriggersForThisCollectible(10));
List<String> values;
foreach (KeyValuePair<string, List<string>> kvp in textMap)
{
values = kvp.Value;
int foundAtIndex = values.IndexOf("absent");
if (foundAtIndex > -1)
{
gameObject.GetComponent<TextMeshProUGUI>().text = kvp.Key;
values[foundAtIndex] = "present";
textMap.Remove(kvp.Key);
textMap.Add(kvp.Key, values);
logTextMap();
return;
}
}
// if nothing is found, then default the text to empty, since nothing left to be collected now
gameObject.GetComponent<TextMeshProUGUI>().text = "";
}
//called when something exits the trigger
private void OnTriggerExit2D(Collider2D collision)
{
if (disableExit || isCollected)
return;
//Debug.Log("Player is leaving me.. :(");
string key = gameObject.GetComponent<TextMeshProUGUI>().text;
StartCoroutine(disableTriggersForThisCollectible(0.1f));
List<string> values = textMap[key];
int index = -1;
if (values != null)
index = values.IndexOf("present");
if(index >= 0)
{
values[index] = "absent";
textMap.Remove(key);
textMap.Add(key, values);
gameObject.GetComponent<TextMeshProUGUI>().text = "";
logTextMap();
}
}
IEnumerator disableEntryTrigger(float t)
{
disableEntry = true;
// disable the trigger collider for t seconds
yield return new WaitForSeconds(t);
disableEntry = false;
}
IEnumerator disableExitTrigger(float t)
{
disableExit = true;
// disable the trigger collider for t seconds
yield return new WaitForSeconds(t);
disableExit = false;
}
void logTextMap()
{
string debugString = "";
foreach (KeyValuePair<string, List<string>> kvp in textMap)
{
debugString += "Key === " + kvp.Key + " Values are ==== { " + String.Join(", ", kvp.Value.ToArray()) + " }";
}
Debug.Log(debugString);
}
}
The script detects trigger collisions with BoxCollider2D attached to my player, and it has "Sensor" tag name attached to it. I disable the triggers for 10s whenever OnTriggerEnter2D event occurs and for 0.1s whenever OnTriggerExit2D event occurs.
I have some fixed textobjects scattered in my level and trying to populate the text in them on the basis of this script above. This script is attached to every such text object.
With the help of these events, I detect if the player is in the vicinity of a text object. If the player is found, then a random key will be populated from the textMap provided that key has at least one value which says "absent". Each key has a list of values, which could be "absent", "present", or "collected". "absent" means that the key is absent from the camera view and thus can be assigned to new collectible text objects. "present" means that the key is present in the current camera view and is not available for the other text objects. "collected" means that the key has already been collected and is not available either. In the example value of textMap which I shown above, for example, there can be 4 copies of the key "O2" in the map, and 3 copies of the key "O3". Out of these, 3 "O2" and 2 "O3" have already been collected. Only 1 copy of "O2" can be assigned to newly triggered text objects and no copy of "O3" is available for them. The script works mostly as expected, except a few time which I am not able to debug. The debug log show that one copy of "O3" is already present in the view, but I went to my scene and could not find "O3" anywhere. I am afraid that this might be happening because all the triggered text objects (to which the above script is attached) are trying to modify the textMap at the same time. I have wasted a lot of time trying to figure this out, but I am just banging my head in the wall. I'd really appreciate if someone could point me in the right direction. My scene with these objects is shown below:
Edit: I found the problem to be DontDestroyOnLoad. The said gameobjest to which SensePlayerProximity script is attached are all children of a DontDestroyOnLoad gameobject called ScenePersist. The problem is happening only when I reload the scene, upon player death. When the scene reloads, new ScenePersist is loaded in the scene, and just before it called the triggerentry method on children before being destroyed. Because of this, OnTriggerEnter2D is called twice, instead of once. How do I fix this problem?
One way to fix this would be to keep all these objects far away from the player spawn point, so that the triggers doesn't occur, but that is not a good way to fix it. Another is to run a coroutine enableTriggers in start method, i.e. disable the triggers by default, but that is not a good solution either.
void Start () {
disableEntry=true;
disableExit = true;
StartCoroutine(enableTriggers());
}
IEnumerator enableTriggers()
{
yield return new WaitForSeconds(0);
disableEntry = false;
disableExit = false;
}
This is how my scene hierarchy looks like:
Here the ScenePersist is set to DontDestroyOnLoad, and it has a lots of sub-child objects (highlighted as collectible) to which the SensePlayerProximity is attached.

The problem is because you disable the trigger, the Enter and Exit methods are not called in pairs. But no code to protect it.
Enter -> present +1, absent -1
Exit -> present -1, absent +1
If the method Enter is called twice, but the method Exit is skipped, now 2 elements in the dictionary become present, but there is only one object (with text O2/O3) in the scene.
Enter -> present +1, absent -1
Left without trigger Exit
Wait 10 seconds
Enter -> present +1, absent -1
Exit -> present -1, absent +1

I solved it by using the OnDestroy method. Before getting destroyed, the object was triggering the OnTriggerEnter2D method. So on getting destroyed I did something similar to what OnTriggerExit2D was doing, as shown below.
void OnDestroy()
{
string key = gameObject.GetComponent<TextMeshProUGUI>().text;
Debug.Log("destroed with key "+ key);
if (!textMap.ContainsKey(key))
return;
List<string> values = textMap[key];
int index = -1;
if (values != null)
index = values.IndexOf("present");
if (index >= 0)
{
values[index] = "absent";
textMap.Remove(key);
textMap.Add(key, values);
gameObject.GetComponent<TextMeshProUGUI>().text = "";
}
}

Related

Unity C# Saved Game via Binary Formatting not retaining Data

Update: This issue may be caused by binaryformatter's issues with editing data in existing fields. A comment that I can no longer find described alternate methods which I am currently implementing. Will update after these methods are attempted if it is a solution to the problem.
I should start by saying I am a student so please go easy on me, I got reported when I first started coming here and hope to keep learning. I an new to unity, my C# is decent, but full of gaps since my schooling was rather terrible. I have watched hundreds of hours of unity tutorials and am studying the new concepts I learn for 4 hours every night after I get out of work, so if you see something just let me know.
This is actually a problem I have had for a while, but thought I fixed months ago. I was attempting to save games for the first time, and read into binary formatting and such to save. I had problems getting it up, but I I managed to get it to save and pull properly from a file. I verify that the data going into the file is correct, and the data coming out is correct, and even made the data private with a control function so nothing will access and change it without jumping through my debug. And yet after I leave the scope where I define the data it changes, without anything accessing my update function.
To break it down I have a class called PlayerType that stores all player information including my scenemanager, and it serializes this and saves to a file as a list. I create a for loop using the current length of the loaded list using an instance of the saveload class (this is what holds the list of save games and the access to the file) and it loops through instantiating my buttons in the order. Slot 1 will click to save game 1 and so to speak. The issue I am having is clicking slot 1 clicks slot 16, so does slot 2. In face, it seems practically random which buttons go to which slot. I should say here I am not sure whether it is actually going to the wrong slot, or simply renaming the player names wrong, but either way it does not appear this should be the case.
Here is my load function
public void Load() //Loads file from .gd file after checking if exists, then deserializes it back into a list
{
accessDataPath(false);
Debug.Log("Size of Save File" + listSize);
for (int i = 0; i < listSize; i++)
{
Debug.Log("First Loop " + SavedGames[i].returnName());
}
foreach (PlayerType player in GameManager.instance.saveStorage.returnList())
{
Debug.Log("Second Loop" + player.returnName());
}
}
and here is my accessDataPath function
public void accessDataPath(bool isSave)
{
//This stucture checks if file exists or not, then checks if saving or loading for 4 outcomes
if (isSave && SavedGames == null)//if saving and save file to update has nothing in it
{
Debug.Log("Error attempted to save null list in SaveLoad.accessDataPath!");
}
if (File.Exists(Application.persistentDataPath + "/SavedGames.gd"))//if the file exists
{
if (isSave)//if you are saving
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/SavedGames.gd", FileMode.Open);
bf.Serialize(file, SavedGames);
file.Close();
}
else //if you are not saving
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Open(Application.persistentDataPath + "/SavedGames.gd", FileMode.Open);
SavedGames = (List<PlayerType>)bf.Deserialize(file);
file.Close();
listSize = SavedGames.Count;
}
}
else//if the file does not exist
{
if (isSave)//if you are saving
{
BinaryFormatter bf = new BinaryFormatter();
FileStream file = File.Create(Application.persistentDataPath + "/SavedGames.gd");
bf.Serialize(file, SavedGames);
file.Close();
}
else//if you are not saving
Debug.Log("Error Loading File. Does not Exist!");//Display Later that file does not exist to user
}
updateNames();
}//End accessDataPath
here is the function that updates the names
public void updateNames()
{
for (int i = 0; i < listSize; i++)
{
SavedGames[i].updateName("Player " + i);
Debug.Log("Bump " + i);
}
}
As you can see I verify if it exists, then check if I am saving or loading. The save function calls with a true and SavedGames is the list that is pulled from the file. Now After I run the name change I can check to see if it worked, and it does. Running the check here they all come back as the proper name, however by the time it gets back to my load function and runs my first loop they are wrong, it never leaves this section of code but as soon as it exits the scope they change to be almost random.
Now I had this problem for a while, but thought it had to do with my file maybe having corrupt data, so I deleted the save game and created new ones to test. The numbers changed, but were still not right. This tells me that somehow the file is effecting the names even though I rename them almost immediately after they come out. I am not used to loading or saving to files so I am not sure where to start here.
I will post more of my code below in case some of it stands out as being blatantly wrong, I also appreciate advice on structure as I took college classes on game programming but they were terrible and filled with gaps even if you don't spot the answer to my problem.
First I have my menu system setup to be toggled on or off, called by onclick events.
public class SaveMenu : MonoBehaviour {
public GameObject menu;
bool isActive;
bool isSave;
PopulateSave playerSaves;
public static SaveMenu instance;
void Awake()
{
isSave = false;
instance = this;
menu = GameObject.Find ("SaveList");
menu.SetActive (false);
isActive = false;
playerSaves = menu.GetComponentInChildren<PopulateSave> ();
}
public bool getActive()
{
return isActive;
}
public void toggleMenu(bool saveLoad)
{
if (isActive)
{
menu.SetActive (false);
isActive = false;
} else
{
menu.SetActive (true);
isActive = true;
}
bool isSave = saveLoad;
menu.transform.Find("Scroll View").transform.Find("Viewport").transform.Find("Content").transform.Find("NewSave").gameObject.SetActive(isSave);
Debug.Log(isSave);
}
public void updateSave()
{
playerSaves.startList();//Seems redundant but is used to make this access public with limited use
}
public bool getSave()
{
return isSave;
}
}
I call populatesaves as a child after everything is created because I had issues with it not existing when I tried to bring it in via the inspector. PopulateSaves is where most of my functional code is.
public class PopulateSave : MonoBehaviour{
public Button NewSave;
public Button OldSaves; // This is our prefab object that will be exposed in the inspector
void Awake()
{
GameManager.instance.saveStorage.Load();
Populate();
}
void Update()
{
}
void Populate()
{
Button newObj = Instantiate(NewSave, transform); // Create GameObject instance
newObj.name = "NewSave";
startList();
}
public void startList()
{
clearList();
for (int i = 1; i < GameManager.instance.saveStorage.returnCount() + 1; i++)
{
createButton(i);
}
Debug.Log("Done creating");
}
public void updateList(PlayerType newSave)
{
Button newObj;
newObj = (Button)Instantiate(OldSaves, transform);
//GameManager.instance.saveStorage.Save(i);
}
public void clearList()
{
GameObject[] gameObjects;
gameObjects = GameObject.FindGameObjectsWithTag("SaveSlot");
for (var i = 0; i < gameObjects.Length; i++)
Destroy(gameObjects[i]);
Debug.Log("Done Destroying");
}
public void ButtonClicked(int slot)
{
if (SaveMenu.instance.getSave())
{
Debug.Log("Save");
GameManager.instance.saveStorage.Save(slot);
}
else
{
Debug.Log("load");
GameManager.instance.currentPlayer.newPlayer(slot);
}
}
public void createButton(int i)
{
// Create new instances of our prefab until we've created as many as is in list
Button newObj = (Button)Instantiate(OldSaves, transform);
//increment slot names
newObj.name = "Slot " + i;
newObj.GetComponentInChildren<Text>().text = "Slot " + i;
newObj.onClick.AddListener(() => ButtonClicked(i));
}
}
Now I also have never written a listener with a script like I have here, could that section be assigning the wrong number to them? I checked to make sure all my indexes had the right numbers, if not being 1 off. One of my loops might use a setup that is 1 off but I am more worried about getting this off the ground and fixing the specifics at this point.
Here is my player storage
[System.Serializable]
public class PlayerType {
string playerName;
SceneManagement currentScene;
public PlayerType()
{
playerName = "Starting Name";
}
public void updateName(string name)
{
//Debug.Log("New Name: " + name);
playerName = name;
}
public void updateScene(SceneManagement newScene)
{
currentScene = newScene;
}
public string returnName()
{
return playerName;
}
public SceneManagement returnScene()
{
return currentScene;
}
public void newPlayer(int slotSave)
{
//Tmp player has wrong name from this point, initiated wrong?
//Debug.Log("New Name to update" + tmpPlayer.returnName());
this.updateName(GameManager.instance.saveStorage.returnSaves(slotSave).returnName());
this.updateScene(GameManager.instance.saveStorage.returnSaves(slotSave).returnScene());
}
}
Update:
Bump correctly goes through displaying 0-16
then Size of Save File displays 17 total saves
First loop then starts by outputting 'First Loop Player 15' a total of 16 times
Then it displays 16 so the last one is correct, though one off I guess.
Second loop does the same as first, unsurprisingly.
I left the call to updateNames in but commented out the lines and ran it including taking out bump.
It starts with 17 saves again and the 16 time iteration of player 15, however this time around the last one displays 'Temp Name' which I only define once at the beginning of my sceneManagement script for the current player, it should have never been saved, and even if it had been should have been overwritten by my name loop, at least that was my intent.
SceneManagement
[System.Serializable]
public class SceneManagement : MonoBehaviour {
DialogueManager dialogueManager;
PlayerType currentPlayer;
bool isSave;
bool isActive;
string sceneName;
int lineCount;
void Start()
{
isSave = false;
//loads player object for the current save
currentPlayer = new PlayerType();
currentPlayer.updateName ("Temp Name");
//This loads the prologue from the DB and sets the dialoguemanager up, defaults to prologue for now but can be updated to another scene later
isActive = true;
sceneName = "Prologue";
string conn = "URI=file:" + Application.dataPath + "/Text/Game.sqlite3";
List<string> sceneLines = new List<string>();
List<string> sceneCharacters = new List<string>();
int tmpInt;
string tmpString = "NOTHING";
int count = 0;
lineCount = 1;
IDbConnection dbConn;
dbConn = (IDbConnection)new SqliteConnection (conn);
dbConn.Open (); //Open database connection
IDbCommand dbCmd = dbConn.CreateCommand();
string sqlQuery = "SELECT Line, Flags, Character, Image, Text, Color FROM Prologue";
dbCmd.CommandText = sqlQuery;
IDataReader reader = dbCmd.ExecuteReader ();
while (reader.Read ()) {
tmpInt = reader.GetInt32 (0);
tmpString = reader.GetString (1);
sceneCharacters.Add(reader.GetString (2));
tmpInt = reader.GetInt32 (3);
sceneLines.Add(reader.GetString (4));
tmpString = reader.GetString (5);
//Debug.Log (tmpString);
//Debug.Log (count);
count++;
}
reader.Close ();
reader = null;
dbCmd.Dispose ();
dbCmd = null;
dbConn.Close ();
dbConn = null;
dialogueManager = new DialogueManager(sceneCharacters, sceneLines, lineCount);
}
//These are the returnvalues that might be used, may or may not be kept depending on future use.
public string getSceneName()
{
return sceneName;
}
public int getLineCount()
{
return lineCount;
}
public bool getSave()
{
return isSave;
}
//These return the lines for displaymanager, preventing it from directly interacting with dialoguemanager
public string getNextLine()
{
return dialogueManager.getNextLine();
}
public string getNextCharacter()
{
return dialogueManager.getNextCharacter();
}
//This function sets up visibility and is only accessed to properly display the screen after the scene has been started.
public void startScene ()
{
GameManager.instance.screenDisplay.changeVisible(true);
}
void Update()
{
if (Input.GetKeyDown ("space") & isActive) {
dialogueManager.incrementCurrentLine ();
GameManager.instance.screenDisplay.changeText(dialogueManager.getNextLine ());
GameManager.instance.screenDisplay.changeCharacter(dialogueManager.getNextCharacter ());
}
if (Input.GetKeyDown ("escape")) {
if (!PauseMenu.instance.getActive () && !SaveMenu.instance.getActive ())
{
PauseMenu.instance.toggleMenu ();
}
else if (SaveMenu.instance.getActive())
{
SaveMenu.instance.toggleMenu (true);
PauseMenu.instance.toggleMenu ();
}
else if (PauseMenu.instance.getActive())
{
PauseMenu.instance.toggleMenu ();
}
}
}
public void initialScreen()
{
SceneManager.sceneLoaded += OnLevelFinishedLoading;
}
void OnDisable()
{
SceneManager.sceneLoaded -= OnLevelFinishedLoading;
}
void OnLevelFinishedLoading (Scene scene, LoadSceneMode mode)
{
GameManager.instance.screenDisplay.initialScreen(dialogueManager.getNextLine(), dialogueManager.getNextCharacter(), null, null,
null, null, null, null);
}
public PlayerType returnPlayer()
{
return currentPlayer;
}
}
while im at it here is my GameManager too, though I havent messed with it a ton. Mostly using it as a DontDestroyOnLoad thing to hold everything else at this point.
public class GameManager : MonoBehaviour{
public static GameManager instance;
public DisplayScreen screenDisplay;
public SceneManagement managerScene;
public SaveLoad saveStorage;
public PlayerType currentPlayer;
//leave out until load data is setup
//PlayerType currentPlayer;
void Awake()
{
instance = this;
DontDestroyOnLoad (transform.gameObject);
managerScene = gameObject.AddComponent(typeof(SceneManagement)) as SceneManagement;
screenDisplay = gameObject.AddComponent (typeof(DisplayScreen)) as DisplayScreen;
saveStorage = gameObject.AddComponent (typeof(SaveLoad)) as SaveLoad;
//initialize player and sceneManager here, but only load through main menu options
//of new game or load game
}
void update()
{
}
}
I have continued troubleshooting and brought it down to a particularly confusing part for me. I took out all debugging logs and added 3 loops into my load function, they are these.
Debug.Log("LOAD CALLED");
accessDataPath(false);
for (int i = 0; i < listSize; i++)
{
Debug.Log("First Loop " + SavedGames[i].returnName());
SavedGames[i].updateName("Updated Player " + (i + 1));
}
for (int i = 0; i < listSize; i++)
{
Debug.Log("Second Loop " + SavedGames[i].returnName());
}
foreach (PlayerType player in GameManager.instance.saveStorage.returnList())
{
Debug.Log("Third Loop " + player.returnName());
}
So the first one displays player 15, then correctly displays the first loops 1-15 then temp name again, still havent 'figured out why that is popping up but I think it is related. Then the second loop iterates all wrong. Literally changed and back to back the loops are wrong, the only difference being it left the scope of the for loop. I ran a third loop using foreach to see if the type of call made a difference and it does not.
Even changing the name, and then immediately calling a loop to check shows that the values are changing. I think it might have something to do with how I am storing the objects, but I am not sure how the problem could be arising. Its not going null, and the names are changing to be the same every time so it isn't completely random. I am posting this here just after I found this hoping that soon I will solve it, but am also hoping if I do not someone else might spot something. I have the next 3 hours to work on it so I will be trying this entire time checking back every now and then. Thanks in advance for anyone that might glance at it for me.
Ok so I finally got the implementation done. I had to swap around a ton of my code, and ended up doing a wrapper object/list combination using JSON. Essentially the problem is that binary formatter messes up objects after you de-serialize them. Every time I updated my objects values they would randomly change on me for no reason without being accessed.
This was surprising as about half the posts I read on saving say its good, others like This one say that it is bad practice. I had done some research initially but had not come across the negative aspects of using it. I am still having problems, but Json is successfully retaining data and my objects are not messing up. I am pretty sure this was the problem as the only sections that I changed were my objects value structure to public for the serializing, and implemented the json structure into my SaveLoad script. This video was very helpful for the overall structure and getting started, and This thread helped me with troubleshooting when I ran into several problems.
I should also note that one thing I did not catch for a while. While Json can load lists, the initial object to be loaded must not be a list. I was attempting to save a list of my PlayerType directly into a folder, which it will not do. I ended up creating a quick object that contained my list and then saving the object. Since everywhere I read said that lists were fine it took a while to discover that this was causing part of my problem. It was not giving me any errors, just returning a blank string which most threads said was because it was not public or serializable.
Anyway here is to hoping my struggles and searches for answers might help as the things I found were quite scattered and hard to come across.

Input only stores two values?

When pressing two keys and then pressing a third key, all without releasing, Input.GetKeyDown does not know that the third key has been pressed.
Here is my code:
public class Keys
{
internal bool leftPressed = false;
internal bool rightPressed = false;
internal bool upPressed = false;
internal bool downPressed = false;
}
public Keys keys = new Keys();
void Start () {
}
void Update ()
{
updateButton(KeyCode.LeftArrow);
updateButton(KeyCode.RightArrow);
updateButton(KeyCode.UpArrow);
updateButton(KeyCode.DownArrow);
}
void updateButton(KeyCode key)
{
if (Input.GetKeyDown(key))
{
if (key == KeyCode.LeftArrow)
keys.leftPressed = true;
if (key == KeyCode.RightArrow)
keys.rightPressed = true;
if (key == KeyCode.UpArrow)
keys.upPressed = true;
if (key == KeyCode.DownArrow)
keys.downPressed = true;
}
else if (Input.GetKeyUp(key))
{
if (key == KeyCode.LeftArrow)
keys.leftPressed = false;
if (key == KeyCode.RightArrow)
keys.rightPressed = false;
if (key == KeyCode.UpArrow)
keys.upPressed = false;
if (key == KeyCode.DownArrow)
keys.downPressed = false;
}
}
All I wanted to do was have a player class move a gameobject when a key was pressed down. If both directions were pressed they would cancel each other out and set the player's velocity to 0.
Hitting left and right at the same time does so, but trying to press up or down doesn't change the up or downs value at that point. It's as though Input.GetKeyDown only contained 2 values and not the whole keyboard keys states?
Note: even pressing two random keys on the keyboard would stop left, down, up or right from changing values.
Does anyone have a suggestion or a fix to my code?
Thanks,
Unless I'm missing something it seems like you would be better off using the built in axes system.
Also your keyboard might not support more than two keys down at once.
Also GetKeyDown does not return if the key is down it returns if it was pushed down last frame: https://docs.unity3d.com/ScriptReference/Input.GetKeyDown.html
You want Input.GetKey to get the keys current state if you don't want to use the axes system.
void Start()
{
StartCoroutine(crMoveIt());
}
private IEnumerator crMoveIt()
{
while(true) // have a bool var for this
{
if(Input.GetKeyDown(KeyCode.A))
{
// Move object here
}
yield return null;
}
}
Where you want to move the object, see this
https://docs.unity3d.com/ScriptReference/Vector3.MoveTowards.html
Or if you're using a rigidbody please read
https://docs.unity3d.com/ScriptReference/Rigidbody.html
Instead of passing in a keycode to check each frame, just have a coroutine handle the input and then call something after that each frame. A coroutine will do that same as an Update, but you can have several of these running versus just one Update() method.
I would of wrote more code for the movement of an object, but you provided no references to any GameObject in your example. I answered a question not to long ago that shows how to move an object a different way that does not use Vector3.MoveTowards() here
Unity; Random Object Movement
Hope this helps. Edited, forgot something. =)

Unity3D Vive throw objects when let go of

I am attempting to make a physics sandbox-type game for the Vive, but the velocity of an object is completely reset and just begins falling when you let go of an object you were previously holding, making throwing impossible.
The system I am using currently will disable gravity on an object, and disable colliders of on an object when you pick it up. It will also child the object to your controller, making it like holding the object. When you let go of the button to release the object, it will enable gravity, enable colliders, and then set its parent object to null. This works to pick up and release an object, but it does not work at all for throwing objects. I have played around with timing and order of the components of the code, and nothing works.
Is there any way to find the velocity of an object and the directional velocity, without the object using gravity? Velocity doesn't work if gravity is disabled.
Here is my code:
using UnityEngine;
using System.Collections;
public class WandController : MonoBehaviour
{
//Basic Controller tracking stuff
private Valve.VR.EVRButtonId gripButton = Valve.VR.EVRButtonId.k_EButton_Grip;
public bool gripButtonDown = false;
public bool gripButtonUp = false;
public bool gripButtonPressed = false;
private Valve.VR.EVRButtonId triggerButton = Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger;
public bool triggerButtonDown = false;
public bool triggerButtonUp = false;
public bool triggerButtonPressed = false;
private SteamVR_Controller.Device controller { get { return SteamVR_Controller.Input((int)trackedObj.index); } }
private SteamVR_TrackedObject trackedObj;
//Game Variables
public GameObject wouldSelect; //What is in the select zone, has tiny script for the zone that sets the newest triggerenter to this variable
public GameObject isHolding; //When you hold something, it goes from wouldselect to isholding
public bool holding = false;
public GameObject holdingZone; //The holding zone, also where objects go if they are picked up
// Use this for initialization
void Start()
{
trackedObj = GetComponent<SteamVR_TrackedObject>();
}
// Update is called once per frame
void Update()
{
//Basic Controller configuration & button management stuff
if (controller == null)
{
Debug.Log("Controller not initialized");
return;
}
gripButtonDown = controller.GetPressDown(gripButton);
gripButtonUp = controller.GetPressUp(gripButton);
gripButtonPressed = controller.GetPress(gripButton);
triggerButtonDown = controller.GetPressDown(triggerButton);
triggerButtonUp = controller.GetPressUp(triggerButton);
triggerButtonPressed = controller.GetPress(triggerButton);
if (gripButtonDown)
{
Debug.Log("Grip Button was just pressed");
}
if (gripButtonUp)
{
Debug.Log("Grip Button was just unpressed");
}
if (triggerButtonDown)
{
Debug.Log("Trigger Button was just pressed");
}
if (triggerButtonUp)
{
Debug.Log("Trigger Button was just unpressed");
}
//Calling void that allows you to grab
CanGrab();
}
void CanGrab ()
{
if(wouldSelect != null && wouldSelect.tag == "Object" && triggerButtonDown == true && holding == false)
{
wouldSelect.GetComponent<Collider>().enabled = false;
wouldSelect.GetComponent<Rigidbody>().useGravity = false;
isHolding = wouldSelect;
wouldSelect.transform.SetParent(this.transform);
wouldSelect.transform.position = holdingZone.transform.position;
holding = true;
}
if(holding == true && triggerButtonUp == true)
{
wouldSelect.GetComponent<Collider>().enabled = true;
isHolding.GetComponent<Rigidbody>().useGravity = true;
isHolding.transform.SetParent(null);
holding = false;
wouldSelect = null;
isHolding = null;
}
}
}
I had a similar issue and solved it by:
tracking the position of the object at every frame, and storing it as lastPosition
when the object is let go, using (transform.position - lastPosition) to give me a rough estimate of the velocity of that object
From there, you can add an impulse force if you have a rigidbody, etc, I found a thread with some details on the various ways Unity does it -- http://answers.unity3d.com/questions/696068/difference-between-forcemodeforceaccelerationimpul.html
I did a number of throwing mechanics in VR using the same parameters you had (Setting the object as a child of your controller, disabling gravity, disabling colliders).
The way I did it was to record the position of the object on the previous and current frame, and take the difference between them as the velocity. However, there are three main factors to consider:
There might be some jitter in the tracking of the controller, and sometimes the object does not fly in the direction of throw.
There might be a lag time between the user pressing/letting go of the throwing button and the button press/release getting recorded, and so the object is thrown only towards the falling arc of the throw. (This happens a lot in my playtests)
There is a peak force during a throw during which an object gains the most velocity for its flight, and sometimes people let go of the object a split second after. This is rather inconsequential, but you should definitely consider if you want very realistic throwing (Take a look at The Lab demo and you will know what I mean)
My implementation:
Record the last x frames (for me the sweet spot is between 10-15 frames) for the object's position.
Take the difference in the first and last frame in the window and use that to calculate the velocity.
transform.velocity = position[n] - position[0];
If I want a slightly more accurate implementation, I'll calculate the force of the throw by taking the differences in velocities for adjacent frames. If there is a time window between the peak force and the release of the object, do not take the velocities in the later half of the window.
For example, if I decide to record the last 10 frames of a throw, and there are 4 frames between the peak force and the release, I will take frame current-12 to current-2, instead of frame current-10 to current for the velocity.

How to check current status of gameObject and update to a new animation and check again?

I am creating a 2D platform game with Unity and C#. I searching for how I can do the following.
I have 5 sets of animations and one gameObject(later in game there should be more than one object that can be changed, so maybe we can keep that in mind) with the tag "Campfire0" stored in anim0 inside the start function. What I want is when the user pressed E on his keyboard it will check which is the current animation of the current gameObject and change that animation to "Camfire25" or another number like 50, 75 or 100 and update the gameObject tag. After that is done, it should again check all if statements and the second time you pressed E it should look at the if statement with if(anim25) and so on.
Problem
Below code I use currently. But the problem is the following. I declare inside the start function the animator of each game object tag, but this will be fired one time if the game is started. After pressing the second time on E, the error "NULL" NullReferenceException will occur. So I think I should store the values inside an array? And how should I do that.
For checking which animation is the current one, I use if statements, but can it also be done with a foreach loop or is there a shorter way to do this?
Current Code:
public Animator anim0;
public Animator anim25;
public Animator anim50;
public Animator anim75;
public Animator anim100;
// Use this for initialization
void Start () {
count = 0;
setCountText ();
currentValue = startingValue;
//anim = GameObject.Find("Campfires").GetComponent<Animator>(); // Parent of all Campfires
anim0 = GameObject.FindGameObjectWithTag("Campfire0").GetComponent<Animator>();
anim25 = GameObject.FindGameObjectWithTag("Campfire25").GetComponent<Animator>();
anim50 = GameObject.FindGameObjectWithTag("Campfire50").GetComponent<Animator>();
anim75 = GameObject.FindGameObjectWithTag("Campfire75").GetComponent<Animator>();
anim100 = GameObject.FindGameObjectWithTag("Campfire100").GetComponent<Animator>();
}
void Update () {
if (inTrigger){
if (Input.GetKeyDown(KeyCode.E) && count > 0){
// Update counter on press
updateCount();
if (anim0){
anim0.SetTrigger ("Campfire25");
GameObject.FindGameObjectWithTag("Campfire0").transform.tag = "Campfire25";
}
if (anim25){
Debug.Log ("hello");
anim25.SetTrigger ("Campfire50");
GameObject.FindGameObjectWithTag("Campfire25").transform.tag = "Campfire50";
}
if (anim50){
anim50.SetTrigger ("Campfire75");
transform.tag = "Campfire75";
}
if (anim75){
anim75.SetTrigger ("Campfire100");
transform.tag = "Campfire100";
}
}
}
}
Above script works for the first time, it will change the campfire tag to "Campfire25" and show another animation clip, after pressing E for the second time it will give me an error of NullReferenceException. This is because the Start Function will be fired ones.

How to reset a list XNA and C#

I need to do a "retry" option when the player finishes the game.For doing this I thought to reset the lists of Monsters and other objects that moved at the first playing or which have been "killed".for example I have a list like that:
//the enemy1 class is already done
// in Game1 I declare it
List<enemy1> enem1 = new List<enemy1>();
//Initialize method
List<enemy1> enem1 = new List<enemy1>();
//LoadContent
foreach (enemy1 enemy in enem1)
{
enemy.Load(Content);
}
enem1.Add(new enemy1(Content.Load<Texture2D>("enemy"), new Vector2(5900, 12600)));
//Update
foreach (enemy1 enemy in enem1)
{
enemy.Update(gameTime);
}
//after being shooted the enemies disappear and i remove them
//if the monsters are shooted the bool "visible" goes from false to true
for (int i = enem1.Count - 1; i >= 0; --i)
{
if (enem1[i].visible == true)
enem1.RemoveAt(i);
}
//Draw
foreach (enemy1 enemy in enem1)
{
if(enemy.visble==false)
{
enemy.Draw(spriteBatch, gameTime);
}
}
//So my problem is to restart the game.
if(lost==true)
{
//here I have to put the code that restore the list
//I tried:
foreach (enemy1 enemy in enem1)
{
enemy.visible=false;
}
}
}
}
they should be drawn again but if I removed them they won't be drawn anymore.If I don't remove them ,instead, the enemies are in different places (because they follow me).
Any suggestions to restore or reinitialize the list??
I'm not sure if I understood your question right but...
When restarting the game you could just empty the list
enem1.Clear();
and then refill it like you do at the first start of the game:
enem1.Add(new enemy1(....));

Categories