Collider conflicts
In my game, I have a cube that is made of smaller cubes. I have colliders on both the smaller cubes and the larger cube(see image). I did this because I want to be able to use mouse/touch to rotate the large cube and to select the smaller cubes individually. I would like to have it where the player drags their mouse/finger to rotate and clicks/taps to select. The problem I'm having is that the collider on the larger cube obscures the colliders on the smaller cubes, thus making it where they don't detect the mouse click.
Does anyone have any experience with something like this or any good suggestions on how to handle it?
Answer by CodesCove · Dec 11, 2021 at 02:24 AM
One way would be to handle the dragging with the OnMouseDrag() event and the selection in OnMouseUp() with separate Phycics.Raycast where you set layermask so that it only hits with layer the small cubes are.
In the OnMouseDrag you check that is user actually dragging or just pressing mouse down (because OnMouseDrag is called even if the mouse is not moving)
In the OnMouseUp you check that has mouse been dragged and if not then fire Raycast.
Pseudocode. This should be attached to the big colliders GameObject:
Vector3 lastPos;
GameObject selectedCube;
bool isDragging;
public LayerMask layerMaskWithOnlySmallCubeLayer;
private void OnMouseDrag()
{
if (lastPos != Input.mousePosition) isDragging = true;
lastPos = Input.mousePosition;
}
private void OnMouseUp()
{
if(!isDragging)
{
Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out RaycastHit hit, 1000, layerMaskWithOnlySmallCubeLayer);
selectedCube = hit.collider?.gameObject;
return;
}
isDragging = false;
}
Notice that you need the set the small cubes to the different physics layer that the big collider. Then you select that layer in the layer mask.
Notice. The mouse movement check is not optimal in the above code. You may want to give it more tolerance.
@CodesCove thanks for the assist. I will give this a try and let you know if I can make it work. My only question is the OnMouse events, I thought I had read at one point that those did not work properly with touch input, or that they were expensive in terms of performance. Is this not the case?
Answer by alagrad94 · Dec 11, 2021 at 06:48 PM
@CodesCove, Thanks for the assist and hopefully this will help someone else along the way, this I what I finally came up with. It is working well in the editor with the mouse, I haven't built it and tried the touch portion yet. I'm handling everything through my Input Manager and C# Events because I try to keep as much as possible to keep things out of the Update loop. I've noticed in doing this it really helps with battery consumption, etc... on mobile devices. So, doing it this way, I have one script running an Update loop (the manager) instead of hundreds (each individual square).
using UnityEngine;
namespace CubeGame {
public class InputManager : Manager {
//Creates instance of InputManager singleton. Awake() ensures that this instance is
//the only instance of InputManager to enforce singleton pattern.
public static InputManager Instance { get; private set; }
/*IsDragging prevents background selecting of squares while dragging.
*LastClickWasSelect prevents having to click a square multiple times if they are on opposite corners of
the board, since the distance then become greater than the drag threshold.
*The reference to the board collider is to disable/enable it. This turned out the be easier and more
*reliable than the LayerMask method.*/
public bool IsDragging;
private bool LastClickWasSelect;
public BoxCollider BoardCollider;
public float DragThreshold;
private float Distance;
//Necessary variables for Raycast
private Ray Ray;
private RaycastHit Hit;
public Camera BoardCamera;
//Separate variables for mouse and touch because touch.position returns a Vector2 and Input.mousePosition
//returns a Vector3
#if UNITY_EDITOR
private Vector3 CurrentPos;
private Vector3 LastPos;
#elif UNITY_IOS || UNITY_ANDROID
private Vector2 CurrentPos;
private Vector2 LastPos;
#endif
private void Awake() {
//Ensure only one InputManager instance exists
if (Instance != null && Instance != this) {
Destroy(gameObject);
} else {
Instance = this;
}
IsLoaded = true;
}
private void Update() {
#if UNITY_EDITOR
if (Input.GetMouseButton(0)) {
CurrentPos = Input.mousePosition;
Distance = !LastClickWasSelect ? Vector3.Distance(CurrentPos, LastPos) : 0;
if (Distance >= DragThreshold) {
IsDragging = true;
EventParams eventParams = new EventParams();
EventManager.TriggerEvent("RotateCube", eventParams);
LastClickWasSelect = false;
}
LastPos = Input.mousePosition;
}
if (Input.GetMouseButtonUp(0)) {
if (Distance < DragThreshold && !IsDragging) {
BoardCollider.enabled = false;
Ray = BoardCamera.ScreenPointToRay(Input.mousePosition);
Physics.Raycast(Ray, out Hit);
EventParams eventParams2 = new EventParams() {
StringParam = Hit.collider.gameObject.name
};
EventManager.TriggerEvent("SelectSquares", eventParams2);
LastClickWasSelect = true;
}
LastPos = Input.mousePosition;
IsDragging = false;
BoardCollider.enabled = true;
}
#elif UNITY_IOS || UNITY_ANDROID
if (Input.touchCount > 0) {
Touch touch = Input.touches[0];
CurrentPos = touch.position;
Distance = !LastClickWasSelect ? Vector2.Distance(CurrentPos, LastPos) : 0;
if (touch.phase == TouchPhase.Moved) {
if (Distance >= DragThreshold) {
IsDragging = true;
EventParams eventParams = new EventParams() {
FloatParam = touch.position.x,
FloatParam2 = touch.position.y
};
EventManager.TriggerEvent("RotateCube", eventParams);
LastClickWasSelect = false;
}
LastPos = touch.position;
}
if (touch.phase == TouchPhase.Ended) {
if (Distance < DragThreshold && !IsDragging) {
BoardCollider.enabled = false;
Ray = BoardCamera.ScreenPointToRay(touch.position);
Physics.Raycast(Ray, out Hit);
EventParams eventParams2 = new EventParams() {
StringParam = Hit.collider.gameObject.name
};
EventManager.TriggerEvent("SelectSquares", eventParams2);
LastClickWasSelect = true;
}
LastPos = touch.position;
IsDragging = false;
BoardCollider.enabled = true;
}
}
#endif
}
}
}