Write text on sprites in Unity - c#

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.

Related

How do I convert between enums for passing between different functions

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.

move along a grid in Unity

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.

Set static space between custom axes

I have to have static space (lets say 20 px) between axes.
If I have more axes on the left, it is possible to set their StartPosition and EndPosition in pixels or in percents.
For pixels setting - is there some way, how I can get the height of the space, where are the axes (red line in the picture)?
For percents setting - is there some way, how I can automatically convert 20 px to percents? I could calculate that by myself if I know the height, but I don't know how to get it - see 1.
I am able to get the height of the whole panel, where the chart is, but I don't know where to get the actual height of the space for left axes.
tChart1.Chart.ChartRect gives you the Rectangle of the "drawing zone". In your case, where you have to get that size before the axes calculations, you can use GetAxesChartRect event and use e.AxesChartRect as follows:
private void testChartRect()
{
for (int i = 0; i < 4; i++)
{
Line line = new Line(tChart1.Chart);
tChart1.Series.Add(line);
line.Chart = tChart1.Chart;
line.FillSampleValues();
Axis axis = new Axis();
tChart1.Axes.Custom.Add(axis);
line.CustomVertAxis = axis;
axis.AxisPen.Color = line.Color;
axis.Labels.Font.Color = line.Color;
}
tChart1.Aspect.View3D = false;
tChart1.Panel.MarginLeft = 10;
//tChart1.AfterDraw += TChart1_AfterDraw1;
tChart1.GetAxesChartRect += TChart1_GetAxesChartRect;
}
private void TChart1_GetAxesChartRect(object sender, GetAxesChartRectEventArgs e)
{
Rectangle chartRect = e.AxesChartRect;
int axisLength = (chartRect.Bottom - chartRect.Top) / tChart1.Axes.Custom.Count;
int margin = 20;
for (int i = 0; i < tChart1.Axes.Custom.Count; i++)
{
Axis axis = tChart1.Axes.Custom[i];
axis.StartEndPositionUnits = PositionUnits.Pixels;
axis.StartPosition = i * axisLength;
axis.EndPosition = (i + 1) * axisLength - (i != (tChart1.Axes.Custom.Count - 1) ? margin : 0);
}
}
private void TChart1_AfterDraw1(object sender, Graphics3D g)
{
tChart1.Graphics3D.Brush.Color = Color.Red;
tChart1.Graphics3D.Brush.Transparency = 80;
tChart1.Graphics3D.Rectangle(tChart1.Chart.ChartRect);
}

UI prefabs are instantiated below the canvas

I am trying to copy the Zelda health system. The code looks really fine and works fine.
But the heart containers are placed wrong. They get instantiated below the canvas.
This is the important code, the heart containers are correct, just at the wrong position.
The calculation of x and y is correct, but on the canvas it is not.
private Transform healthBar = GameObject.FindGameObjectWithTag("HealthController").transform; // container for the heartContainers
private GameObject healthWrapperObject = Resources.Load("HealthContainer") as GameObject; // the backgroundImage and parent of the heart
private List<Image> healthContainers = new List<Image>(); // list of hearts for later usages
private int maxHealth = 6;
private int currentHealth;
private int healthPerHealthContainer = 4; // 4 lifepoints per heart
private int healthContainersPerRow = 5; // 5 hearts per row
private int healthContainerStartPositionX = 0; // Healthbar starts at 0 on x
private int healthContainerStartPositionY = 0; // Healthbar starts at 0 on y
private int healthContainerSpacingX = 10; // horizontal spacing
private int healthContainerSpacingY = -10; // vertical spacing
private void Start()
{
currentHealth = maxHealth;
InitializeHealthBar();
}
public void InitializeHealthBar()
{
int neededHealthContainers = maxHealth % healthPerHealthContainer == 0 ? maxHealth / healthPerHealthContainer : maxHealth / healthPerHealthContainer + 1; // Calculate the needed container count
int counter = 0; // counts the hearts per row
int x = healthContainerStartPositionX; // horizontal position of the heartContainer
int y = healthContainerStartPositionY; // vertical position of the heartContainer
for (int i = 0; i < neededHealthContainers; i++)
{
counter++;
if (counter >= healthContainersPerRow) // start a new line after 5 hearts per row
{
x = healthContainerStartPositionX; // move back to the left
y += healthContainerSpacingY; // go for the next line
counter = 0; // reset the counter
}
else
x += healthContainerSpacingX; // place the new container right next to the previous
Transform newHealthContainerTransform = Instantiate(healthWrapperObject, new Vector2(x, y), healthWrapperObject.transform.rotation).transform; // create the healthContainer parent / backgroundImage
newHealthContainerTransform.SetParent(healthBar); // take the container and make it a child of the healthBar
healthContainers.Add(newHealthContainerTransform.GetChild(0).GetComponent<Image>()); // get the heart of the heartContainer and add it to the heartList
}
}
I added the transform settings for the healthBar, the healthContainer / backgroundImage and the heart ("healthfill").
On all 3 elements I pressed Strg+Alt and Shift for anchoring them.
The heartcontainter should be added to the healthbar, the heart is a child of the heartcontainer and is set to stretch (it should be the same size as its parent)
Why are the UI prefab Objects instantiated below the canvas?
I assume you are getting something like this:
You fix this by pass false to the second parameter of the SetParent function. By doing this, you will make the Transform keep its local orientation rather than its global orientation.
Simply replace :
newHealthContainerTransform.SetParent(healthBar);
with:
newHealthContainerTransform.SetParent(healthBar, false)
You can also set the parent Object and make the instantiated Object's Transform keep its local orientation in the Instantiate function. The only disadvantage of this is that you now have to set the position of object in another line of code instead of the Instantiate function like before.
Transform newHealthContainerTransform = Instantiate(healthWrapperObject, healthBar, false).transform;
newHealthContainerTransform.GetComponent<RectTransform>().anchoredPosition3D = new Vector2(x, y);
When moving a UI Object you should be modifying it's RectTransform variables instead of the Transform variables.
Below are other useful variables that determines where to position the UI:
These are anchoredPosition, anchoredPosition3D, anchorMax and anchorMin which can be modified with:
yourUIObj.GetComponent<RectTransform>().anchoredPosition = ...
yourUIObj.GetComponent<RectTransform>().anchoredPosition3D = ...
yourUIObj.GetComponent<RectTransform>().anchorMax = ...
yourUIObj.GetComponent<RectTransform>().anchorMin = ...

Draw CIE color Space in MSchart

I have a 6300 * 5 array with:
Columns 1,2 = CIE data
Columns 3,4,5 = S R G B
How should I Draw this in MsChart?
You have several options:
Add DataPoints with Markers in the respective Colors
Add Annotations
Use one of the xxxPaint events
With only 6500 points you can't really fill the area by setting single pixels. So you better use a FillElipse call for each point.
If you use the Pre- or PostPaint event you will need to use the AxisX/Y methods ValueToPixelPosition for calculating the pixel coordinates from the CIE values.
In any case you set the Minimum and Maximum for both Axes.
Also you will need to calculate either the Markers' or the Annotations' or the ellipses' size from the chart's ClientSize to avoid ugly gaps in the colored area.
If you want to use DataPoints set the ChartType = Point and use this function for each of your data:
DataPoint Cie2DataPoint(float x, float y, float r, float g, float b)
{
var dp = new DataPoint(x, y);
dp.Color = Color.FromArgb((int)(256 * r), (int)(256 * g),(int)(256 * b));
dp.MarkerColor = dp.Color;
return dp;
}
Here are examples of helper function:
int MarkerSize(Chart chart, int count)
{
return Math.Max(chart.ClientSize.Width, chart.ClientSize.Height )/ count + 1
}
void Rescale(Chart chart)
{
Series s = chart3.Series[0];
s.MarkerSize = MarkerSize(chart3, (int)Math.Sqrt(s.Points.Count));
}
The former takes an estimate of how many plot points you expect per axis; you may need to experiment a little. The next one assumes the points are actually filling a square; also that you only have one ChartArea.
This should also be modified for your data!
We need to rescale the sizes when the Chart is resized:
private void chart3_Resize(object sender, EventArgs e)
{
Rescale (sender as Chart);
}
Here is an example of setting it up with a calculated set of data. You should loop over your list of data instead..:
Series s = chart3.Series[0];
s.ChartType = SeriesChartType.Point;
s.MarkerSize = 3;
for (int x = 0; x < 100; x++)
for (int y = 0; y < 100; y++)
{
s.Points.Add(Cie2DataPoint(x/100f, y/100f, x/100f, y/100f, (x+y)/200f));
}
ChartArea ca = chart3.ChartAreas[0];
ca.AxisX.Minimum = 0;
ca.AxisY.Minimum = 0;
ca.AxisX.Maximum = 1;
ca.AxisY.Maximum = 1;
ca.AxisX.Interval = 0.1f;
ca.AxisY.Interval = 0.1f;
ca.AxisX.LabelStyle.Format = "0.00";
ca.AxisY.LabelStyle.Format = "0.00";
Rescale(chart3);
Result:
After grabbing ~6k colors from a CIE color chart the result looks rather grainy but basically correct:
Note that you probably need to allow for the reversed y-axis somehow; I simply subtracted my y-values from 0.9f. Use your own numbers!

Categories