Raycast 2D ricochet in Unity? - unity3d

I'm new to Raycasting so I might be going about this in a bad way, but I would to send a raycast outward to direction a gameobject is facing, bounce off the first object it hits and go a short distance before disappearing.
As far as I can tell there is no built in function for reflecting raycasts in Unity, so I have been trying to generate a another raycast where the first one hits but my luck hasn't been going well. Here's what I have so far:
public Gameobject firePoint; // I have an object attached to my main object that I use as a point of origin
void DrawLazer()
{
Vector2 origin = new Vector2(firePoint.transform.position.x, firePoint.transform.position.y);
Vector2 direction = transform.TransformDirection(Vector2.up);
RaycastHit2D hit = Physics2D.Raycast(origin, direction, 10f);
Debug.DrawLine(origin, direction *10000, Color.black);
if (hit)
{
Debug.Log("Hit: " + hit.collider.name);
var whatWeHit = new Vector2(hit.transform.position.x, hit.transform.position.y);
var offset = whatWeHit + hit.point;
offset.y = 0;
RaycastHit2D hit2 = Physics2D.Raycast(offset, Vector3.Reflect(direction, hit.normal) * -10000);
if (hit2)
{
Debug.DrawLine(offset, -Vector3.Reflect(direction, hit.normal) * -10000);
}
}
}
I call DrawLaxer(); in update.
This current script is sort of able to generate a 2nd raycast, however as you can see, the first raycast still does not stop when it hits something, and more importantly while this solution works well when it hits a flat object on a horizontal plane. But if it hits on object on a vertical or diagonal plane it applys several calculations to the wrong axis:
Any help would be greatly appreciated.

Here is a complete example monobehavior that uses Vector2.Reflect and SphereCast.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ReflectionExample : MonoBehaviour
{
public GameObject firePoint;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
DrawPredictionDisplay();
}
private void DrawPredictionDisplay()
{
Vector2 origin = firePoint.transform.position; //unity has a built in type converter that converts vector3 to vector2 by dropping the z component
Vector2 direction = firePoint.transform.up;
float radius = 1.0f;
RaycastHit2D distanceCheck = Physics2D.Raycast(origin, direction);
RaycastHit2D hit = Physics2D.CircleCast(origin, radius, direction);
Debug.DrawLine(origin, direction * 10000, UnityEngine.Color.black);
DrawCircle(origin, 1.0f, UnityEngine.Color.black);
if (hit)
{
origin = hit.point + (hit.normal * radius);
direction = Vector2.Reflect(direction, hit.normal);
hit = Physics2D.CircleCast(origin, radius, direction);
Debug.DrawLine(origin, direction * 10000, UnityEngine.Color.blue);
DrawCircle(origin, 1.0f, UnityEngine.Color.blue);
}
}
private void DrawCircle(Vector2 center, float radius, UnityEngine.Color color)
{
Vector2 prevPoint = new Vector2(Mathf.Sin(0f), Mathf.Cos(0f));
for (float t = 0.1f; t < 2 * Mathf.PI; t = t + 0.1f)
{
var nextPoint = new Vector2(Mathf.Sin(t), Mathf.Cos(t));
Debug.DrawLine(center + prevPoint, center + nextPoint, color);
prevPoint = nextPoint;
}
}
}

Related

Unity3D: World Rotation to Local Rotation

I'm really struggling with this. I have a turret that is mounted on a spaceship. It's a child object of the ship. The turret is allowed to point left, right, up, but not down, because that would be pointing through the spaceship.
So, I need to limit the rotation of the turret so it won't point down. I started with this code:
`
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TurretScriptTest : MonoBehaviour
{
public float rotateSpeedBase = 1f;
public float rotateSpeedCurr = 1f;
public float yMin = 0;
public float yMax = 1;
public Transform target;
// Start is called before the first frame update
void Start()
{
rotateSpeedCurr = rotateSpeedBase;
}
// Update is called once per frame
void Update()
{
if (target && target.gameObject.activeSelf)
{
// Rotate
RotateWithLock();
}
}
public virtual void RotateWithLock()
{
if (target)
{
Vector3 targetDir = target.position - transform.position;
float step = rotateSpeedCurr * Time.deltaTime;
Vector3 newDir = Vector3.RotateTowards(transform.forward, targetDir, step, 0.0f);
newDir.y = Mathf.Clamp(newDir.y, yMin, yMax);
transform.rotation = Quaternion.LookRotation(newDir);
}
}
}
`
This works great except for one thing. The spaceship is also rotating all over the place. As soon as the parent spaceship goes off its original axis the above code is worthless. The turret will point through the spaceship or whatever else is in the way.
I'm assuming that what I need to do is convert everything to local rotation and position so that "down" for the turret will always be its base, not "down" in world space. So I try this code:
`
public virtual void RotateWithLock()
{
if (target)
{
Vector3 targetDir = target.position - transform.position;
float step = rotateSpeedCurr * Time.deltaTime;
Vector3 newDir = Vector3.RotateTowards(transform.forward, targetDir, step, 0.0f);
Vector3 newDirLocal = transform.InverseTransformDirection(newDir);
newDirLocal.y = Mathf.Clamp(newDirLocal.y, yMin, yMax);
transform.localRotation = Quaternion.LookRotation(newDirLocal);
}
}
`
Now the turret doesn't move at all. What to do?
If your turret's gun is a child of the turret's base, then you can just use "transform.localRotation" to get the rotation relative to the base.
If not, then try this:
If you have the Transform of the base, then you can use "Quaternion.Inverse" to get the inverse of the base's rotation. Then just take the LookRotation and multiply it by this inverse (Quaternion1 * Quaternion2 just adds them together). To convert that to a Direction Vector for the clamp calculations, just do "Quaternion * new Vector3(0, 0, 1)"
Vector3 targetDir = target.position - transform.position;
//Gets rotation to the target
Quaternion rotToTarget = Quaternion.LookRotation(newDirLocal);
float step = rotateSpeedCurr * Time.deltaTime;
//Gets new rotation
Quaternion newRot = Quaternion.RotateTowards(transform.rotation, rotToTarget, step);
//Gets the local rotation by adding (yes adding) the inverse of the base rotation
//Make sure to set the turretBase variable to the turret base's GameObject
newRot = newRot * Quaternion.Inverse(turretBase.transform.rotation);
//Gets a vector that points to the direction
Vector3 dirFromRot = newRot * new Vector3(0, 0, 1);
//This sets the y-limit. The yLimit should be less than 1.
//I am using your code, but a more effective way would be to limit the angle of the quaternion.
dirFromRot.y = Mathf.Clamp(dirFromRot.y, -yLimit, yLimit);
//Gets the LookRotation of this clamped Vector3 and sets the result.
transform.localRotation = Quaternion.LookRotation(dirFromRot.normalized);

How to control player camera on angled surface/independent of xyz axis in Unity?

An illustration of my issue/what I'm trying to achieve
I managed to have my player move around the inside surface of a cylinder using gravity but an issue comes up when trying to rotate the camera to look around.
First, there's a script that simulates gravity by pushing the player against the inside walls of the cylinder. This script also keeps the player upright by changing the "up" direction of the player to always be facing the center of the cylinder. I know this keeps my player from looking up so for now I'm just working on getting them to look left and right.
Second, when the player is on the bottom of the cylinder and parallel with the Y-axis I can look left and right without issue because the camera rotates using the x-axis (see circle on the left side of the image). However when the player moves around the side of the cylinder the camera is still trying to rotate based on the X-axis even though they are not aligned resulting in the camera not accurately rotating (see the circle on the right side of the image), and by the time the player is 90deg around the cylinder cannot rotate at all (thanks I think to the gravity keeping the player perpendicular to the sides of the cylinder), and at 180deg around the rotation is inverted.
I assume there are two possible solutions that I have not been able to successfully implement:
ignore the world xyz axis' and rotate relative to the player's xyz.
do some math to figure out the proper angles when you take into account the player's rotation around the cylinder and the angle of the current "left" direction.
The problem with the first solution is I have not been able to successfully rotate the player independent of the world xyz. I tried adding the Space.Self to the Rotate() method but no success. The problem with the second is math scares me and I've managed to avoid Quaternions & Euler angles so far so I'm not even sure how to begin figuring that out.
If anyone has had a similar issue I would greatly appreciate any insight or suggestions on how to figure it out.
Here's my code for controlling the play movement/camera direction and my code for the gravity:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
InputManager inputActions;
InputManager.PlayerMovementActions playerMovement;
public GravityAttractor GravityAttractor;
public float moveSpeed = 15;
public float jumpHeight = 10f;
public float sensitivityX = 1f;
public float sensitivityY = 1f;
float mouseX, mouseY;
Vector2 mouseInput;
private bool isJumping = false;
private Vector3 moveDir;
private Vector2 moveInput;
private Rigidbody rbody;
private void Awake()
{
inputActions = new InputManager();
playerMovement = inputActions.PlayerMovement;
playerMovement.Movement.performed += context => moveInput = context.ReadValue<Vector2>();
playerMovement.Jump.performed += ctx => Jump();
//playerMovement.MouseX.performed += context => mouseInput = context.ReadValue<Vector2>();
playerMovement.MouseX.performed += context => mouseInput.x = context.ReadValue<float>();
playerMovement.MouseY.performed += context => mouseInput.y = context.ReadValue<float>();
rbody = GetComponent<Rigidbody>();
isJumping = false;
}
private void Update()
{
moveDir = new Vector3(moveInput.x, 0, moveInput.y).normalized;
if (rbody.velocity.y == 0)
isJumping = false;
}
private void FixedUpdate()
{
rbody.MovePosition(rbody.position + transform.TransformDirection(moveDir) * moveSpeed * Time.deltaTime);
MouseLook(mouseInput);
}
void Jump()
{
//use brackeys method of checking for contact with ground
if(isJumping == false && rbody.velocity.y == 0)
{
isJumping = true;
rbody.velocity = transform.up * jumpHeight;
Debug.Log("player jumped");
}
}
void MouseLook(Vector2 mouseInput)
{
mouseX = mouseInput.x * sensitivityX;
mouseY = mouseInput.y * sensitivityY;
var upTransform = GravityAttractor.transform.position - transform.position;
Vector3 relativeLook = upTransform - transform.forward;
Vector3 qLook = transform.forward - transform.position;
transform.Rotate(transform.up * mouseX * Time.deltaTime, Space.Self);
//transform.Rotate(relativeLook.normalized);
//transform.rotation = Quaternion.LookRotation(qLook);
//transform.Rotate(relativeLook.normalized, mouseX);
}
private void OnEnable()
{
inputActions.Enable();
}
private void OnDisable()
{
inputActions.Enable();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GravityAttractor : MonoBehaviour
{
public float gravityMultiplier = -10f;
private float radius;
public float gravity;
public float distance;
private void Awake()
{
radius = transform.localScale.x/2;
}
public void Attract(Transform body)
{
Vector3 centerOfGravity = new Vector3(transform.position.x, transform.position.y, body.position.z);
distance = Vector3.Distance(centerOfGravity, body.position);
//gravity will match multiplier no matter the radius of cylinder
gravity = gravityMultiplier * (distance/radius);
Vector3 gravityUp = (centerOfGravity - body.position).normalized;
Vector3 bodyUp = body.up;
body.GetComponent<Rigidbody>().AddForce(gravityUp * gravity);
Quaternion targetRotation = Quaternion.FromToRotation(bodyUp, gravityUp) * body.rotation;
body.rotation = Quaternion.Slerp(body.rotation, targetRotation, 50 * Time.deltaTime);
}
}
Problem is that you are trying to Rotate in local space around transform.up, which is in world space.
try to use just Vector3.up, instead of transform.up.
Or you can transform any vector from world to local space with transform.InverseTransformDirection()
transform.Rotate(Vector3.up * mouseX * Time.deltaTime, Space.Self);
I don't know hierarchy of your GameObjects. In order for this to work, you shuld rotate Player GameObject to face up to center of cilinder. And camera should be child of Player GameObject

2D Rigidbody with Touchscript movement in Unity wont stay in boundaries

I'm trying to make a pong game where the player has to move both paddles. They are currently set as dynamic 2D Rigidbodies(collision detection continuous) and have a box collider (is NOT trigger) attached.
The problem is the paddles won't collide with the surrounding box colliders I set on the camera, using the following script :
using UnityEngine;
using System.Collections;
namespace UnityLibrary
{
public class EdgeCollider : MonoBehaviour
{
public float colDepth = 4f;
public float zPosition = 0f;
private Vector2 screenSize;
private Transform topCollider;
private Transform bottomCollider;
private Transform leftCollider;
private Transform rightCollider;
private Vector3 cameraPos;
// Use this for initialization
void Start () {
//Generate our empty objects
topCollider = new GameObject().transform;
bottomCollider = new GameObject().transform;
rightCollider = new GameObject().transform;
leftCollider = new GameObject().transform;
//Name our objects
topCollider.name = "TopCollider";
bottomCollider.name = "BottomCollider";
rightCollider.name = "RightCollider";
leftCollider.name = "LeftCollider";
//Add the colliders
topCollider.gameObject.AddComponent<BoxCollider2D>();
bottomCollider.gameObject.AddComponent<BoxCollider2D>();
rightCollider.gameObject.AddComponent<BoxCollider2D>();
leftCollider.gameObject.AddComponent<BoxCollider2D>();
//Make them the child of whatever object this script is on, preferably on the Camera so the objects move with the camera without extra scripting
topCollider.parent = transform;
bottomCollider.parent = transform;
rightCollider.parent = transform;
leftCollider.parent = transform;
//Generate world space point information for position and scale calculations
cameraPos = Camera.main.transform.position;
screenSize.x = Vector2.Distance (Camera.main.ScreenToWorldPoint(new Vector2(0,0)),Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, 0))) * 0.5f;
screenSize.y = Vector2.Distance (Camera.main.ScreenToWorldPoint(new Vector2(0,0)),Camera.main.ScreenToWorldPoint(new Vector2(0, Screen.height))) * 0.5f;
//Change our scale and positions to match the edges of the screen...
rightCollider.localScale = new Vector3(colDepth, screenSize.y * 2, colDepth);
rightCollider.position = new Vector3(cameraPos.x + screenSize.x + (rightCollider.localScale.x * 0.5f), cameraPos.y, zPosition);
leftCollider.localScale = new Vector3(colDepth, screenSize.y * 2, colDepth);
leftCollider.position = new Vector3(cameraPos.x - screenSize.x - (leftCollider.localScale.x * 0.5f), cameraPos.y, zPosition);
topCollider.localScale = new Vector3(screenSize.x * 2, colDepth, colDepth);
topCollider.position = new Vector3(cameraPos.x, cameraPos.y + screenSize.y + (topCollider.localScale.y * 0.5f), zPosition);
bottomCollider.localScale = new Vector3(screenSize.x * 2, colDepth, colDepth);
bottomCollider.position = new Vector3(cameraPos.x, cameraPos.y - screenSize.y - (bottomCollider.localScale.y * 0.5f), zPosition);
}
}
}
The ball collides with the paddles and bounces off as it should as well as collides with the surrounding box colliders and bounces off perfectly. However, the paddles move right through the surrounding box colliders (EdgeColliders). Please note that I use Touchscript package from the unity asset store to control movement. It does not move the Rigidbody it uses transform. Another thing to note is that when I make the paddles very light (0.0001 mass) and add gravity to them, they do collide with the edge colliders and don't go through the screen.
If you want the paddle to collide with the surrounding box, you should move the Rigidbody with AddForce(), not move the transform.
Or you can limit the movement of the paddles like this :
if (transform.position.x < LeftScreenEdge)
{
transform.position = new Vector3(LeftScreenEdge, transform.position.y);
}
Delete Rigidbody2D attached to paddles.
Get the mouse pos int world coordinates:
Vector2 mP = Camera.main.ScreenToWorldPoint(Input.mousePosition);
And to prevent it from going out of boundaries just clamp It's position:
Vector3 screenBounds;
void Start()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
}
void SetPaddlePos()
{
Vector2 clampedPos;
clampedPos.x = Mathf.Clamp(mP.x, screenBounds.x, -screenBounds.x);
clampedPos.y = Mathf.Clamp(mP.y, screenBounds.y, -screenBounds.y);
paddle.transform.position = clampedPos;
}

How to identify multiple objects in a sphere cast?

I am trying to implement a RADAR sensor in unity. I am currently using sphere cast to identify the object and measure its velocity and distance. At the moment even though there are several objects in within the radius of the sphere cast ,It only identifies the nearest one. How can i make it recognize all the objects within the sphere cast.?
Any help would be appreciated..
This is what i have done now which just identifies identify the closest object.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class spherecast : MonoBehaviour
{
Rigidbody rb;
public GameObject curobject;
public float radius;
public float maxdist;
public LayerMask layermask;
public float velocity;
public Time deltatime;
public Vector3 previous;
private Vector3 origin;
private Vector3 direction;
private float hitdist;
// Use this for initialization
void Start()
{
previous = curobject.transform.position;
}
// Update is called once per frame
void Update()
{
velocity = hitdist/Time.deltaTime;
origin = transform.position;
direction = transform.forward;
RaycastHit hit;
if (Physics.SphereCast(origin, radius, direction, out hit, maxdist, layermask, QueryTriggerInteraction.UseGlobal))
{
curobject = hit.transform.gameObject;
hitdist = hit.distance;
Debug.Log("Distance" + hitdist);
velocity = ((curobject.transform.position - previous).magnitude) / Time.deltaTime;
previous = curobject.transform.position;
Debug.Log("Velocity" + velocity);
// Debug.Log("Velocity = " + velocity);
}
else
{
hitdist = maxdist;
curobject = null;
}
}
private void ondrawgizmosselected()
{
Gizmos.color = Color.red;
Debug.DrawLine(origin, origin + direction * hitdist);
Gizmos.DrawWireSphere(origin + direction * hitdist, radius);
}
}
How can i make it recognize all the objects within the sphere cast.?
I believe you need to use SphereCastAll instead of SphereCast for that.
https://docs.unity3d.com/ScriptReference/Physics.SphereCastAll.html
Answer:
"Like Physics.SphereCast, but this function will return all hits the sphere sweep intersects."
It returns a RaycastHit[].

Why my code fires bullet at random positions?

I see that the bullets are being fired at random positions and not actually in forward direction of the camera. What's wrong here and how should I fix it?
So I am using pooling and each time the bullet is enabled this code is run:
private void OnEnable()
{
transform.position = Camera.main.transform.position;
transform.rotation =Quaternion.identity;
GetComponent<Rigidbody>().AddForce((Camera.main.transform.forward + new Vector3(0, 0, 0)) * 5000);
Invoke("Destroy", 1.5f);
}
I have also changed it to the below code but even the second one doesn't work.
private void OnEnable()
{
Rigidbody rb = GetComponent<Rigidbody>();
rb.position = Camera.main.transform.position;
rb.rotation = Quaternion.identity;
rb.AddForce((Camera.main.transform.forward + new Vector3(0, 0, 0)) * 5000);
Invoke("Destroy", 1.5f);
}
First of all make sure the code works with out pooling. Secondly disable the collider component on the bullet (they might be colliding with themselves).
I've quickly tried this on my machine and this is the result I get.
using UnityEngine;
public class BulletController : MonoBehaviour
{
[SerializeField]
GameObject bulletPrefab;
void Update()
{
GameObject bullet = Object.Instantiate(bulletPrefab);
Rigidbody body = bullet.GetComponent<Rigidbody>();
body.position = Camera.main.transform.position;
body.AddForce(Camera.main.transform.forward * 75f, ForceMode.Impulse);
}
}
Here is the code I use with direction, position and velocity variables.
void Inception::shootGeode(std::string typeGeode, const btVector3& direction) {
logStderr(VERBOSE, "MESSAGE: Shooting geode(s)...\n");
std::shared_ptr<Geode> geode;
glm::vec3 cameraPosition = m_camera->getPosition();
btVector3 position = glm2bullet(cameraPosition + 3.0f * glm::normalize(m_camera->getTarget() - cameraPosition));
if (typeGeode == "cube")
geode = m_objectFactory->createCube("cube", new btBoxShape(btVector3(1.0f, 1.0f, 1.0f)), position, "dice");
if (typeGeode == "sphere")
geode = m_objectFactory->createSphere("sphere", new btBoxShape(btVector3(1.0f, 1.0f, 1.0f)), position);
btVector3 velocity = direction;
velocity.normalize();
velocity *= 25.0f;
geode->getRigidBody()->setLinearVelocity(velocity);
}