For picking-up objects, I chose to make the picked-up object float in the middle of the screen in front of the camera following a certain point.
raycastPos = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width / 2, Screen.height / 2, 0));
RaycastHit hit;
if (Physics.Raycast(raycastPos, mainCamera.transform.forward, out hit, maxDistance, 1 << interactableLayerIndex))
{
lookObject = hit.collider.transform.gameObject;
reticle.color = new Color(1, 0, 0, 0.75f);
}
else
{
lookObject = null;
reticle.color = new Color(1, 1, 1, 0.75f);
}
if (PickingUp)
{
if (currentlyPickedUpObject == null)
{
if (lookObject != null)
{
if (hit.distance >= minDistance)
{
PickUpObject();
}
}
}
}
public void PickUpObject()
{
physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
currentlyPickedUpObject = lookObject;
// currPicked = true;
pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
physicsObject.playerInteractions = this;
}
I'm facing an issue with this, Whenever the player is standing over a pickable object and picks it up (while standing on it), the player flies on top of the object like this:
]
Here is another image from the Scene window :
You can see clearly here what's happening when the player picks up an object underneath him.
How can I prevent that from happening?
Put the picked up object in a new layer. Then disable any physics interaction between player and said layer. You will then observe that the picked up object and the player may intersect. To fix this, you can project the object outside of your player collider using a few more ray casts. Given that the object's collider is convex, calculations should be fairly simple.
Related
I'm using Raycast for picking up objects, whenever the player looking directly at a pickable object, the reticle colors turns to red meaning that it can be picked up. However, objects are still pickable even if there is a barrier between the raycast and the pickable object. like this image
as you can see the drawer is closed and the pickable object is inside, but the player still can pick it up!. it should pe pickable only when the object is exposed and the raycast hits it directly like this:
if (PickingUp)
{
if (heldObj == null) //if currently not holding anything
{
//perform raycast to check if player is looking at object within pickuprange
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, pickUpRange) && hit.transform.GetComponent<canPickUp_>() != null)
{
//make sure pickup tag is attached
// if (hit.transform.gameObject.tag == "canPickUp")
Debug.Log("Color should be changed");
if (hit.transform.CompareTag("TargetObj") && !targetObjectsList.Contains(hit.transform.gameObject))
{
/* if (aSource)
{
aSource.Play();
}*/
targetObjectsList.Add(hit.transform.gameObject);
}
PickUpObject(hit.transform.gameObject);
}
}
else
{
if (canDrop == true)
{
StopClipping(); //prevents object from clipping through walls
DropObject();
}
}
private void PickUpObject(GameObject pickUpObj)
{
if (pickUpObj.GetComponent<Rigidbody>()) //make sure the object has a RigidBody
{
heldObj = pickUpObj; //assign heldObj to the object that was hit by the raycast (no longer == null)
heldObjRb = pickUpObj.GetComponent<Rigidbody>(); //assign Rigidbody
heldObjRb.isKinematic = true;
heldObjRb.transform.parent = holdPos.transform; //parent object to holdposition
heldObj.layer = LayerNumber; //change the object layer to the holdLayer
//make sure object doesnt collide with player, it can cause weird bugs
Physics.IgnoreCollision(heldObj.GetComponent<Collider>(), player.GetComponent<Collider>(), true);
reticle.color = new Color(1, 1, 1, 0.75f);
reticle.enabled = false;
//transform.localScale = new Vector3(1 / heldObj.transform.localScale.x, 1 / heldObj.transform.localScale.y, 1 / heldObj.transform.localScale.z);
}
}
void DropObject()
{
//re-enable collision with player
Physics.IgnoreCollision(heldObj.GetComponent<Collider>(), player.GetComponent<Collider>(), false);
heldObj.layer = 0; //object assigned back to default layer
heldObjRb.isKinematic = false;
heldObj.transform.parent = null; //unparent object
heldObj = null; //undefine game object
reticle.color = new Color(1, 1, 1, 0.75f);
reticle.enabled = true;
}
How would I prevent this from happening?
As discussed in the comments the issue was that the "barrier" objects had their layers set to "Ignore Raycast", preventing the Physics.Raycast from reporting them as the hit.
Changing the layer to default (or any other layer that is not "Ignore Raycast") fixes the issue, as the "barrier" will be reported as the hit instead of the pickable object
I have a player character in a topdown game that moves in grid-like movements (1 unit at a time), but when it hits a patch of ice (square), I want it to switch to lerp-movement, slide to the edge, and stop.
Currently I have 5 different colliders as children for each patch of ice: the ice collider itself, and 4 slightly distanced colliders, one for each side of the ice. When it hits the ice collider, depending on which direction it was heading in, it should lerp to the distanced collider associated.
Like so (it's hard to see the main collider but it's there):
Here is the code I have been using for the down key (its basically the same for all keys):
else if (Input.GetKeyDown(KeyCode.DownArrow))
{
Vector2 movementDown = new Vector2(0, -1);
RaycastHit2D hitDown = Physics2D.Raycast(transform.position, movementDown, 0.05f);
if (hitDown.collider && hitDown.collider.gameObject.tag == "barrier")
{
Debug.Log("N/A");
}
else if (onIce)
{
player.transform.position = Vector3.Lerp(transform.position, downIce.transform.position, 100 * Time.fixedDeltaTime);
}
else
{
player.transform.position += new Vector3(movementDown.x, movementDown.y, -0.1f);
}
}
EDIT: code that updates bool 'onIce':
void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "ice") {
onIce = true;
}
}
void OnTriggerExit2D(Collider2D collision)
{
if (collision.gameObject.tag == "ice")
{
onIce = false;
}
}
Lerp is only getting called once, when you push the button. One way to fix it is by using a coroutine:
IEnumerator Slide() {
var t = 0f;
var start = player.transform.position; //we will change the position every frame
//so for lerp to work, we need to save it here.
var end = downIce.transform.position;
while(t < 1f){
t += Time.deltaTime;
player.transform.position = Vector3.Lerp(start, end , t);
yield return null; //returning null waits until the next frame
}
}
And then you use it like this:
...
else if (onIce)
{
this.StartCoroutine(this.Slide());
}
else
...
I think this is still not exactly what you want in your game because it will lerp to the center of the collider. If that's the case you can easily fix it by changing how it calculates the end variable in the coroutine to make the player only slide along the correct axis.
I have RayCast represented by a LineRenderer in Unity, so it looks like a laser. I want this laser to move objects it collides with so that the object follows the hit.point of the Raycast hit.
My code doesn't work, because I move these GameObjects to the hit.point, which causes the object to comes towards the start point of the Raycast, because a new hit.point gets calculated since the object is moving to hit.point. I understand why this is happening, but I'm not sure how to get the object to move with the Raycast, but not effect a new hit.point.
Here's my update function in my script attached to my Laser GameObject. Does anyone know how I can fix my code so that the object moves with the hit.point?
void Update()
{
Vector3 target = calculateDeltaVector();
lr.SetPosition(0, palm.transform.position);
RaycastHit hit;
if (Physics.Raycast(palm.transform.position, target , out hit))
{
if (hit.collider)
{
lr.SetPosition(1, hit.point);
if (hit.transform.gameObject.tag == "Chair")
{
GameObject chair = hit.transform.gameObject;
// !!! move object to hit point, problem HERE
chair.transform.position = hit.point;
hitLock = false;
}
}
}
else lr.SetPosition(1, target * 50);
}
In Unity Inspector, you can select the object and change the layer to "2: Ignore Raycast" This will make the raycast ignore the object and go through it.
Don't know, but your code should move the chair to the chair, which likely will make the chair go towards you.
You have to implement Begin and End the raycast move, e.g using mouse click.
Below is the example
public class Mover : MonoBehaviour
{
public Collider selectedChair;
void Update ()
{
Vector3 target = calculateDeltaVector();
lr.SetPosition(0, palm.transform.position);
RaycastHit hit;
if (Physics.Raycast(palm.transform.position, target , out hit))
{
if (hit.collider)
{
lr.SetPosition(1, hit.point);
if (hit.transform.gameObject.tag == "Chair" && Input.GetMouseButton(0)) //if you want to move it you have to click mouse button first
{
selectedChair = hit.collider;
hit.collider.enabled = false; //disable the collider of currently selected chair so it won't go towards you
}
if (selectedChair)
{
// !!! move object to hit point, problem HERE
selectedChair.transform.position = hit.point;
hitLock = false;
}
if (Input.GetMouseButton(0))
{
selectedChair.enabled true;
selectedChair = null; //release it
}
}
}
else lr.SetPosition(1, target * 50);
}
}
I have written a script where a gameobject is intended to move to a raycast.point thrown from the player camera. For the most part this works fine, however there are times (approximately when camera is 45 degrees up from the object) when the object rapidly moves towards the camera (i.e. raycast source).
I have tried a number of approaches attempting to resolve this, however I can’t seem to dig out the root of this issue. Managed to prevent this from occurring by deactivating the collider attached to the object being moved. However I need the collider for various reasons so this approach is not appropriate.
If anyone can provide any pointers as to where I am going wrong I would be incredibly grateful.
NB: coding in uJS
Many thanks in advance, Ryan
function FixedUpdate() {
if (modObj != null && !guiMode) {
//Panel Control
if (!selectObjPanel.activeSelf && !modifySelectObjPanel.activeSelf) //if the selectpanel not open and modSelect not already activated
{
activateModSelectObjPanel(true); //activate it
} else if (selectObjPanel.activeSelf) {
activateModSelectObjPanel(false);
}
//Move
if (Input.GetKey(KeyCode.E)) {
if (Input.GetKeyDown(KeyCode.E)) {
// modObj.GetComponent(BoxCollider).enabled = false;
modObj.GetComponent(Rigidbody).isKinematic = true;
modObj.GetComponent(Rigidbody).useGravity = false;
//
initPos = modObj.transform.position;
var initRotation = modObj.transform.rotation;
}
moveObject(modObj, initPos, initRotation);
} else {
// modObj.GetComponent(BoxCollider).enabled = true;
modObj.GetComponent(Rigidbody).isKinematic = false;
modObj.GetComponent(Rigidbody).useGravity = true;
}
}
}
function moveObject(modObj: GameObject, initPos: Vector3, initRotation: Quaternion) {
//Debug.Log("Moving Object");
var hit: RaycastHit;
var foundHit: boolean = false;
foundHit = Physics.Raycast(transform.position, transform.forward, hit);
//Debug.DrawRay(transform.position, transform.forward, Color.blue);
if (foundHit && hit.transform.tag != "Player") {
//Debug.Log("Move to Hit Point: " + hit.point);
modifyObjGUIscript.activateMoveDisplay(initPos, hit.point);
var meshHalfHeight = modObj.GetComponent. < MeshRenderer > ().bounds.size.y / 2; //helps account for large and small objects
// Debug.Log("CurObj Mesh Min: " + meshHalfHeight);
// modObj.transform.position = hit.point; //***method 01***
// modObj.transform.position = Vector3.Lerp(initPos, hit.point, speed); //***method 02***
// modObj.transform.position = Vector3.SmoothDamp(initPos, hit.point, velocity, smoothTime); //***method 02***
var rb = modObj.GetComponent. < Rigidbody > ();
rb.MovePosition(hit.point); //***method 03***
modObj.transform.position.y = modObj.transform.position.y + meshHalfHeight + hoverHeight;
modObj.transform.rotation = initRotation;
}
}
Turns out the issue was being caused by the raycast hitting the object being moved. Resolved this by only allowing hits from the terrain to be used as points to move to.
if(foundHit && hit.transform.tag == "Terrain")
public static bool IsTopmost(GameObject go)
{
RaycastHit[] hits;
Vector3 wp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
wp.z = Camera.main.transform.position.z;
hits = Physics.RaycastAll(wp, Vector3.forward, Vector3.Distance(Camera.main.transform.position, go.transform.position) * 2);
if (hits.Length == 0)
{
return false;
}
GameObject topMostSoFar = hits[0].collider.gameObject;
RaycastHit hit;
for (int i = 1; i < hits.Length; i++)
{
hit = hits[i];
if (Compare(topMostSoFar, hit.collider.gameObject) == -1)
{
topMostSoFar = hit.collider.gameObject;
}
}
Debug.Log("finishes method");
return topMostSoFar.name == go.name;
}
My camera position.z = -5, all sprites in the game position.z = 0. This method never finishes, it always enters the first if which says there are no colliders under the mouse position. What do I do wrong here?
EDIT: It turns out my mistake is very stupid, I'm using Physics instead of Physics2D for 2d colliders. How can I check same thing but with 2d raycasting?
The solution is to use the 2d methods and variable types everywhere and for the direction of the RaycastAll to use Vector2.zero, unless you put Vector2.zero it;s not going to work.