I have GUI Labels over NPC's in my game, and the labels display their name above their head. But when I walk away and go somewhere else, their name's stay on screen like it's mirrored or something?
Here's what I'm talking about:
This is how it looks normally
This is what happens when I walk out of view.
The Code:
using UnityEngine;
using System.Collections;
public class NPC : MonoBehaviour {
private float left;
private float top;
public float leftModifier;
public float topModifier;
private Vector3 NPCScreenPosition;
//FIX THE MIRRORING ISSUE
void Start () {
}
void Update () {
Vector3 NPCNameWorldPosition = (transform.position + new Vector3(0.0f, transform.lossyScale.y, 0.0f));
NPCScreenPosition = Camera.main.WorldToScreenPoint(NPCNameWorldPosition);
left = NPCScreenPosition.x + leftModifier;
top = Screen.height - (NPCScreenPosition.y + topModifier);
}
void OnGUI() {
GUI.Label(new Rect(left, top, 150, 25), gameObject.name.ToString());
}
}
The simplest solution to your issue would be to check the depth (z value) of the screen coordinates to see if the object is in front of the camera, i.e. if it's visible or not. If so, the value will be positive.
Being said, changing your draw call as below should do the trick:
void OnGUI
{
if(NPCScreenPosition.z >= Camera.main.nearClipPlane)
GUI.Label(new Rect(left, top, 150, 25), gameObject.name.ToString());
}
Related
everyone!
I'm making a game on Unity 2D and I encountered a problem. I need to dig specific tile of snow when player is holding LeftShift and is in the trigger with tag "Snow" (the tilemap does have such a tag). I decided to change the scale, since it's the most understandable for player of variants that i thought (making the sprite darker, destroying it, etc.)
Right now I have this code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class SnowTaking : MonoBehaviour
{ //Tile variables
public Tilemap tilemap;
public ITilemap iTilemap;
public Tile whiteTile;
public Tile redTile;
public Tile greenTile;
public Tile blueTile;
void OnTriggerStay2D(Collider2D other) {
if(other.gameObject.tag == "Snow") {
if(Input.GetKey(KeyCode.LeftShift)) {
//При нажатии LeftShift
float x = gameObject.transform.position.x;
float y = gameObject.transform.position.y;
float z = 0;
Vector3 position = new Vector3(x, y, z);
Vector3Int tilePosition = tilemap.WorldToCell(position);
Tile currentTile = tilemap.GetTile<Tile>(tilePosition);
//Conditions for each type of snow
if(whiteTile == currentTile) {
//Scaling tile here
Debug.Log("White");
} else if(redTile == currentTile) {
//Scaling tile here
Debug.Log("Red");
} else if(greenTile == currentTile) {
//Scaling tile here
Debug.Log("Green");
} else if(blueTile == currentTile) {
//Scaling tile here
Debug.Log("Blue");
} else {
Debug.Log("None");
}
}
}
}
}
What can I use to scale tiles? Thank you in advance!
I tried few things already:
First of all, I searched through the net (especially in the documentation) functions, that do something with the exact tile;
Then I tried to use Matrix4x4 to scale tile, but it didn't work as intended (it didn't work at all, but at least there were no errors);
currentTile.transform = Matrix4x4.Scale(new Vector3(0.5f, 0.5f, 1));
When I was out of options, I tried to do something myself, and used Sprites. Of course, it didn't work;
Sprite currentSprite = currentTile.sprite;
currentSprite.localScale -= new Vector3(0.01f, 0.01f, 0);
Then I searched such a question here, on StackOverflow, but didn't find anything that would help me, so here my quesion is!
You can use tilemap.SetTransformMatrix();
In your case, instead of
currentTile.transform = Matrix4x4.Scale(new Vector3(0.5f, 0.5f, 1));
You can use tilemap.SetTransformMatrix(tilePosition, Matrix4x4.Scale(new Vector3(0.5f, 0.5f, 1)));
If you want to animate it :
void Update() {
if (Input.GetKey(KeyCode.LeftShift)) {
time = 0f;
}
if (time < scaleDuration) {
time += Time.deltaTime;
var scaleValue = _animationCurve.Evaluate(time / scaleDuration);
tilemap.SetTransformMatrix(tilePosition, Matrix4x4.Scale(new Vector3(scaleValue, scaleValue, 1)));
}
I am working on a clicker game, which implies the interaction of 2 players. One of the objects (images made with ui) becomes larger if one of the players taps more. But I ran into a problem, for some reason unity creates a gap between objects. Also, if the red object becomes larger than blue - the problem disappears. I have attached the code and video with the problem.
The problem isn't in random values.
Also i tried to fix it with Transform.SetSiblingIndex, and it didn't work either.
There's a link to screenshot with the problem
there's a link to video with the problem
The code from GameCore:
public void OnPointerClick(PointerEventData eventData)
{
ScaleEvents();
Vector3 clickPosition = Camera.main.ScreenToWorldPoint(eventData.position);
if (eventData.rawPointerPress == Player1)
{
Player1.transform.localScale += scaleChange;
Player2.transform.localScale -= scaleChange;
}
else if(eventData.rawPointerPress == Player2)
{
Player2.transform.localScale += scaleChange;
Player1.transform.localScale -= scaleChange;
Player1.transform.SetSiblingIndex(0);
}
Debug.Log(clickPosition);
Debug.Log(eventData.rawPointerPress);
Debug.Log(randomScaleChange);
}
private void ScaleEvents()
{
randomScaleChange = Random.Range(0.05f, 0.3f);
scaleChange = new Vector3(0.0f, randomScaleChange, 0f);
}
}
From what I understand, should you rather use RectTransform.deltaSize.y, and adjust the actual height of your UI elements, instead of the scale. Someone smarter than me can explain to you why. If I tried to explain it, I would probably cause more confusion.
Your blue element should be anchored to the top, and your red element should be anchored to the bottom. If you now add 50 pixel to the height of your blue element, and substract 50 from the height of your red element, it should work out, without a gap in between.
Also, please note that with your current solution, your red bar grows bigger than the amount of which your blue bar shrinks. So yes, you don't have a gap. But your Red bar is actually overdrawing over your blue bar.
Edit:
Below I present you a possible solution that hopefully gets you on the right track.
For it to work, blue needs to be anchored to the top of your screen. Anchors Min: 0, 1; Max; 1, 1; Pivot 0.5, 1.
Red needs to be anchored to the bottom of the screen. Anchors Min: 0,0; Max; 1, 0; Pivot 0.5, 0.
Set the hight for both elements so that they touch seamlessly.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class ClickGame : MonoBehaviour, IPointerClickHandler
{
public RectTransform Player1;
public RectTransform Player2;
float randomScaleChange;
public void OnPointerClick(PointerEventData eventData)
{
ScaleEvents();
Vector3 clickPosition = Camera.main.ScreenToWorldPoint(eventData.position);
if (clickPosition.y > 0f) // Blue clicked
{
Player1.sizeDelta = new Vector2(100f, Player1.sizeDelta.y + randomScaleChange);
Player2.sizeDelta = new Vector2(100f, Player2.sizeDelta.y - randomScaleChange);
}
if (clickPosition.y < 0f) // Red clicked
{
Player2.sizeDelta = new Vector2(100f, Player2.sizeDelta.y + randomScaleChange);
Player1.sizeDelta = new Vector2(100f, Player1.sizeDelta.y - randomScaleChange);
}
}
private void ScaleEvents()
{
randomScaleChange = Random.Range(5f, 50f);
}
}
There's what a code looks like now and still didn't work:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class ClickerZone : MonoBehaviour, IPointerClickHandler
{
[SerializeField] private GameObject _Player1;
[SerializeField] private GameObject _Player2;
private RectTransform _player1Rect;
private RectTransform _player2Rect;
private Vector3 _scaleChange;
private float _randomScaleChange;
private void Awake()
{
_player1Rect = _Player1.GetComponent<RectTransform>();
_player2Rect = _Player2.GetComponent<RectTransform>();
}
public void OnPointerClick(PointerEventData eventData)
{
ScaleEvents();
Vector3 clickPosition = Camera.main.ScreenToWorldPoint(eventData.position);
if (eventData.rawPointerPress == _Player1)
{
_Player1.transform.localScale += _scaleChange;
_player1Rect.sizeDelta = new Vector2(0f, _player1Rect.sizeDelta.y + _randomScaleChange);
_Player2.transform.localScale -= _scaleChange;
_player2Rect.sizeDelta = new Vector2(0f, _player2Rect.sizeDelta.y - _randomScaleChange);
}
else if(eventData.rawPointerPress == _Player2)
{
_Player2.transform.localScale += _scaleChange;
_player2Rect.sizeDelta = new Vector2(0f, _player2Rect.sizeDelta.y + _randomScaleChange);
_Player1.transform.localScale -= _scaleChange;
_player1Rect.sizeDelta = new Vector2(0f, _player1Rect.sizeDelta.y - _randomScaleChange);
}
Debug.Log(clickPosition);
Debug.Log(eventData.rawPointerPress);
Debug.Log(_randomScaleChange);
}
private void ScaleEvents()
{
_randomScaleChange = Random.Range(0.05f, 0.3f);
_scaleChange = new Vector3(0.0f, _randomScaleChange, 0f);
}
}
First, i will explain myself...
I was trying to make an script for the camera to follow the player and keep itself inside the Map.
After a litle research i found out that you can use a property of tileMap called tileMap.localBounds.min to find the bottomLeft corner and tileMap.localBounds.max to find the topRight corner of a tilemap.
The problem with that is that my map is not a tileMap, it`s a sprite.
So i try the following...
public Transform target;
public GameObject theMap;
private Vector3 bottomLeftLimit;
private Vector3 topRightLimit;
// These values are for the camera
private float halfHeight;
private float halfWidth;
void start()
{
target = PlayerController.instance.transform;
halfHeight = Camera.main.orthographicSize;
halfWidth = halfHeight * Camera.main.aspect;
// We assign the corners of our map
// Maybe we`ll have to change the image for a tileMap to use: tileMap.localBounds
bottomLeftLimit = theMap.GetComponent<SpriteRenderer>().sprite.bounds.min + new Vector3(halfWidth, halfHeight, 0f);
topRightLimit = theMap.GetComponent<SpriteRenderer>().sprite.bounds.max + new Vector3(-halfWidth, -halfHeight, 0f);
// We send the bound limits to the PlayerController script to keep the player inside the map
PlayerController.instance.SetBounds(theMap.GetComponent<SpriteRenderer>().sprite.bounds.min, theMap.GetComponent<SpriteRenderer>().sprite.bounds.max);
}
void LateUpdate()
{
transform.position = new Vector3(target.position.x, target.position.y, transform.position.z);
// Keep the camera inside the bounds
transform.position = new Vector3(Mathf.Clamp(transform.position.x, bottomLeftLimit.x, topRightLimit.x), Mathf.Clamp(transform.position.y, bottomLeftLimit.y, topRightLimit.y), transform.position.z);
}
But it seems that the value of sprite.bounds.max and sprite.bounds.min are always 0 (the center), so it doesn`t work to set the bounds for the camera.
Anyone can help? I would aprecciate it a lot...
SpriteBoundsFinder.cs
using UnityEngine;
using UnityEngine.UI;
public class SpriteBoundsFinder : MonoBehaviour
{
public Image image;
private void Update()
{
var rectTransform = image.GetComponent<RectTransform>();
var sizeDelta = rectTransform.sizeDelta;
var position = image.transform.position;
var bottomLeft = position - new Vector3(sizeDelta.x / 2, sizeDelta.y / 2);
var topRight = position + new Vector3(sizeDelta.x / 2, sizeDelta.y / 2);
Debug.Log($"bottomLeft: {bottomLeft}, topRight: {topRight}");
}
}
You can use this to find the bottom left and top right corner of a sprite in world space. Assuming you're using an overlay canvas. If not, let me know.
The reason this works is because a sprite will always be stretched to have the same width and height as the RectTransform of its Image component's GameObject.
Hey i wondered if you can draw GUI.Label to mouse position, and how you can do it?
I'am currently doing the script in Unity C#.
Script layout:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour {
public bool Hover = false;
void OnMouseOver()
{
Hover = true;
}
void OnMouseExit()
{
Hover = false;
}
void OnGUI()
{
if(Hover = true)
{
GUI.Label(new Rect(//Transform to mouse positon))
}
}
}
Use Input.mousePosition to create the Rect that is passed to GUI.Label. Note that both of these use screen coordinates but for Input.mousePosition the bottom-left of the screen is (0, 0), and for the Rect used by GUI.Label the top-left of the screen is (0, 0). Thus, you need to flip the y-values like this:
void OnGUI()
{
var mousePosition = Input.mousePosition;
float x = mousePosition.x;
float y = Screen.height - mousePosition.y;
float width = 200;
float height = 200;
var rect = new Rect(x, y, width, height);
GUI.Label(rect, "Test");
}
For more information, see Unity's documentation on Input.mousePosition and GUI.Label
You need to convert screen coordinates (mouse position) to GUI ones.
void OnGUI()
{
if(Hover = true)
{
Vector2 screenPos = Event.current.mousePosition;
Vector2 convertedGUIPos = GUIUtility.ScreenToGUIPoint(screenPos);
GUI.Label(new Rect(convertedGUIPos.x, convertedGUIPos.y, width, height))
}
}
I have not actually tested the code though.
More info here:
https://docs.unity3d.com/ScriptReference/GUIUtility.ScreenToGUIPoint.html
[SOLVED]
I've been following the Learn To Code By Making Games course (Block Breaker Section) from Udemy and I have a flippin' annoying issue. My paddle doesn't clamp properly to the values I put in. I would have had no trouble with this but I decided that I want a small menu on the left side with a speed controller, sound controller and lives. This means I cant just say that I want the paddle to stop on the edge of the screen.
Observed Behavior:
The paddle does not constrain itself to the area I put in. As the screen size changes the paddle will either go off the screen or will be stopped somewhere like the middle of the screen.
What I Want It To Do:
I want the paddle to stop when it hits the edge of my menu on the left and to stop when it hits the screen on the right. I also want this to stay the same as my screen size changes. So basically I need a mathematic equation that can determine these points and it needs to be able to adjust based on the screen size.
Here is my code for the paddle:
using UnityEngine;
using System.Collections;
public class Paddle : MonoBehaviour {
public Texture texture;
public void Update () {
Vector3 paddlePos = new Vector3 (0.5f, this.transform.position.y , 0f);
float mousePosInBlocks = Input.mousePosition.x;
paddlePos.x = Mathf.Clamp(mousePosInBlocks, //Left Vaule, //Right Value);
this.transform.position = paddlePos;
}
}
this happens because Input.mousePosition returns position of mouse on screen, not in world space. just convert mousePosition into world position, and then assign its value to mousePosInBlocks.
using UnityEngine;
using System.Collections;
public class Paddle : MonoBehaviour {
public Texture texture;
public void Update () {
Vector3 paddlePos = new Vector3 (0.5f, this.transform.position.y , 0f);
float mousePosInBlocks = Camera.main.ScreenToWorldPoint(Input.mousePosition).x;
paddlePos.x = Mathf.Clamp(mousePosInBlocks, leftValue, rightValue);
this.transform.position = paddlePos;
}
}
Edit: Sorry, not WorldToScreenPoint, ScreenToWorldPoint
Try just setting the paddlePos to the new vector you're creating (in world space):
public Texture texture; // if needed for something outside this code
private Vector3 paddlePos;
private float mousePosInBlocks;
private float xValue;
private float leftValue;
private float rightValue;
public void Update () {
mousePosInBlocks = Camera.main.ScreenToWorldPoint(Input.mousePosition).x;
//compute leftValue on this line
//compute rightValue on this line
xValue = Mathf.Clamp(mousePosInBlocks, leftValue, rightValue);
paddlePos = new Vector3 (xValue, transform.position.y , 0f);
transform.position = paddlePos;
}
EDIT Based on updated question title, the following should work:
public Texture texture; // if needed for something outside this code
private Vector3 playerPosOnScreen;
private float mousePosInBlocks;
public void Update () {
playerPosOnScreen = Camera.main.WorldToScreenPoint(transform.position);
if (playerPosOnScreen.x > Screen.Width) {
transform.position = Camera.main.ScreenToWorldPoint (new Vector3 (Screen.Width, transform.position.y, transform.position.z);
} else if (playerPosOnScreen.x < 0f) {
transform.position = Camera.main.ScreenToWorldPoint (new Vector3 (0f, transform.position.y, transform.position.z);
}
}
Try something like this: (pseudo code, untested, to give you an idea)
Please check the comment for each step.
public void Update () {
// left bound
Vector3 left = Camera.main.ViewPortToScreenPoint(new Vector3(0f, 0f, 0f));
// Menu width
left.x += fmyMenuWidth; // Width of menu
// Right bound
Vector3 right= Camera.main.ViewPortToScreenPoint(new Vector3(1f, 1f, 0f));
// Temporary position vector
Vector3 paddlePos = Vector3.zero;
// capture mouse x
float mouseX = Camera.main.ScreenToWorldPoint(Input.mousePosition).x;
// capture mouse y
float mouseY= Camera.main.ScreenToWorldPoint(Input.mousePosition).y;
// Combine and limit the position's X-value, should not go off screen menu's width---->screen's width
paddlePos.x = Mathf.Max(Mathf.Min(right.x, mouseX), left.x);
// Combine and limit the position's Y-value, should not go off screen 0---->screen's height
paddlePos.y = Mathf.Max(Mathf.Min(right.y, mouseY), left.y);
// Finally set the bounded position
this.transform.position = paddlePos;
}