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);
}
}
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 currently working on a 2D isometric game where the player will be able to control different units, and interact through them.
I created a Scriptable Object called UnitType. Thanks to this system I can define an action range and a moving range, that is the maximum distance in cells that the player can move to or interact with.
The problem is I don't know how to implement this through code. This is what I want to achieve, using an action range of 2 cells for the demonstration.
This is the goal
With a friend of mine, we thought about calculating the linear equation of those 4 lines to check if the raycast hit was within them, but it's not working right with negative x.
This is the current system
What would be the best practice ?
Thank you very much for your time and attention,
Mousehit is a Unity GameObject that is the child of the character. Its positions is bound to the center of the tilemap cells thanks to mousePosition that is a script attached to the GameManager.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class Interact : MonoBehaviour
{
[SerializeField] private Sprite upperLeft, upperRight, midLeft, midRight, bottomLeft, bottomMid, bottomRight;
[SerializeField] private GameObject mouseHit;
public UnitType unitType;
private GameObject gameManager;
private CheckMousePosition mousePosition;
private bool isInteracting, isAtGoodDistance;
private SpriteRenderer spriteRenderer;
private Tilemap tilemap;
private RaycastHit2D hit;
//private Transform mouseHit;
void Start()
{
tilemap = GameObject.Find("Tilemap").GetComponent<Tilemap>();
gameManager = GameObject.FindGameObjectWithTag("GameManager");
mousePosition = gameManager.GetComponent<CheckMousePosition>();
spriteRenderer = gameObject.GetComponent<SpriteRenderer>();
isInteracting = false;
mouseHit.SetActive(false);
}
// Update is called once per frame
void Update()
{
if (isInteracting)
{
mouseHit.SetActive(true);
mouseHit.transform.position = new Vector3(mousePosition.tilemap.GetCellCenterLocal(mousePosition.cellPosition).x, mousePosition.tilemap.GetCellCenterLocal(mousePosition.cellPosition).y, 1);
if (Input.GetMouseButtonDown(0))
{
Interaction();
}
}
if (isInteracting == false)
{
spriteRenderer.sprite = null;
mouseHit.SetActive(false);
}
}
public void InitInteraction()
{
isInteracting = true;
transform.root.GetComponent<ContextualMenu>().CloseContextualMenu();
}
private void Interaction()
{
//When the player interacts, we cast a ray that will determine what he is interacting with.
hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
if (hit.collider != null)
{
if (isInteracting)
{
isInteracting = false; //the player is no longer interacting.
//If the player is interacting with a farmable cell using the worker.
if (transform.root.name == "Worker"
&& hit.collider.GetComponent<FarmableCell>() != null)
{
CheckInteractionDistance();
//hit.collider.GetComponent<FarmableCell>().CheckIfFarmed();
}
}
}
}
private void CheckInteractionDistance()
{
if (mouseHit.transform.localPosition.y <= (-0.5 * (mouseHit.transform.localPosition.x) + unitType.actionDistance / 2)
&& mouseHit.transform.localPosition.x >= (-0.5 * (mouseHit.transform.localPosition.x) - unitType.actionDistance / 2)
&& mouseHit.transform.localPosition.x >= (0.5 * (mouseHit.transform.localPosition.x) - unitType.actionDistance / 2)
&& mouseHit.transform.localPosition.x <= (0.5 * (mouseHit.transform.localPosition.x) + unitType.actionDistance / 2))
{
Debug.Log("ok");
}
else
{
Debug.Log("not ok");
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spin : MonoBehaviour
{
public Vector3 targetRotation;
public float rotationSpeed;
public float duration;
public Light[] lights;
public GameObject material;
private Material _material;
// 0,31,191
// 255,0,0
// Start is called before the first frame update
void Start()
{
_material = material.GetComponent<MeshRenderer>().material;
}
// Update is called once per frame
void Update()
{
transform.Rotate(new Vector3(Time.deltaTime * rotationSpeed, 0, 0));
var c = Color.Lerp(new Color(255, 0, 0), new Color(0, 31, 191), 0.5f);
_material.SetColor("_EmissionColor", c);
}
}
The original color is :
original color
The color I want to change it to and then to keep changing between the two colors is :
The color to change
but instead it's just making white color.
in both sides it's just white.
just white color it's not changing between the two colors I was setting
Okay, now I understand your problem...
The thing you actually want to change is the albedo. That's the colour a material appears.
Unfortunately, as you're already using a texture for your albedo, that's not so simple.
You have a couple of options. The optimal way (performance-wise) is to write a shader that takes your texture and identifies parts of it (eg using a known color key like hot pink) and inserts the fading colour at runtime.
This requires some knowledge of writing shaders but is discussed here: https://answers.unity.com/questions/284747/how-can-i-change-the-colour-of-part-of-a-texture.html
Alternatively, you can "cut out" the bits that need to change colour in the main material by making them transparent.
You can then add a second material to the object, ordered beneath the first. You then change the albedo of this second material and it will be visible through the "holes" in the first.
This approach is discussed here: https://answers.unity.com/questions/395405/color-specific-sections-of-a-texture-1.html
This is working :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChangeColors : MonoBehaviour
{
public List<Transform> objectsToRotate = new List<Transform>();
public float rotationSpeed;
public Light[] lights;
public Material material;
public Vector3 targetAngle = new Vector3(-90f, 0f, 0f);
private bool resetPos = false;
private Vector3 currentAngle;
// 0,31,191
// 255,0,0
// Start is called before the first frame update
void Start()
{
currentAngle = objectsToRotate[0].transform.eulerAngles;
StartCoroutine(SetTrue());
}
// Update is called once per frame
void Update()
{
if(rotationSpeed == 0 && resetPos == false)
{
for (int i = 0; i < objectsToRotate.Count; i++)
{
currentAngle = new Vector3(
Mathf.LerpAngle(currentAngle.x, targetAngle.x, Time.deltaTime), 0, 0);
objectsToRotate[i].transform.eulerAngles = currentAngle;
}
resetPos = true;
}
else
{
resetPos = false;
}
for(int i = 0; i < objectsToRotate.Count; i++)
{
objectsToRotate[i].Rotate(new Vector3(Time.deltaTime * rotationSpeed, 0, 0));
}
}
public void setHighlight(bool highlight)
{
if (highlight)
{
material.EnableKeyword("_EMISSION");
material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;
material.SetColor("_EmissionColor", new Color(0, 31, 191));
}
else
{
material.EnableKeyword("_EMISSION");
material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;
material.SetColor("_EmissionColor", new Color(255,0,0));
}
}
IEnumerator SetTrue()
{
setHighlight(true);
yield return new WaitForSeconds(0.5f);
StartCoroutine(SetFalse());
}
IEnumerator SetFalse()
{
setHighlight(false);
yield return new WaitForSeconds(0.5f);
StartCoroutine(SetTrue());
}
}
I am trying to instantiate a number of planes to use as my terrain and give them OnMouseOver() to change them to some color and OnMouseExit() to change them back to the original color.
I attached a plane instantiation script to the main camera to generate the plane prefab and a mouse event script to the plane prefab. I get them all instantiated and the events pass to them, but in-game the mouse events are being applied to either long strips of planes, an entire quadrant, or a random single plane not at the location of the mouse cursor.
I made a new material and applied it to the place prior to turning it into a prefab to replace the standard material set upon creation of the plane.
I have started attempting to use the mouse position to apply the color change to the plane at the current mouse position with Physics.CheckSphere, but I don't fully understand how to tell specifically what gameObject is at a specific position.
public class TerrainGeneration : MonoBehaviour {
[SerializeField]
private Transform groundTile;
private Vector3 row;
private int max = 10;
// Use this for initialization
void Start () {
for ( int i = 0; i <= max; i++)
{
for (int x = 0; x <= max; x++) {
row = new Vector3(i, 0, x);
Instantiate(groundTile, row, Quaternion.identity);
}
}
}
}
public class MouseEvents : MonoBehaviour {
private Color isTargeted;
private Color notTargeted;
private MeshRenderer groundTileMeshRenderer;
private Vector3 mousePosition;
private float mouseX;
private float mouseY;
void Start () {
groundTileMeshRenderer = gameObject.GetComponent<MeshRenderer>();
isTargeted = Color.cyan;
notTargeted = groundTileMeshRenderer.material.color;
}
void Update()
{
mouseX = Mathf.RoundToInt(Input.GetAxis("Mouse X"));
mouseY = Mathf.RoundToInt(Input.GetAxis("Mouse Y"));
mousePosition = new Vector3(mouseX, 0, mouseY);
if (Physics.CheckSphere(mousePosition, 1))
{
**//Get the specific gameObject located at the current mouse position
//Set the gameObject as the target for the color change**
}
}
void OnMouseOver()
{
groundTileMeshRenderer.material.color = isTargeted;
}
void OnMouseExit()
{
groundTileMeshRenderer.material.color = notTargeted;
}
}
So, I found out what the answer is thanks to the Unity forums. My error was in the instantiation transform script. Because I was using the plane primitive 3d object I was not accounting for the size of the plane correctly. The following code snippet for instantiation of each row removes the overlapping that was occurring as the planes are too large to simply place at (0,0,0), (0,0,1), (0,0,2) for example.
row = new Vector3(i * 10, 0, x * 10);
Instantiate(groundTile, row, Quaternion.identity);
tag your gameObjects as necessary and then you can identify them.
If you do, then you can use them like so (purely for example):
public virtual void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "planeA")
{
//do something
}
}
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());
}