How do I restrict one random prefab to be used only once but placed randomly with a whole bunch of prefabs of arrays on top of other object? - arrays

How do I restrict one random prefab to be used only once but placed randomly with a bunch of prefabs of arrays on top of other object?
using System.Collections.Generic;
using UnityEngine;
public class LevelRoomsScript : MonoBehaviour
{
[SerializeField]
private GameObject[] memberWoodArray = null;
[SerializeField]
private GameObject[] memberRoomPrefabArray = null;
void Start()
{
foreach (GameObject localWood in memberWoodArray)
{
int localNumRoomPrefab = memberRoomPrefabArray.Length;
int localRoomIndex = Random.Range(0, localNumRoomPrefab);
GameObject localRoomPrefab = memberRoomPrefabArray[localRoomIndex];
Instantiate(localRoomPrefab, localWood.transform.position, Quaternion.identity);
}
}
}

The way I understand your question is that you want to instantiate each element in memberRoomPrefabArray at most once.
You could create a temporary list that is a copy of memberRoomPrefabArray and remove each element that is instantiated before the next loop cycle.
void Start()
{
List<GameObject> temp = new List<GameObject>(memberRoomPrefabArray);
foreach (GameObject localWood in memberWoodArray)
{
int localRoomIndex = Random.Range(0, temp.Count);
Instantiate(temp[localRoomIndex], localWood.transform.position, Quaternion.identity);
temp.RemoveAt(localRoomIndex);
}
}
You might want to add some checks like if (temp.Count == 0) { break; } if it's possible for memberRoomPrefabArray to be shorter than memberWoodArray.
Edit: Changed Random.Range(0, temp.Count - 1) to Random.Range(0, temp.Count) since, apparently, it's only maximally inclusive with floats and not integers.

You rather want to "shuffle" the array once and then iterate the shuffled array e.g. using Linq OrderBy and using Random.value as order like
using System.Linq;
...
void Start()
{
if(memberRoomPrefabArray.Length < memberWoodArray.Length)
{
Debug.LogError($"Not enough prefabs available for {memberWoodArray.Length} unique spawns!", this);
return;
}
// as the method names suggest this will be a randomized array of the available prefabs
var shuffledPrefabs = memberRoomPrefabArray.OrderBy(m => Random.value).ToArray();
for (var i = 0; i < memberWoodArray.Length; i++)
{
// Since the array is already shuffled we can now go by consecutive order
Instantiate(suffledPrefabs[i], memberWoodArray[i].transform.position, Quaternion.identity);
}
}

Related

Out of range exception in Unity

I create a game in Unity. And I got this error:
System.ThrowHelper.ThrowArgumentOutOfRangeException (System.ExceptionArgument argument, System.ExceptionResource resource)
Here is my short code:
public class OnlineGame : MonoBehaviour
{
private List<GameObject> domino = new List<GameObject>(7);
public void Start()
{
StartGame();
}
private void StartGame()
{
for (int i = 0; i < 7; i++)
{
domino[i] = Instantiate(dominoPrefab, new Vector3(11, 0, 0), Quaternion.identity) as GameObject;
}
}
}
If you need more details, write a comment. Thanks for help
private List<GameObject> domino = new List<GameObject>(7)
new List<T>(int capacity) constructor doesn't mean the resulting list will have hold capacity objects at beginning. The Count of elements in the list will still be 0.
When you use List class in c#, it will have capacity memory reserved for the list. When you add some elements to the list and its Count reaches capacity(or close to it, I'm not exactly sure), the list will automatically increase capacity by allocating additional memory so that you can add additional element to the list.
Usually when you use new List<T>() the list will have initial capacity of 0 where program doesn't know how many Count the list can possibly have and will dynamically adjust capacity to match your need.
Using new List<T>(int capacity) is like telling the program that the list will have at most capacity number of elements and list should have that capacity ready to avoid overhead of allocating more memory. Of course, when list's Count reach capacity it will increase as well.
To fix your problem, use Add method instead of assigning to array slot.
public class OnlineGame : MonoBehaviour
{
private List<GameObject> domino = new List<GameObject>(7);
public void Start()
{
StartGame();
}
private void StartGame()
{
for (int i = 0; i < 7; i++)
{
// domino[i] = Instantiate(dominoPrefab, new Vector3(11, 0, 0), Quaternion.identity) as GameObject;
domino.Add(Instantiate(dominoPrefab, new Vector3(11, 0, 0), Quaternion.identity) as GameObject);
}
}
}

Unity prefab array instantiate and destroy

I am having a very little problem in my Unity project but can't find a proper help or way to do. I am stuck at point where I have an array of prefab GameObjects and I am trying to instantiate index 1 GameObject and when it destroyed instantiate the next index. Here is how I am doing it. I have two scripts: One to instantiate and other one to destroy it.
Scripts 1:
public class GameObjectsArray : MonoBehaviour {
public static GameObjectsArray Instance { get; set; }
public GameObject[] Objects;
public int i=0;
// Use this for initialization
void Start()
{
InstiatingMethod();
//Instantiate(Objects[i]);
}
public void InstiatingMethod()
{
Instantiate(Objects[i]);
}
}
Scripts 2:
public class CheckDestroy : MonoBehaviour {
//public GameObject[] Objects;
//int i;
// Use this for initialization
void Start () {
Debug.Log("executed");
//Objects = GameObject.FindGameObjectsWithTag("Player");
//OnMouseDown();
//Instantiate(Objects[i], transform.position, transform.rotation);
}
// Update is called once per frame
void Update () {
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
BoxCollider boxCollider = hit.collider as BoxCollider;
if (boxCollider != null)
{
GameObjectsArray.Instance.i++;
GameObjectsArray.Instance.InstiatingMethod();
Destroy(boxCollider.gameObject);
}
}
}
}
}
So I created a very quick project to make a good response:
Scene Image
In the scene, we will have an empty game object that will contain our script that I called "GameManager", basically this script will do everything, it's more logic to put your logic in one script.
public GameObject[] GameObjects;
private int _targetIndex = -1;
private RaycastHit _hit;
private void Start()
{
InstantiateNextGameObject();
}
public void InstantiateNextGameObject()
{
//if the index is pointing at the last game object in the array, init the index to -1
if (_targetIndex == GameObjects.Length - 1)
_targetIndex = -1;
Instantiate(GameObjects[++_targetIndex]);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out _hit))
{
BoxCollider boxCollider = _hit.collider as BoxCollider;
if (boxCollider != null)
{
InstantiateNextGameObject();
Destroy(boxCollider.gameObject);
}
}
}
}
we will have an array of gameobjects following with the targetIndex which will start from -1.
The function InstantiateNextGameObject() will simply increment targetIndex and then instantiate a gameobject from the array (++targetIndex the first time will be 0, second time 1 etc). We have to check also if the targetIndex reaches the end of the array, put it back to -1.
Then basically what you did in the update, when you click on a gameobject, instantiate the next one and destroy the current.
At the end, you will get something like that:
https://ayoub-gharbi.org/youba_docs/stackoverflow/stackoverflow01.mp4
Feel free to ask me if you didn't understand anything:)
Happy coding!

AS3 / TweenLite - How to return an array element as a variable in TweenLite?

The code is below. When Tweenlite is called it treats "tileList[count1]" as a string instead of a variable name. However the trace seems to return what I would expect (tile1, tile2, tile3...). If I remove "tileList[count1]" from the tween and replace it with a direct call to the MovieClip (tile1, tile2, etc) the code works perfectly...
public class wtpMain extends MovieClip {
public var tileList:Array = new Array(tile1,tile2,tile3,tile4,tile5,tile6,tile7,tile8,tile9,tile10,tile11,tile12,tile13,tile14,tile15 ,tile16);
public var count1:int = 0;
public function wtpMain() {
nextButton.buttonMode = true;
nextDis.mouseEnabled=false;
nextButton.addEventListener(MouseEvent.CLICK, nextButtonClickh);
tileList.sort(randomSort);
}
public function nextButtonClickh(event:MouseEvent):void {
nextButtonClick();
}
public function nextButtonClick():void{
TweenLite.to(tileList[count1], 5, {y:700, alpha:0});
trace(tileList[count1]);
count1++;
}
public function randomSort(objA:Object, objB:Object):int{
return Math.round(Math.random() * 2) - 1;
}
}
}
Thanks!
---added---
Things I've tried:
Using a vector instead of an array.
Setting tileList[count1] to a variable and then calling that variable.
Removing the randomSort.
Removing count1 and calling the array element directly (ie, tileList[5]).
After hours of searching I stumbled on Convert String into an object instance name
and got the following solution from it (tested and working!)
public var tileList:Array = new Array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
TweenLite.to(this["tile"+tileList[count1]], 5, {y:700, alpha:0});

AS3- how to addchild this array(john) correctly

I do not know how to add the john array and make a hittestobject with it.
Bal is a different class non relevant to this problem.
I've tried to do john[new Bal]
tried john[ k ]
tried z and to specify z as a for-loop but then i would just get Z balls place.
This is supposed to become a space-invader type of game. I'm trying to make a hit test object between HENK and the 'falling balls' (JOHN). I do not know how to work with arrays especially given the fact that is should be timer-triggered.
Thanks
public class Main extends Sprite
{
public var henk:Sprite = new Sprite();
public var level:Timer = new Timer (2000, 0);
public var valTijd:Number = new Number
public var i:Number = 2000;
public var john:Array = new Array();
public var k:Number = 9000;
public function Main():void
{
henk.graphics.beginFill(0xFF00FF);
henk.graphics.drawCircle(0, 500, 20);
henk.graphics.endFill();
addChild(henk);
level.addEventListener(TimerEvent.TIMER, up);
level.start();
henk.addEventListener(Event.ENTER_FRAME, muis);
henk.addEventListener(Event.ENTER_FRAME, hit);
}
public function up(e:TimerEvent):void
{
var tijdje:Timer = new Timer( i, 0)
tijdje.addEventListener(TimerEvent.TIMER, tijdLuisteraar);
tijdje.start();
i = i - 250;
}
public function muis (e:Event):void
{
henk.x = mouseX;
}
public function hit (e:Event): void
{
if ( henk.hitTestObject(john [k] ))
{
if (contains(john[k] ))
{
removeChild(henk);
}
}
}
public function tijdLuisteraar(e:TimerEvent):void
{
john.push(new Bal);
addChild(john[k]);
}
}
}
welcome to stackoverflow!
This problem is actually fairly simple, I will describe how you will probably want to use an array in the case you described.
At the part where you create new Balls you want to append them to an array, which will be something like the following:
var ball = new Bal();
john.push(ball);
addChild(ball);
This will go inside your timer-triggered function, obviously.
Secondly, you want to have a hitTestObject with henk and all of the balls stored in the john array.
for(var i = 0; i < john.length; i++) {
if (henk.hitTestObject(john[i])) {
// well, that's a bummer for your player, henk hit one of the balls in the john array
// display something like a message here
}
}
This will automatically detect the size of the array, so all elements are tested. Be careful with hitTestObject when you have a lot of elements in the john-array, this can slow down your game drastically.
Furthermore, reflecting your code I suggest the following:
remove public var i:Number = 2000; and public var k:Number = 9000;, these have no meaning anymore
use a mouse event to move your henk object, not an ENTER_FRAME. I guess you will be able to find how this works. This will only trigger the function when it has to do something, resulting in less CPU-power needed and a cleaner code.
if you want to make the game even cooler, you could add the support for using the arrow keys

How to move by code the BindingSource to a specific record

Using datagridview bound to BindingSource control bound to a LINQ to SQL class, I wonder how to position the bindingSource to a specific record, that is, when I type a Product name in a textbox, the bindingsource should move to that specific product. Here is my code:
In my form FrmFind:
NorthwindDataContext dc;
private void FrmFind_Load(object sender, EventArgs e)
{
dc = new NorthwindDataContext();
var qry = (from p in dc.Products
select p).ToList();
FindAbleBindingList<Product> list = new FindAbleBindingList<Product>(qry);
productBindingSource.DataSource = list.OrderBy(o => o.ProductName);
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextBox tb = sender as TextBox;
int index = productBindingSource.Find("ProductName", tb.Text);
if (index >= 0)
{
productBindingSource.Position = index;
}
}
In the program class:
public class FindAbleBindingList<T> : BindingList<T>
{
public FindAbleBindingList()
: base()
{
}
public FindAbleBindingList(List<T> list)
: base(list)
{
}
protected override int FindCore(PropertyDescriptor property, object key)
{
for (int i = 0; i < Count; i++)
{
T item = this[i];
//if (property.GetValue(item).Equals(key))
if (property.GetValue(item).ToString().StartsWith(key.ToString()))
{
return i;
}
}
return -1; // Not found
}
}
How can I implement the find method to make it work?
You can combine the BindingSource.Find() method with the Position property.
For example, if you have something like this in your TextBox changed event handler:
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextBox tb = sender as TextBox;
int index = bs.Find("Product", tb.Text);
if (index >= 0)
{
bs.Position = index;
}
}
This of course will depend on a lot of things like the particular implementation of the Find method the data source for the binding source has.
In a question you asked a little while ago I gave you an implementation for Find which worked with full matches. Below is a slightly different implementation that will look at the start of the property being inspected:
protected override int FindCore(PropertyDescriptor property, object key)
{
// Simple iteration:
for (int i = 0; i < Count; i++)
{
T item = this[i];
if (property.GetValue(item).ToString().StartsWith(key.ToString()))
{
return i;
}
}
return -1; // Not found
}
Do note that the above method is case sensitive - you can change StartsWith to be case insensitive if you need.
One key thing to note about the way .Net works is that the actual type of an object is not sufficient all the time - the declared type is what consuming code knows about.
This is the reason why you get a NotSupported exception when calling the Find method, even though your BindingList implementation has a Find method - the code that receives this binding list doesn't know about the Find.
The reason for that is in these lines of code:
dc = new NorthwindDataContext();
var qry = (from p in dc.Products
select p).ToList();
FindAbleBindingList<Product> list = new FindAbleBindingList<Product>(qry);
productBindingSource.DataSource = list.OrderBy(o => o.ProductName);
When you set the data source for the binding source you include the extension method OrderBy - Checking this shows that it returns IOrderedEnumerable, an interface described here on MSDN. Note that this interface has no Find method, so even though the underlying FindableBindingList<T> supports Find the binding source doesn't know about it.
There are several solutions (the best is in my opinion to extend your FindableBindingList to also support sorting and sort the list) but the quickest for your current code is to sort earlier like so:
dc = new NorthwindDataContext();
var qry = (from p in dc.Products
select p).OrderBy(p => p.ProductName).ToList();
FindAbleBindingList<Product> list = new FindAbleBindingList<Product>(qry);
productBindingSource.DataSource = list;
In WinForms there are no entirely out of the box solutions for the things you are trying to do - they all need a little bit of custom code that you need to put together to match just your own requirements.
I took a different approach. I figured, programmatically, every record must be checked until a match is found, so I just iterated using the MoveNext method until I found a match. Unsure if the starting position would be the First record or not, so I used the MoveFirst method to ensure that is was.
There is one assumption, and that is that what you are searching for is unique in that column. In my case, I was looking to match an Identity integer.
int seekID;
this.EntityTableBindingSource.MoveFirst();
if (seekID > 0)
{
foreach (EntityTable sd in EntityTableBindingSource)
{
if (sd.ID != seekID)
{
this.t_EntityTableBindingSource.MoveNext();
}
else
{
break;
}
}
}
I didn't really care for either answer provided. Here is what I came up with for my problem:
// Create a list of items in the BindingSource and use labda to find your row:
var QuickAccessCode = customerListBindingSource.List.OfType<CustomerList>()
.ToList().Find(f => f.QuickAccessCode == txtQAC.Text);
// Then use indexOf to find the object in your bindingSource:
var pos = customerListBindingSource.IndexOf(QuickAccessCode);
if (pos < 0)
{
MessageBox.Show("Could not find " + txtQAC.Text);
}
else
{
mainFrm.customerListBindingSource.Position = pos;
}

Resources