How can I make a 3D object (a car) to move along the surface which was recognized by ARKit (Unity, C#)?
So that instead of finger tap I could use UI to manipulate the car, while the car will use the surface, recognized by ARKit, for movement and collide with it in case of any rigid body effects.
Is it possible to make it also collide with other surfaces (for example the walls)?
Something like this will allow you to tap on your object and drag to move it along a surface. In regards to collisions, etc - make sure your objects have appropriate colliders and rigidbody's. ARKit currently only provides horizontal surfaces, so not sure how to deal with wall collisions...
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DragAlongSurface : MonoBehaviour {
private bool holding;
void Start () {
holding = false;
}
void Update() {
if (holding) {
Move();
}
// One finger
if (Input.touchCount == 1) {
// Tap on Object
if (Input.GetTouch(0).phase == TouchPhase.Began ) {
Ray ray = Camera.main.ScreenPointToRay (Input.GetTouch(0).position);
RaycastHit hit;
if (Physics.Raycast (ray, out hit, 100f)) {
if (hit.transform == transform) {
holding = true;
}
}
}
// Release
if (Input.GetTouch(0).phase == TouchPhase.Ended) {
holding = false;
}
}
}
void Move(){
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay (Input.GetTouch (0).position);
// The GameObject this script attached should be on layer "Surface"
if(Physics.Raycast(ray, out hit, 30.0f, LayerMask.GetMask("Surface"))) {
transform.position = new Vector3(hit.point.x,
transform.position.y,
hit.point.z);
}
}
}
Related
So I have UI elements that can be dragged, so that a prefab can be instantiated OnDragEnd(). I am using below code attached to the UI buttons to achieve this
public class MyButton : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler
{
..
..
..
public void OnDrag(PointerEventData eventData)
{
dragRect.anchoredPosition += eventData.delta;
}
public void OnBeginDrag(PointerEventData eventData)
{
// if we're on deploy cooldown, dont allow dragging this button
if (disableDrag )
{
eventData.pointerDrag = null;
}
}
public void OnEndDrag(PointerEventData eventData)
{
RaycastHit hit;
Ray ray = mainCamera.ScreenPointToRay(eventData.pointerCurrentRaycast.screenPosition);
Physics.Raycast(ray, out hit);
if (hit.collider.gameObject.tag == "Ground")
{
createUnit(hit);
disableDrag = true;
}
}
}
This works fine without any problems, but recently I also implemented the ability to drag the camera. I attached the following code to my camera to achieve this:
public class CameraController : MonoBehaviour
{
public float movementTime;
public Vector3 newPosition;
private Vector3 startPosition;
public Vector3 dragStartPosition;
public Vector3 dragCurrentPosition;
public float xLimit1, xLimit2;
float clampedX;
Camera mainCamera;
private void Start()
{
mainCamera = Camera.main;
newPosition = transform.position;
startPosition = transform.position;
}
// Update is called once per frame
void Update()
{
HandleMouseInput();
clampedX = Mathf.Clamp(newPosition.x, xLimit1, xLimit2);
transform.position = Vector3.Lerp(transform.position, new Vector3 (clampedX, startPosition.y, startPosition.z), Time.deltaTime * movementTime);
}
void HandleMouseInput()
{
// when mouse is pressed,
if (Input.GetMouseButtonDown(0))
{
if (!EventSystem.current.IsPointerOverGameObject())
{
Plane plane = new Plane(Vector3.up, Vector3.zero);
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
float entry;
if (plane.Raycast(ray, out entry))
{
dragStartPosition = ray.GetPoint(entry);
}
}
}
// if it is still held down
if (Input.GetMouseButton(0))
{
if (!EventSystem.current.IsPointerOverGameObject())
{
Plane plane = new Plane(Vector3.up, Vector3.zero);
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
float entry;
if (plane.Raycast(ray, out entry))
{
dragCurrentPosition = ray.GetPoint(entry);
newPosition = transform.position + dragStartPosition - dragCurrentPosition;
}
}
}
}
}
And the camera script works as well as it behaves as expected and the camera drags with mouse. But the problem is that when I drag my UI button onto screen that moves the camera also.
I tried to include the if condition of checking if its over a UI if (!EventSystem.current.IsPointerOverGameObject()) But this does not seem to work, the camera still drags when I drag the UI button.
How do I make them work together?
You are on the right path as
!EventSystem.current.IsPointerOverGameObject())
is the right way forward, but make sure to use it like this:
// Check if the left mouse button was clicked
if(Input.GetMouseButtonDown(0))
{
// Check if the mouse was clicked over a UI element
if(!EventSystem.current.IsPointerOverGameObject())
{
Debug.Log("Did not Click on the UI");
}
}
Make sure that the RaycastTarget toggle on your UI component is not enabled.
Also, check the Event Mask in the Physics Raycaster on your camera. IsPointerOverGameObject() will return true if the mouse is over any objects on those layers. If you only want to check for the UI layer, make sure that only UI is checked.
Here are some references:
EventSystem.IsPointerOverGameObject Docu.
EventSystem.IsPointerOverGameObject Manual
Thread that might help you
I'm making a small space game in 3D, but it looks awful when my ship is rotating in the middle of movement
So can someone please help me with code ? I need to rotate to that position and than move to it. (And i forgot to say, that rotation needs to be on Y axis)
Here is my code for that movement.
NavMeshAgent agent;
void Start()
{
agent = GetComponent<NavMeshAgent>();
}
void Update()
{
if(Input.GetMouseButton(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit) && hit.collider.tag == "Ground")
{
agent.SetDestination(hit.point);
}
}
}
You are supposed to stop the agents rotation.
this is the code:
NavMeshAgent agent;
void Start()
{
agent = GetComponent<NavMeshAgent>();
}
void Update()
{
if(Input.GetMouseButton(0))
{
agent.updateRotation = false;
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit) && hit.collider.tag == "Ground")
{
agent.SetDestination(hit.point);
}
}
}
And I also have a more efficient way for you to check if the player is on Ground.
Here is what you should do instead of
hit.collider.tag == "Ground
First, make a Layer named "Ground" (anything... depends on you).
Then, add a LayerMask in the script above the NavMeshAgent. public LayerMask groundLayer
And in the if statement
do this:
if (Physics.Raycast(ray, out hit, groundLayer))
{
agent.SetDestination(hit.point);
}
What this does is, it only considers the click on objects with the selected layer.The plus point here is that you can select multiple layers in the Inspector.
And do not forget to select the layer in the Inspector.
Thank you! <3
I spawn objects that have laser beam property. When I click on one of them(specific object), I want it to only show its laser beam not the others.
How can I prevent it? I have a static GameObject variable (touch_detect.clickedObject) by which I can determine which object is clicked.
using UnityEngine;
using System.Collections;
public class Laser : MonoBehaviour
{
private LineRenderer lr;
private bool clicked = false;
RaycastHit rh;
// Use this for initialization
void Start()
{
lr = GetComponent<LineRenderer>();
}
// Update is called once per frame
void Update()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Input.GetMouseButtonDown(0))
{
if (Physics.Raycast(ray, out rh, Mathf.Infinity))
{
if (rh.collider.gameObject.name == touch_detect.clickedObject.name)
{
Debug.Log(rh.collider.gameObject.name + " clicked.");
Debug.Log("static object name" + touch_detect.clickedObject.name + " clicked.");
clicked = true;
lr.enabled = true;
}
}
}
if (Input.GetMouseButtonUp(0))
{
if (Physics.Raycast(ray, out rh, Mathf.Infinity))
{
if (rh.collider.gameObject.name == touch_detect.clickedObject.name)
{
Debug.Log(rh.collider.gameObject.name + " clicked.");
Debug.Log("static object name" + touch_detect.clickedObject.name + " clicked.");
clicked = false;
lr.enabled = false;
}
}
}
if (clicked)
{
lr.SetPosition(0, transform.position + new Vector3(0, 0, 0));
RaycastHit hit;
if (Physics.Raycast(transform.position + new Vector3(0, 0, 0), -transform.up, out hit))
{
if (hit.collider)
{
lr.SetPosition(1, hit.point);
}
}
else lr.SetPosition(1, -transform.up * 5000);
}
}
}
The issue is that since this script is attached to both of your gameobjects, there are two rays being cast at the mouse position (one from each script). Because you are just looking to see that the raycollider matches the static object, this statement is true for both scripts no matter which you click on:
if (rh.collider.gameObject.name == touch_detect.clickedObject.name) // always true
To get an immediate fix, you should change the above statement to something like this to check that the ray is intersecting the same gameobject that the script is attached to:
if (rh.collider.gameObject.name == gameObject.name)
This really is not the best method though since you are still casting two rays and therefore doing all the logic twice (or more times if you spawn more cubes).
A better method would be to have one master gameobject that casts the ray. When this ray intersects a cube, you would then activate a method inside that cubes script to show the laser. So for example:
on the master object you would have:
if (Physics.Raycast(ray, out rh, Mathf.Infinity))
{
// add a tag to all objects with the laser script
if (rh.collider.gameObject.tag == "hasLaser") //verify object has laser script via tag
rh.collider.GetComponent<laser>().activateLaser(); // call public method in collider script
}
and then the cube would have the laser script with a public method:
public void activateLaser()
{
lr.enabled = true;
}
I have a FPSController attached to it: a RigidBody , Animator , NavMeshAgentand two scripts: PlayerController and AgentController.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 10.0f;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
float translatioin = Input.GetAxis("Vertical") * speed;
float straffe = Input.GetAxis("Horizontal") * speed;
translatioin *= Time.deltaTime;
straffe *= Time.deltaTime;
transform.Translate(straffe, 0, translatioin);
if (Input.GetKeyDown("escape"))
Cursor.lockState = CursorLockMode.None;
}
}
And
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AgentController : MonoBehaviour
{
public Camera camera;
public NavMeshAgent agent;
public bool agentControl = false;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update ()
{
if (Input.GetMouseButtonDown(0) && agentControl == true)
{
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
agent.SetDestination(hit.point);
}
}
}
}
With the PlayerController I can move around using the keys WSAD and with the AgentController I can click on some point on the terrain and the Agent will walk over there.
When I click for example on one of the big cubes the player is walks over it and stop near it. But then when I'm using the keys WSAD to move back away from the cube the player will keep moving automaticaly back to the cube.
Like a magnet that make the player keep moving to the cube to the same point I clicked on before.
On the FPSCamera I have a script: Just to use the mouse look around.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class camMouseLook : MonoBehaviour
{
Vector2 mouseLook;
Vector2 smoothV;
public float sensitivity = 5.0f;
public float smoothing = 2.0f;
GameObject character;
// Use this for initialization
void Start ()
{
character = this.transform.parent.gameObject;
}
// Update is called once per frame
void Update ()
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
var md = new Vector2(Input.GetAxisRaw("Mouse X"), Input.GetAxisRaw("Mouse Y"));
md = Vector2.Scale(md, new Vector2(sensitivity * smoothing, sensitivity * smoothing));
smoothV.x = Mathf.Lerp(smoothV.x, md.x, 1f / smoothing);
smoothV.y = Mathf.Lerp(smoothV.y, md.y, 1f / smoothing);
mouseLook += smoothV;
mouseLook.y = Mathf.Clamp(mouseLook.y, -90f, 90f);
transform.localRotation = Quaternion.AngleAxis(-mouseLook.y, Vector3.right);
character.transform.localRotation = Quaternion.AngleAxis(mouseLook.x, Vector3.up);
}
}
I found that while the game is running if I uncheck the NavMeshAgent then I will be able to walk around again fine with the keys but when the NavMeshAgent is on and I click on some cube it will keep moving to that cube even if I'm moving with the keys to other places.
I want to be able either using the AgentController and/or the PlayerControllerwhen the game is running.
UPDATE:
I tried to use OnCollisionEnter and OnCollisionExit
But once I set the move to false again on the OnCollisionExit I'm getting the same problem. The reason is that I set the move to true again in the OnCollisionExit is to be able to click the mouse and move again.
So I'm stuck again.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AgentController : MonoBehaviour
{
public Camera camera;
public NavMeshAgent agent;
public bool move = true;
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
if (Input.GetMouseButtonDown(0) && move == true)
{
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
agent.SetDestination(hit.point);
}
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "HitPoint")
{
move = false;
}
}
private void OnCollisionExit(Collision collision)
{
move = true;
}
}
This probably happened because you're setting the destination to positions inside the cubes, so your agent keeps trying to reach them though it's impossible.
First of all, you may want to tell your agent to stop once one of the movement keys is pressed to avoid conflicts. You can use this to stop:
agent.isStopped = true;
agent.velocity = Vector3.zero;
If this is not enought to solve the problem, one workaround would be to put a Nav Mesh Obstacle Component in your cubes, check Carve and rebake your Nav Mesh. This way the agent would stop in the nearest point from the one you set, outside the cube.
A second option would be to check the collision with the cubes, and tell your agent to stop once it collides with the one you clicked.
Hopefully one of these will work for you!
How can I raycast between two moving objects?
I want to raycast from a moving enemy to a moving player. I dont know how to actually code to make the direction work.
using UnityEngine;
using System.Collections;
public class Enemy_Manage_Two : MonoBehaviour {
public GameObject player;
// Use this for initialization
void Start () { }
// Update is called once per frame
void Update () {
player = GameObject.FindGameObjectWithTag ("Player");
//Debug.Log (player.transform.position + " " + transform.position);
Ray ray = new Ray (transform.position, player.transform.position);
RaycastHit hit;
Debug.DrawRay (transform.position, player.transform.position,
Color.red);
if(Physics.Raycast(ray, out hit)) {
gameObject.renderer.material.color = Color.blue;
} else {
gameObject.renderer.material.color = Color.white;
}
}
}
Currently I haven't Unity so treat my answer as proof of concept.
//1. You have to calculate vector between enemy and player:
Vector3 direction=player.transform.position - transform.position;
direction.Normalize(); //Normalize vector
//2. Now you can create Ray
Ray ray=new Ray(transform.position,direction);