I always have the following problem when I execute the code in Unity:
ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
The property Grid I set directly in the Unity editor with a value of 10 for x and y.
public class World : MonoBehaviour
{
public Room[,] Dungeon { get; set; }
public Vector2 Grid;
private void Awake()
{
Dungeon = new Room[(int)Grid.x, (int)Grid.y];
StartCoroutine(GenerateFloor());
}
public IEnumerator GenerateFloor()
{
for (int x = 0; x < Grid.x; x++)
{
for (int y = 0; y < Grid.y; y++)
{
Dungeon[x, y] = new Room
{
RoomIndex = new Vector2(x, y)
};
}
}
yield return new WaitForSeconds(3);
Vector2 exitLocation = new Vector2((int)Random.Range(0, Grid.x), (int)Random.Range(0, Grid.y));
Dungeon[(int)exitLocation.x, (int)exitLocation.y].Exit = true;
Dungeon[(int)exitLocation.x, (int)exitLocation.y].Empty = false;
Debug.Log("Exit is at: " + exitLocation); }}
I hope you can help me in this case
It's pretty hard to see an error like this, without debugging the code. But in General,try to break public Vector2 Grid; 2 int variables and to use them,so do not need to be explicitly cast to an int, and your code will become significantly readable if you want x==y then we can make the test in Awake and fail to accidentally ask the wrong value. And Vector2 exitLocation can be replaced with Vector2Int or 2 variables.
Related
guys so I'm trying to make an endless Map Generator for my 2D grappling Hook Game Kinda like Danis square Game and I was trying to generate a set number of swingable points in certain range to test.
Everything works fine except that some Points overlap each other when spawned So I was wondering how could I prevent this from happening?
Should I change my code or is there any method of spawning things I should try so they don't overlap?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapGenerator : MonoBehaviour
{
public Transform Endpositin;
public int NumOfSwwingablePoints;
public GameObject SwinggablePoint;
public int MinX, MaxX;
public int MinY, MaxY;
public int Xpos;
public int Ypos;
Vector2 PointPos;
void Start()
{
for (int i = 0; i <= NumOfSwwingablePoints; i++)
{
Xpos = Random.Range(MinX, MaxX);
Ypos = Random.Range(MinY, MaxY);
PointPos = new Vector2(Xpos, Ypos);
Instantiate(SwinggablePoint, PointPos, Quaternion.identity);
}
}
// Update is called once per frame
void Update()
{
}
}
I have Tried this Method and I have also tried to increase values and giving the points colliders to see if they overlap but none of that works. I have also tried update function, but they just keep spawning, and it never stops
My suggestion is everytime you create a new block then save the position of the block in a list.
when next time you create a new block, check the new position with the list.
Here is the code.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapGenerator : MonoBehaviour
{
public Transform Endpositin;
public int NumOfSwwingablePoints;
public GameObject SwinggablePoint;
public int MinX, MaxX;
public int MinY, MaxY;
private int Xpos;
private int Ypos; // this can be private since we use only privatly
Vector2 PointPos;
List<Vector2> PointPosList = new List<Vector2>();
void Start()
{
for (int i = 0; i < NumOfSwwingablePoints; i++) // I change <= to < since we start from 0
{
Generating();
}
}
void Generating()
{
bool foundSamePosition;
do
{
foundSamePosition = false;
Xpos = Random.Range(MinX, MaxX);
Ypos = Random.Range(MinY, MaxY);
PointPos = new Vector2(Xpos, Ypos);
// finding the generated position is already exist
for (int i = 0; i < PointPosList.Count; i++)
{
if (SamePosition(PointPos, PointPosList[i]))
{
foundSamePosition = true;
}
}
} while (foundSamePosition);// if we found the position try again
// if not found, add the new position to a list for next time check
PointPosList.Add(PointPos);
Instantiate(SwinggablePoint, PointPos, Quaternion.identity);
}
private bool SamePosition(Vector2 v1, Vector2 v2)
{
return (v1.x == v2.x && v1.y == v2.y);
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VoxelRenderer : MonoBehaviour
{
public Mesh mesh;
public Material material;
[SerializeField]
public int size = 10;
[SerializeField]
public int offset = -10;
// Start is called before the first frame update
void Start()
{
}
void Update()
{
int limit = size;
for (int x = 0; x < limit; x++)
{
for (int y = 0; y < limit; y++)
{
for (int z = 0; z < limit; z++)
{
Graphics.DrawMesh(mesh, new Vector3(x*2 +offset, y*2 + offset, z * 2 + offset), Quaternion.identity, material, 0);
}
}
}
}
}
Now this renders fine for size 10~ but if i go for 100+ (which is obviously low for which is required for a planet) i get massive fps drops presumably because of 10000's of batches per frame. Am i going the wrong way here? Or is it an occlussion issue or something similar i am running into here?
Draw calls can really hurt your performance. You can use batching and GPU instancing, but I think the best solution is using CombineMeshes. By combining 1000s of meshes into 1, you can get that many fewer draw calls.
Unity has a limit of slightly less than 2^16=65536 vertices per mesh. This prevents you from having a giant voxel as only one mesh. I suggest that you combine groups of a few thousand meshes ("chunks") and when you need to update the voxel data, only redo the mesh groups whose data changed. This not only gets around the vertex limit, but also saves performance by recombining only the meshes that need it.
In a minecraft-like game that I worked on, I made a script that generated cube blocks without drawing any hidden faces (e.g. two blocks touching each other wouldn't generate faces between them), but this will only work if your meshes are cubes and the code is messy.
I think the best solution would be to make two MonoBehaviours.
A Chunk class could handle may voxels, but not the whole planet, and the VoxelRenderer can handle spawning as many chunks are needed to make the whole planet. Make a prefab with a MeshRenderer, this Chunk component, and the right material assigned.
Please note that this is just a quick skeleton of what you'd need.
Chunk:
public class Chunk : MonoBehaviour
{
public Mesh mesh;
// Start is called before the first frame update
void UpdateChunk()
{
CombineInstance[] ci = new CombingInstance[CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE];
for(int x = 0; x < VoxelRenderer.CHUNK_SIZE;x++){
//add 2 more for loops for y and z
ci[x*CHUNK_SIZE*CHUNK_SIZE + y*CHUNK_SIZE + z].Transform =...
}
GetComponent<MeshFilter>().mesh = new Mesh();
GetComponent<MeshFilter>().mesh.CombineMeshes(ci);
}
VoxelRenderer:
public class VoxelRenderer : MonoBehaviour
{
public const int CHUNK_SIZE = 8;
public GameObject chunkPrefab;
[SerializeField]
public int size = 10;
[SerializeField]
public int offset = -10;
private Chunk[,,] chunks;
// Start is called before the first frame update
void Start()
{
int limit = size;
int limitDividedByChunkSize = Mathf.RountToInt(limit/(float)CHUNK_SIZE);
chunks = new Chunk[limitDividedByChunkSize,limitDividedByChunkSize,limitDividedByChunkSize];
for (int x = 0; x < limit; x+=CHUNK_SIZE)
{
for (int y = 0; y < limit; y+=CHUNK_SIZE)
{
for (int z = 0; z < limit; z+=CHUNK_SIZE)
{
GameObject g = Instantiate(chunkPrefab, new Vector3(x*2*CHUNK_SIZE +offset, y*2*CHUNK_SIZE + offset, z * 2*CHUNK_SIZE + offset));
Chunks[x,y,z] = g.GetComponent<Chunk>();
}
}
}
}
//this updates all voxel chunks, for minor changes to the voxel data use chunks[x,y,z].UpdateChunk();
void UpdateAllVoxelChunks()
{
for (int x = 0; x < chunks.Length; x++)
{
for (int y = 0; y < chunks[x].Length; y++)
{
for (int z = 0; z < chunks[y].Length; z++)
{
Chunks[x,y,z].UpdateChunk();
}
}
}
}
}
As the title pointed out, for some reason when my game is paused my co-routine still runs. I even went as far as to put the time scale condition in a while condition so that the while doesnt run if it paused but to no avail. I've added my code in it's entirety and hope that someone will be able to assist.
using UnityEngine;
using System.Collections;
using Chronos;
public class ObjectSpawn : BaseBehaviour //MonoBehaviour
{
public float minTime = 3f;
public float maxTime = 9f;
public float minX = -65.5f;
public float maxX = -5.5f;
public float topY = -5.5f;
public float z = 0.0f;
public int count = 50;
public GameObject prefab;
public bool doSpawn = true;
public float fallGrav =1.0f;
int first = 1;
void Start()
{
Clock clock = Timekeeper.instance.Clock("MovingOneWayPlatforms");
StartCoroutine(Spawner());
}
IEnumerator Spawner()
{
while (first == 1) {
yield return time.WaitForSeconds(8.0f);
first = 0;
}
while (doSpawn && count > 0 /*&& time.timeScale != 0 */)
{
Renderer renderer = GetComponent<Renderer>();
float min = renderer.bounds.min.x;
float max = renderer.bounds.max.x;
Vector3 v12 = new Vector3(Random.Range(minX, maxX), this.gameObject.transform.position.y, 0f);
prefab.GetComponent<Rigidbody2D>().gravityScale = fallGrav;
prefab = Instantiate(prefab, v12, Random.rotation);
count--;
// yield return new WaitForSeconds(Random.Range(minTime, maxTime));
yield return time.WaitForSeconds(Random.Range(minTime, maxTime));
Destroy(prefab, 6);
}
}
}
Try to uncomment your 2nd while statement, I think that is your problem.
I am new and still not used to Chronos.
Maybe I'm wrong but my guess is this line.
Destroy(prefab, 6);
In my understanding, Destroy's delay should not affected by chronos.
You better use new Coroutine to destroy it.
like this
StartCoroutine(DestroyRoutine(prefab))
IEnumurator DestroyRoutine(GameObject gameobject)
{
yield return time.WaitForSeconds(6);
Destroy(gameObject)
}
I have a list of gameObjects added to a List within the area as shown. What I need is a directional input to choose a target from the origin point. I have got it work to get this origin point.
My first attempt of this was to get it via rayCast, but by doing that there were times directional inputs need to directly hit target object by the ray. This is no problem if input was done like case #1.
However, what I need it to really work is when input direction was as if case #2 or #3 the input will still get a target. My second attempt was to do this with sphereCast, but it still needed a target in sphere proximity and multiple targets hit by sphereCast still needed to result in only one and more accurate target selection by input.
Since I have all the transform.position of all the possible targets as well as the origin point I wondered there would be a more elegant way of resolving this via comparing vector3's of these coordinates(origin and targets in the general direction).
Here's my latest approach:
//
// m_targetList is the list containing all GameObjects as GameObjects in other script m_collector. All m_collector does is this.
//
using System.Collections.Generic;
using UnityEngine;
public class TargetSwitcher : MonoBehaviour
{
private TargetCollector m_collector;
private Transform m_origin;
public bool m_targetChanged = false;
public GameObject m_target;
public LayerMask m_targetMask;
private Dictionary<Vector3, float> m_targetInfo = new Dictionary<Vector3, float>();
private void Awake()
{
m_collector = GetComponent<TargetCollector>();
m_origin = GameObject.Find("TargetOrigin").transform;
m_tracker = GameObject.Find("TargetTracker").transform;
m_bound = GetComponent<BoxCollider>();
}
public void UpdateBearing(GameObject origin)
{
m_origin = origin.transform;
foreach (GameObject target in m_collector.m_targetList)
{
Vector3 dir = (target.transform.position - origin.transform.position).normalized
float dist = Vector3.Distance(origin.transform.position, target.transform.position);
m_targetInfo.Add(dir, dist);
}
}
public void SwitchTarget()
{
if (!m_targetChanged)
{
Vector2 dir = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")).normalized;
// Find closest direction value from Dictionary to dir of here
// Compare distance from origin if multiple targets and choose the nearest
}
}
public void ReturnToIdle()
{
m_origin.position = m_target.transform.position;
m_targetChanged = false;
m_targetInfo.Clear();
}
public struct TargetInfo
{
public Vector3 bearing;
public float distance;
public TargetInfo(Vector3 bearing, float distance)
{
this.bearing = bearing;
this.distance = distance;
}
}
}
Generally, I'm trying to compare the normalized vector of directional input to the normalized vector from the origin to each target before SwitchTarget(). The input method here is Gamepad axis x and y as Horizontal and Vertical.
Reposting this question since a provided answer was very far from the question and marked as duplicate(Given answer was about finding gameObject by distance only, this question is about direction and distance portion is to compare second-handedly when multiple items were found in the direction)
Edit
After some trials with dot product now I'm sure this is much likely where I want to head. There are much inconsistency I need to get on with, though.
Here's my most recent attempt:
private void Update()
{
UpdateBearing();
Vector3 input = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0);
if (input != Vector3.zero)
{
SwitchTarget();
}
}
public void UpdateBearing(GameObject origin)
{
m_origin.position = origin.transform.position;
foreach (GameObject target in m_collector.m_targetList)
{
Vector3 dir = (target.transform.position - origin.transform.position).normalized;
if (!m_targetInfo.ContainsKey(target))
{
m_targetInfo.Add(target, dir);
}
}
}
public void SwitchTarget()
{
GameObject oldTarget = m_collector.m_target;
if (!m_targetChanged)
{
Vector3 dir = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0).normalized;
Debug.DrawRay(m_origin.position, dir * 100, Color.yellow, 0.5f);
foreach (KeyValuePair<GameObject, Vector3> possibleTarget in m_targetInfo)
{
float dot = Vector3.Dot(dir, possibleTarget.Value);
if (dot > 0.5f) // Compare DP difference of added dot and input dot
{
GameObject newTarget = possibleTarget.Key;
if (oldTarget != newTarget)
{
Debug.Log(possibleTarget.Value + " // " + dot);
m_target = newTarget;
m_collector.m_target = newTarget;
m_targetChanged = true;
}
}
}
}
}
With this, I'm kind of getting gameObject selection without raycasting and missing any targets. However, I'm sure I need better case comparison than if(dot > 0.5f). Also, my rough assumption is that if I don't update the value of the dictionary m_targetInfo for each Key I'd have another inconsistency if those targets ever move. Anyways, I'm still confused how properly utilize this to achieve my end goal.
Since you have all the desired game objects in the area you can create a for loop and check the angle between your look direction and their position, if it is lower than some value (you can make it super low so it's precise or a little bit higher to allow for some margin of error) put it in a list of gameobjects and if there's more than one object there get the closest one.
The code for getting closest object in angle would look something like this:
GameObject CheckObjects()
{
List<GameObject> InAngle = new List<GameObject>();
for(int i = 0; i < YourObjectsList.Count; i++)
{
GameObject tested = YourObjectsList[i];
Vector3 dir = tested.transform.position - origin.transform.forward;
// I'm assuming here that youre rotating your origin with the
directional input, not then instead of origin.transform.forward
place there your directional vector3
float angle = Vector3.Angle(dir, tested.transform.position);
if(angle <= desiredAngle)
{
InAngle.Add(tested);
}
}
GameObject closest = null;
for(int j = 0; j < InAngle.Count; i++)
{
GameObject tested = InAngle[i];
Vector3 dir1 = tested.transform.position - origin.transform.position;
Vector3 dir2 = closest.transform.position - origin.transform.position;
if(!closest)
{
closest = tested;
}
else
{
if(dir2.sqrMagnitude > dir1.sqrMagnitude)
{
closest = tested;
}
}
}
return closest;
}
I'm getting an Argument Out of Range Exception for this script I'm following in a Unity tutorial.
Here's the exception:
ArgumentOutOfRangeException: Argument is out of range.
Parameter name: index
System.Collections.Generic.List`1[UnityEngine.Vector3].get_Item (Int32 index) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:225)
BoardManager.RandomPosition () (at Assets/Scripts/BoardManager.cs:75)
And here's the actual script for the project:
using UnityEngine;
using System; //Allows serializable attribute, which modifies how variables appear
using System.Collections.Generic; //Generic allows use of lists
using Random = UnityEngine.Random; //Used because Random class is in both System and Unity engine namespaces
public class BoardManager : MonoBehaviour
{
[Serializable]
public class Count
{
public int maximum;
public int minimum;
public Count (int min, int max) //Assignment constructor for Count to set values of min, max when we declare a new Count
{
minimum = min;
maximum = max;
}
}
public int columns = 8; //8x8 gameboard
public int rows = 8;
public Count wallCount = new Count(5, 9); //Specifies random range for walls to spawn in each level
public Count foodCount = new Count(1, 5); //Random range for food spawns
public GameObject exit; //Variable that holds exit prefab
public GameObject[] floorTiles; //Game Object Arrays hold our different prefabs to choose between
public GameObject[] wallTiles;
public GameObject[] foodTiles;
public GameObject[] enemyTiles;
public GameObject[] outerWallTiles;
private Transform boardHolder; //Manages Hierarchy
private List<Vector3> gridPositions = new List<Vector3>(); //Declares a private list of Vector3s
//Tracks all possible positions on gameboard, and keeps track of whether object has been spawned in that position or not
void InitialiseList()
{
gridPositions.Clear();
for (int x = 1; x < columns - 1; x++) //Nested FOR loops to fill list with each of the positions on our gameboard as a Vector3
{
for (int y = 1; y < rows - 1; y++)
{
gridPositions.Add(new Vector3(x, y, 0f)); //Adds x and y positions to the list
}
}
}
void BoardSetup() //Sets up outer wall and floor of gameboard
{
boardHolder = new GameObject("Board").transform;
for (int x = -1; x < columns + 1; x++)
{
for (int y = -1; y < rows + 1; y++)
{
GameObject toInstantiate = floorTiles[Random.Range(0, floorTiles.Length)]; //Defines a new GameObject
if (x == -1 || x == columns || y == -1 || y == rows)
{
toInstantiate = outerWallTiles[Random.Range(0, outerWallTiles.Length)];
}
GameObject instance = Instantiate(toInstantiate, new Vector3(x, y, 0f), Quaternion.identity) as GameObject;
//Instantiates a new GameObject. Rotation is always the same. Used as GameObject.
instance.transform.SetParent(boardHolder);
}
}
}
Vector3 RandomPosition()
{
try
{
int randomIndex = Random.Range(0, gridPositions.Count);
Vector3 randomPosition = gridPositions[randomIndex]; //THROWING AN EXCEPTION
gridPositions.RemoveAt(randomIndex); //Prevents duplicate position spawns
return randomPosition;
}
catch (Exception ex1)
{
Debug.Log(ex1);
throw;
}
}
void LayoutObjectAtRandom(GameObject[] tileArray, int minimum, int maximum)
{
int objectCount = Random.Range(minimum, maximum + 1);
for (int i = 0; i < objectCount; objectCount++)
{
Vector3 randomPosition = RandomPosition();
GameObject tileChoice = tileArray[Random.Range(0, tileArray.Length)];
Instantiate(tileChoice, randomPosition, Quaternion.identity);
}
}
public void SetupScene(int level) //Called by the GameManager
{
BoardSetup();
InitialiseList();
LayoutObjectAtRandom(wallTiles, wallCount.minimum, wallCount.maximum);
LayoutObjectAtRandom(foodTiles, foodCount.minimum, foodCount.maximum);
int enemyCount = (int)Mathf.Log(level, 2f); //Scales enemy count based on level
LayoutObjectAtRandom(enemyTiles, enemyCount, enemyCount);
Instantiate(exit, new Vector3(columns - 1, rows - 1, 0f), Quaternion.identity);
}
}
I'm assuming that the RandomPositions() function is throwing the exception, though I am unable to find a problem with the code. Does anyone see the problem?