Unity RectTransform.sizeDelta doesn't change size - c#

I am trying to change size of a gameobject that has TextMeshPro - Text (UI) component to fit his parent. Here is the code.
static public Cube CreateCube(Transform parent, Vector2 anchors, Vector2 position)
{
Cube c = Instantiate(cubePrefab); // prefab has Cube component
var tr = (RectTransform)c.transform;
tr.SetParent(parent); // canvas or other 2d object
tr.anchorMin = tr.anchorMax = anchors;
tr.anchoredPosition = position;
var textObj = new GameObject("Text");
var textTr = textObj.AddComponent<RectTransform>();
textTr.SetParent(tr);
textTr.anchorMin = textTr.anchorMax = Vector2.one / 2; // anchors at the center of the parent
textTr.anchoredPosition = Vector2.zero;
// tr.sizeDelta is Vector2(60, 60) from prefab;
print(new Vector2(tr.sizeDelta.x, tr.sizeDelta.y * 2 / 3)); // Vector2(60, 40)
textTr.sizeDelta = new Vector2(tr.sizeDelta.x, tr.sizeDelta.y * 2 / 3); // width is the same as parent's width
// height is two thirds of parent's height
print(textTr.sizeDelta); // also Vector2(60, 40)
var text = textObj.AddComponent<TextMeshProUGUI>();
text.text = "Test";
text.fontSize = 14;
text.alignment = TextAlignmentOptions.Center;
return c;
}
Second print shows that sizeDelta has been set, but in the inspector and on the scene size is (200, 50) like without textTr.sizeDelta = ... line.
But if I add
private void Start()
{
((RectTransform)transform.GetChild(0)).sizeDelta = new Vector2(60, 40);
}
into the Cube class, it will work. Why doesn't it work when I create text?
Objects hierarchy:
-> Some 2d object
---> Object with Cube component
-----> Text object

Canvas hierarchy is calculated not immediately when you create and add new objects to it, and it often may lead to some unpredictable results. The reccomended workaround is to wait at least 1 frame after the object is created and only then apply changes for properties like sizeDelta. You can do this using Coroutine and skip 1 frame with yield return null. As an alternative for you specific use case, you can try to use RectTransform.SetSizeWithCurrentAnchors instead of directly setting sizeDelta, try this approach first I'd say.
textRt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 60);
textRt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 40);

Related

button.transform.parent = shopPanel; and button.transform.SetParent(shopPanel); not working

I'm attempting to create a new button game object. When I click the button to create the new game objects, it does not assign the game objects to the parent I'm telling it to.
public void DisplayUpgrades()
{
// Get the RectTransform component of the shop panel
RectTransform shopPanelRectTransform = shopPanel.GetComponent<RectTransform>();
// Set the initial position of the first button to 0
float yPos = 0;
foreach (var upgrade in upgrades)
{
// Create a new button game object
GameObject button = new GameObject();
button.name = "UpgradeButton";
// Set the parent of the button to the shop panel
button.transform.parent = shopPanel;
// Set the size of the button to 10 units wide and 10 units tall
button.transform.localScale = new Vector3(10, 10, 1);
// Add a RectTransform component to the button
RectTransform rectTransform = button.AddComponent<RectTransform>();
// Set the position of the button using the RectTransform component
rectTransform.localPosition = new Vector3(0, yPos, 0);
rectTransform.sizeDelta = new Vector2(150, 20);
// Add a Button component to the button
Button btn = button.AddComponent<Button>();
btn.transform.SetParent(shopPanel);
// Add a TextMeshProUGUI component to the button
TextMeshProUGUI text = button.AddComponent<TextMeshProUGUI>();
text.transform.SetParent(shopPanel);
// Set the font size of the text to 20 pixels
text.fontSize = 8;
text.alignment = TextAlignmentOptions.Center;
// Set the text of the button to the name and cost of the upgrade
text.text = $"{upgrade.Key}: cost: {upgrade.Value.cost}";
// Add a listener to the button to handle when it is clicked
btn.onClick.AddListener(() => PurchaseUpgrade(upgrade.Key));
// Increment the y position by 20 pixels for the next button
yPos += 90;
}
}
To give some more info, I have 2 GUI elements, a canvas named 'Canvas' (parent of shopPanel), and 'shopPanel'. When I create new objects, they are being created as children of 'Canvas' rather than 'shopPanel'. 'shopPanel' is a Rect Transform. 'Canvas', also is a Rect Transform. I am needing 'button' to be a child of 'shopPanel' because when I want to add additional GUI elements, I don't want the addtl GUI elements to be destroyed when I attempt to destroy the children of shopPanel.
I have attempted button.transform.parent = shopPanel;, button.transform.SetParent(shopPanel). I have tried to see if 'button' is a child of 'shopPanel', and if it isn't, assign it as the Parent. I have tried creating different canvas's and different shop panels. I've tried restarting the program and my computer. Nothing has worked this far.

Unity Align prefab to top left (2D Game)

I'm making my first game where obstacles (which are prefabs) are placed by a script into a scene. They are all different sizes in a 2D environment. I am placing them using this code below
Instantiate(normal1, new Vector3(x, y , 0), Quaternion.identity);
This works perfectly, except that I need all of the obstacles to be positioned from the top left. So if I would have 0, 0 for my x and y, only the obstacle's corner would be covering the 0, 0 position, not the entire thing. From the little I've worked with GUI elements, you can align to however you like. Is this the same with prefab, and if so, how? Or can you somehow move the origin of the object?
I assume you are talking about non-UI elements.
The easiest thing would be to give your objects a parent GameObject and arrange them in a way so the parent GameObject already has the pivot where you want it (the top-left corner). You do this by first positioning the (future) parent object correctly and then simply drag the child object into it in the hierachy (while it keeps its current position).
Then in your script you have to use Camera.ScreenToWorldPoint in order to find the top-left screen corner position in the 3D world like
// an optional offset from the top-left corner in pixels
Vector2 PixelOffsetFromTopLeft;
// Top Left pixel is at
// x = 0
// y = Screen height
// and than add the desired offset
var spawnpos = new Vector2(0 + PixelOffsetFromTopLeft.x, Screen.height - PixelOffsetFromTopLeft.y);
// as z we want a given distance to camera (e.g. 2 Units in front)
spawnedObject = Instantiate(Prefab, camera.ScreenToWorldPoint(new Vector3(spawnpos.x, spawnpos.y, 2f)), Quaternion.identity);
Full script I used as example
public class Spawner : MonoBehaviour
{
public GameObject Prefab;
public Vector2 PixelOffsetFromTopLeft = Vector2.zero;
private GameObject spawnedObject;
private Camera camera;
private void Start()
{
camera = Camera.main;
}
private void Update()
{
if (Input.anyKeyDown)
{
// you don't need this I just didn't want to mess up the scene
// so I just destroy the last spawned object before placing a new one
if (spawnedObject)
{
Destroy(spawnedObject);
}
// Top Left pixel is at
// x = 0
// y = Screen height
// and than add the desired offset
var spawnpos = new Vector2(0 + PixelOffsetFromTopLeft.x, Screen.height - PixelOffsetFromTopLeft.y);
// as z we want a given distance to camera (e.g. 2 Units in front)
spawnedObject = Instantiate(Prefab, camera.ScreenToWorldPoint(new Vector3(spawnpos.x, spawnpos.y, 2f)), Quaternion.identity);
}
}
}
This will now always spawn the object anchored to the top left. It will not keep it this way if the camera moves or the window is resized.
If your question was about keeping that position also when the window is resized you can use the same code e.g. in an Update method (later you due to efficiency you should only call it when really needed / window actually was resized) like
public class StickToTopLeft : MonoBehaviour
{
private Camera camera;
public Vector2 PixelOffsetFromTopLeft = Vector2.zero;
private void Start()
{
camera = Camera.main;
}
// Update is called once per frame
private void Update()
{
var spawnpos = new Vector2(0 + PixelOffsetFromTopLeft.x, Screen.height - PixelOffsetFromTopLeft.y);
// This time simply stay at the current distance to camera
transform.position = camera.ScreenToWorldPoint(new Vector3(spawnpos.x, spawnpos.y, Vector3.Distance(camera.transform.position, transform.position)));
}
}
This now keeps the object always anchored to the top-left corner also after resizing the window and allows to adjust its offset afterwards.

How to fade out / disapear a GameObject slowly?

I have a function where I have to dispear some GameObjects, at the moment I use SetActive(false) but it should not disapear instantly, it should disapear slowly in 2 or 3 seconds. I guess opacity should go slowly to transparent or something like that...
public void SlotCheck(string gemColor,GameObject slotColor,GameObject
puzzleStuk,ref int scoreGem,ref bool Visibility)
{
if (DragHandler2.itemBegingDragged.name.Contains(gemColor)
DragHandler2.itemBegingDragged.transform.parent.name == "green_small_b")
{
Visibility=false;
puzzleStuk.SetActive(Visibility);
slotColor.SetActive(false);
DragHandler2.itemBegingDragged.SetActive(false);
scoreGem--;
}
}
This is my approach using coroutines.
void Awake()
{
StartCoroutine(FadeOutMaterial(1f));
}
IEnumerator FadeOutMaterial(float fadeSpeed)
{
Renderer rend = objectToFade.transform.GetComponent<Renderer>();
Color matColor = rend.material.color;
float alphaValue = rend.material.color.a;
while (rend.material.color.a > 0f)
{
alphaValue -= Time.deltaTime / fadeSpeed;
rend.material.color = new Color(matColor.r, matColor.g, matColor.b, alphaValue);
yield return null;
}
rend.material.color = new Color(matColor.r, matColor.g, matColor.b, 0f);
}
You can get the Renderer component of a GameObject and then change the alpha of the color gradually in Update().
Ex:
Renderer renderer;
void Start() {
renderer = GetComponent<Renderer>();
}
void Update() {
Color oldCol = renderer.material.color;
Color newCol = new Color(oldCol.r, oldCol.g, oldCol.b, oldCol.a - 0.01f);
renderer.material.color = newCol;
}
Of course you shouldn't use hard coded values like 0.01f, but instead a value from the inspector. This should also be multiplied by Time.deltaTime as to not make the fade speed FPS-based.
Some of the names or the like might not be 100% correct in this example, but it should give you an idea of which part of the Unity API documentation to look around in.
You can use a Coroutine combined with Color.Lerp to reduce the alpha value of all materials over time.
(I'm assuming you mean meshes with Renderers here not UI stuff but this would work in similar ways as well)
// How long should fading take (in seconds)
public float fadingDuration = 1;
// For storing Renderer components
private Renderer[] renderers;
// For storing original color data
private List<Color> originalColors = new List<Color>();
// For storing FadeOut color data
// (= Original data with alpha=0)
private List<Color> fadeOutColors = new List<Color>();
private void Awake() {
// Get all renderers, own and children
renderers = GetComponentInChildren<Renderer>(true);
// Run through renderers
foreach(var rend in renderers)
{
// Run through all materials
foreach (vat mat in rend.materials)
{
// Get original color
var color = mat.color;
// Add to original list
originalColors.Add(color);
// Get fadeout color (alpha 0)
var fadeoutColor = new Color(color.r, color.g, color.b, 0);
// Add to fadeout list
fadeoutColors.Add(fadeOutColor);
}
}
// Call this to start fading
public void StartFadeout()
{
StartCoroutine(FadeOut());
}
private IEnumerator FadeOut()
{
var timePassed = 0;
while(timePassed < fadingDuration)
{
// Get factor between 0 and 1 depending on passed time
var lerpFactor = timePassed / fadingDuration;
// Run through all renderers
int colorIndex = 0;
foreach(var rend in renderers)
{
// Run through all materials
foreach (vat mat in rend.materials)
{
// Set color interpolated between original and FadeOut color depending on lerpFactor
mat.color = Color.Lerp(originalColors[colorIndex], fadeOutColors[colorIndex], lerpFactor);
// Count up index
colorIndex++;
}
}
// Add time passed since last frame
timePassed += Time.deltaTime;
// return to render the frame and continue from here in the next frame
yield return null;
}
// In the end just to be sure apply the final target values (FadeOut colors)
// Alternatively you could also deactivate/destroy the object here
// Run through all materials
int colorIndex = 0;
foreach(var rend in renderers)
{
// Run through all materials
foreach (vat mat in rend.materials)
{
// Set color to final FadeOut value
mat.color = fadeOutColors[colorIndex];
// Count up index
colorIndex++;
}
}
}
Note that for transparency to work at all your materials have to use a shader supporting transparency.
This might however not be the best solution regarding performance. Especially since multiple Renderers might reference the same Material so in the current example there are maybe some redundancies.
But if you anyway have only one Renderer with one Material you could adjust the code a bit and skip all the loops ;)
As mentioned by previous answers, you can do it easily manipulating
meshrenderer.material.color=new Color (,,,x);
One important detail to add is that material needs to be set up in the transparent queue (and supports transparency). This is achieved via a dropdown in the material inspector, for Standard Surface shaders, but has to be dealt with in custom shaders by using one minus alpha blending mode and transparent queue in custom shaders
Tags {"Queue"="Transparent" "RenderType"="Transparent" }
Blend SrcAlpha OneMinusSrcAlpha

NullReferenceException Can't figure out what's Null

I get this error in Unity:
NullReferenceException: Object reference not set to an instance of an object
TowerSlot.OnGUI () (at Assets/TowerSlot.cs:26)
I'm relatively new to Unity and can't figure out what line this error is from(I'm assuming 26) and I don't know what is null. If someone could please help explain to me how to understand what the error is pointing at and what I need to do it would be much appreciated.
TowerSlot.cs:
using UnityEngine;
using System.Collections;
public class TowerSlot : MonoBehaviour {
public GUISkin skin = null;
bool gui = false;
// Tower prefab
public Tower towerPrefab = null;
void OnGUI() {
if (gui) {
GUI.skin = skin;
// get 3d position on screen
Vector3 v = Camera.main.WorldToScreenPoint(transform.position);
// convert to gui coordinates
v = new Vector2(v.x, Screen.height - v.y);
// creation menu for tower
int width = 200;
int height = 40;
Rect r = new Rect(v.x - width / 2, v.y - height / 2, width, height);
GUI.contentColor = (Player.gold >= towerPrefab.buildPrice ? Color.green : Color.red);
GUI.Box(r, "Build " + towerPrefab.name + "(" + towerPrefab.buildPrice + " gold)");
// mouse not down anymore and mouse over the box? then build the tower
if (Event.current.type == EventType.MouseUp &&
r.Contains(Event.current.mousePosition) &&
Player.gold >= towerPrefab.buildPrice) {
// decrease gold
Player.gold -= towerPrefab.buildPrice;
// instantiate
Instantiate(towerPrefab, transform.position, Quaternion.identity);
// disable gameobject
gameObject.SetActive(false);
}
}
}
public void OnMouseDown() {
gui = true;
}
public void OnMouseUp() {
gui = false;
}
}
Also I'm trying to follow this tutorial here http://makeagame.info/unity-tower-defense-game-step-4-scripting
Thanks!
You set towerPrefab to null early on, and then on line 26, you reference its buildPrice property or field before assigning it any non-null value. That will throw a null exception.
This line is the problem:
GUI.contentColor = (Player.gold >= towerPrefab.buildPrice ? Color.green : Color.red);
The screenshot in OP's reply to #Hatchet definitely shows the tower prefab has not been set in the editor.
Either it's supposed to be set by some other code (which is not working) or it should be set manually. You can do this by dragging a tower prefab onto the slot, or by clicking on the little circle to the right of 'None (Tower)': That'll pop up a picker dialog where you can choose a tower prefab. It shows up as 'Tower' because that's the kind of variable it wants.
BTW the GUIskin is also not set. That will probably cause a problem when you hit the line
GUI.skin = skin;
Docs on the Unity gui for assigning references like this are here: http://docs.unity3d.com/Documentation/Manual/EditingReferenceProperties.html
hatchet has pointed the problem. I think this may fix it:
public Tower towerPrefab = new Tower();
Because Tower class has the the property buildPrice which you are trying to access.

Quaternion rotation in XNA

Am I doing the following right?
Well obviously not cause otherwise I wont be posting a question here, but I'm trying to do a Quaternion rotation of a model around another model.
Lets say I have a box model that has a vector3 position and a float rotation angle.
I also have a frustum shaped model that is pointing towards the box model, with its position lets say 50 units from the box model. The frustum also has a vector3 position and a Quaternion rotation.
In scenario 1, the box and frustum are "unrotated". This is all fine and well.
In scenario 2, I rotate the box only and I want the frustum to rotate with it (kinda like a chase camera) with the frustum always pointing directly at the box and at the same distance from the box as in the unrotated distance. Obviously if I just rotate the model and the frustum by using Matrix.CreateRotationY() for both the box and the frustum, the frustum is slightly offset to the side.
So I thought a Quaternion rotation of the frustum around the box would be best?
To this end I have tried the following, with no luck. It draws my models on the screen, but it also draws what looks like a giant box to the screen and no matter how far away I move the camera the box is always in the way
For the purpose of testing, I have 3 boxes and their 3 associated frustums
In my Game1 class I initialize the box[0] with positions and rotations
boxObject[0].Position = new Vector3(10, 10, 10);
boxObject[1].Position = new Vector3(10, 10, 10);
boxObject[2].Position = new Vector3(10, 10, 10);
boxObject[0].Rotation = 0.0f;
boxObject[1].Rotation = 45.0f;
boxObject[2].Rotation = -45.0f;
So all 3 boxes drawn at the same position but at different angles.
Then to do the frustums, I initiate their position:
float f = 50.0f;
frustumObject[0].Position = new Vector3(boxObject[0].Position.X,
boxObject[0].Position.Y, boxObject[0].Position.Z + f);
frustumObject[1].Position = new Vector3(boxObject[1].Position.X,
boxObject[1].Position.Y, boxObject[1].Position.Z + f);
frustumObject[2].Position = new Vector3(boxObject[2].Position.X,
boxObject[2].Position.Y, boxObject[2].Position.Z + f);
And then try and rotate around their associated box model:
frustumObject[0].ModelRotation = new Quaternion(boxObject[0].Position.X,
boxObject[0].Position.Y, boxObject[0].Position.Z + f, 0);
frustumObject[0].ModelRotation = new Quaternion(boxObject[0].Position.X,
boxObject[0].Position.Y, boxObject[0].Position.Z + f, 45);
frustumObject[0].ModelRotation = new Quaternion(boxObject[0].Position.X,
boxObject[0].Position.Y, boxObject[0].Position.Z + f, -45);
And finally, to draw the models, I Draw() them in my GameModel class which also has:
public Model CameraModel { get; set; }
public Vector3 Position { get; set; }
public float Rotation { get; set; }
public Quaternion ModelRotation { get; set; }
public void Draw(Matrix view, Matrix projection)
{
transforms = new Matrix[CameraModel.Bones.Count];
CameraModel.CopyAbsoluteBoneTransformsTo(transforms);
// Draw the model
foreach (ModelMesh myMesh in CameraModel.Meshes)
{
foreach (BasicEffect myEffect in myMesh.Effects)
{
// IS THIS CORRECT?????
myEffect.World = transforms[myMesh.ParentBone.Index] *
Matrix.CreateRotationY(Rotation) * Matrix.CreateFromQuaternion(ModelRotation) * Matrix.CreateTranslation(Position);
myEffect.View = view;
myEffect.Projection = projection;
myEffect.EnableDefaultLighting();
myEffect.SpecularColor = new Vector3(0.25f);
myEffect.SpecularPower = 16;
}
myMesh.Draw();
}
}
Can anyone spot where I am going wrong? Is it because I am doing 2 types of rotations n the Draw()?
myEffect.World = transforms[myMesh.ParentBone.Index] *
Matrix.CreateRotationY(Rotation) * Matrix.CreateFromQuaternion(ModelRotation) * Matrix.CreateTranslation(Position);
From a quick glance, it would be best to create your Quaternions using a static create method such as Quaternion.CreateFromAxisAngle(Vector3. UnitY, rotation). The values of X,Y,Z and W of a Quaternion do not relate to position in any way. The handy static methods take care of the tricky math.
In your situation it appears as though you want to keep the frustum pointing at the same side of the box as the box rotates, therefore rotating the frustum about the box. This requires a slightly different approach to the translation done in your draw method.
In order to rotate an object about another, you first need to translate the object so that the centre of the desired rotation is at the origin. Then rotate the object and translate it back by the same amount as the first step.
So in you situation, something like this should do it (untested example code to follow);
// Construct the objects
boxObject.Position = new Vector3(10, 10, 10);
boxObject.Rotation = 45.0f;
frustumObject.Position = new Vector3(0, 0, 50f); // Note: this will be relative to the box (makes the math a bit simpler)
frustumObject.TargetPosition = boxObject.Position;
frustumObject.ModelRotation = Quaternion.CreateFromAxisAngle(Vector3. UnitY, boxObject.Rotation); // Note: this rotation angle may need to be in radians.
// Box Draw()
// Draw the box at its position, rotated about its centre.
myEffect.World = transforms[myMesh.ParentBone.Index] * Matrix.CreateTranslation(Position) * Matrix.CreateRotationY(Rotation);
// Frustum Draw()
// Draw the frustum facing the box and rotated about the boxes centre.
myEffect.World = transforms[myMesh.ParentBone.Index] * Matrix.CreateTranslation(Position) * Matrix.CreateFromQuaternion(ModelRotation) * Matrix.CreateTranslation(TargetPosition);
Assumptions:
The box rotates about its own centre
The frustum stays facing the box and rotates around the boxes centre
Hope this helps.

Categories