Mouse events for Unity3D isometric tile map - c#

I have been reading up on the new tile map system in Unity3D. I have managed to get to the point of setting up a grid -> tile-map and setting up a tile palette. However now i'm struggling with finding up-to-date tutorials for handling mouse events for this new tile map system.
I'm attempting to set a highlight when the mouse is over the tile and if the tile is clicked I want to be able to trigger scripts and other events. However the available tutorials online don't go into mouse events for the tile map system and very few talk about isometric tile maps.
Are there any good up to date tutorials for handling mouse events on an isometric tile map? Even a simple tutorial showing a hover effect on the tile and a "hello world from tile x.y" when tile is clicked, would be all i would really need to get going.
This is what I have so far:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseManager : MonoBehaviour
{
void Update()
{
Vector3 clickPosition = Vector3.one;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if(Physics.Raycast(ray, out hit))
{
clickPosition = hit.point;
}
Debug.Log(clickPosition);
}
}

This should get you started:
using UnityEngine;
using UnityEngine.Tilemaps;
public class Test : MonoBehaviour {
//You need references to to the Grid and the Tilemap
Tilemap tm;
Grid gd;
void Start() {
//This is probably not the best way to get references to
//these objects but it works for this example
gd = FindObjectOfType<Grid>();
tm = FindObjectOfType<Tilemap>();
}
void Update() {
if (Input.GetMouseButtonDown(0)) {
Vector3 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3Int posInt = gd.LocalToCell(pos);
//Shows the cell reference for the grid
Debug.Log(posInt);
// Shows the name of the tile at the specified coordinates
Debug.Log(tm.GetTile(posInt).name);
}
}
}
In short, get a reference to the grid and tilemap. Find the local coordinates using ScreenToWorldPoint(Input.mousePosition). Call the LocalToCell method of the grid object to get your local coordinates (Vector3) converted to cell coordinates (Vector3Int). Pass the cell coordinates to the GetTile method of the Tilemap object to get the Tile (then use the methods associated with the Tile class to make whatever changes you want to make).
In this example, I just attached the above script to an empty GameObject in the world. It would probably make sense to attach it to the Grid, instead. The general logic remains the same nonetheless.

This is a slightly different version from the way HumanWrites does it. It doesn't require a reference to the grid, and the mousePos is declared as a Vector2, rather than a Vector3 - this will avoid problems when working in 2D.
using UnityEngine;
using UnityEngine.Tilemaps;
public class MouseManager : MonoBehaviour
{
private Tilemap tilemap;
void Start()
{
tilemap = FindObjectOfType<Tilemap>();
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3Int gridPos = tilemap.WorldToCell(mousePos);
if (tilemap.HasTile(gridPos))
Debug.Log("Hello World from " + gridPos);
}
}
}
The 'tilemap' that we're referencing is a gameObject in your scene. You may have renamed it to something else, but it would be a child of the 'Grid' object.

Related

Is it possible to override the texture component values even though the variable is set to public?

I have a texture issue with a VR whiteboard. When I attach a texture to a plane in Unity that has the whiteboard.cs script attached, the whiteboard plane defaults to plain white when I press run. The plane is still reactive to the marker in a VR space, but I want to be able to put up a stencil texture/material that allows users to trace it. I've seen the similar issue when the variables are private and not when set to public. Has anyone experienced the same texture GetComponent issue? I'm using Unity 2021.2,10f.
Attached is a screenshot of the object before and after pressing run and the mentioned script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Whiteboard : MonoBehaviour
{
public Texture2D texture;
public Vector2 textureSize = new Vector2(x:2048, y:2048);
void Start()
{
var r = GetComponent<Renderer>();
texture = new Texture2D(width:(int)textureSize.x,
height:(int)textureSize.y);
r.material.mainTexture = texture;
}
}

Detecting a click on a specific tile in a tilemap

I'm trying to detect when the mouse clicks on a specific tile on a tile map. The game I'm making is a overhead 2d style, and I want to trigger code when a specific resource node (tile) is clicked on. I've tried to use RaycastHit2D:
public class DetectClickOnTile : MonoBehaviour
{
Vector2 worldPoint;
RaycastHit2D hit;
// Update is called once per frame
void Update()
{
worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Input.GetMouseButtonDown(0))
{
hit = Physics2D.Raycast(worldPoint, Vector2.down);
if (hit.collider != null)
{
Debug.Log("click on " + hit.collider.name);
Debug.Log(hit.point);
}
}
}
But it only detects collision with the tilemap itself and not any of the individual tiles. Does anyone know why that is? For reference this is the map im trying to detect collision on, only the black tiles are filled in with placeholders, the rest of the tilemap is left blank for now.
Afaik you can get the tile at the given world hit point using something like e.g.
// Reference this vis the Inspector
[SerializeField] TileMap tileMap;
...
hit = Physics2D.Raycast(worldPoint, Vector2.down);
if (hit.collider != null)
{
Debug.Log("click on " + hit.collider.name);
Debug.Log(hit.point);
var tpos = tileMap.WorldToCell(hit.point);
// Try to get a tile from cell position
var tile = tileMap.GetTile(tpos);
...
}
However, I'm pretty sure your Raycast is not what you want to use but rather directly try and get the tile from the worldPoint and if you got one you hit one ;)
Your Raycast goes down meaning you cast from the click point to the bottom of the screen which doesn't sound like what you are trying to achieve.
So probably rather something like
if (Input.GetMouseButtonDown(0))
{
worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
var tpos = tileMap.WorldToCell(worldPoint);
// Try to get a tile from cell position
var tile = tileMap.GetTile(tpos);
if(tile)
{
...
}
}

How to make Camera follow only the horizontal movement (x axis) of the player?

I am a super newbie in Unity and am learning by making my first game. I want the camera to follow the player, but only in the X axis. I had earlier made the camera a child of the player, but that didn't work as I wanted. So I whipped up a C# script to follow the player, as given below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class cameraFollow : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.position = new Vector3(GameObject.Find("robot_body").transform.position.x, 0f, 0f);
}
}
However, this is showing only the blue background when run. Am I doing something wrong?
By setting your z position to 0 your camera probably ends up to close to everything in order to render it.
Try to not overwrite y and z with 0 but rather keep the current values:
// If possible rather already reference this via the Inspector!
[SerializeField] private GameObject robot;
private void Start()
{
// As fallback get it only ONCE
if(!robot) robot = GameObject.Find("robot_body");
}
void Update()
{
// get the current position
var position = transform.position;
// overwrite only the X component
position.x = robot.transform.position.x;
// assign the new position back
transform.position = position;
}

Need to instantiate prefab at mouse position

So, I am dragging 2D sprites from the canvas and instantiating a 3D object. But the issue is prefabs are spawning at their default location.This is the code. Removed all the things I tried for spawning
You are trying to instatiate a prefab without specifying the position to instantiate. Try something like this.
public void OnEndDrag(PointerEventData eventData)
{
Instantiate(prefab,eventData.position);
}
Reference:
https://docs.unity3d.com/ScriptReference/Object.Instantiate.html
https://docs.unity3d.com/2017.3/Documentation/ScriptReference/EventSystems.PointerEventData-position.html
You don't do anything with the objects position so it will spawn at the origin.
Without seeing your project it's hard to guess what you game requires for placing behavior, I'm assuming you have some an environment you want to place it on and that can easily be done with a Physics.Raycast(). But if you're not over any environment then you can fire a ray from your mouse position & project it an arbitrary amount forwards from the camera and place the object there for a good effect.
This snippet should be enough for that:
public float raycastDistance = 10f;
public float projectDistance = 4f;
public void OnEndDrag(PointerEventData eventData)
{
var spawned = Instantiate(prefab);
var ray = Camera.main.ScreenPointToRay(eventData.position);
if (Physics.Raycast(ray, out RaycastHit hit, raycastDistance)
{
spawned.transform.position = hit.point;
}
else
{
spawned.transform.position = Camera.main.transform.position + (ray * projectDistance);
}
}
If you prefer you can update the position of the spawned object in OnDrag() instead of just setting it in OnDragEnd() which can look quite nice if you interpolate the position of the object slightly as the mouse moves.

2D Raycast does not correctly detect overlapping colliders and ignores Sorting Layer

I want to be able to interact with my gameobjects while organising my sprites with Sorting Layer.
Right now, Sorting Layer do not affect my Raycast/collider situation and only change the visual appearance of my order.
In my Test scene I have two sprites: a Background and one Object.
Both have a 2D Box Collider component.
My Raycast script is suppose to detect on which Object my mouse is hovering on; either the Background (BG) or the Object, which lays on top of the BG.
As long as the BG collider is enabled in the inspector, my script will ONLY recognize the BG, even if the other object lays on top of my BG (visually and according to the Sorting Layer) (both Z-positions = 0, Sorting Layer Default (BG: 0, Object 1)).
The collider have the correct size and if I disable or remove the Collider from the BG, my script will detect the other Object just fine.
The only way to make sure that my Object gets detected, while my BG collider is activated is by changing the Objects Z position to a position between my BG and my Camera (between smaller 0 and greater -10)
I tried to:
this is a fresh 2D project, so my cameras Projection is set to Orthograph, my BG and Object use Sprite Renderer and is use 2D collider
change the "Order in Layer", back and forth, including negative numbers.
add a Foreground (and Background) "Sorting Layer"
(both options only had a visual effect and did not affect the Raycast output)
change the positions in the hierarchy.
removing and re-adding the colliders. (!) I observed that the 2D Box collider I added last, will always be behind every other colliders. This is something I can work with for now. But will become a problem later in the future, when the project gets bigger.
I want to be able to interact with items and doors etc. (clicks and reactions to mouse hovering) in my 2D click adventure. If 2D Raycast is not the way to go to archive this, I would be glad to know about the proper technique to use in this situation.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayCast : MonoBehaviour
{
[SerializeField] private Vector2 mousePosWorld2D;
RaycastHit2D hit;
void Update()
{
mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
hit = Physics2D.Raycast(mousePosWorld2D, Vector2.zero);
if (hit.collider)
{
Debug.Log("Found collider: " + hit.collider.name);
}
else
{
Debug.Log("No collider found.");
}
}
}
I expected the "Sorting Layer" or "Order in Layer" to have an impact on my Raycast, while changing the Z position does nothing. But it is the other way around. Sorting Layer only have a visual effect and moving my Object closer to the camera (Z position) helps to detect the right collider.
How do I solve the problem?
What gets hit first from a Ray has only to do with how close the collider is to the camera, and nothing with the order in the Hierarchy or the SortinLayers.
To find the "highest" RaycastTarget based on the SortingLayer, I created the Script below.
We fire a Raycast and iterate over all the Colliders that we hit, by using a foreach-loop. Then the function will return the GameObject with the highest SortingLayer. Note: If there is more than one GameObject with the highest SortingLayer, the function will return the last one it found.
using UnityEngine;
public class Raycast : MonoBehaviour
{
// The name of the relevant Layer
[SerializeField] string layerToCheck = "Default";
void Update()
{
Vector2 mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
GameObject result = GetHighestRaycastTarget(mousePosWorld2D);
// Do Stuff example:
Debug.Log($"Highest Layer: {result}");
if (Input.GetMouseButtonDown(0)
&& result != null)
{
Destroy(result);
}
}
// Get highest RaycastTarget based on the Sortinglayer
// Note: If multiple Objects have the same SortingLayer (e.g. 42) and this is also the highest SortingLayer, then the Function will return the last one it found
private GameObject GetHighestRaycastTarget(Vector2 mousePos)
{
GameObject topLayer = null;
RaycastHit2D[] hit = Physics2D.RaycastAll(mousePos, Vector2.zero);
foreach (RaycastHit2D ray in hit)
{
SpriteRenderer spriteRenderer = ray.transform.GetComponent<SpriteRenderer>();
// Check if RaycastTarget has a SpriteRenderer and
// Check if the found SpriteRenderer uses the relevant SortingLayer
if (spriteRenderer != null
&& spriteRenderer.sortingLayerName == layerToCheck)
{
if (topLayer == null)
{
topLayer = spriteRenderer.transform.gameObject;
}
if (spriteRenderer.sortingOrder >= topLayer.GetComponent<SpriteRenderer>().sortingOrder)
{
topLayer = ray.transform.gameObject;
}
}
}
return topLayer;
}
}
Have you tried with the script that it's in the Unity official documentation?
https://docs.unity3d.com/ScriptReference/Physics.Raycast.html
using UnityEngine;
// C# example.
public class ExampleClass : MonoBehaviour
{
void Update()
{
// Bit shift the index of the layer (8) to get a bit mask
int layerMask = 1 << 8;
// This would cast rays only against colliders in layer 8.
// But instead we want to collide against everything except layer 8. The ~ operator does this, it inverts a bitmask.
layerMask = ~layerMask;
RaycastHit hit;
// Does the ray intersect any objects excluding the player layer
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
Debug.Log("Did Hit");
}
else
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}

Categories