I have enums set up in a script called Grid, as follows:
public enum CellType
{
Empty,
Road,
Structure,
SpecialStructure,
None
}
Then I have a script called placement manager which adds data relevant to that enum like this:
internal void PlaceObjectOnTheMap(Vector3Int position, GameObject structurePrefab, CellType type, int width = 1, int height = 1)
{
StructureModel structure = CreateANewStructureModel(position, structurePrefab, type);
var structureNeedingRoad = structure.GetComponent<INeedingRoad>();
if (structureNeedingRoad != null)
{
structureNeedingRoad.RoadPosition = GetNearestRoad(position, width, height).Value;
Debug.Log("My nearest road position is: " + structureNeedingRoad.RoadPosition);
}
for (int x = 0; x < width; x++)
{
for (int z = 0; z < height; z++)
{
var newPosition = position + new Vector3Int(x, 0, z);
placementGrid[newPosition.x, newPosition.z] = type;
structureDictionary.Add(newPosition, structure);
DestroyNatureAt(newPosition);
}
}
}
finally, I have another script called Grid Helper which is supposed to call placement manager and add itself to the grid which I've set like this:
public enum CellType
{
Empty,
Road,
Structure,
SpecialStructure,
None
}
[SerializeField]
private CellType structureType = CellType.Empty;
public PlacementManager placementManager;
// Start is called before the first frame update
void Start()
{
placementManager.PlaceObjectOnTheMap(new Vector3Int(Mathf.FloorToInt(transform.position.x),
Mathf.FloorToInt(transform.position.y),
Mathf.FloorToInt(transform.position.z)), this.gameObject, structureType);
}
but I somehow keep getting told Argument3 cannot convert from GridHelper.CellType to CellType.
What am I doing wrong?
Remove your enum in the GridHelper or in the Grid class and use GridHelper.CellType or Grid.CellType in the other class depending on what you removed.
Related
I have a code for a crafting system that checks if the inventory has the ingredients needed to craft an item and adds a button to craft it. The problem is when I want to position my button it goes way off the canvas. I have seen some people saying that it has something to do with rect transform. I've been stuck with it for over an hour. Any help is appreciated.
I have tried
removing the setparent() function,
using anchoredPosition,
using localPosition
My code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Crafting : MonoBehaviour
{
public List<recipe> recipes = new List<recipe>();
public GameObject base_item, parent;
List<GameObject> items = new List<GameObject>();
public int y = 75;
public int x = -45;
public Inv inv;
private void Start()
{
inv = GetComponent<Inv>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
checkitems();
Debug.Log("y = " + y + " x = " + (x - 40));
}
}
public void checkitems()
{
for (int i = 0; i < recipes.Count; i++)
{
recipe r = recipes[i];
for (int x = 0; x < r.ingredients.Count; x++)
{
if (!inv.hasitem(r.ingredients[x])){
return;
}
}
showitem(r.result);
}
}
public void onClick(int _slot)
{
recipe r = recipes[_slot];
for (int i = 0; i < r.ingredients.Count; i++)
{
inv.removeitem(inv.getitem(r.ingredients[i]));
}
inv.additem(inv.getFirstAvailable(), r.result, r.stack);
}
public void showitem(string name)
{
GameObject obj = Instantiate(base_item);
if (items.Count != 0)
{
if (((items.Count) % 3) != 0)
{
Debug.Log("first thing");
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
x = x + 40;
Debug.Log("x + 40");
}
else if (((items.Count + 1) % 3) == 0)
{
Debug.Log("second thing");
x = -45;
Debug.Log("x + 40");
y = y + 40;
Debug.Log(" y + 40");
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
}
}else
{
obj.GetComponent<RectTransform>().position = new Vector2(x, y);
obj.transform.SetParent(parent.transform);
obj.SetActive(true);
items.Add(obj);
x = x + 40;
Debug.Log("x + 40");
}
}
}
Blue circle where it spawns. Red circle where I want it to be
Seems you are confusing a bunch of terms for being the issue of your problem. Firstly I want to address the red X over your scroll bar. Whenever this occurs, it means that your RectTransform of this UI object has been dragged from its positive vertices to negative or vice versa, causing it to almost invert. I would correct this but it is not the reason your objects are not childing correctly.
Generally, with UI objects, I would never use LocalPosition, just AnchoredPosition. LocalPosition is a field from Transform which I believe RectTransform inherits from. As RectTransforms have a lot of modifications to their position from pivots, anchors, and anchored positions, the LocalPosition will most likely need to recalculate data to properly move the object, whereas AnchoredPosition has already done these calculations.
I believe the issue with your current code is how you are using SetParent. There is a second parameter of SetParent which governs whether the object keeps the same position based in world space after being childed. As you are not passing in a new bool for this parameter, it is defaulting to true. As you want your objects to be childed to the parent but not keep their world space positions, you would want to pass in false.
In your case, as it looks as if you want to set objects in a grid-like pattern childed to this ScrollRect, I would attach a GridLayoutGroup to the Content of your scroll and child the new objects to this object. You can set the max columns of this grid and spacing to give the same layout you are attempting to achieve in code.
To summarize, I would remove all the hand placement you are doing in code with LocalPosition and AnchorPosition and just attach a GridLayoutGroup. To fix the current positioning of your objects relative to the parent, change all lines of obj.transform.SetParent(parent.transform); to obj.transform.SetParent(parent.transform, false);. If you want to keep changing position locally in code instead of a layout element, use SetParent first, and use AnchoredPosition instead of LocalPosition as the SetParent with false passed in will override the position you set.
I am struggling with how to make my board consist of a number of individual instances of my tile class. This is what I've tried so far.
I also eventually want animals to move on the board, but that is for later.
class Tile
{
public int xAxis = 0;
public int yAxis = 0;
public string type = "blank";
public Tile(int xAxis, int yAxis, string type)
{
this.xAxis = xAxis;
this.yAxis = yAxis;
this.type = type;
}
}
class Program
{
static void Main(string[] args)
{
int width = 20;
int height = 20;
int[,] grid = new int[20, 20];
grid[2, 3] = 3;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Console.Write(grid[x, y] + "");
}
Console.WriteLine();
}
Console.ReadLine();
}
}
I suggest you base your project on the design pattern state in order to complete your project.
Since I get you are a beginner, I made you a fully working example you can base your project on, understand it and continue your work to add the animals behaviors your wanted to add.
Here is a game where the tiles change depending on their neighbors and the initial state.
You can see good practices in the comments I left you too.
The GitHub link of the simple board game in C# follows :
Simple board game project
Have fun creating your game and I hope I was helpful!
Cheers
I am attempting to use a custom FlowLayoutGroup as described in the answers of this question ( also up on GitHub) in a situation where it needs to resize vertically to contain it's children.
My setup looks like this:
ScrollableRect
Panel with VerticalLayoutGroup comp (content of parent scrollrect) that should resize vertically to fit children:
Panel with FlowLayoutGroup that should resize vertically to fit children
Panel with FlowLayoutGroup (2) also must resize...
etc...
I have added a content size fitter to the FlowLayoutGroup, tweaked the layout child size controls of the vertical group, but with no success.
The user may add and remove children of the groups while the app is running and I want the UI to respond so it is not possible to set the height of everything in advance.
I have also looked in the unity source code to try and figure out how to write this into the component myself. This is looking the best bet but taking me considerable time as I'm new to Unity and C#. Hoping someone has solved a similar problem already.
Everything functions as desired/expected except for the missing behaviour of LayoutGroups resizing to fit their children vertically.
How can I do this?
After some time and a tumbleweed badge I've decided to put the time in to make a solution, hopefully someone else benefits too.
Again, this is a modified version of the work done here. Thanks for that. This component now computes it's own preferred size.
Main changes:
I stripped it back quite severely:
All horizontal overrides are emptied, I only need the horizontal wrapping behaviour
Removed some apparent hangover variables from GridLayout class
Logic to calculate child positions and in turn number of rows, preferred height is in it's own method.
Child positions are stored in an Vector2 array to separate calculation from child setting.
This fixes the problem of height of the entire component not adjusting, it also immediately responds, with the original script because of the way children rectTransforms were set then accessed the script took two 'cycles' to recognize the dimensions of a child.
This suits all my needs, I imagine it can be fairly easily reworked to handle vertical wrap too...
using UnityEngine;
using UnityEngine.UI;
[AddComponentMenu("Layout/Wrap Layout Group", 153)]
public class WrapLayoutGroup : LayoutGroup
{
[SerializeField] protected Vector2 m_Spacing = Vector2.zero;
public Vector2 spacing { get { return m_Spacing; } set { SetProperty(ref m_Spacing, value); } }
[SerializeField] protected bool m_Horizontal = true;
public bool horizontal { get { return m_Horizontal; } set { SetProperty(ref m_Horizontal, value); } }
private float availableWidth { get { return rectTransform.rect.width - padding.horizontal + spacing.x; } }
private const float MIN_HEIGHT = 80;
private int preferredRows = 1;
private float calculatedHeight = MIN_HEIGHT;
private Vector2[] childPositions = new Vector2[0];
protected WrapLayoutGroup()
{ }
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
}
#endif
public override void CalculateLayoutInputVertical()
{
calculatePositionsAndRequiredSize();
SetLayoutInputForAxis(calculatedHeight, calculatedHeight, -1, 1);
}
public override void SetLayoutHorizontal() { }
public override void SetLayoutVertical()
{
SetChildren();
}
private void SetChildren()
{
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform child = rectChildren[i];
SetChildAlongAxis(child, 0, childPositions[i].x, LayoutUtility.GetPreferredWidth(child));
SetChildAlongAxis(child, 1, childPositions[i].y, LayoutUtility.GetPreferredHeight(child));
}
}
private void calculatePositionsAndRequiredSize()
{
childPositions = new Vector2[rectChildren.Count];
Vector2 startOffset = new Vector2(
GetStartOffset(0, 0),
GetStartOffset(1, 0)
);
Vector2 currentOffset = new Vector2(
startOffset.x,
startOffset.y
);
float childHeight = 0;
float childWidth = 0;
float maxChildHeightInRow = 0;
int currentRow = 1;
for (int i = 0; i < rectChildren.Count; i++)
{
childHeight = LayoutUtility.GetPreferredHeight(rectChildren[i]);
childWidth = LayoutUtility.GetPreferredWidth(rectChildren[i]);
//check for new row start
if (currentOffset.x + spacing.x + childWidth > availableWidth && i != 0)
{
currentOffset.x = startOffset.x;
currentOffset.y += maxChildHeightInRow + spacing.y;
currentRow++;
maxChildHeightInRow = 0;
}
childPositions[i] = new Vector2(
currentOffset.x,
currentOffset.y
);
//update offset
maxChildHeightInRow = Mathf.Max(maxChildHeightInRow, childHeight);
currentOffset.x += childWidth + spacing.x;
}
//update groups preferred dimensions
preferredRows = currentRow;
calculatedHeight = currentOffset.y + maxChildHeightInRow + padding.vertical - spacing.y;
}
}
I can set four different movement directions for my player
Vector2.up => (0,1)
Vector2.down => (0,-1)
Vector2.left => (-1,0)
Vector2.right => (1,0)
and I have a two dimensional array that contains Cell objects
public class Cell
{
public Cell(GameObject cellObject, bool isObstacle)
{
CellObject = cellObject;
IsObstacle = isObstacle;
}
public GameObject CellObject { get; set; }
public bool IsObstacle { get; set; }
}
My array is initialized by the size of the map.
private const int MAP_SIZE = 10;
private Cell[,] mapCells = new Cell[MAP_SIZE, MAP_SIZE];
I fill this array by using two loops, this will give me 100 cells.
for (int x = 0; x < MAP_SIZE; x++)
{
for (int y = 0; y < MAP_SIZE; y++)
{
Vector3 newCellPosition = new Vector3(x, 0, y);
GameObject newCellObject = Instantiate(cell, newCellPosition, cell.transform.rotation);
bool isObstacle = false; // TEST
Cell newCell = new Cell(newCellObject, isObstacle);
mapCells[x, y] = newCell;
}
}
When moving the player I want to return the Cell he has to move to. The movementDirection parameter will set the row and the column to search for.
If there is an obstacle cell the player should just move to this obstacle and stop.
public Cell GetTargetCell(Vector2 movementDirection)
{
Cell targetCell = null;
// get the path
// get the closest obstacle
return targetCell;
}
Is there an elegant way to calculate the correct target cell by a 2D direction?
I think the most elegant way of doing it is by using two separate for loops and the ?: operator.
//returns cell
Cell GetTargetCell(Vector2 dir)
{
if(dir.y == 0) //if we're going horizontal
{
//HorizontalMovement
for(int i = 0; i < ((dir.x==1)?mapSize-PLAYER_X:PLAYER_X); i++)
{
if(mapCells[(int)dir.x*i,PLAYER_Y].IsObstacle()) //if we encounter an obstacle
return mapCells[(int)dir.x*i,PLAYER_Y]; //return cell that's an obstacle
}
//if we didn't encounter an obstacle
return mapCells[(dir.x == 1)?mapSize:0,PLAYER_Y]; //return the cell
}
else if(dir.x == 0)
{
//VerticalMovement
//copy paste the previous for loop and edit the parameters, I'm too lazy :P
}
else
{
//NoMovement
Debug.Log("Please enter a valid Direction");
return mapCells[0,0];
}
}
Replace the PLAYER_X and PLAYER_Y values, with the x, and y values of the cell the player is currently in. I didn't check if the code contains any errors, but I think it should work.
I want to create a grid with sprites in Unity. Each cell should have a number on it.
This is how it should look
and my grid looks this
So I generate the cells and add them to a empty gameobject called
Map
private GameObject cellPrefab;
private const int CELL_COUNT_X = 10; // create 100 cells
private const int CELL_COUNT_Y = 10;
private const float CELL_SPACING = 1.1f; // with a small spacing
private List<Cell> cells = new List<Cell>(); // store all cells here
private const int NUM_RANGE_MIN = 1; // cell value range
private const int NUM_RANGE_MAX = 10;
private void Start()
{
cellPrefab = Resources.Load(StringCollection.CELL) as GameObject;
for (int x = 0; x < CELL_COUNT_X; x++)
{
for (int y = 0; y < CELL_COUNT_Y; y++)
{
float spawnPosX = x * CELL_SPACING - CELL_COUNT_X / 2;
float spawnPosY = y * CELL_SPACING - CELL_COUNT_Y / 2;
GameObject cell = Instantiate(cellPrefab, new Vector2(spawnPosX, spawnPosY), cellPrefab.transform.rotation); // create the new cell
cell.transform.SetParent(transform); // add the cell to the map
Cell cellComponent = cell.GetComponent<Cell>();
cellComponent.InitCell(Random.Range(NUM_RANGE_MIN, NUM_RANGE_MAX)); // init the cell value
cells.Add(cellComponent); // add to list
}
}
}
Each cell got this script attached
private Text txtCellValue;
private int cellValue;
public void InitCell(int value)
{
txtCellValue = transform.GetChild(0).GetChild(0).GetComponent<Text>(); // get the text component of the cell
cellValue = value; // set the value
txtCellValue.text = cellValue.ToString(); // update the GUI
}
So in the hierarchy, each cell is added to the "Map" and got this own hierarchy
The canvas is set on "Scale with Screensize" and the text itself has these settings
I just want to write the value of the cell on this sprite. Maybe there is a more clean way?
Would be nice if someone could help fixing this!
You will select render mode "world" for your canvas. Then set the scale and width/height values.
Also, you will remember about sorting layers. Canvas layer would be bigger than sprite renderer, if you dont use separate camera for UI.