Im created a 3d tilemap using free asset models as shown below:
and here is the code where I am trying to get its position:
// Update is called once per frame
void Update()
{
if(Input.GetMouseButtonDown(0))
{
//read in another post that changing to vector 2 will help
Vector2 mousePosition = (Vector2)Input.mousePosition;
Ray ray = cameraMain.ScreenPointToRay(mousePosition);
//performing raycast
if(Physics.Raycast(ray, out RaycastHit hitInfo))
{
//checking if hit the collider of gameobject and checking if the renderer is not null
if(hitInfo.collider.gameObject.GetComponent<Target>() != null)
{
Vector3 rayPosition = hitInfo.point;
//Vector3Int posInt = grid.WorldToCell(hitInfo.point);
Vector3Int posInt = grid.LocalToCell(rayPosition);
//Vector3Int posInt2 = tilemap.WorldToCell(mp);
//Shows the cell reference for the grid
Debug.Log(posInt);
// Shows the name of the tile at the specified coordinates
Debug.Log(tilemap.HasTile(posInt));
//Debug.Log(tilemap.GetTile(posInt).name);
}
}
}
}
and this is the result:
I want to use getTile, but it keeps saying object reference not set to the instance of the object, so I used hasTile to check if the tile is there. For some reason it is displaying false. Can someone tell me what is wrong?
Related
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)
{
...
}
}
I'm attempting to detect when a player wants to open/close a door so I create an emptyObject called Door Hinge that has the tag "Door". I then created a cube object named Door Body which is a child of Door Hinge and gave it no tag but did give it a layer of Ignore Raycast. I have the scaling of the parent set to (1, 1, 1) but did change the scaling of the child as well as its x position slightly.
I'm not sure why but the raycast seems to only be detecting the child cube and not the parent empty object. Could anyone let me know if I'm missing anything or doing something wrong? I'll add my detection code for this below.
void CheckInteraction()
{
// origin starts from the camera
Vector3 origin = cam.transform.position;
// direction of the camera
Vector3 direction = cam.transform.forward;
// The distance for the raycast
float distance = 4f;
// Used to store info about the object that the raycast hits
RaycastHit hit;
if (Physics.Raycast(origin, direction, out hit, distance))
{
Debug.Log(hit.transform.tag);
if (hit.transform.tag == "Door")
{
Debug.Log("HIT");
if (Input.GetKeyDown(KeyCode.E))
{
hit.transform.gameObject.GetComponent<DoorOpen>().enabled = true;
}
}
}
}
The solution that I was missing was that the parent empty object needed to have a box collider added to it.
I've created a (3D)-canvas which includes a list. The canvas is located in the world space. It also faces to the camera all the time when it's activated by selecting a GameObject. Now I'd like the add a dynamic Line Renderer between the upper left corner of the canvas and the point of the RaycastHit / selected object (see sketch). The position of the LR on the GO-side should be static, the LR on the list-side should be dynamic. Thus, it should stay attached at the corner even the canvas starts rotating in all axis (x,y,z).
I already stuck at the positioning of the LR on the list-side. I don't get the corner-coordinates to attach the LR there. I've already tried the bounds-method and some others but I'm not getting ahead. My code so far:
//GUI elements
public Canvas canvas_list;
public GameObject content_list;
LineRenderer lr_list;
// Use this for initialization
void Start ()
{
Setup_LR();
}
// Update is called once per frame
void Update ()
{
// Canvas facing to the camera
canvas_list.transform.rotation = ar_camera.transform.rotation;
// LineRenderer is always attachted to canvas (Part 1)
lr_list.SetPosition(1, new Vector3(canvas_list.GetComponent<Collider>().bounds.min.x, canvas_list.GetComponent<Collider>().bounds.max.y, canvas_list.GetComponent<Collider>().bounds.min.z));
// Selecting objects
if (Input.GetMouseButtonDown(0))
{
Selecting_Objects();
}
}
void Selecting_Objects()
{
raycastHit = new RaycastHit();
if (Physics.Raycast(ar_camera.ScreenPointToRay(Input.mousePosition), out raycastHit))
{
if (!EventSystem.current.IsPointerOverGameObject())
{
GameObject selected_object = raycastHit.collider.gameObject;
// LineRenderer is always attachted to building component (Part 2) - avoids that the line renderer gets attached to GUI element
lr_list.SetPosition(0, raycastHit.point);
}
}
}
void Setup_LR()
{
canvas_list.transform.gameObject.AddComponent<BoxCollider>();
lr_list = new GameObject().AddComponent<LineRenderer>();
lr_list.material.color = Color.red;
lr_list.startWidth = 0.01f;
lr_list.endWidth = 0.01f;
}
That's how it looks now:
Thanks!
Hi im creating a 2D platformer, and i want to be able to draw for example lines ingame (playmode) with my cursor (like paint) that can act as walkable terrain. Im pretty new to coding and c# which im using, and im having a really hard time imagining how this can be achieved let alone if its possible? Would appreciate if you guys could give me some ideas and maybe could help me push it in the right direction. Thanks!
EDIT:
So i got got some code now which makes me being able to draw in playmode. The question now is how i can implement a type of collider to this? Maybe each dot can represent a little square or something? How can i go through with it? Heres some code. Thanks.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class DrawLine : MonoBehaviour
{
private LineRenderer line;
private bool isMousePressed;
private List<Vector3> pointsList;
private Vector3 mousePos;
// Structure for line points
struct myLine
{
public Vector3 StartPoint;
public Vector3 EndPoint;
};
// -----------------------------------
void Awake()
{
// Create line renderer component and set its property
line = gameObject.AddComponent<LineRenderer>();
line.material = new Material(Shader.Find("Particles/Additive"));
line.SetVertexCount(0);
line.SetWidth(0.1f,0.1f);
line.SetColors(Color.green, Color.green);
line.useWorldSpace = true;
isMousePressed = false;
pointsList = new List<Vector3>();
}
// -----------------------------------
void Update ()
{
// If mouse button down, remove old line and set its color to green
if(Input.GetMouseButtonDown(0))
{
isMousePressed = true;
line.SetVertexCount(0);
pointsList.RemoveRange(0,pointsList.Count);
line.SetColors(Color.green, Color.green);
}
else if(Input.GetMouseButtonUp(0))
{
isMousePressed = false;
}
// Drawing line when mouse is moving(presses)
if(isMousePressed)
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z=0;
if (!pointsList.Contains (mousePos))
{
pointsList.Add (mousePos);
line.SetVertexCount (pointsList.Count);
line.SetPosition (pointsList.Count - 1, (Vector3)pointsList [pointsList.Count - 1]);
}
}
}
}
Regarding means and tools:
Input.mousePosition
Input.GetKey, Input.GetKeyDown, Input.GetKeyUp
Line renderer manual
Line renderer scripting API
Regarding general idea:
Your script may use Input.GetKey to trigger the feature functionality (keyboard key, for example).
When the feature is activated the script awaits of mouse button to be clicked by the means of Input.GetKeyUp and when the event happens it must capture current mouse position using Input.mousePosition.
Only two points are necessary to build a segment of line, so when the script detects input of the second point it may create game object that will represent a piece of walkable terrain.
Visually such an object may be represented with line renderer. To enable iteraction with other game objects (like player character) it is usually enough to enhance object with a collider component (presumably, with a BoxCollider).
Regarding of how-to-add-a-collider:
GameObject CreateLineCollider(Vector3 point1, Vector3 point2, float width)
{
GameObject obj = new GameObject("LineCollider");
obj.transform.position = (point1+point2)/2;
obj.transform.right = (point2-point1).normalized;
BoxCollider boxCollider = obj.AddComponent<BoxCollider>();
boxCollider.size = new Vector3( (point2-point1).magnitude, width, width );
return obj;
}
You can add collider to object with line renderer but you still must orient it properly.
Example of integration in your code:
void Update ()
{
...
// Drawing line when mouse is moving(presses)
if(isMousePressed)
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z=0;
if (!pointsList.Contains (mousePos))
{
pointsList.Add (mousePos);
line.SetVertexCount (pointsList.Count);
line.SetPosition (pointsList.Count - 1, (Vector3)pointsList [pointsList.Count - 1]);
const float ColliderWidth = 0.1f;
Vector3 point1 = pointsList[pointsList.Count - 2];
Vector3 point2 = pointsList[pointsList.Count - 1];
GameObject obj = new GameObject("SubCollider");
obj.transform.position = (point1+point2)/2;
obj.transform.right = (point2-point1).normalized;
BoxCollider boxCollider = obj.AddComponent<BoxCollider>();
boxCollider.size = new Vector3( (point2-point1).magnitude, ColliderWidth , ColliderWidth );
obj.transform.parent = this.transform;
}
}
}
So before I explain my problem. I will first tell what I am really doing.
I am working on a click to move/zoom camera script. There are 3 planes in front of my Main Camera. Now what I am doing is, creating a script which says " The camera will zoom on the plane which gets clicked. I made several attempts to come up with a working script but it didn't worked well. Every time I come across new bugs , errors and what not. :|
I got frustrated and deleted the buggy script. Now I want to start from scratch. I am doing it in C#
Since I am not a professional, Can anyone explain me in detail to get it done?
I am confused how to deal with the planes I placed. I want to know what is missing in my script.
Here is a screenshot of how I placed those planes.
Edit. - I managed to work on it. Now I need advice, how to target the planes I placed in front of the camera.
using UnityEngine;
using System.Collections;
public class CameraZoom : MonoBehaviour
{
public int zoomLevelSelected = 0;
public float[] ZoomLevels = new float[] { 60, 40, 20 };
void Update()
{
int zoomChange = 0;
if (Input.GetMouseButtonDown(0)) { zoomChange = +1; } // back
else if (Input.GetMouseButtonDown(1)) { zoomChange = -1; } // forward
if (zoomChange != 0)
{
zoomLevelSelected = Mathf.Clamp(zoomLevelSelected + zoomChange, 0, ZoomLevels.Length - 1);
camera.fieldOfView = ZoomLevels[zoomLevelSelected];
}
}
}
Heck with it, here is one way to create a click zoom. The gist is that you create a ray from your camera in to the scene through the mouse cursor. When that ray intersects an object create a second ray from the point of intersection back out along the intersecting face's normal.
void Update () {
if(Input.GetMouseButtonDown(0)){
// get ray from camera in to scene at the mouse position
Ray ray = Camera.mainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
// hardcoded "zoom" distance.
float zoomDist = 15.0f;
// Raycast from camera to mouse cursor, if object hit, zoom.
if (Physics.Raycast(ray,out hit,Mathf.Infinity)){
// Create a second ray from the hit object back out, zoom the camera along this ray.
Ray r = new Ray(hit.point,hit.normal);
Camera.mainCamera.transform.position = r.GetPoint(zoomDist);
}
}
}
Things to keep in mind:
Physics.Raycast, as written, will return true for any GameObject with a collider. Use layers if you only want to zoom when selecting specific GameObjects.
The camera won't directly center on the GameObject you click. I use the exact point of intersection to create the position where the camera will zoom to.
zoomDist is the distance away from the object.
This code only works with perspective cameras, if you use orthographic you'll need to modify the size value of the camera to zoom.
The problem with your script is that your var int zoomChange is getting set to zero every frame so move that variable to class level.
using UnityEngine;
using System.Collections;
public class CameraZoom : MonoBehaviour
{
public int zoomLevelSelected = 0;
public float[] ZoomLevels = new float[] { 60, 40, 20 };
int zoomChange = 0; //<<<<<<<<<<<<<
void Update()
{
if (Input.GetMouseButtonDown(0)) { zoomChange = +1; } // back
else if (Input.GetMouseButtonDown(1)) { zoomChange = -1; } // forward
if (zoomChange != 0)
{
zoomLevelSelected = Mathf.Clamp(zoomLevelSelected + zoomChange, 0, ZoomLevels.Length - 1);
camera.fieldOfView = ZoomLevels[zoomLevelSelected];
}
}
}