I have a problem. In a scene I have a list and when I click on it shows me with some clones prefab the contents of the database. The point is that once generated, no matter how much I select another item from the list, the prefabs do not update me and show the contents of the first selection. I've tried to destroy them. But the most I've managed to do is destroy the original prefab and don't believe me anymore. How can I fix this?
public Transform contentParent;
public List<TablePlayerTransctData> playersTransct;
public GameObject playerTransactItemPrefab;
private Models.Club _club;
public TextMeshProUGUI memberUuid;
public void Start()
{
GetTablePlayerTransactRequest();
}
private void GetTablePlayerTransactRequest()
{
_club = TablesManager.Instance.club;
new LogEventRequest().SetEventKey("Transactions")
.SetEventAttribute("clubId", _club.id)
.SetEventAttribute("playerId", memberUuid.text)
.Send(GetTablePlayerTransactResponse);
}
private void HandleResults()
{
var cont = 0f;
Vector3 posicion = new Vector3();
foreach (TablePlayerTransctData data in playersTransct)
{
GameObject item = Instantiate(playerTransactItemPrefab, contentParent);
PlayerTransact playerTableTransact = item.GetComponent<PlayerTransact>();
if (cont == 0)
{
posicion = playerTableTransact.transform.position;
}
else
{
posicion.Set(posicion.x, (posicion.y) - 0.5f, posicion.z);
playerTableTransact.transform.position = posicion;
}
playerTableTransact.Setup(data);
cont++;
}
}
private void GetTablePlayerTransactResponse(LogEventResponse response)
{
if (response.HasErrors)
{
return;
}
GSData scriptData = response.ScriptData;
var list = scriptData.GetGSDataList("list");
playersTransct = new List<TablePlayerTransctData>();
foreach (GSData entry in list)
{
TablePlayerTransctData data = new TablePlayerTransctData()
{
chips = GSUtil.GetInt(entry, "chips"),
date = GSUtil.GetDouble(entry, "date"),
};
playersTransct.Add(data);
}
HandleResults();
}
public class TablePlayerTransctData
{
public int chips;
public double date;
}
I think these prefabs should be reset or removed just before they are re-created. But I don't know how to do this or maybe there's another, more correct way to do it.
Edit:
I changed Start to Update and magically works almost as it should. I don't understand what this miracle is like. Since with starting every time you enter you should cool off on your own. Now all that remains is to remove the leftover prefabs which is what I don't finish doing properly.
Before instantiating the new items you could destroy them all like e.g.
// General hint: Make this of the correct type
// - Makes sure that the referenced prefab actually HAS that component
// - Gets rid of some GetComponent calls later since Instantiate already
// returns the same type as the given prefab has
public PlayerTransact playerTransactItemPrefab;
foreach(Transform child in contentParent)
{
Destroy(child.gameObject);
}
foreach (var data in playersTransct)
{
PlayerTransact item = Instantiate(playerTransactItemPrefab, contentParent);
...
}
Alternatively you could of course only create the amount of objects you need and destroy others and instead of destroying and recreating all of them reuse those that already exists like e.g.
// cache the counts
var childCount = contentParent.childCount;
var dataCount = playersTransct.Count;
// we want to iterate the count that is greater
var count = Mathf.Max(childCount, dataCount);
for (var i = 0; i < count; i++)
{
if (i < dataCount)
{
// there is data for this index
var data = playersTransct[i];
// so we need a child for it
PlayerTransact playerTableTransact;
if (i < childCount)
{
// We are still in the range of existing childs -> Re-use a child that already exists
playerTableTransact = contentParent.GetChild(i).GetComponent<PlayerTransact>();
posicion = playerTableTransact.transform.position;
}
else // i < dataCount && i >= childCount
{
// There is still data but no childs left -> create a new one now
playerTableTransact = Instantiate(playerTransactItemPrefab, contentParent);
if (i == 0)
{
posicion = playerTableTransact.transform.position;
}
else
{
posicion -= Vector3.up * 0.5f;
playerTableTransact.transform.position = posicion;
}
}
playerTableTransact.Setup(data);
}
else // i < childCount && i >= dataCount
{
// There are more childs than data -> destroy the unneeded childs
Destroy(contentParent.GetChild(i).gameObject);
}
}
Related
I am new to PUN2. I am facing an issue that I want to describe some betting amount for joining my room which I define at the time of room creation. The issue is that I want that amount must be shown to others when they are in a lobby, from where they could decide whether they join/not by paying that amount. For this I am doing:
I have 1 MenuManager.cs script in which
public InputField amount;
[SerializeField] Transform _content; //container of list
[SerializeField] RoomListing _roomListing;
List<RoomListing> _listings = new List<RoomListing>();
public int SetBetAmount()
{
if (string.IsNullOrEmpty(amount.text))
{
return -1;
}
else
return (int.Parse(amount.text));
}
// The Script that runs when Create Room Button Clicks as follow:
public void OnCreateRoomBtnClicked()
{
string roomName = "Room " + Random.Range(1, 800);
int maxPlayersInt = SetMaxPlayers();
RoomOptions roomOptions = new RoomOptions();
roomOptions.MaxPlayers = (byte)maxPlayersInt;
string[] roomPropertiesInLobbby = { "betAmount" };
betAmount = SetBetAmount();
//customRoomProps["betAmount"] = (byte)SetBetAmount();
Debug.Log("Bet Amount Updated" + customRoomProps["betAmount"]);
SetLaps();
roomOptions.CustomRoomPropertiesForLobby = roomPropertiesInLobbby;
roomOptions.CustomRoomProperties = customRoomProps;
PhotonNetwork.CreateRoom(roomName, roomOptions);
}
OnRoomListUpdate Callback works fine for info data but not sending correct betAmount but the garbage value 0;
public override void OnRoomListUpdate(List<RoomInfo> roomList)
{
foreach (RoomInfo info in roomList)
{
if (info.RemovedFromList)
{
int index = _listings.FindIndex(x => x.RoomInfo.Name == info.Name);
if (index != -1)
{
Destroy(_listings[index].gameObject);
_listings.RemoveAt(index);
}
}
else
{
RoomListing listing = Instantiate(_roomListing, _content);
if (listing != null)
{
listing.GetComponent<RoomListing>().bettingAmt = -3; //there I tried betAmount but it sends 0
listing.SetRoomInfo(info);
_listings.Add(listing);
}
}
}
}
I have also tried this but not sure how to do this
public override void OnJoinedLobby()
{
//if (customRoomProps.ContainsKey("betAmount"))
//{
// //Debug.Log("Call From Master");
// object _amount;
// if (customRoomProps.TryGetValue("betAmount", out _amount))
// {
// Debug.Log("Bet Amount" + _amount.ToString());
// betAmountS = (int)_amount;
// Debug.Log("BetAmount " + betAmount);
// }
//}
//else
//{
// Debug.Log("Call From local");
//}
}
Plus, I have also tried PUNRPC but it works when others join the Room then they could see that data.
I don't have your entire code so I can only suspect what happens but my guess is the following:
The key is definitely added to the room properties otherwise it wouldn't return 0 but throw a KeyNotFoundException.
So from your given code I guess you somewhere have a predefined
private Hashtable customRoomProps = new Hashtable ();
private int betAmount;
and somewhere already set
customRoomProps ["betAmount"] = betAmount;
now when doing
betAmount = SetBetAmount();
you expect that the value is also updated within the customRoomProps.
BUT int is a value-type! The value you stored in the customRoomProps is a copy by value of whatever value was currently stored in betAmount the moment you added it.
The value stored in customRoomProps is in no way related to the betAmount anymore!
You rather need to set the value the moment you need it
betAmount = SetBetAmount();
customRoomProps["betAmount"] = betAmount;
basically the line you have commented out.
And then in OnRoomListUpdate you should be able to get it like
if(info.CustomProperties.TryGetValue("betAmount", out var betAmount))
{
listing.GetComponent<RoomListing>().bettingAmt = (int) betAmount;
}
I've tried to make a script that if all the lights in my scene tagged "Light" are active at the same time, the game proceeds. None of the answers I've found have helped so far. I always end up with it only scanning the active objects, just randomly jumping out of the loop, or stopping when it has found one object that's active. Here is a simple piece of code that should have worked according to other posts
void Update()
{
bool allActive = false;
GameObject[] allLights = GameObject.FindGameObjectsWithTag("Light");
if(currenObjective > 0 && currenObjective < 3)
{
for (int i = 0; i < allLights.Length; i++)
{
if (allLights[i].activeInHierarchy)
{
allActive = true;
break;
}
}
if (allActive)
{
currentObjective = 2;
}
}
}
This code just sets the allActive variable to true at the moment one light is turned on.
You would need to invert the check:
private void Update()
{
// Since Find is always a bit expensive I would do the cheapest check first
if(currenObjective > 0 && currenObjective < 3)
{
var allLights = GameObject.FindGameObjectsWithTag("Light");
var allActive = true;
for (int i = 0; i < allLights.Length; i++)
{
if (!allLights[i].activeInHierarchy)
{
allActive = false;
break;
}
}
if (allActive)
{
currentObjective = 2;
}
}
}
Or You can do this in one line using Linq All
using System.Linq;
...
private void Update()
{
// Since Find is always a bit expensive I would do the cheapest check first
if(currenObjective > 0 && currenObjective < 3)
{
var allLights = GameObject.FindGameObjectsWithTag("Light");
if (allLights.All(light => light.activeInHierarchy))
{
currentObjective = 2;
}
}
}
As a general note: You should avoid using FindGameObjectsWithTag every frame! Either store these references ONCE at start, or if you spawn more lights on runtime implement it event driven and add the newly spawned lights to a list and then use that list to check.
If i understant you want to know if all objects are active:
using Linq does the job
you have to add using system.Linq to your script.
GameObject[] allLights = GameObject.FindGameObjectsWithTag("Light");
bool result = allLights.All(p => p.activeInHierarchy);
you could simplify your code like this:
private void Update()
{
if(currenObjective > 0 && currenObjective < 3 && GameObject.FindGameObjectsWithTag("Light").All(p => p.activeInHierarchy))
{
currentObjective = 2;
}
}
As says derHugo, FindGameObjectsWithTag is very expensive in each frame...
I am preparing a puzzle game. I can drag and drop from down generated sticks to up places. Let's say that there are two sticks on the first node and we have 2 more sticks in bottom. We can drag it to right place and make them one parent again. I can make them one parent but second stick from buttom is destroying when i drop on the first node.
In this picture it is aqua color:
before drop
after drop
How to resolve this issue?
if(nodes[smallestId].transform.childCount > 0)
{
for (int i = 0; i < rayhit.transform.childCount; i++)
{
nodes[smallestId].transform.GetChild(0).GetComponent<Node>().sticks.Add(rayhit.transform.GetChild(i).GetComponent<Stick>());
rayhit.transform.GetChild(i).transform.SetParent(nodes[smallestId].transform.GetChild(0));
}
Destroy(rayhit.transform.gameObject);
}
else
{
rayhit.transform.SetParent(nodes[smallestId].transform);
Debug.Log("Stick : " + nodes[smallestId].transform);
}
The issue is that you iterate over rayhit.transform.childCount.
When you remove a child due to rayhit.transform.GetChild(i).transform.SetParent the rayhit.transform.childCount is already reduced by one.
Therefore in the next iteration i might already be greater then rayhit.transform.childCount so e.g. the second object is not moved to the new parent!
Instead You could use
// do this only once
var targetParent = nodes[smallestId].transform;
if(targetParent > 0)
{
targetParent = targetParent.GetChild(0);
// do also this only once
var node = targetParent.GetComponent<Node>();
var toMove = new List<Transform>();
foreach (Transform child in rayhit.transform)
{
node.sticks.Add(child.GetComponent<Stick>());
toMove.Add(child);
}
foreach(var child in toMove)
{
child.SetParent(targetParent);
}
Destroy(rayhit.transform.gameObject);
}
else
{
rayhit.transform.SetParent(targetParent);
Debug.Log("Stick : " + targetParent);
}
Which first stores them all in a list so you don't depend on whether they already where moved to a new parent or not.
As alternative I think you could even rather use GetComponentsInChildren here which would completely save you from the trouble and you wouldn't need to use two loops at all:
var targetParent = nodes[smallestId].transform;
if(targetParent > 0)
{
targetParent = targetParent.GetChild(0);
// do also this only once
var node = targetParent.GetComponent<Node>();
// These references now don't depend on their parent object
// so you can safely iterate on them without any issues
foreach (var stick in rayhit.transform.GetComponentsInChildren<Stick>(true))
{
node.sticks.Add(stick);
stick.transform.SetParent(targetParent);
}
Destroy(rayhit.transform.gameObject);
}
else
{
rayhit.transform.SetParent(targetParent);
Debug.Log("Stick : " + targetParent);
}
guys here is solution :)
rayhit.transform.position = nodes[smallestId].transform.position;
if (rayhit.transform.parent != nodes[smallestId].transform)
{
if (nodes[smallestId].transform.childCount > 0 && nodes[smallestId].transform != rayhit.transform.parent)
{
if (currNode != null)
{
for (int i = 0; i < currNode.sticks.Count; i++)
{
nodes[smallestId].transform.GetChild(0).GetComponent<Node>().sticks.Add(currNode.sticks[i]);
currNode.sticks[i].transform.SetParent(nodes[smallestId].transform.GetChild(0));
}
Destroy(rayhit.transform.gameObject);
}
}
else
{
if (currNode != null)
{
currNode.isMoved = true;
}
rayhit.transform.SetParent(nodes[smallestId].transform);
}
}
Thank you for your help.
In a script :
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class NaviDialogue : MonoBehaviour
{
public ObjectsManipulation op;
public bool scaling = true;
public Scaling scale;
public ConversationTrigger conversationTrigger;
private bool ended = false;
private bool startConversation = false;
private void Update()
{
if (scaling == true && DOFControl.hasFinished == true)
{
DOFControl.hasFinished = false;
scaling = false;
op.Scaling();
PlayerController.disablePlayerController = true;
ConversationTrigger.conversationsToPlay.Add(0);
ConversationTrigger.conversationsToPlay.Add(1);
ConversationTrigger.conversationsToPlay.Add(2);
StartCoroutine(conversationTrigger.PlayConversations());
}
}
And in the top of ConversationTrigger :
public static List<int> conversationsToPlay = new List<int>();
In the method PlayConversations :
public IEnumerator PlayConversations()
{
for (int i = 0; i < conversationsToPlay.Count; i++)
{
yield return StartCoroutine(PlayConversation(conversationsToPlay[i]));
}
}
And the Play Conversation method :
public IEnumerator PlayConversation(int index)
{
isRunning = true;
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");
conversationsToPlay.Remove(index);
}
}
In the last method Play Conversation I'm removing the current played item :
conversationsToPlay.Remove(index);
The problem is that in the PlayConversations method now I value is 1 so it will play next the last item. So if there are 3 items it will play the first and the last but the middle one will not be played.
You should never modify a collection you are currently iterating over (the problem you encountered is one of the reasons for that). In your case, there are a few options, a simple solution could be to copy the list of conversations and at the same time clear the original list:
public IEnumerator PlayConversations()
{
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]));
}
}
The array you create stays local to the coroutine, so you can clear the original list and work with the copy.
Alternatively, you could just change the loop to a while-loop and process the list from the start until it's empty:
public IEnumerator PlayConversations()
{
while (conversationsToPlay.Count > 0)
{
// Better remove the item right here, close to the loop condition.
// Makes things easier to understand.
var conversationIndex = conversationsToPlay[0];
conversationsToPlay.RemoveAt(0);
yield return StartCoroutine(PlayConversation(conversationIndex));
}
}
When going with the second example, you might just as well use a Queue<T> instead of a List<T> for the conversations, as a queue is designed specifically with first-in, first-out access in mind.
If you dont have a business requirment to maintain order, then Always iterate the collection in reverse order when you are planning to remove items. This you can remove items without breaking the sequence of the array. So here
public IEnumerator PlayConversations()
{
for (int i = conversationsToPlay.Count-1; i >=0; i++)
{
yield return StartCoroutine(PlayConversation(conversationsToPlay[i]));
}
}
This method works in general for all the situations where we remove something from the collection. However on a side note, Removing the conversation in Playconversation method is just a bad practice. you will end up with hard to maintain code. Remove it in some method which is specifically for this purpose. otherwise you are violating SRP
as I know that to get the top most parent is transform.root .How about if I want to get the bottom most child and in the condition that I don't know the child name?I have try
for (int i=0; i<theParent.childCount; i++)
{
int bottommost=theParent.childCount-1;
Debug.Log(theParent.GetChild(bottommost).name);
}
But is not the result I expect,I just get the first child but I want the bottom most one.I mean the bottom most child in a hierarchy by the way.Is that any tips to get the most bottom child?Thanks.
lastChild = transform.GetChild(transform.childCount - 1);
First you would need to define what is the bottom child, since the depth of the children can depend also on the siblings.
for example:
Root
child_1
child_2
child_3
But then each of the children can have their own children
Child_1
Child_1A
Child_1B
Child_2
Child_2A
Child_2B
Child_2C
Child_2D
Child_2E
So in this case al of the childrens' children are at the same level.
Maybe you are refering to the bottom most as seen in the editor?
In that case maybe add something to the nae in order to search for it...
If you want the child object itself rather than only the transform:
int lastChildIndex = parentObject.transform.childCount - 1;
lastChildObject = parentObject.transform.GetChild(lastChildIndex).gameObject;
I recently had this issue, where I was making a Klondike solitaire game with stacks of cards. I knew that they would all only have 1 child and I wanted to grab the bottom most child of 1 column and move that child to another column. Unfortunately, all of the other answers here simply speculate on if this should be done rather than providing a method to solve the problem. So, I created a method to grab children. Please note that this will work best when used with a stack of single children.
/**
* returns parent if there are no children
*/
private GameObject GetLastChild(GameObject parent)
{
//the parent object
GameObject lastChild = parent;
/*
Initialize a checkChild element
while check child is not null, continue checking
assign the checkChild to its child
*/
for (GameObject checkChild = parent; checkChild != null; checkChild = checkChild.transform.GetChild(0).gameObject)
{
lastChild = checkChild;
if (checkChild.transform.childCount == 0)
{
break;
}
}
return lastChild;
}
Any child could have some children and it can continues interminably. #Caribenz mentioned this. So I propose the code below.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChildGetter : MonoBehaviour
{
private List<DepthLevelHandler> _allChildren = new List<DepthLevelHandler>();
private List<DepthLevelHandler> _allChildrenAlternative = new List<DepthLevelHandler>();
private DepthLevelHandler _deepest;
private GameObject _mostBottomDeepest;
private DepthLevelHandler _deepestMostBottom;
private IEnumerator GetDeepestChild(Action callBack = null)
{
for (int i = 0; i < transform.childCount; i++)
{
DepthLevelHandler dLH = new DepthLevelHandler
{
depthLevel = 1,
gameObject = transform.GetChild(i).gameObject
};
_allChildren.Add(dLH);
}
_allChildrenAlternative = _allChildren;
for (int i = 0; i < transform.childCount; i++)
{
StartCoroutine(AddAllChildrenToList(_allChildren[i]));
yield return new WaitForSeconds(1f);
}
_deepest = _allChildren[0];
foreach (var child in _allChildren)
{
if (child.depthLevel > _deepest.depthLevel)
{
_deepest = child;
}
}
Debug.Log("Name of deepest child : "+_deepest.gameObject.name);
callBack?.Invoke();
}
private IEnumerator AddAllChildrenToList(DepthLevelHandler parent)
{
if (parent.gameObject.transform.childCount == 0) yield break;
for (int i = 0; i < parent.gameObject.transform.childCount; i++)
{
GameObject newChild = parent.gameObject.transform.GetChild(i).gameObject;
int newDepthLevel = parent.depthLevel + 1;
DepthLevelHandler newDepthHandler = new DepthLevelHandler
{
depthLevel = newDepthLevel,
gameObject = newChild
};
if (!_allChildrenAlternative.Contains(newDepthHandler))
{
_allChildrenAlternative.Add(newDepthHandler);
if (newDepthHandler.gameObject.transform.childCount != 0)
{
yield return new WaitForSeconds(0.01f);
yield return AddAllChildrenToList(newDepthHandler);
}
}
}
}
private void GetDeepestMostBottomChild()
{
List<DepthLevelHandler> allChildrenWithSameDepthLevel = new List<DepthLevelHandler>();
allChildrenWithSameDepthLevel.Add(_deepest);
_deepestMostBottom = _deepest;
foreach (var child in _allChildrenAlternative)
{
if (child.depthLevel == _deepest.depthLevel)
{
allChildrenWithSameDepthLevel.Add(child);
}
}
foreach (var child in allChildrenWithSameDepthLevel)
{
if (child.gameObject.transform.GetSiblingIndex() > _deepestMostBottom.gameObject.transform.GetSiblingIndex())
{
_deepestMostBottom = child;
}
}
Debug.Log("name of deepest most bottom child : "+_deepestMostBottom.gameObject.name);
}
private IEnumerator GetMostBottomDeepestChild()
{
Transform mostBottom = transform;
while (mostBottom.childCount != 0)
{
yield return new WaitForSeconds(0.02f);
mostBottom = mostBottom.GetChild(mostBottom.childCount - 1);
}
_mostBottomDeepest = mostBottom.gameObject;
Debug.Log("name of most bottom deepest child : "+_mostBottomDeepest.gameObject.name);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.A)) StartCoroutine(GetDeepestChild(GetDeepestMostBottomChild));
if (Input.GetKeyDown(KeyCode.B)) StartCoroutine(GetDeepestChild());
if (Input.GetKeyDown(KeyCode.C)) StartCoroutine(GetMostBottomDeepestChild());
}
}
public class DepthLevelHandler
{
public int depthLevel = 0;
public GameObject gameObject = null;
}
Create a list of all the children under the parent's transform.
List<GameObject> list = new List<GameObject>();
foreach(Transform t in transform)
{
list.Add(t.gameObject);
if(t.childCount > 0)
foreach(Transform c in t)
list.Add(c);
}
The last element is the last child:
GameObject lastChild = list[list.Count-1];