I have been trying to make my own level generator, which generates a prefab with the correct sprite based on the color that is match in the small map that I made.
More specifically, I made this small map.
If one of the colors matches one of the colors that are found in a color array, it will instantiate a prefab with the correct sprite and so on for the other colors. So far it instantiates the objects with the correct sprites.
However this is what it looks like when I play the game.
And this is what it's suppose to look like
I don't know why it is doing this because based on my code,
public Texture2D needColors;
public Texture2D tiles;
public Texture2D level;
public GameObject tile;
public Transform parent;
public Color32[] colors;
public Sprite[] sprites;
void Start(){
for(int x = 0; x < level.width; x++){
for(int y = 0; y < level.height; y++){
GenerateLevel(x, y);
}
}
}
void GenerateLevel(int x, int y){
Color32 pixelColor = level.GetPixel(x, y);
if(pixelColor.a == 0){
return;
}
for(int i = 0; i < colors.Length; i++){
if(pixelColor.Equals(colors[i])){
Vector2 position = new Vector2(x, y);
Instantiate(tile, position, Quaternion.identity, parent);
tile.GetComponent<SpriteRenderer>().sprite = sprites[i];
}
}
}
it should be skipping the transparent pixels.
How can I do this correctly?
Related
I am currently generating a set amount of GameObjects in code. What is the best way to call the plane positions in a loop where I can get the positions of each plane to guarantee the walls spawning within them?
public class LevelGenerator: MonoBehaviour
{
public NavMeshSurface surface;
private float[,] positionX = { {-9.5f, 9f}, {12f, 30f}, {12f, 30f}};
private float[,] positionZ = {{9f, -9.5f}, {9f, -9.5f}, { 11.5f,30f}};
public GameObject wall;
void OnEnable(){
GenerateMaze();
surface.BuildNavMesh();
}
void GenerateMaze(){
//For every plane
for (int i = 0; i<=2 ; i++){
//Place some walls on that plane
for (int j = 0; j <= 10; j++)
{
Vector3 pos = new Vector3(Random.Range(positionX[i,0], positionX[i,1]), 0.5f, Random.Range(positionZ[i,0], positionZ[i,1]));
Instantiate(wall, pos, Quaternion.identity, transform);
}
}
}
}
You can use GetComponent when Instantiating your objects. After that, you can either store them or mutate them however you like. There are also other functions such as GameObject.FindGameObjectsWithTag and FindObjectsOfType. I would only use these if you absolutely need and do not use them in functions called often like Update. Any of the Find methods come with a decent amount of overhead.
I am unsure of what your script is called that you want to access on your objects, so I will fill it in with YourScriptHere.
// if you want to store the objects, creat a list to reference them
private List<YourScriptHere> yourScripts = new List<YourScriptHere>();
void GenerateMaze(){
//For every plane
for (int i = 0; i<=2 ; i++){
//Place some walls on that plane
for (int j = 0; j <= 10; j++)
{
Vector3 pos = new Vector3(Random.Range(positionX[i,0], positionX[i,1]), 0.5f, Random.Range(positionZ[i,0], positionZ[i,1]));
YourScriptHere tmpScript = Instantiate(wall, pos, Quaternion.identity, transform).GetComponent<YourSciptHere>();
// mutate the data in tmpScript however you like
tmpScript.FunctionCall(data);
// if you want to track and store the data, then add it to a list for further processing later
yourScripts.Add(tmpScript);
}
}
}
If you just want the position, you can just access the instantiated object and grab its Transform, then access the position. If you want to access objects by position later and you are storing the positions, you can make a <Vector3, GameObject> Dictionary.
i am new here. I was studing procedural terrain with meshes. And i started a make a procedural terrain like Minecraft. And i don't know how i can remove the hidden vertices and triangles in the hidden blocks.
The hidden vertices and triangles
My code:
Voxel.cs:
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshCollider))]
[RequireComponent(typeof(MeshRenderer))]
public class Voxel : MonoBehaviour {
public Vector3[] vertices;
public int[] triangles;
public Vector3 pos;
public GameObject chunk;
Mesh mesh;
public bool blockSolid;
public Voxel(Vector3 position, GameObject obj)
{
pos = position;
chunk = obj;
}
void Start ()
{
mesh = new Mesh();
//Cube(0,0,0);
}
public void Cube(int x, int y, int z)
{
GameObject cube = new GameObject("Cubo");
cube.AddComponent(typeof(MeshFilter));
cube.AddComponent(typeof(MeshCollider));
cube.AddComponent(typeof(MeshRenderer));
cube.transform.parent = chunk.transform;
mesh = cube.GetComponent<MeshFilter>().mesh;
//cube.transform.position = pos;
vertices = new Vector3[]
{
new Vector3(x,y,z), // 0
new Vector3(x,y+1,z), // 1
new Vector3(x+1,y+1,z), // 2
new Vector3(x+1,y,z), // 3
new Vector3(x+1,y,z+1), // 4
new Vector3(x+1,y+1,z+1), // 5
new Vector3(x,y+1,z+1), // 6
new Vector3(x,y,z+1) // 7
};
triangles = new int[]
{
0,1,2, 0,2,3, // Face frontal
3,2,5, 3,5,4, // Face direita
0,7,6, 0,6,1, // Face esquerda
7,4,5, 7,5,6, // Face traseira
1,6,5, 1,5,2, // Face superior
0,3,4, 0,4,7 // Face inferior
};
UpdateMesh();
}
void UpdateMesh()
{
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.RecalculateNormals();
}
}
Chunk.cs:
using System.Collections.Generic;
using UnityEngine;
public class Chunk : MonoBehaviour {
public int tx, ty, tz;
public Vector3 pos;
IEnumerator BuildChunk()
{
for (int x = 0; x < tx; x++)
{
for (int y = 0; y < ty; y++)
{
for (int z = 0; z < tz; z++)
{
pos = new Vector3(x,y,z);
Voxel block = new Voxel(pos, this.gameObject);
block.Cube(x,y,z);
yield return null;
}
}
}
}
void Start ()
{
StartCoroutine(BuildChunk());
}
}
I just need to remove the hidden vertices and triangles, but i don't know how i gonna make this.
The trick is to generate only the triangles that are visible to the player. So you shouldn't be generating cubes for every voxel, but a subset of the faces of a cube.
The way you go about this, is to check all 6 sides of each voxel. If a given side borders another voxel, then ignore that side and move on. If there is no voxel bordering the side, then add the corresponding cube face to that side. This will result in a monolithic mesh encompassing your entire terrain, or the terrain you loaded in.
I am attempting to use an overlapSphere to determine whether a tile within a tile map contains a game object. Then, if the tile is not already occupied, a new game object will be spawned in that tile (if certain other conditions are met). I'm trying to prevent game objects from overlapping with one another. However, I am having trouble centering the spheres about the right location.
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Vector3 tilePos = tileMap.GetCellCenterWorld(new Vector3Int(x, y, 0));
if (terrainMap[x, y] == blocked)
{
bool spawn = detectOverlap(tilePos, obstCheckRad);
if (spawn)
{
GameObject obstacle = Instantiate(obstaclePrefab) as GameObject;
obstacle.transform.position = tilePos;
obstacle.transform.eulerAngles = new Vector3(0, 0, (Random.Range(0, 359)));
}
}
}
}
}
public bool detectOverlap (Vector3 center, float radius)
{
Collider[] collider = Physics.OverlapSphere(center, radius);
if (collider.Length == 0)
{
return true;
}
else
{
return false;
}
}
I am using "GetCellCenterWorld" to as the center of my overlapSpheres, however they always seem to originate in the bottom left corner of the map, causing this region of the map to be completely empty, and objects elswhere in the map to still overlap with one another. (see image)
My confusion is based around the fact that I am using the same vector3 (tilePos) to place down the game objects. The objects are all moved to the correct position, however, the overlapSpheres all stay at the bottom left.
I am making a grid based game. For it I need to debug certain tiles, for example where there is a collision and where the player is aiming. I have made a debugger class that draws a square mesh at a position using a color.
public class GridDebugger : MonoBehaviour {
[SerializeField] private float alpha;
private Mesh mesh;
private Material mat;
void Start() {
mat = new Material(Shader.Find("Sprites/Default"));
mesh = new Mesh();
mesh.vertices = new Vector3[] { new Vector3(.5f, .5f, 0), new Vector3(.5f, -.5f), new Vector3(-.5f, -.5f), new Vector3(-.5f, .5f) };
mesh.triangles = new int[] { 0, 1, 2, 2, 3, 0 };
}
public void Debug(Vector3 position, Color color) {
color.a = alpha;
mat.color = color;
Graphics.DrawMesh(mesh, position, Quaternion.identity, mat, 0);
}
}
Currently the class has two users:
[RequireComponent(typeof(GridDebugger))]
public class CollisionSystem : GridSystem {
private List<Node>[,] grid;
private GridDebugger debugger;
void Awake() {
debugger = GetComponent<GridDebugger>();
}
//Logic....
void Update() {
if (grid != null) {
for (int x = 0; x < grid.GetLength(0); x++) {
for (int y = 0; y < grid.GetLength(1); y++) {
if (grid[x, y] != null) {
for (int i = 0; i < grid[x, y].Count; i++) {
if (grid[x,y][i].Parent.Debug) {
Vector3 worldPos = GridToWorldPos(new Vector2Int(x, y), grid.GetLength(0), grid.GetLength(1));
worldPos.z = -0.0001f;
debugger.Debug(worldPos, Color.Red);
}
}
}
}
}
}
}
}
And
public class GridMeleeWeapon : MonoBehaviour {
private GridDebugger debugger;
void Awake() {
debugger = FindObjectOfType<GridDebugger>();
}
public void Aim(Vector2 dir) {
Vector3 position = transform.position + dir;
debugger.Debug(position, Color.blue);
}
}
In CollisionSystem I loop every collider and draw a red square at every position.
In GridMeleeWeapon I just draw a blue square where the player aims.
The issue is that every square is drawn with the color used in CollisionSystem i.e red. When drawing the blue squares in GridMeleeWeapon they turn out red. If I remove debugger.Debug(worldPos, Color.Red) in CollisionSystem the squares I draw from GridMeleeWeapon are displayed blue.
With the current code I can't seem to draw meshes of different colors. What I think is happening is that the color specified by the first caller are used for consecutive calls, even though the color of the material is changed but why I have no idea.
How do I set the color correctly?
Material in Unity 3D is a reference type.
Your code in GridMeleeWeapon and CollisionSystem use the same instance of GridDebugger and material. What happens is since you change the material color OnUpdate() it will change the color from blue to red instantly (every frame) on each object using this material.
Instead of passing a color to your debugger().Debug() method, pass a material with a different color. For instance create a material with color red and another material with color blue. Use these materials to set the color.
I am creating an infinite terrain generator and I need to constantly update the terrain as the player moves around.
Everything is fine, but I am having trouble with finding information about the fastest method of creating and rendering sprites on the fly.
Information about sprites:
I am using 1 sprite sheet which has all the frames I need for my terrain. Grass, sand, water etc. all in 1 single .png file. All frames are stored in an array from which I can easily grab them.
Steps I need to do to display my sprite object correctly currently:
Create new object.
Set their position in 2d space.
Add component.
Scale them as needed.
Generated sprites get stored in a GameObject array called chunk. This is the way I am currently generating sprites.
chunk[i] = new GameObject();
chunk[i].gameObject.transform.position = new Vector2(spriteCoordX, spriteCoordY);
chunk[i].AddComponent<SpriteRenderer>();
SpriteRenderer renderer = chunk[i].GetComponent<SpriteRenderer>();
renderer.sprite = tiles[calculatedFrameId]; //Set correct sprite frame.
chunk[i].gameObject.transform.localScale = new Vector2(6.75f , 6.75f);
I don't know, adding component and scaling every single time I want to create a new sprite in code seems redundant and unnecessary and I am sure there is a better way to do that.
To sum up:
I need the best (fastest) possible way to generate large number of sprites, set their frame, position and proper scale.
Cannot help posting this image here, as this is really of thousands of words, thanks #AidenH for the "Object pooling" comment!
I apologize this took me some time to get to... I ended up creating a project where I generate an open world, and I use Perlin Noise to generate portions of the map depending on where the player is(Since I dont have an actual terrain)
I used Object Pooling for this, so I have a World Manager that knows what the world looks like (or in my case it uses Perlin Noise), this WorldManager pools tiles.
NOTE: This is one way to do it, using Object Pooling.
So basically it would look something like this:
public class WorldManager : MonoBehaviour {
// Being cheap here for the time, if you use this route make sure to use a proper singleton pattern
static public WorldManager instance;
[SerializeField] // Created a prefab for all my tile objects
private TileObject tilePrefab;
[SerializeField]
private int StartingTilePool = 300;
[SerializeField] // In my case this list stored 1 sprite, and I just changed that sprite color depending on the value of perlin noise
List<Sprite> terrainSprites;
private Queue<TileObject> objectPool = new Queue<TileObject>();
void Start() {
instance = this; // Again use a proper singleton pattern in your project.
GenerateTilePool();
LoadFirstSetOfTiles();
}
private void LoadFirstSetOfTiles()
{
// my player always starts at 0,0..
for(int x = -SpawnTileBoundry.HorizontalExtents; x <= SpawnTileBoundry.HorizontalExtents; ++x)
{
for(int y = -SpawnTileBoundry.VerticalExtents; y <= SpawnTileBoundry.VerticalExtents; ++y)
{
SetActiveTile(x,y);
}
}
}
private void GenerateTilePool()
{
for(int i =0; i < tilesToGenerate; ++i)
{
TileObject tempTile = Instantiate(tilePrefab);
EnqueTile(tempTile);
}
}
public void EnqueTile(TileObject tile)
{
objectPool.Enqueue(tile);
tile.gameObject.SetActive(false);
}
public void SetActiveTile(int x, int y)
{
TileObject newTile = null;
if(objectPool.count > 0)
{
newTile = objectPool.Dequeue();
}
else
{
// We didn't have enough tiles store in our pool so lets make a new 1.
newTile = Instantiate(tilePrefab);
}
newTile.transform.position = new Vector3(x,y,1); // Used 1 to put it behind my player...
newTile.gameObject.SetActive(true);
// The sprite here would be based off of your world data, where mine is only a white box, I use the second parameters to give it a gray-scaled color.
newTile.UpdateSprite(terrainSprites[0], Mathf.PerlinNoise(x/10.0f, y / 10.0f));
}
}
That is just my WorldManager that handles ObjectPooling for me...
Here is my TileObject
[RequireComponent(typeof(SpriteRenderer))]
public class TileObject : MonoBehaviour {
private SpriteRenderer myRenderer;
private void Awake() {
myRenderer = getComponent<SpriteRenderer>();
}
void Update()
{
if(Mathf.Abs(transform.position.x - Camera.main.transform.position.x) > SpawnTileBoundry.HorizontalExtents || Mathf.Abs(transform.position.y - Camera.main.transform.position.y) > SpawnTileBoundry.VerticalExtents)
{
// With this check the tile knows it is no longer visible,
// I could have used OnBecameInvisible to handle this
// but I added a threshold to the extents, that prevent it so
// players would see tiles "appearing" this caused a nice bug, where
// if you moved just right a row/col of tiles wouldn't spawn.
WorldManager.instance.EnqueTile(this);
}
}
public void UpdateSprite(Sprite sprite)
{
myRenderer.sprite = sprite;
}
public void UpdateSprite(Sprite sprite, float grayColor)
{
UpdateSprite(sprite);
myRenderer.color = new Color(grayColor,grayColor,grayColor,1f);
}
}
Here is my SpawnTileBoundry script:
public class WorldManager : MonoBehaviour {
private int lastX = 0;
private int lastY = 0;
static public int HorizontalExtents;
static public int VerticalExtents;
void Start() {
VerticalExtents = (int)Camera.main.orthograpicSize + 2; // +2 is just my threshold
HorizontalExtents = (int)(VerticalExtents * Screen.width/Screen.height) +3; // +3 is just my threshold you can change these to what you want.
lastX = (int)transform.position.x;
lastY = (int)transform.position.y;
}
void Update() {
int newX = (int)transform.position.x;
int newY = (int)transform.position.y;
HandleNewTileSpawn(lastX - newX, lastY - newY);
}
// This will tell the WorldManager which tiles need to appear
// We are no longer creating new tiles unless we absolutely have to.
// We are also only making new tiles appear in the circumstance that
// we are about to see them.
void HandleNewTileSpawn(int x, int y)
{
if(x != 0)
{
// This code could be refactor to a method so it was less error prone for changes or tweaks...
if(x < 0)
{
for(int i = lastY - VerticalExtents; i < lastY + VerticalExtents; ++i)
{
WorldManager.instance.SetActiveTile(lastX + HorizontalExtents, i);
}
}
else
{
for(int i = lastY - VerticalExtents; i < lastY + VerticalExtents; ++i)
{
WorldManager.instance.SetActiveTile(lastX - HorizontalExtents, i);
}
}
lastX = (int)transform.position.x;
}
if(y != 0)
{
if(lastY < 0)
{
for(int i = lastX - HorizontalExtents; i < lastX + HorizontalExtents; ++i)
{
WorldManager.instance.SetActiveTile(i, lastY + VeritcalExtents);
}
}
else
{
for(int i = lastX - HorizontalExtents; i < lastX + HorizontalExtents; ++i)
{
WorldManager.instance.SetActiveTile(i, lastY - VeritcalExtents);
}
}
lastY = (int)transform.position.y;
}
}
}
I am using the WorldManager to handle my object pooling, yes I am still instantiating quite a few sprites at the beginning but then I eventually stop spawning as there is no need to keep spawning new objects.
Unity doesn't have alot in their documentation in regards to object pooling however they do have a video tutorial that goes over some of the basics of Object Pooling: https://unity3d.com/learn/tutorials/topics/scripting/object-pooling