Unity Listing below and moving if disabled - c#

so here is the idea i have a couple of game objects in a UI listed below each other so the idea is as follows
GameObject1
GameObject2
GameObject3
GameObject4
GameObject5
so i want to disable GameObject4 but when doing so GameObject5 will stay in the position its at i want it to automatically move up in to GameObject4's position
like so
GameObject1
GameObject2
GameObject3
GameObject5
and not
GameObject1
GameObject2
GameObject3
GameObject5
anyone have an Idea on how to do this?

Try creating an array of those objects (I think you mean UI elements) in an array, store their initial positions onStart in another array then loop to pop them from a stack.
Try fiddling with this to suit your needs:
public GameObject[] buttons;
float[] buttonPos;
private void Start()
{
buttonPos = new float[buttons.Length];
for (int i = 0; i < buttons.Length; i++)
{
buttonPos[i] = buttons[i].transform.position.y;
print(buttonPos[i]);
}
}
private void Update()
{
if (Input.GetKeyDown("space"))
{
DestroyButton(1);
}
}
void DestroyButton(int i)
{
Destroy(buttons[i]);
Stack(i);
}
void Stack(int i)
{
for ( int j= i; j < buttons.Length; j++)
{
if(j != buttons.Length-1)
buttons[j + 1].transform.position = new Vector3(buttons[j + 1].transform.position.x, buttonPos[j], buttons[j + 1].transform.position.z);
}
}

Your question is pretty unclear and incomplete; even the (missing) line-breaks are confusing...
Since it's a UI and your question contains spaces and not-spaces, I guess your gameObjects are in a VerticalLayoutGroup? Far fetched, if they are, shame on you for not stating that in your question.
If you want the elements to be disabled without the lower elements to "move up"
If I understood your question correctly, the solution would be to put your content as a child of an other one like so:
EDIT:
as #comphonia correctly suggests, it is preferable to have this menu made programmatically, with every button being a child of it's never disabled parent game object, and putting them into a List or array, such that you can disable them or reenable them individually

Related

How to delete all prefabs when an event happens in unity

Extremely new to unity and c# after switching across from Python. Once all the balls are scored in my game I want all the 'blockers' (prefabs that I have instantiated) to be removed from the screen and a new set to be spawned randomly on screen in random positions. The blocker prefabs spawn randomly when all balls are scored, however, the old prefabs, besides the one which deletes each time, stay on screen rather than deleting. I have tried looping through the blockers in the code below to delete and I think this is where the issue is as only one game object deletes at this stage:
if (SpawnManager.tempclonecount == 0 )
{
for (int i = 0; i < SpawnManager.blockeramounts; i++)
{
Destroy(gameObject);
}
SpawnManager.tempclonecount = 1;
}
SpawnManager is an empty object which I have used to spawn objects onto the screen, tempclonecount is a variable stating when the old game objects should be removed from the game. This part of the code works well. blockeramounts is the number of prefabs initially on screen and I hoped to loop through the number of prefabs would delete all of the prefabs. It only deletes one. How do I change this?
Here is the code for creating the blockers in spawn manager also, if helpful:
void Update()
{
int blockeramount = Random.Range(2, 7);
blockeramounts = blockeramount;
for (int i = 0; i < blockeramount; i++)
{
int blockerindex = Random.Range(0, blockerPrefabs.Length);
Instantiate(blockerPrefabs[blockerindex], new Vector3(Random.Range(-30, 30), 0, Random.Range(-30, 30)), blockerPrefabs[blockerindex].transform.rotation);
}
}
While reading your description of the problem I got a bit confused by some of the terms you used. That's all fine since you are new to Unity and still learning. What i managed to figure out is that you have a SpawnManager script attached to an empty game object which instantiates your blocker prefabs. Then in another script you are getting the SpawnManager refernce and checking if you should destroy the current ones and instantiate a new set.
First of all what i would do is, after instantiating an object, to store it in an array or a list
...
public List<GameObject> blockers = new List<GameObject>();
...
void Start()
{
...
}
void Update()
{
int blockeramount = Random.Range(2, 7);
blockeramounts = blockeramount;
for (int i = 0; i < blockeramount; i++)
{
int blockerindex = Random.Range(0, blockerPrefabs.Length);
var blocker = Instantiate(blockerPrefabs[blockerindex], new Vector3(Random.Range(-30, 30), 0, Random.Range(-30, 30)), blockerPrefabs[blockerindex].transform.rotation);
blockers.Add(blocker);
}
}
After which i would add a new method which does the check to see how many remaining blockers there are. This method should go inside SpawnManager.
public void CheckAndDeleteBlockers()
{
if (tempclonecount == 0 )
{
foreach(var blocker in blockers)
{
blockers.Remove(blocker);
Destroy(blocker);
}
}
}
And you should call it from the other script with:
...
public SpawnManager spawnManager;
...
void Start()
{
spawnManager = FindOjectOfType<SpawnManager>();
}
//for example
void Update()
{
spawnManager.CheckAndDeleteBlockers();
}
I understand the way you are trying to do this, but let's say that this isn't the correct way. I would suggest that you look up what object pooling is.
NOTE: The creator of the pooling tutorial that I mentioned above is a great source for Unity and C# beginners, so I would recommend that you watch some of his other videos. Good luck in learning Unity.
What I believe is causing your issue, is that your are only specifying one gameObject to be destroyed. You need to call Destroy() on each blocker you instantiated.
Hope my advice helps.

How to call a randomized loot table method again from within itself to prevent duplicates in Unity?

I have a basic loot table with weighted drop rarities. I am trying to make it so that when the game starts, it will re-roll if the item already exists in a duplicated list.
I've created an empty list in shopManagerScript and am adding each instantiated item to that list. Then I would like to check against that list to see if the item exists. If it does, I want to re-roll again. If it doesn't then go ahead and instantiate the item.
This current code is executing endlessly however, and is crashing my game.
public GameObject shopManager;
public ShopManager shopManagerScript;
[System.Serializable]
public class DropItem
{
public string name;
public GameObject item;
public int dropRarity;
}
public List<DropItem> ShopItemPool = new List<DropItem>();
private void Start()
{
shopManager = GameObject.FindGameObjectWithTag("ShopManager");
shopManagerScript = shopManager.GetComponent<ShopManager>();
SpawnItem();
}
void SpawnItem()
{
int itemWeight = 0;
for (int i = 0; i < ShopItemPool.Count; i++)
{
itemWeight += ShopItemPool[i].dropRarity;
}
int randomValue = Random.Range(0, itemWeight);
for (int i = 0; i < ShopItemPool.Count; i++)
{
if (randomValue <= ShopItemPool[i].dropRarity && !shopManagerScript.shopItems.Contains(ShopItemPool[i].item.ToString()))
{
Instantiate(ShopItemPool[i].item, transform.position, Quaternion.identity);
shopManagerScript.shopItems.Add(ShopItemPool[i].item.ToString());
return;
}
else
{
SpawnItem();
}
randomValue -= ShopItemPool[i].dropRarity;
}
}
The problem here is that SpawnItem method calls SpawnItem inside the for, which results in having more running SpawnItem. Then these running SpawnItem call more SpawnItem. The process continues until stack is overflowed and it falls with StackOverflowException.
In order to fix this you can use continue as mentioned before, but be careful with calling SpawnItem, because if the random keeps generating inappropriate values the method can still be called too many times and the error will be the same.
Another way to fix it is to remove recursive call of it and make another method that loops calling SpawnItem. Just make sure that the logic of looping doesn't fully rely on random, otherwise it's still possible to call the method too many times

Moving a object randomly without it being inside a wall - Unity 5

i am trying to make some types of pickups spawn inside a given area, although some usually get stuck within the walls, how would i fix this?
Code in question for moving objects
for (int x = 0; x < garbage.Length; x++)
{
if (x < 5)
{
garbage[x].transform.position = new Vector3(Random.Range(-33.0f, 30.0f), 2.35f, Random.Range(30.0f, -35.0f));
}
}
Fixed it using Physics.OverlapSphere. Thanks.
You could have a while loop inside your if statement, so it would be like
int attempts = 0;
while(garbage[x].transform.position == /*[the range of coordinates for the wall]*/ || attempts = 0)
{
garbage[x].transform.position = new Vector3(Random.Range(-33.0f, 30.0f), 2.35f, Random.Range(30.0f, -35.0f));
attempts += 1;
}
You can try OnCollisionStay to tackle this with collisions. OnCollisionStay can be very cpu heavy if not used carefully, so you may want to think of a better way if you can.
You will have to create a new script using the following code, which you will attach to your power-up prefab.
bool keepChecking = true;
void OnCollisionStay(Collision collision)
{
if(keepChecking)
{
if(collision.gameobject.tag == "Wall")
{
collision.gameobject.transform.position = new Vector3(Random.Range(-33.0f, 30.0f), 2.35f, Random.Range(30.0f, -35.0f));
}
else
{
keepChecking = false;
}
}
}
https://docs.unity3d.com/ScriptReference/Collider.OnCollisionStay.html
Read that link and make sure your objects have all the requirements. Your wall and Power-Up should have colliders, and at least one of these two should have a rigid body. None of these objects should be kinematic.
Let me know if this works for you.

Why is my game lagging huge when I call a method?

I've been messing around trying to create an analog style scoring system for my game that I am building with Unity3D and I've noticed a huge lag when I call AnalogScoreProcessing() from the updateScore() function:
/******************************** Update Score ********************************/
public void updateScore (int newScore)
{
// Send the old score/new score to AnalogScoreProcessing()
if (oldScore != newScore)
{
AnalogScoreProcessing(oldScore, newScore);
}
// Display GUI score
oldScore = newScore;
guiText.text = "" + oldScore;
}
/*************************** Analog Scoring System ****************************/
void AnalogScoreProcessing(int oldScore, int newScore){
for(int i = oldScore; i <= newScore; i++){
print (i);
}
}
Do I have to create a new thread or a co-routine to complete the looping task while the remaining parts of updateScore() are carried out? I've never done any threading but I have used co-routines. I'm just not sure what I should be using here.
The easiest solution is to use coroutines here.
You basically want the players to see the effect of counting up right?
So say you want to display a count up to the user being +1 to the user ever few frames or so so the count up will be perceivable.
Try...
I would do it like this
int current =0;
int target = 0;
float percentateOfTarget = 0;
float step = 0.1f;
IEnumerable AnalogScoreProcessing()
{
while(current <= target)
{
current = (int)Math.Ceiling(Mathf.Lerp(current, target, percentateOfTarget));
return yield new WaitForSeconds(step);
percentageOfTarget += step;
// Display GUI score
guiText.text = "" + current;
}
}
public void updateScore (int newScore)
{
// Send the old score/new score to AnalogScoreProcessing()
if (oldScore != newScore)
{
StopCoroutine("AnalogScoreProcessing");
current = oldScore;
target = newScore;
StartCoroutine("AnalogScoreProcessing");
}
}
Please be aware this code is pulled directly out of my head so youll probably have to tweek some things here and there but should give you something close to what you desire.
You could/should even scale the GUIText up while the coroutine running for an even more dramatic effect. Let me know how it goes.
As I side note coroutines are not separate threads in unity they are ran on unity's main thread.
Well i dont understandt the Logic behind the function, but cant you just print the score once it changes?
if (oldScore != newScore)
{
//AnalogScoreProcessing(oldScore, newScore);
Debug.log(newScore);
}
Also you have to set the GUI calls inside the
OnGUI()
function inside your file.
If there is a large difference between oldScore and newScore then the print function in AnalogScoreProcessing will be run many times.
You might want to look at using a tween to change the displayed value, as you cannot display more than one value per frame anyway. Or if you're printing to the console then... why are you doing that?
edit: have moved quick and dirty solution a more appropriate question/answer

Cannot get the counters to move (game in C# with PictureBox).

I'm trying to learn C# (so go easy on me!) and as part of a coursework I have been asked to create a game called strikeout.
The game rules are very simple. It is simply a case of knocking one counter into another, with the aim of leaving one counter left on the screen.
I am having trouble simply moving one counter into another. I have created a number of picture boxes using a loop (to make up my board), and created an event handler using the loop. How can I use the single even handler to move all of my counters?
{
(...)
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
int x = j * (75);
int y = i * (75);
pictureBoxCounter[j, i] = new PictureBox();
pictureBoxCounter[j, i].Location = new System.Drawing.Point(x, y);
pictureBoxCounter[j, i].Size = new System.Drawing.Size(75, 75);
pictureBoxCounter[j, i].BackColor = System.Drawing.Color.Transparent;
panelGame.Controls.Add(pictureBoxCounter[j, i]);
this.pictureBoxCounter[j, i].Click += new System.EventHandler(this.pictureBoxCounter_Click);
}
}
} // end of some function
private void pictureBoxCounter_Click(object sender, EventArgs e)
{
//I need some code here but nothing seems to work :(
}
I've spent way to long on this problem. Even asked my tutor to help. Instead of helping he managed to break most of my code. So after fixing the problems he caused I am now back with a compiling program!
(When running the program you will need to enter player info to enable the start game button.)
If I can help with any other information don't hesitate to ask!
This programming problem appears intended to teach you about separation of concerns. If it isn't, it should be. (It is now!)
You have two problems, not one. (Actually, you have more than two, but you have two big ones.) One problem is: How do I create a set of objects representing the counters in this game, and what rules do those objects follow? The other problem is: How do I represent the counters on the screen?
If your solution to the first problem is to create a bunch of PictureBoxes, you're going down a rabbit hole that it's going to be tough to get back out of. You should solve the first problem first, and then the second problem.
Here's a rough sketch (very rough, because I don't know the rules of this game) of an object model that addresses the first problem:
public class Board
{
public const int Height = 8;
public const int Width = 8;
private Counters[Height][] Counters { get; set; }
public Counter GetCounter(int row, int col)
{
return Counters[row][col];
}
public void Initialize() { }
public void ExecuteMove(Counter c) { }
}
public class Counter
{
public int Row { get; set; }
public int Column { get; set; }
}
So, a Counter object knows where it is (its Row and Column). A Board object knows about all of the Counter objects, it knows how to find a Counter given its row and column, and it knows how to execute a move when a counter gets clicked on. (I don't know anything about the rules of the game, but they're going to live in the ExecuteMove method.
You can now trivially write a method that prints the board to the console:
public void PrintBoard(Board b)
{
for (int col = 0; col < board.Width; col ++)
{
for (int row = 0, row < board.Height; row++)
{
Counter c = board.GetCounter[row][col];
Console.Write(c == null ? " " : "*");
}
Console.WriteLine();
}
}
and a method to input a move:
public Counter InputMove(Board b)
{
string s;
Console.Write("Row: ");
s = Console.ReadLine();
int row = Convert.ToInt32(s);
if (s == "") return null;
Console.Write("Column: ");
s = Console.ReadLine();
if (s == "") return null;
int column = Convert.ToInt32(s);
return b.GetCounter(row, column);
}
...and now you have everything you need in order to code and test the ExecuteMove method. Get that working. I'll wait.
You done? Good. There are a couple of problems that you probably ran into that I haven't addressed. For instance, you probably discovered that when you move a Counter, you have to update both the board's array and the Counter itself. And you also probably discovered that you have to come up with some way of keeping track of what happens when a Counter is destroyed. Aren't you glad you weren't screwing around with mouse clicks and UI elements too?
Now for part two. Let's make a UI that knows how to talk to the Board. In your form, loop through the counters in the board, create a PictureBox for each (with its position based on the Row and Column properties), and add it to a Dictionary<PictureBox, Counter> called, say, Counters. You'll also want a Dictionary<Counter, PictureBox> called, say, PictureBoxes. These maps give you the ability to find a Counter given its PictureBox, and vice versa.
Attach this event handler to each PictureBox:
private void PictureBox_Click(object sender, EventArgs e)
{
PictureBox p = (PictureBox) sender;
Counter c = Counters[p];
Board.ExecuteMove(c);
}
(This, by the way, is the answer to your original question.)
Well, that's all well and good: you can click on a PictureBox, find its Counter, and execute the move. But how do you update the visual state of the game in the UI? That depends, a lot, on what the actual rules of the game are.
For instance, if clicking on a counter makes it disappear, you might add a Visible property to the Counter class and have the Board class's ExecuteMove update it. If, when a Counter becomes invisible, the other Counters in its row move left or right, the ExecuteMove method will have to update their Row and Column, and do something with the now-invisible Counter that was at that position.
But you already worked all of this out back when you tested all of the game logic, right? All you need to implement in your UI is an equivalent method to the one you built for printing it to the console. Only this one iterates through all of the Counters in the board, and updates their PictureBox based on their state.
There are a lot of subtleties that I'm glossing over here, mostly because a) I don't fully understand your problem and b) I'm not actually trying to do your homework for you.
You'll need to find the clicked picture box back in the Click event handler. You can use the sender argument for that. Trivially:
private void pictureBoxCounter_Click(object sender, EventArgs e)
{
PictureBox box = sender as PictureBox;
box.BackColor = System.Drawing.Color.Coral;
}
But you'll need to implement your game logic as well, which will require you know the box's location on the game board. You could use the PictureBox.Tag property for that. It could store a Point for example:
...
pictureBoxCounter[j, i].BackColor = System.Drawing.Color.Transparent;
pictureBoxCounter[j, i].Tag = new Point(j, i);
panelGame.Controls.Add(pictureBoxCounter[j, i]);
...
private void pictureBoxCounter_Click(object sender, EventArgs e)
{
PictureBox box = sender as PictureBox;
box.BackColor = System.Drawing.Color.Coral;
Point pos = (Point)box.Tag;
int row = pos.X;
int col = pos.Y;
//etc...
}
But you probably want to use a helper class that stores more info than just the row and column number.

Categories