I'm trying to use Physics2D.Raycast in order to check if the player is on the ground (I know that there are other approaches to check if the player is on the ground but I think that the raycast is the most reliable).
The problem is that in my scenario it returns the player itself as hit and I really don't understand why and what should I do.
My code (in PlayerController) is the following:
public bool IsGrounded () {
Bounds bounds = this.playerCollider.bounds;
Vector3 rayOrigin = bounds.center;
rayOrigin.y -= bounds.extents.y;
RaycastHit2D hit = Physics2D.Raycast (rayOrigin, Vector2.down, 0.1f);
if (hit.collider != null) {
Debug.Log ("Collider is: " + hit.collider.name);
}
return hit.collider != null;
}
And I can debug the casted ray using:
Debug.DrawLine (rayOrigin, new Vector3 (rayOrigin.x, rayOrigin.y - 0.1f, rayOrigin.z), Color.magenta);
...and it gets casted as expected, while the Debug.Log always reports "Player" which is itself and I don't know how it's possible. So what's wrong?
ps. I'm using Unity 5.3
The problem is occurring because your Player is overlapping at the start of the raycast. There are few ways to fix this:
1.Disable Queries Start In Colliders.
Go to Edit->Project Settings->Physics 2D then make sure that Queries Start In Colliders is NOT checked. Your code ran fine after changing that. Here is a screenshot:
2.Another solution is to use layers.Raycasting but ignoring the Player layer.Before you do that, make sure to create a layer called Ground and put your ground GameObject to the Ground layer then create another layer called Player and put your player in the Player layer. We can now use bitwise operator to exclude Player layer from the raycast.
Now, lets assume that Player layer number is 9. The code below should fix your problem.
public int playerLayer = 9;
int layerMask = ~(1 << playerLayer); //Exclude layer 9
RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.down, 0.1f, layerMask);
That's it. Both of these were able to solve your problem.
For other people reading, below are other ways to easily detect when Player is touching the floor without using Physics2D.Raycast or doing all those things above.
Simply attach to the Player.
public class Player : MonoBehaviour
{
public LayerMask groundLayer;
Collider2D playerCollider;
bool grounded;
void Start()
{
playerCollider = gameObject.GetComponent<Collider2D>();
}
public bool IsGrounded()
{
grounded = Physics2D.OverlapCircle(playerCollider.transform.position, 1, groundLayer);
return grounded;
}
}
Or you can use IsTouchingLayers.
public bool IsGrounded()
{
grounded = grounded = playerCollider.IsTouchingLayers(groundLayer.value);
return grounded;
}
Vector2.down vector does not present the object space's down vector all the time. You can use -transform.up.
Also, if it is acceptable you can add some padding to start point of the ray, like rayOrigin.y += 0.0001f;.
Related
I'm trying to check collision with Polygon collider using Raycast on my field in my Unity game. And then create crop on position of ray cast if conditions met by clicking button. Even though it checks collision perfectly i have a few issues that i just cant figure out how to fix. Like:
my shooter game object teleport on scene randomly, but raycasting is always correct O_o.
Instantiated crop disappear in moment of next instantiation...
Can some one help me to fix it? Here is the code:
public class PlayerRay : MonoBehaviour
{
float borderLeft = -4.34f;
float borderRight = 3.85f;
float borderUp = 1.61f;
float borderDown = -2.32f;
public GameObject field;
public GameObject shooter;
public Transform shooterPos;
PolygonCollider2D fieldCollider;
public GameObject repka;
private void Start()
{
fieldCollider = field.GetComponent<PolygonCollider2D>();
}
void Update()
{
}
public void Sow()
{
Vector3 rayCastPos = new Vector3(Random.Range(borderLeft, borderRight), Random.Range(borderDown, borderUp), 0);
shooter.transform.Translate(rayCastPos);
Debug.DrawRay(transform.position, transform.forward * 10, Color.yellow);
RaycastHit2D hit = Physics2D.Raycast(rayCastPos, Vector3.forward);
if (hit == fieldCollider)
{
Debug.Log("We hit collider");
Instantiate(repka, shooterPos);
}
else
{
Debug.Log("There is no colider at: " + rayCastPos);
}
}
}
enter image description here
I found solution! It's just that i tried to use Transform overload for instantiate instead of using Vector3. Because I forgot to add "rotation" as third parameter. So I switched to Vector3 overload, added Quaternion.identity for rotation and it's all works just fine now :)
Here is the fix:
Instantiate(repka, rayCastPos, Quaternion.identity);
I have a moving platform in a 2D Sidescroller built in Unity 2020.1
The Moving Platform translates between two points using the MoveTo method. It does not have a RigidBody2D component.
I attach the Player to the platform by making it the child of the platform using OnCollisionEnter2D and OnCollisionExit2D to parent the Player to the parent and reset to null respectively. Works great.
I'm using the CharacterController from Standard Assets.
The problem:
The player just walks in place when I try to move him back and forth on the platform.
What I've tried so far:
Changing the current velocity of the player by adding a constant to the x dimension of it's move vector.
Works kinda sorta but that constant needs to be huge to get it to move even a little bit. It's a huge kluge that violates every sense of coding propriety.
Put a RigidBody2D on the platform. Make it kinematic so it doesn't fall to the ground when I land on it. Move the platform via "rb.velocity = new Vector2(speed, rb.velocity.y)";
2a) Attempt to make the Player a child of the kinematic platform.
Player is made a child, but it doesn't move with the platform as expected. I believe that this is because both objects have RigidBody2D components, which I gather don't play well together based on what I've read.
2b) Attempt to add the platform's moving vector to the player's movement vector to make him stay in one place. Player stays stationary to make sure he stays fixed on the platform.
No dice.
I'm all out of ideas. Perusing videos on making player's stick to moving platforms all use the platform to move the player from place to place, without expecting that the game may want the player to move back and forth on the platform as the platform is moving.
I can't believe that this isn't a solved problem, but my Google foo isn't getting me any answers.
Thanks.
I'm a fairly newbie to Unity and C# but I wanted to help so I tried simulating your game for a solution and I didn't run into any problems using this script as the Player movement (you can modify variables as u like, add a separate variable for jump speed to make it smoother etc)
public class Player : MonoBehaviour {
Rigidbody2D rb;
float speed = 7f;
Vector3 movement;
public bool isOnGround;
public bool isOnPlatform;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
movement = new Vector3(Input.GetAxis("Horizontal"), 0f, 0f);
transform.position += movement * speed * Time.deltaTime;
Jump();
}
void Jump()
{
if (Input.GetButtonDown("Jump") && isOnGround || Input.GetButtonDown("Jump") && isOnPlatform)
{
rb.AddForce(transform.up * speed, ForceMode2D.Impulse);
}
}
}
Also add an empty child object to your Player gameObject and add a BoxCollider2D at his feet, narrow it down on Y axis like this
also attach this script to that child gameObject to check if player is on the ground(tag ground collider objects with new tag "Ground") so u don't jump infinitely while in the air OR if the player is on the platform(tag platform collider objects with "Platform") so you're still able to jump off it
public class GroundCheck : MonoBehaviour {
Player player;
MovingPlatform mp;
// Start is called before the first frame update
void Start()
{
player = FindObjectOfType<Player>();
mp = FindObjectOfType<MovingPlatform>();
}
private void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Ground")
{
player.isOnGround = true;
}
if (other.gameObject.tag == "Platform")
{
player.isOnPlatform = true;
transform.parent.SetParent(other.transform);
mp.MoveThePlatform();
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.tag == "Ground")
{
player.isOnGround = false;
}
if (other.gameObject.tag == "Platform")
{
transform.parent.SetParent(null);
}
}
}
and finally for platform movement (no RigidBody2Ds, just a collider)
public class MovingPlatform : MonoBehaviour {
bool moving;
public Transform moveHere;
// Update is called once per frame
void Update()
{
if (moving)
{
gameObject.transform.position = Vector2.MoveTowards(transform.position, moveHere.position, 2f * Time.deltaTime);
}
}
public void MoveThePlatform()
{
moving = true;
}
}
additional images
Player, Platform
P.s. Forgot to add - on Player's RigidBody2D, under Constraints, check the "Freeze Rotation Z" box.
I am trying to make a 2d plat-former where you see the player from the side. I want him to be continuously moving and you have to press space at the right time so he doesn't fall. Right now everything works but he doesn't collide with the ground. I want it to be like he's running behind a wall so I want to ignore a certain layer I have made and collide with the boxes below that. So far I have tried ray casting, watched multiple tutorials, and did box collisions. Box collisions worked but to get all the platforms counted as solid I'd need like 50 box colliders. Here is my current code:
public int playerSpeed = 10;
public int playerJumpPower = 1250;
public float moveX;
public float playerYSize = 2;
public LayerMask mainGround;
public float playerFallSpeed = 5;
void Awake(){
}
// Update is called once per frame
void Update()
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, new Vector2(10, 0));
if(hit.distance < 0.7f){
print("hi");
}
Vector3 characterTargetPosition = new Vector3(transform.position.x + playerSpeed, transform.position.y, transform.position.z);
transform.position = Vector3.Lerp(transform.position, characterTargetPosition, playerSpeed * Time.deltaTime);
if(Input.GetKeyDown("space")){
// float playerTargetPosY = transform.position.y + playerJumpPower;
// Vector3 characterTargetPosition = new Vector3(transform.position.x, playerTargetPosY, transform.position.z);
// transform.position = Vector3.Lerp(transform.position, characterTargetPosition, playerJumpPower * Time.deltaTime);
gameObject.GetComponent<Rigidbody2D>().AddForce(Vector2.up * playerJumpPower);
}
//PlayerMove();
}
I have a rigidBody2D on my player so right now he just falls through the ground but the jump does work. If there is any easy way to do this. Like some script, a tutorial, or website I'm open for it. Please help.
Do you have a Rigidbody2D in your player? Things that will move usually have to have a RigidBody
(sorry for posting this as an answer. Cant comment yet)
EDIT:
try this:
Rigidbody2D rb;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
//Physics usually are done in FixedUpdate to be more constant
public void FixedUpdate(){
if (Input.GetKeyDown("space"))
{
if(!rb.simulated)
//player can fall
rb.simulated = true;
rb.AddForce(Vector2.up * playerJumpPower);
}
else
{
//third argument is the distance from the center of the object where it will collide
//therefore you want the distance from the center to the bottom of the sprite
//which is half of the player height if the center is actually in the center of the sprite
RaycastHit2D hit = Physics2D.Raycast(transform.position, -Vector2.up, playerYSize / 2);
if (hit.collider)
{
//make player stop falling
rb.simulated = false;
}
}
}
If the player is the only thing that will collide with something you can just take out the colliders from the object that the player will not collide with.
Else you can check for the layer of the collided object with hit.collider.gameObject.layer and decide if the player will collide with that layer or not
(note that you have to compare with the index of the layer. If you want to get the index by its name you can use LayerMask.NameToLayer(/*layer name*/))
you will have to do rb.simulated = true everytime you want to do something with the RigidBody (like AddForce())
hope it helped :)
I want to be able to interact with my gameobjects while organising my sprites with Sorting Layer.
Right now, Sorting Layer do not affect my Raycast/collider situation and only change the visual appearance of my order.
In my Test scene I have two sprites: a Background and one Object.
Both have a 2D Box Collider component.
My Raycast script is suppose to detect on which Object my mouse is hovering on; either the Background (BG) or the Object, which lays on top of the BG.
As long as the BG collider is enabled in the inspector, my script will ONLY recognize the BG, even if the other object lays on top of my BG (visually and according to the Sorting Layer) (both Z-positions = 0, Sorting Layer Default (BG: 0, Object 1)).
The collider have the correct size and if I disable or remove the Collider from the BG, my script will detect the other Object just fine.
The only way to make sure that my Object gets detected, while my BG collider is activated is by changing the Objects Z position to a position between my BG and my Camera (between smaller 0 and greater -10)
I tried to:
this is a fresh 2D project, so my cameras Projection is set to Orthograph, my BG and Object use Sprite Renderer and is use 2D collider
change the "Order in Layer", back and forth, including negative numbers.
add a Foreground (and Background) "Sorting Layer"
(both options only had a visual effect and did not affect the Raycast output)
change the positions in the hierarchy.
removing and re-adding the colliders. (!) I observed that the 2D Box collider I added last, will always be behind every other colliders. This is something I can work with for now. But will become a problem later in the future, when the project gets bigger.
I want to be able to interact with items and doors etc. (clicks and reactions to mouse hovering) in my 2D click adventure. If 2D Raycast is not the way to go to archive this, I would be glad to know about the proper technique to use in this situation.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayCast : MonoBehaviour
{
[SerializeField] private Vector2 mousePosWorld2D;
RaycastHit2D hit;
void Update()
{
mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
hit = Physics2D.Raycast(mousePosWorld2D, Vector2.zero);
if (hit.collider)
{
Debug.Log("Found collider: " + hit.collider.name);
}
else
{
Debug.Log("No collider found.");
}
}
}
I expected the "Sorting Layer" or "Order in Layer" to have an impact on my Raycast, while changing the Z position does nothing. But it is the other way around. Sorting Layer only have a visual effect and moving my Object closer to the camera (Z position) helps to detect the right collider.
How do I solve the problem?
What gets hit first from a Ray has only to do with how close the collider is to the camera, and nothing with the order in the Hierarchy or the SortinLayers.
To find the "highest" RaycastTarget based on the SortingLayer, I created the Script below.
We fire a Raycast and iterate over all the Colliders that we hit, by using a foreach-loop. Then the function will return the GameObject with the highest SortingLayer. Note: If there is more than one GameObject with the highest SortingLayer, the function will return the last one it found.
using UnityEngine;
public class Raycast : MonoBehaviour
{
// The name of the relevant Layer
[SerializeField] string layerToCheck = "Default";
void Update()
{
Vector2 mousePosWorld2D = (Vector2)Camera.main.ScreenToWorldPoint(Input.mousePosition);
GameObject result = GetHighestRaycastTarget(mousePosWorld2D);
// Do Stuff example:
Debug.Log($"Highest Layer: {result}");
if (Input.GetMouseButtonDown(0)
&& result != null)
{
Destroy(result);
}
}
// Get highest RaycastTarget based on the Sortinglayer
// Note: If multiple Objects have the same SortingLayer (e.g. 42) and this is also the highest SortingLayer, then the Function will return the last one it found
private GameObject GetHighestRaycastTarget(Vector2 mousePos)
{
GameObject topLayer = null;
RaycastHit2D[] hit = Physics2D.RaycastAll(mousePos, Vector2.zero);
foreach (RaycastHit2D ray in hit)
{
SpriteRenderer spriteRenderer = ray.transform.GetComponent<SpriteRenderer>();
// Check if RaycastTarget has a SpriteRenderer and
// Check if the found SpriteRenderer uses the relevant SortingLayer
if (spriteRenderer != null
&& spriteRenderer.sortingLayerName == layerToCheck)
{
if (topLayer == null)
{
topLayer = spriteRenderer.transform.gameObject;
}
if (spriteRenderer.sortingOrder >= topLayer.GetComponent<SpriteRenderer>().sortingOrder)
{
topLayer = ray.transform.gameObject;
}
}
}
return topLayer;
}
}
Have you tried with the script that it's in the Unity official documentation?
https://docs.unity3d.com/ScriptReference/Physics.Raycast.html
using UnityEngine;
// C# example.
public class ExampleClass : MonoBehaviour
{
void Update()
{
// Bit shift the index of the layer (8) to get a bit mask
int layerMask = 1 << 8;
// This would cast rays only against colliders in layer 8.
// But instead we want to collide against everything except layer 8. The ~ operator does this, it inverts a bitmask.
layerMask = ~layerMask;
RaycastHit hit;
// Does the ray intersect any objects excluding the player layer
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
Debug.Log("Did Hit");
}
else
{
Debug.DrawRay(transform.position, transform.TransformDirection(Vector3.forward) * 1000, Color.white);
Debug.Log("Did not Hit");
}
}
}
Im currently developing an Air hockey game in Unity3d. The issue I'm having is that when the player attempts to hit the puck too quickly, the player ends up going through the puck and therefore there is no collision. The game works perfectly as expected if the player stays still and the puck hits the player or if the player hits the puck at a slow pace.
The player has a rigidbody using continuous collision detection using a capsule collider. The puck also has rigidbody with continuous dynamic collision detection and a mesh collider with convex.
I tried setting the fixed timestep to 0.01 but that didn't have an effect. Here is the script for the player movement:
void ObjectFollowCursor()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Vector3 point = ray.origin + (ray.direction * distance);
Vector3 temp = point;
temp.y = 0.2f; // limits player on y axis
cursorObject.position = temp;
}
and here is the code for the puck when it collides with the player:
// If puck hits player
if(collision.gameObject.tag == "Player")
{
Vector3 forceVec = this.GetComponent<Rigidbody>().velocity.normalized * hitForce;
rb.AddForce(forceVec, ForceMode.Impulse);
Debug.Log ("Player Hit");
}
Any help would be much appreciated. Thanks.
The problem you are having its called "tunneling".
This happens because your object is moving at a high speed and in that specific frame the collision is not detected. In frame n the ball is just in front of the bat, but when frame n+1 is calculated the ball has moved behind the bat, thus "missing" the collision completely.
It is a common problem but there are solutions.
I recommend you study this script and try to implement on your game.
This is not my code:
SOURCE: http://wiki.unity3d.com/index.php?title=DontGoThroughThings
using UnityEngine;
using System.Collections;
public class DontGoThroughThings : MonoBehaviour
{
// Careful when setting this to true - it might cause double
// events to be fired - but it won't pass through the trigger
public bool sendTriggerMessage = false;
public LayerMask layerMask = -1; //make sure we aren't in this layer
public float skinWidth = 0.1f; //probably doesn't need to be changed
private float minimumExtent;
private float partialExtent;
private float sqrMinimumExtent;
private Vector3 previousPosition;
private Rigidbody myRigidbody;
private Collider myCollider;
//initialize values
void Start()
{
myRigidbody = GetComponent<Rigidbody>();
myCollider = GetComponent<Collider>();
previousPosition = myRigidbody.position;
minimumExtent = Mathf.Min(Mathf.Min(myCollider.bounds.extents.x, myCollider.bounds.extents.y), myCollider.bounds.extents.z);
partialExtent = minimumExtent * (1.0f - skinWidth);
sqrMinimumExtent = minimumExtent * minimumExtent;
}
void FixedUpdate()
{
//have we moved more than our minimum extent?
Vector3 movementThisStep = myRigidbody.position - previousPosition;
float movementSqrMagnitude = movementThisStep.sqrMagnitude;
if (movementSqrMagnitude > sqrMinimumExtent)
{
float movementMagnitude = Mathf.Sqrt(movementSqrMagnitude);
RaycastHit hitInfo;
//check for obstructions we might have missed
if (Physics.Raycast(previousPosition, movementThisStep, out hitInfo, movementMagnitude, layerMask.value))
{
if (!hitInfo.collider)
return;
if (hitInfo.collider.isTrigger)
hitInfo.collider.SendMessage("OnTriggerEnter", myCollider);
if (!hitInfo.collider.isTrigger)
myRigidbody.position = hitInfo.point - (movementThisStep / movementMagnitude) * partialExtent;
}
}
previousPosition = myRigidbody.position;
}
}
You were right to try continuous collision detection (CCD). There are some constraints (especially in this case where you want to use CCD with two moving objects rather than one moving object and one static object), but it is designed for this kind of scenario. The Rigidbody documentation goes into these constraints:
Set the collision detection mode to Continuous to prevent the
rigidbody from passing through any static (ie, non-rigidbody)
MeshColliders. Set it to Continuous Dynamic to also prevent the
rigidbody from passing through any other supported rigidbodies with
collision detection mode set to Continuous or Continuous Dynamic.
Continuous collision detection is supported for Box-, Sphere- and
CapsuleColliders.
To sum up, both puck and paddle need to be set to Continuous Dynamic, and both need to be Box-, Sphere-, or Capsule Colliders. If you can make these constraints work for your game you should be able to get continuous collision detection without writing it yourself.
A note about Unity's CCD that bears repeating:
Note that continuous collision detection is intended as a safety net
to catch collisions in cases where objects would otherwise pass
through each other, but will not deliver physically accurate collision
results, so you might still consider decreasing the fixed Time step
value in the TimeManager inspector to make the simulation more
precise, if you run into problems with fast moving objects.
But since you are manually specifying the collision reaction, that might not be an issue.