unity raycast , on raycast leave , how to ? c# - c#

i have a problem understanding how to keep a reference to an object that has previously been hit by a raycast .
for example i can have a raycast script put on the camera of my 1rst person controller going from the camera position to the forwad vector * some value
this script is attached to the camera
public class raycast : MonoBehaviour {
float lenthRay = 10.0f;
Vector3 originePos;
Vector3 dir;
RaycastHit hitinfo;
GameObject hitten;
bool isHitting;
Color beforC;
int selectionLayer = 9;
void Update () {
originePos = Camera.main.transform.position;
dir = Camera.main.transform.forward * lenthRay;
Debug.DrawRay(originePos, dir, Color.blue);
if (Physics.Raycast(originePos, dir, out hitinfo, lenthRay , selectionLayer)) {
hitten = hitinfo.transform.gameObject;
MeshRenderer tmp = hitten.transform.GetComponent<MeshRenderer> ();
beforC = tmp.material.color;
tmp.material.color = Color.black;
}
//hitten.transform.GetComponent<MeshRenderer> ().material.color = beforC;
print(hitten.name);
}
}
it is working great , except if i try to access the GameObject Hitten outside my if condition (like the print print(hitten.name))
i get this error before hitting an object from the right layer :
NullReferenceException: Object reference not set to an instance of an object
raycast.Update () (at Assets/raycast.cs:30)
then when i hit the object it is ok
but the problem is , i dont understand how i can change back the object color to its original color (beforC) after turning it to Color.black when the ray exit the object
this is what i try to do in the commented line , but i just get the same error than with the print , and nothing is turning black .
i have tried this :
originePos = Camera.main.transform.position;
dir = Camera.main.transform.forward * lenthRay;
Debug.DrawRay(originePos, dir, Color.blue);
isHitting = Physics.Raycast (originePos, dir, out hitinfo, lenthRay, selectionLayer);
if (isHitting) {
hitten = hitinfo.transform.gameObject;
MeshRenderer tmp = hitten.transform.GetComponent<MeshRenderer> ();
beforC = tmp.material.color;
tmp.material.color = Color.black;
}
if(!isHitting){
hitten.transform.GetComponent<MeshRenderer> ().material.color = beforC;
print(hitten.name);
}
but it is not working either
can you help me understand the logic i should be using
thanks in advance

I had this same need, when I was trying to detect when a wall was obstructing the player. I had never used a Raycast (or Linecast) before, and was surprised there wasn't a built in method to detect 'Enter', 'Stay', and 'Leave' events.
So I created this simple class to handle the details for me. I didn't bother creating any class constructors, and just exposed the properties as public. It handles both Raycasts and Linecasts.
This class tracks your objects each frame for you once you set it up using the properties.
Here's some sample code to demonstrate potential usage (the code I use for my wall detection):
private RayCaster wallRay;
void Start() {
wallRay = new RayCaster();
wallRay.OnRayEnter += WallRay_OnEnter;
wallRay.OnRayExit += WallRay_OnExit;
wallRay.LayerMask = RayCaster.GetLayerMask("Wall");
wallRay.StartTransform = camera.transform;
wallRay.EndTransform = PlayerManager.Player.transform;
}
void Update() {
wallRay.CastLine();
}
void WallRay_OnEnter(Collider collider) {
// Fade OUT wall section [Needs DOTween (free) installed]
collider.gameObject.renderer.material.DOFade(0.65f, 0.2f);
}
void WallRay_OnExit(Collider collider) {
// Fade IN wall section
collider.gameObject.renderer.material.DOFade(1f, 0.2f);
}
Here's the RayCaster class:
using System;
using System.Collections;
using UnityEngine;
public class RayCaster {
public Transform StartTransform;
public Transform EndTransform;
public Vector3 Direction;
public float RayLength;
public int LayerMask = 0;
public event Action<Collider> OnRayEnter;
public event Action<Collider> OnRayStay;
public event Action<Collider> OnRayExit;
Collider previous;
RaycastHit hit = new RaycastHit();
public bool CastRay() {
Physics.Raycast(StartTransform.position, Direction, out hit, RayLength, LayerMask);
ProcessCollision(hit.collider);
return hit.collider != null ? true : false;
}
public bool CastLine() {
Physics.Linecast(StartTransform.position, EndTransform.position, out hit, LayerMask);
ProcessCollision(hit.collider);
return hit.collider != null ? true : false;
}
private void ProcessCollision(Collider current) {
// No collision this frame.
if (current == null) {
// But there was an object hit last frame.
if (previous != null) {
DoEvent(OnRayExit, previous);
}
}
// The object is the same as last frame.
else if (previous == current) {
DoEvent(OnRayStay, current);
}
// The object is different than last frame.
else if (previous != null) {
DoEvent(OnRayExit, previous);
DoEvent(OnRayEnter, current);
}
// There was no object hit last frame.
else {
DoEvent(OnRayEnter, current);
}
// Remember this object for comparing with next frame.
previous = current;
}
private void DoEvent(Action<Collider> action, Collider collider) {
if (action != null) {
action(collider);
}
}
public static int GetLayerMask(string layerName, int existingMask=0) {
int layer = LayerMask.NameToLayer(layerName);
return existingMask | (1 << layer);
}
}

If your question is how to access the last object hit by your raycast then I suggest creating a global variable where you can store it.
Instead of setting the local variable in your method you can then set the global variable when you raycast. This way you can always access the object until you hit a new one(because this one is now stored in your global variable)
EDIT: In the event that you want to be able to keep track of all the targets that you have ever raycast I suggest making an global array where you store each item, appending them as you hit a new target.

so i did it with the mouse and it works
you have to put your object on the right layer first (9th here)
you middle mouse click on a object to change its color (here black), and right click anywhere (on the object or not) to change back its original color
only one object has its color changed at any moment , (lets call this state "selected")
when you "select" the next object by middle mouse clicking on it wile one is already "selected", it'll change the first one to its original color , as it is now "unselected"
public class raycast : MonoBehaviour {
float lenthRay = 10.0f;
Vector3 originePos;
Vector3 dir;
RaycastHit hitinfo;
GameObject hitten;
bool isHitting;
Color beforC;
int selectionLayer = 9;
bool alreadyHitten =false;
void Update () {
originePos = Camera.main.transform.position;
dir = Camera.main.transform.forward * lenthRay;
Debug.DrawRay(originePos, dir, Color.blue);
if (Input.GetMouseButtonDown (2)) {
isHitting = Physics.Raycast (originePos, dir, out hitinfo, lenthRay, selectionLayer);
if(isHitting) {
if(hitinfo.transform.gameObject == null){
hitten= null;
}
if(hitten != null && hitinfo.transform.gameObject == hitten){
alreadyHitten = true;
}
if(hitten != null && hitinfo.transform.gameObject != hitten){
alreadyHitten = false;
hitten.transform.GetComponent<MeshRenderer> ().material.color = beforC;
hitten = hitinfo.transform.gameObject;
}
hitten = hitinfo.transform.gameObject;
if(hitten != null && !alreadyHitten){
print (hitten.name);
MeshRenderer tmp = hitten.transform.GetComponent<MeshRenderer> ();
beforC = tmp.material.color;
tmp.material.color = Color.black;
}
}
}
if (Input.GetMouseButtonDown (1)) {
if(hitten != null){
alreadyHitten = false;
hitten.transform.GetComponent<MeshRenderer> ().material.color = beforC;
hitten = null;
}
}
}
}

Related

How to check if the reycast has stoped hitting a object

I Want it to set the material back to the material i setup when the reycast isint hitting the object anymore i only want it to be the hightlight material when its hitting it, Thanks in advance
private void OnMouseDrag()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Vector3 rayPoint = ray.GetPoint(distance);
rayPoint.y = 2;
transform.position = rayPoint;
// Debug.Log("m: " + Input.mousePosition);
// Debug.Log("r: " + rayPoint);
RaycastHit hit;
Ray BoardCheck = new Ray(transform.position, Vector3.down);
if (Physics.Raycast(BoardCheck, out hit))
{
if (hit.collider.tag == "BoardBlock")
{
// Debug.Log("Hit" + hit.collider.gameObject);
RaycastHitObject = hit.collider.gameObject;
RaycastHitObject.GetComponent<Renderer>().material = Hilight;
}
}
}
//IF MORE INFO IS NEEDED JUST ASK
}
A simple if else should do. If you hit your object, change its appearance, if not, change it back. All you need to do is to keep a reference to the object. Assign it via the inspector or inside the if on first hit. You already seem to be doing it (RaycastHitObject) but don't seem to make use of it.
// Target gameobject to change material of
GameObject target;
RaycastHit hit;
Ray BoardCheck = new Ray(transform.position, Vector3.down);
// Perform raycast
if (Physics.Raycast(BoardCheck, out hit))
{
// Compare collider tag
if (hit.collider.tag == "BoardBlock")
{
// Keep reference
target = hit.collider.gameObject;
// Change material
target.GetComponent<Renderer>().material = highlightMat;
}
else
{
// Change back material
target.GetComponent<Renderer>().material = nonHighlightMat;
}
}
You need to store the hit target
Before coming to that personally I would use a dedicated component like e.g.
public class Focusable : MonoBehaviour
{
[SerializeField] private Renderer _renderer;
private Material idleMaterial;
[SerializeField] private Material focusedMaterial;
private void Awake ()
{
if(!_renderer) _renderer = GetComponent<Renderer>();
idleMaterial = _renderer.material;
}
private bool isFocused;
public bool IsFocused
{
get => _isFocused;
set
{
_isFocused = value;
_renderer.material = value ? focusedMaterial : idleMaterial;
}
}
}
And then in your controller do
// Stores the currently hit object
private Focusable currentTarget;
private void OnMouseDrag()
{
// I will just trust that you know what you do with those rays
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
var rayPoint = ray.GetPoint(distance);
rayPoint.y = 2;
transform.position = rayPoint;
var BoardCheck = new Ray(transform.position, Vector3.down);
if (Physics.Raycast(BoardCheck, out var hit)
// instead of the tag you can now directly check for the component
// as this is optional you could of course also just stick to the tag if you want to
&& hit.collider.TryGetComponent<Focusable>(out var focusable))
{
// -> we are hitting one of our objects
// if it is still the same as before we don't need to do anything
if (currentTarget != focusable)
{
if(currentTarget)
{
// did we hit another one before? -> unfocus
currentTarget.IsFocused = false;
}
// now focus and store this hit target once
currentTarget = focusable;
currentTarget.IsFocused = true;
}
}
else
{
// we don't hit a focusable at all
if (currentTarget)
{
// if there was a previous hit unfocus it once
currentTarget.IsFocused = false;
currentTarget = null;
}
}
}

I have a problem with objects copy and the objects mesh in unity

So, i have a script, and two objects, one copy of the another, exactly what one does, the other does too, but in other position. The script do the following:
get the actual value of the heat variable of the object and use this for the second part;
the second part is get the heat value and check if it's higher or equal than 850,
if it is, check if the player pressed the button to transform the object mesh in another mesh
but, when the button is pressed, only the first object has it mesh changed, i already tried raycast, a lot of additional code, ineficient code and don't worked. I know i can just make two similar scripts, but i have plans to create more objects when the player wants, so, it's not going to work...
the script:
using UnityEngine;
using DG.Tweening;
public class Iron : MonoBehaviour
{
private float Heat;
private bool Heating;
[Header("Game Objects")]
[SerializeField] private GameObject WeaponCanvas;
[Header("Materials")]
[SerializeField] private Material HotIron;
[SerializeField] private Material MediumIron;
[SerializeField] private Material CoolIron;
[Space]
[Header("Meshs")]
[SerializeField] private Mesh SimpleSwordMaterial;
[Space]
[Header("Text Mesh Pro")]
[SerializeField] private TMPro.TMP_Text TemperatureText;
private bool Hot;
void Update()
{
if (Heating && Heat <= 1500)
{
Heat += 1.5f;
}
if (Heat >= 850)
{
GetComponent<Renderer>().material = HotIron;
Hot = true;
}
if (Heat >= 600 && Heat <= 849)
{
GetComponent<Renderer>().material = MediumIron;
Hot = false;
}
if (Heat <= 400)
{
GetComponent<Renderer>().material = CoolIron;
Hot = false;
}
}
void OnCollisionStay(Collision other)
{
if (other.gameObject.tag == "HeatSource")
{
Heating = true;
}
if (!(other.gameObject.tag == "HeatSource"))
{
Heating = false;
}
}
public void SimpleSword()
{
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 3.5f))
{
hit.transform.gameObject.GetComponent<MeshFilter>().mesh = SimpleSwordMaterial; // i have problems from here (i guess ;-;)
hit.transform.gameObject.GetComponent<MeshCollider>().sharedMesh = SimpleSwordMaterial;
if (hit.transform.gameObject.GetComponent<BoxCollider>() != null)
{
Destroy(hit.transform.gameObject.GetComponent<BoxCollider>());
}
if (hit.transform.gameObject.GetComponent<SphereCollider>() != null)
{
Destroy(hit.transform.gameObject.GetComponent<SphereCollider>());
}
if (hit.transform.gameObject.GetComponent<CapsuleCollider>() != null)
{
Destroy(hit.transform.gameObject.GetComponent<CapsuleCollider>());
}
transform.localScale = new Vector3(1, 1, 1);
}
WeaponCanvas.SetActive(false);
Player.onMenu = false;
Cursor.lockState = CursorLockMode.Locked;
}
void OnMouseOver()
{
if (Input.GetMouseButtonDown(1) && Hot)
{
WeaponCanvas.SetActive(true);
Player.onMenu = true;
Cursor.lockState = CursorLockMode.None;
}
TemperatureText.text = ((int)Heat).ToString() + "°";
TemperatureText.DOFade(1, 0.4f);
}
void OnMouseExit()
{
TemperatureText.DOFade(0, 0.4f);
}
}
Similar to sharedMaterial
sharedMesh is not what you want to use...
It is recommended to use this function only for reading mesh data and not for writing, since you might modify imported assets and all objects that use this mesh will be affected.
Also, be aware that is not possible to undo the changes done to this mesh.
I'm having a bit of trouble understanding the problem... are all objects with the Iron.cs getting a new mesh? if yes, it's the .sharedmesh =.
Furthermore, it's worth noting that each monobehaviour in Unity has a .GetInstanceId() method. You can compare the id of the instance you hit with your raycast and that of the one with the correct temporature.
You should also consider simply instantiating a prefab of a sword instead.

How to rotate the cube I click instead of all cubes rotating at the same time?

So i am instantiating cubes and i want to rotate a single cube which i click, however when i click on the cube, all instantiated cubes rotate at the same time, I keep trying with boolean but no success. Any help would be appreciated. UNITY 2D
{
public int rotationDirection = -1; //-1 for clockwise
public int rotationStep = 5; //Should be less than 90
public bool noRotation;
// public GameObject cubeBox;
private Vector3 currentRotation, targetRotation;
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
if (hit.collider != null && hit.collider.tag == "colourBox" && noRotation == false)
{
Debug.Log("object clicked: " + hit.collider.tag);
RotateCube();
}
}
}
void RotateCube()
{
currentRotation = gameObject.transform.eulerAngles;
targetRotation.z = (currentRotation.z + (90 * rotationDirection));
StartCoroutine(objectRotationAnimation());
}
IEnumerator objectRotationAnimation()
{
currentRotation.z += (rotationStep * rotationDirection);
gameObject.transform.eulerAngles = currentRotation;
yield return new WaitForSeconds(0);
if (((int)currentRotation.z > (int)targetRotation.z && rotationDirection < 0))
{
StartCoroutine(objectRotationAnimation());
}
}
}
If I understand you correctly this script is present on all cubes.
The problem is that when you click with your mouse every single cube will create its own instance of a raycast. After that every cube will check if the hit.collider is not null, if its tag is colourBox and if noRotation equals false.
You in fact don't check anywhere what cube you just clicked on. You can simply add this check by comparing the instance ID of the gameobject the raycast collided with and the instance ID of the gameobject where the script runs.
if (hit.collider != null && hit.collider.tag == "colourBox" && noRotation == false)
{
// Check if the cube that was clicked was this cube
if (hit.collider.gameObject.GetInstanceID() == gameObject.GetInstanceID())
{
Debug.Log("object clicked: " + hit.collider.tag);
RotateCube();
}
}
This way every cube will compare its instance ID with the instance ID of the clicked object. Only the clicked object will return true in this if statement and thus runs the RotateCube method.
Used this instead of RayCastHit2D ....Deleted everything inside Update and created a new void.
private void OnMouseDown()
{
RotateCube();
}

Object exits portal moving the wrong direction in Unity 2D?

Before I start I'd like to say sorry if this isn't highly professionally written, I've been working on this for hours banging my head against the wall trying to figure this out with no luck, I'm tired and stressed.
I'm trying to get a moving object to enter through 1 portal at a specific point, and come out the other portal at the same point it entered. So if the ball enters the top of the portal, the object will come out at the top of the exit portal, and the if the object enters from the bottom of the portal it will exit the bottom of the other portal. I'm not the best a illustration, but here's what I want to it do:
Here you can see in both images the object enters the blue portal and exits the orange at the point that it entered, so top to top, bottom to bottom.
I've actually gotten this to work fine, but now I need to do it again but this time, one of the portals needs to be horizontal instead of vertical:
So what I've done, is make it so when both a vertical, I leave a bool called "exitIsHorizontal" unchecked (false), and when one of them is on the ceiling on the level, it translates the vertical axis to a horizontal one.
I even sort of got that to work, however it's got a reproducible quark that needs to be fixed. When the object enters the bottom of the portal, it works fine like you'd expect and just like the image above. But when you hit the top of the portal, the object comes out the other side of the portal like you'd expect, but the object begins moving in the opposite direction as see in this image:
Correct exit location, wrong exit direction for some reason. I also need this function to be dynamic so that if say, the object hit the blue portal from the other direction that the exit direction would switch sides as well like this:
Here is my script:
public GameObject otherPortal;
public PortalController otherPortalScript;
private BallController ballController;
public bool exitIsHorizontal = false;
List<PortalController> inUseControllers = new List<PortalController>();
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.tag == "Ball")
{
ballController = other.GetComponent<BallController>();
if (inUseControllers.Count == 0)
{
inUseControllers.Add(otherPortalScript);
var offset = other.transform.position - transform.position;
if(exitIsHorizontal)
{
offset.x = offset.y;
offset.y = 0;
}
else
{
offset.x = 0;
}
other.transform.position = otherPortal.transform.position + offset;
}
}
}
void OnTriggerExit2D(Collider2D other)
{
if (other.gameObject.tag == "Ball")
{
inUseControllers.Clear();
}
}
This script is attached to both portals so that both Portals can handle both entering and exiting. And any variable you don't see declared with anything in the script making it essentially null (such as "otherPotal") I declare the in editor.
I'll bet it's something extremely simple that I just keep missing, I just don't know that something is.
So a portal is basically a wormhole. An object that enters will retain its local position and direction.
Functions:
Unity has functions for transforming from world space to local space and vice versa.
Position:
Transform.InverseTransformPoint
Transforms position from world space to local space.
Transform.TransformPoint
Transforms position from local space to world space.
Direction:
For transforming directions you will have to use:
Transform.InverseTransformDirection
Transforms a direction from world space to local space. The opposite
of Transform.TransformDirection.
Transform.TransformDirection
Transforms direction from local space to world space.
Simple Example:
One script that you attach to both portals. It moves objects with the tag "Ball" over to the exitPortal.
public class Portal : MonoBehaviour
{
[SerializeField] Portal exitPortal;
void OnTriggerEnter2D(Collider2D collider)
{
if (collider.CompareTag("Ball"))
{
GameObject ball = collider.gameObject;
Rigidbody2D rigidbody = ball.GetComponent<Rigidbody2D>();
Vector3 inPosition = this.transform.InverseTransformPoint(ball.transform.position);
inPosition.x = -inPosition.x;
Vector3 outPosition = exitPortal.transform.TransformPoint(inPosition);
Vector3 inDirection = this.transform.InverseTransformDirection(rigidbody.velocity);
Vector3 outDirection = exitPortal.transform.TransformDirection(inDirection);
ball.transform.position = outPosition;
rigidbody.velocity = -outDirection;
}
}
}
You get this:
Complex Example:
You need 3 scripts for this to work:
Portal: The thing that touches warpable objects
Warpable: A thing that travels through the portals
Ghost: A mirrored warpable that shows while going through a portal
This is what the ghost looks like:
You will need two additional layers – Portal and Ghost, with the collision matrix set as in the image.
Scripts:
I've added enough comments within the code for you to make sense as to what it's doing.
Portal:
public class Portal : MonoBehaviour
{
[SerializeField] Portal exitPortal;
void OnTriggerEnter2D(Collider2D collider)
{
// When a warpable enters a portal create a ghost
if (collider.TryGetComponent(out Warpable warpable))
{
// Create a ghost only if we haven't already
if (warpable.Ghost == null) warpable.CreateGhost(this, exitPortal);
}
}
void OnTriggerExit2D(Collider2D collider)
{
// When a warpable exist a portal; check if it has a ghost
if (collider.TryGetComponent(out Warpable warpable))
{
// Teleport to the ghost; apply its position, rotation, velocity
if (warpable.Ghost != null)
{
// Create vectors to compare dot product
Vector3 portalToWarpable = warpable.transform.position - this.transform.position;
Vector3 portalDownwards = -this.transform.up;
// If warpable is on the other side of the portal you get a value that's more than zero
float dot = Vector3.Dot(portalDownwards, portalToWarpable);
bool passedThroughPortal = dot >= 0f;
// If we passed through the portal then teleport to the ghost; otherwise just continue
if (passedThroughPortal)
{
warpable.Position = warpable.Ghost.warpable.Position;
warpable.Rotation = warpable.Ghost.warpable.Rotation;
warpable.Velocity = warpable.Ghost.warpable.Velocity;
}
// Destroy the ghost
warpable.DestroyGhost();
}
}
}
void OnDrawGizmos()
{
Gizmos.color = Color.magenta;
Gizmos.DrawRay(this.transform.position, this.transform.up);
}
}
Warpable:
public class Warpable : MonoBehaviour
{
[SerializeField] new Rigidbody2D rigidbody;
public Ghost Ghost { get; private set; }
public void CreateGhost(Portal inPortal, Portal outPortal)
{
// Move the ghost object to the Ghost layer, this is so that ghost can collide with real objects, other ghosts, but not with the portal.
// Ghost/Ghost = TRUE
// Ghost/Default = TRUE
// Ghost/Portal = FALSE
GameObject original = this.gameObject;
GameObject duplicate = GameObject.Instantiate(original);
duplicate.layer = LayerMask.NameToLayer("Ghost");
Physics2D.IgnoreCollision(
original.GetComponent<Collider2D>(),
duplicate.GetComponent<Collider2D>()
);
// Add the ghost component
Ghost = duplicate.AddComponent<Ghost>();
Ghost.observing = original.GetComponent<Warpable>();
Ghost.warpable = duplicate.GetComponent<Warpable>();
Ghost.inPortal = inPortal;
Ghost.outPortal = outPortal;
}
public void DestroyGhost()
{
GameObject.Destroy(Ghost.gameObject);
Ghost = null;
}
public Vector3 Position
{
get { return transform.position; }
set { transform.position = value; }
}
public Quaternion Rotation
{
get { return transform.rotation; }
set { transform.rotation = value; }
}
public Vector3 Velocity
{
get { return rigidbody.velocity; }
set { rigidbody.velocity = value; }
}
}
Ghost:
public class Ghost : MonoBehaviour
{
public Warpable observing;
public Warpable warpable;
public Portal inPortal;
public Portal outPortal;
void FixedUpdate()
{
warpable.Position = OutPosition(observing.Position);
warpable.Rotation = OutRotation(observing.Rotation);
warpable.Velocity = OutDirection(observing.Velocity);
}
Vector3 OutPosition(Vector3 position)
{
Vector3 inPosition = -inPortal.transform.InverseTransformPoint(position);
return outPortal.transform.TransformPoint(inPosition);
}
Quaternion OutRotation(Quaternion rotation)
{
return Quaternion.Inverse(inPortal.transform.rotation) * outPortal.transform.rotation * rotation;
}
Vector3 OutDirection(Vector3 velocity)
{
Vector3 inDirection = -inPortal.transform.InverseTransformDirection(velocity);
return outPortal.transform.TransformDirection(inDirection);
}
void OnDrawGizmos()
{
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(warpable.Position, 1f);
Gizmos.DrawLine(warpable.Position, warpable.Position + warpable.Velocity);
}
}
And the final result is this:

Drag and Drop on UI

I want to create a simple drag and drop game using Unity UI objects.
My objects right now are images with scripts attached to them. My script is using OnBeginDrag, OnDrag and OnEndDrag events which works fine when I want to move image objects.
However image objects are always square and I want to detect drag only when mouse is on specifid area. So I created a Polygon Collider 2D on my image and adjusted it to shape I wanted. Unfortunetly I can't make it work at all.
public void OnBeginDrag(PointerEventData eventData)
{
var canvas = FindInParents<Canvas>(gameObject);
itemBeingDragged = gameObject;
m_DraggingPlane = canvas.transform as RectTransform;
}
public void OnDrag(PointerEventData eventData)
{
SetDraggedPosition(eventData);
}
public void OnEndDrag(PointerEventData eventData)
{
itemBeingDragged = null;
int i = 0;
foreach (Vector3 hex in otherHexes)
{
Vector3 rayBeginning = hex + transform.position;
rays = Physics2D.RaycastAll(rayBeginning, Vector2.zero);
if (rays.Length > 1) //if there are other elements in RayCast
{
foreach (RaycastHit2D ray in rays)
{
if (ray.transform.gameObject != gameObject && ray.transform.tag == "Hex") //if element is not self and is a HexGrid
{
SuccessfulDrag(ray, i);
}
else if (ray.transform.gameObject != gameObject && ray.transform.tag == "GetOut") //if element is not self and is another puzzle
{
FailedDrag();
break;
}
}
}
else if (rays.Length == 1 && rays[0].transform.gameObject == gameObject) //if there is only one element and it's self
{
FailedDrag();
break;
}
i++;
}
}
Ok, I googled a bit more and I found a solution. Just create a class with this code inside and put it on the object that you want to drag by it's collider.
using UnityEngine;
[RequireComponent(typeof(RectTransform), typeof(Collider2D))]
public class Collider2DRaycastFilter : MonoBehaviour, ICanvasRaycastFilter
{
Collider2D myCollider;
RectTransform rectTransform;
void Awake()
{
myCollider = GetComponent<Collider2D>();
rectTransform = GetComponent<RectTransform>();
}
public bool IsRaycastLocationValid(Vector2 screenPos, Camera eventCamera)
{
var worldPoint = Vector3.zero;
var isInside = RectTransformUtility.ScreenPointToWorldPointInRectangle(
rectTransform,
screenPos,
eventCamera,
out worldPoint
);
if (isInside)
isInside = myCollider.OverlapPoint(worldPoint);
return isInside;
}
}

Categories