I have a programmatically generated board of a certain size. In order to see it, I would normally have to hit run, which is quite annoying and makes it hard to visualize.
Is it possible to make, and thus be able to visualize, the board via InitializeOnLoad?
I gave it a try:
Setup.cs
[InitializeOnLoad]
public class Setup : MonoBehaviour {
public static Map initialMap;
static Setup(){
initialMap = new Map ();
initialMap.createMap ();
}
}
Map.cs
public class Map {
private Tile[,] tiles= new Tile[5,5];
//I had Resources.Load here, but apparently thats not allowed either...
public GameObject defaultObj= new GameObject("MyCreatedGO");
public Map (){
Debug.Log("In Constructor");
}
public void createMap(){
for (int x = 0; x < tiles.GetLength(0); x += 1) {
for (int y = 0; y < tiles.GetLength(1); y += 1) {
// Tile Instantiates the defaultObj
tiles [x, y] = new Tile (defaultObj, new Vector3 (x, y, 0));
}
}
}
}
But unity responded by complaining that
UnityException: Internal_CreateGameObject is not allowed to be called
from a MonoBehaviour constructor (or instance field initializer), call
it in Awake or Start instead. Called from MonoBehaviour 'Setup' on
game object 'Map'.
Is the engine even in a state where it can make objects when the InitializeOnLoad-ed static constructor happens?
If this isn't possible, how are you supposed to visualize a procedurally generated map in Unity?
I'm not sure if Unity can do that, but you can work around it.
Make a width and height variable, then expose them to the editor - either by making them public or by [SerializeField] attribute, and then edit them in the inspector while the editor is running the game. Use those variables for your x and y values in createMap(). Then you'd just have to add a section like this in Update(), to generate a new map on left click:
if (Input.GetMouseButtonDown(0))
{
// call generate map here
}
Check out this tutorial, too: https://unity3d.com/learn/tutorials/projects/procedural-cave-generation-tutorial/cellular-automata
You can create a custom editor for your Setup script.
You can add a couple of buttons to it like Create and Destroy and, onClick, you can execute every code you want at edit-time as opposed to runtime.
Here's the manual with useful informations on how to execute code in the editor and a video tutorial for writing Editor extensions.
Related
First of all: I'm quite new to unity and c#, so please be nice if I ask dumb questions.
I'm trying to make a game like minesweeper. So I want an easy grid with covered tiles and if you klick on one, it opens up.
I use a main script I create a grid like so:
private void CreateTieleSet(int width, int height){
_tiles = new Dictionary<Vector2, Tile>();
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
var spawnedTile = Instantiate(_tilePrefab, new Vector3(x, y), Quaternion.identity);
spawnedTile.name = $"Tile {x} {y}";
spawnedTile.status = field[x, y];
_tiles[new Vector2(x, y)] = spawnedTile;
}
}
}
I use the function
public Tile GetTileAtPosition(Vector2 pos)
{
if(_tiles.TryGetValue(pos, out var tile))
{
return tile;
}
return null;
}
to get the tile at the position xy, but I can't use it since
Tile tempTile = GetTileAtPosition(new Vector2(x, y));
tempTile.ChangeSprite(field[x, y]); //example-funktion from tile-script
allways results in NullReferenceExeption-Error. I know the probleme, since I allways struggle with using scripts from other tiles. Bus usually I can use [SerializeField] Tile... and than dragdrop it onto it. In this case however I obvioulsy can't do that.
Btw: I realy tried to solve this problem with other solutions found online, but everyone has complete different ideas how to do it and nothing seems to work for me :/
Your best bet for this one is going to be using Unity's built-in Tilemaps - you'll want to take your sprites for Minesweeper and add them to a TilePalette, and paint them onto a GameObject with a Grid component, with children GameObjects holding a Tilemap component - one per layer. Then, when you want to change the sprite, you can do something like the following:
using UnityEngine;
using UnityEngine.Tilemaps;
class GridManager
{
[SerializeField] Tilemap gridDisplayLayer;
public void ChangeSpriteAtLocation(Vector3Int cellLocation, Tile tileToChangeTo)
{
gridDisplayLayer.SetTile(cellPosition, tile);
}
}
If you want the simplest answer, just add Tile members to the class above for swapping between them and call ChangeSpriteAtLocation during runtime. Might need to change the flags on the Tilemap to change the sprite and such, but I think that should be good enough to get you started :) When you generate the TilePalette, Unity will give you Tile assets to save that you can then reference for the above example method.
I'm still getting all the intricacies of Tilemaps myself, though, and I'm definitely more design minded, so I won't be surprised if someone has a more succinct or efficient solution.
Hope this helps get you started, though!
Vellv
I'm doing a unity project for an exam I'm attending, so I'm not really good at unity, hope you can help.
I'm doing this 2d endless runner game and I created an empty object to set inactive all the platforms that the player has overtaken. The object moves forward and when its position is > than the platform position, it sets it inactive (since destroying them also removes them from the list so it wouldnt work for others platforms of the same kind). My problem is that I've created a list in which I put all the types of my prefabs but the way I did it, it only sets inactive the first prefab of a kind and then stops working.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DistruttoreLivelli : MonoBehaviour
{
public List<GameObject> objs;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
for (int i = 0; i < objs.Count; i++)
{
if (objs[i].transform.position.x < transform.position.x)
{
objs[i].SetActive(false);
}
}
}
}
How can I manage to make it go forever? I tried to use a while but it makes unity crash. :/
Ty for the help!
The easiest way to make it endless is to move the objects to the furthest position instead of deactivating them. Instead of objs[i].SetActive(false); it should be something like:
Vector3 endOfTheLinePos = objs[i].transform.position;
endOfTheLinePos .x = maxX;
objs[i].transform.position = endOfTheLinePos;
With maxX being a float parameter of the highest value of x that you are using on the objects.
Also, as already pointed out, on the for you should use i < objs.Count instead.
We're trying to put positions in Vector3 array by taking their position in another vector and putting it in as an index in the Vector3 array. We're trying to make a board game,and we want to move the player to one of the positions of the Vector3 array.
public static Vector3[] Steps;
public static void Position()
{
Vector3 step = GameObject.Find("Step").transform.position;
Steps[0] = step;
Vector3 step1 = GameObject.Find("Step (1)").transform.position;
Steps[1] = step1;
and so on
Before assigning values to the array you will have to initialize it with a size like e.g.
public static Vector3[] Steps;
public static void Position()
{
Steps = new Vector3[someSize];
Vector3 step = GameObject.Find("Step").transform.position;
Steps[0] = step;
Vector3 step1 = GameObject.Find("Step (1)").transform.position;
Steps[1] = step1;
}
that someSize ofcourse has to match the amount of positions you are going to add.
Or instead rather do it dynamically using a list like
public static List<Vector3> Steps = new List<Vector3>();
public static void Position()
{
// optionally remove current values first
Steps.Clear();
Steps = new Vector3[someSize];
Vector3 step = GameObject.Find("Step").transform.position;
Steps.Add(step);
Vector3 step1 = GameObject.Find("Step (1)").transform.position;
Steps.Add(step1);
}
you access later exactly the same way
Steps[index]
Easier would be if you already can reference your GameObjects in the Scene beforehand you should do rather something like
[SerializeField] private List<Transform> _steps = new List<Transform>();
and simply reference all your GameObjects in this list via the inspector in Unity. This way you don't even need to get the positions directly but could simply publish this list like
[SerializeField] private List<Transform> _steps = new List<Transform>();
public static List<Transform> Steps;
private void Awake()
{
Steps = _steps;
}
and access them like
Steps[index].position
You could use a multi array:
List<List<Vector3>> Positions;
Then you will have a "2D board" and the positions look like so:
Positions[1][4];
Plus, your question is a bit confusing and hard to understand what you actually want.
Okay so guessing by your stated question I am guessing you are somewhat new to Unity right? So I will go over this a bit more detailed.
First thing I would fix is to make an instance of your class and not a static one. (Instanced one is the script that you can drog onto a game object) This is done so you can reference all objects much easily and actually make interacting of objects work. (If you wish to learn more about the use for this i can recommend you this video: https://www.youtube.com/watch?v=Rgvq8MUtfTo)
Next mistake would be that you did not initialize zhe array. Arrays in object oriented are a bit more tricky then in other languages like let's say Python for example. Here you need to explicitly either set the contents of the array when you initialize it, or at least define its length. This is something that you CANNOT change later on. If you wish to take a more dynamic approach at storing variables for C# I would take a look at Lists. (Check this out if you are unsure as to what exactly is different: https://www.educba.com/c-sharp-array-vs-list/)
And the final thing that is really bad if you get used to doing it is getting references to game object using the find method. While it works for sure and I'm guessing its fast for your example, once you fill your scene with game object it WILL grind to a halt. Its better to just drag refs to the object at the start OR if you are instantiating objects at runtime, you can keep track then.
So overall ASSUMING you have all the objects in the scene, I would solve your issue this way:
using UnityEngine;
public class GameManager : MonoBehaviour
{
public GameObject[] figures;
void Start(){
foreach(GameObject figure in figures){
//this is where you get all of their positions
Debug.log(figure.transform.position)
}
}
}
Bear in mind that you will have to assign the figure before the start to the component variable. You can read more about this here: https://docs.unity3d.com/Manual/VariablesAndTheInspector.html
Hope this helpsyou with your game!
I want an array of game objects, each game object has a simple script to move it. I want a controlling script to be able to trigger the remote script by referencing the array coordinates / game object at that point.
I am missing something basic in referencing a "global" variable - so apologies in advance. I have spent several hours now reading and trying things out.
Example questions like here Accessing a variable from another script C#
or
https://answers.unity.com/questions/42843/referencing-non-static-variables-from-another-scri.html
I believe the array should be static as there is only one set of data, however I don't care if it isnt
CreateGrid.cs
public class CreateGrid : MonoBehaviour
{
public static GameObject[,] gridArray = new GameObject[5, 5];
public GameObject gridSpace;
// Start is called before the first frame update
void Start()
{
GenerateGrid();
}
void GenerateGrid()
{
for (int x = 0; x < 5; x++)
{
for (int z = 0; z < 5; z++)
{
gridArray[x, z] = Instantiate(gridSpace, new Vector3(x, 0, z), Quaternion.identity) as GameObject;
}
}
// test grid works
// gridArray[2, 2].transform.Translate(0.0f, 3.0f, 0.0f);
}
}
test code
public class Pulse : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
for (int x = 0; x < 5; x++)
{
CreateGrid.gridArray[x, 3].GetComponent<MoveObject>().StartBounce(5.0f);
}
}
}
The grid of objects works.
However the test code errors with Object reference not set to an instance of an object from the line
CreateGrid.gridArray[x, 3].GetComponent().StartBounce(5.0f);
specifically CreateGrid.gridArray
Given that I seem to be really struggling with a concept, please explain clearly.
Thanks
Unless you don't clearly define otherwise, you don't know when the Start method of two MonoBehaviours will run.
In your case, the Pulse's Start method is called before CreateGrid's.
To fix your problem, I advise you to call CreateGrid's Generate method inside the Awake method of the class.
I often use this method to initialize members of the class not relying on other classes instances and use the Start method to initialize members needing other instances to be self-initialized.
I would like to change the color of multiple gameobjects in Unity using a single script. I'm kinda lost in the way how to do it. I'm new to Unity and this is some sort of basic training for me.
Unity version: 5.3.4
Observed Behavior:
Added the same script to the other gameobjects and all change to the same color
Expected Behavior:
Change the color of the gameobjects individually
List of things tried:
Using the -FindGameObject-
Tried to acces the materials using the -GameObject-
Tried both at the same time
Thinking in multiple scripts to achieve the results I want
Here's the code
C#:
using UnityEngine;
using System.Collections;
public class ChangeColor : MonoBehaviour
{
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
private Renderer cube;
private Renderer sphere;
void Start ()
{
//Tried here the -FindGameObjectWithTag-
cube = GetComponent<Renderer>();
sphere = GetComponent<Renderer>();
}
void Update ()
{
if(Input.GetKeyDown(KeyCode.A))
{
//Tried here the -FindGameObjectWithTag-
cube.material.color = Color.red;
}
if(Input.GetKeyDown(KeyCode.S))
{
//Tried here the -FindGameObjectWithTag-
sphere.material.color = Color.green;
}
}
}
Maybe I'm doing something wrong, as I said I'm new to Unity, I kindly accept any help, if it is noobfriendly the better.
Thanks
There are a couple different ways you can go about doing this.
If they are all under the same parent without other objects there too you could loop through the children and change their colors
GameObject Parent = GameObject.Find("ParentObject");
for( int x = 0; x > Parent.transform.childCount; x++);
{
Parent.transform.GetChild(x).GetComponent<Renderer>().material.color = Color.red;
}
If there are less than say 20ish, you could create a list and simply drag and drop each transform into the inspector after changing the list's size to the number of objects you have.
/*Make sure you have Using "System.Collections.Generic" at the top */
//put this outside function so that the inspector can see it
public List<Transform> Objs;
// in your function put this (when changing the color)
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red;
}
Similarly to 2, if you want to do it all in code you can do
//Outside function
public List<Transform> Objs;
//inside function
Objs.Add(GameObject.Find("FirstObject").transform);
Objs.Add(GameObject.Find("SecondObject").transform);
//... keep doing this
//now do the same foreach loop as in 2
You could search by tag (if you have a lot of objects) (i would imagine that this would take a bit longer just because it is going through each component but I have no evidence to back that up)
//Outside function
public GameObject[] Objs;
//inside function
Objs = GameObject.FindGameObjectsWithTag("ATagToChangeColor");
foreach(Transform Tform in Objs){
Tform.GetComponent<Renderer>().material.color = Color.red();
}
And about this comment:
//If I change these variables to -GameObject-
//It blocks me to access the renderer
//Making the variables public doesn't work either
If you make them of type GameObject then you should easily be able to access the renderer simply by doing this:
public GameObject cube;
public void Start(){
cube.GetComponent<Renderer>().material.color = Color.red();
}
And making the variables public allows unity's inspector to see the variables so that you can change them in the unity editor without having to open the script.