I'm making a billiard game and I have two questions:
How do I find the velocity of two balls when they collide with each other and how do I apply it to both balls ?
I already know the angles that they're gonna move, I just need to find the velocity that they'll move in those directions.
I was never good at physics/physics programming so I would appreciate any help given! Btw, my game is in 3D.
Thank you for your time!
Edit: What I'm trying to do is make the movement direction match the direction that I'm calculating using this script:
if (Physics.Raycast(ray, out hit, Mathf.Infinity, lmm))
{
location = new Vector3(hit.point.x, 1.64516f, hit.point.z);
}
if (Physics.SphereCast(transform.position, 0.77f, location - transform.position, out hitz, Mathf.Infinity, lmm2))
{
if (hitz.collider.tag == "Ball")
{
Vector3 start = hitz.point;
end = start + (-hitz.normal * 4);
lineRenderer2.SetPosition(1, end);
}
}
You can calculate the new velocities by applying an impulse to each ball. We can apply Newton's Third law to do so. PseudoCode:
RelativeVelocity = ball1.velocity - ball2.velocity;
Normal = ball1.position - ball2.position;
float dot = relativeVelocity*Normal;
dot*= ball1.mass + ball2.mass;
Normal*=dot;
ball1.velocity += Normal/ball1.mass;
ball2.velocity -= Normal/ball2.mass;
This however does not take into account friction between the two balls nor their angular momentum.
If I understand correctly, what you are trying to do is to apply the speed of "Ball A" as a force to "Ball B", and vice versa as well. If that is the case I would suggest something like this:
Attach this script to all the balls:
public class BallCollisionScript : MonoBehaviour
{
public Rigidbody Rigid_Body;
void Start()
{
Rigid_Body = this.GetComponent<Rigidbody>();
}
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag.Equals("Ball"))
{
BallScript otherBall = collision.gameObject.GetComponent<BallScript>();
Rigid_Body.AddForce(otherBall.Rigid_Body.velocity);
}
}
}
Make sure that all balls are tagged with "Ball".
Related
Trying to work myself through basic FPS shooting as practice and I want to have bullet holes where you shoot. However, Texture Z-fighting occurs when I attempt to do so.
The gun works using raycasting like so:
Physics.Raycast(playercam.transform.position, playercam.transform.forward, out hit)
The bullet holes are currently a 2d sprite that I place on the target using this script:
GameObject ceffect = Instantiate(bullethole, hit.point, Quaternion.LookRotation(hit.normal));
theoretically this should work, and it does, however, when it is placed on the world, it Z- fights with the floor texture.
I have tried to render the bullet holes on a different layer, but this simply does not work.
Is there a way to kind of pull the sprite to the top of the object?
Am I missing something with the layers?
If you need more code, here's my shooting script currently, but look at your own risk.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerShooting : MonoBehaviour
{
public float fireRate = 2f;
public float weapon = 1f;
public float pistoldmg = 5f;
public Camera playercam;
public GameObject redblood;
public GameObject bullethole;
private float timetofire = 0f;
void Start()
{
}
void Update()
{
if(Input.GetKeyDown(KeyCode.Mouse0) && (Time.time > timetofire))
{
timetofire = Time.time + 1f / fireRate;
PistolShoot();
}
else if(Input.GetKey(KeyCode.Mouse0))
{
}
}
void PistolShoot()
{
RaycastHit hit;
if(Physics.Raycast(playercam.transform.position, playercam.transform.forward, out hit))
{
Enemy1 enemy = hit.transform.GetComponent<Enemy1>();
if(enemy != null)
{
enemy.Shot(pistoldmg);
GameObject beffect = Instantiate(redblood, hit.point, Quaternion.LookRotation(hit.normal));
GameObject ceffect = Instantiate(bullethole, hit.point, Quaternion.LookRotation(hit.normal));
}
else
{
GameObject beffect = Instantiate(redblood, hit.point, Quaternion.LookRotation(hit.normal));
GameObject ceffect = Instantiate(bullethole, hit.point, Quaternion.LookRotation(hit.normal));
}
}
}
}
`
From the raycast hit information, we get the normal vector, which you're using to rotate the objects. We can use the same normal vector to 'nudge' the objects away from the hit point as well. An example piece of code might look something like this:
void PistolShoot ( )
{
if ( Physics.Raycast ( playercam.transform.position, playercam.transform.forward, out var hit ) )
{
if ( hit.transform.TryGetComponent<Enemy1> ( out var enemy ) )
enemy.Shot ( pistoldmg );
var rotation = Quaternion.LookRotation(hit.normal);
var beffect = Instantiate(redblood, hit.point + hit.normal * 0.01f, rotation );
var ceffect = Instantiate(bullethole, hit.point + hit.normal * 0.02f, rotation );
// .. are you using 'beffect' and 'ceffect' after this? There may not be a need to store these.
}
}
This will nudge the redblood object about "1 cm" away from the hit point, and the bullethole object about "2 cm" away from the hit point. This will likely need to be adjusted to suit your measurements, but if you've followed a one-to-one Unity unit to metre guide, then this will likely do the trick.
Note that some mobile phone use a lower precision when calculating positions, so you might need to increase this value to make it more pronounced.
Here's an example using a slightly exaggerated nudging amount - 0.05 and 0.1 instead of 0.01 and 0.02 - on a 1x1x1 cube.
Currently, I want to make a dash for the player in a 2D game in unity. I'm using game objects to the left and right of the player to use raycasts to check the distance from a wall in a certain area. If its too close to a wall, I need it to dash only that distance (to the wall) instead of the full distance (behind/through the wall).
This is my code for dashing in my player movement (DashCheckLeft is -0.5 from the player) (DashCheckRight is 0.5 from the player):
public float dashDistance = 3f;
float distance;
public Transform DashCheckLeft;
public Transform DashCheckRight;
private void Update() {
if(Input.GetKeyDown(KeyCode.LeftShift)) {
Debug.Log("Dashed");
Dash();
private void FixedUpdate() {
RaycastHit2D hitLeft = Physics2D.Raycast(DashCheckLeft.position, Vector2.left, -dashDistance);
RaycastHit2D hitRight = Physics2D.Raycast(DashCheckRight.position, Vector2.right, dashDistance);
if(hitLeft.collider != null) {
distance = Mathf.Abs(hitLeft.point.x - transform.position.x);
}
else if(hitRight.collider != null) {
distance = Mathf.Abs(hitRight.point.x - transform.position.x);
}
else {
distance = dashDistance;
}
}
private void Dash() {
rb.position = new Vector2(rb.position.x + distance, rb.position.y);
}
The problem now is that apperently I'm only dashing the distance between the player and the DashChecks (0.5f) instead of the intended 3f and I believe it may be because the raycasts are hitting the collider of the player somehow, but changing my player to the "Ignore Raycast" layer makes it fall into the floor, and also doesn't fix the issue.
I worked on this problem, and found the answer. Basically, your raycasts were detecting the player's collider too. There is a simple fix for this. You should just add the fourth argument to your raycast. The fourth argument is a LayerMask. A LayerMask is like a tag, but you can have several on at the same time. This is what I changed the code to (I made some extra adjustments too):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
Rigidbody2D rb;
public float dashDistance = 3f;
public LayerMask floor;
float distance;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.LeftShift))
{
RaycastHit2D left = Physics2D.Raycast(transform.position, -transform.right, dashDistance, floor);
RaycastHit2D right = Physics2D.Raycast(transform.position, transform.right, dashDistance, floor);
if (left.collider != null)
{
print("left");
distance = Mathf.Abs(left.point.x - transform.position.x);
distance = -distance;
}
else if (right.collider != null)
{
print("right");
distance = Mathf.Abs(right.point.x - transform.position.x);
}
else
{
distance = dashDistance;
}
print(distance);
Debug.Log("Dashed");
Dash();
}
void Dash()
{
rb.position = new Vector2(rb.position.x + distance, rb.position.y);
}
}
}
(when I say 'world objects', I mean the objects that make up the terrain. For example the ground or trees) You should add a new layer by clicking on the world objects. Then, you should look at the top of the inspector, then click the 'Layer Default' dropdown menu, and click the 'Add Layer...' button. Then write a new name on that list. Go back to the world game object, and click the same dropdown menu, and select the layer that you want for the world. You should make sure that all of the world objects have the same layer. Go back to the player object, and in the inspector, set the 'floor' variable to the same layer as the world. If that didn't make sense, here is a link of how to set layers.
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 have a really odd issue, I created custom MOB AI in unity for my game (it has procedural generated voxel world so agents didn't work well). I use rigidbody on the mobs which I move around.
But I have this issue where mobs go inside the floor while moving (doesn't happen when standing) and when they stand, they teleport back up!. It's not animation, I disabled animations and it still happens.
here is how I move them:
private void WalkToTarget()
{
if (goal != Vector3.zero)
{
if (goal.y <= 5)
{
currentstatus = MOBSTATUS.STANDING;
return;
}
var distance = Vector3.Distance(VoxelPlayPlayer.instance.transform.position, gameObject.transform.position);
if (distance < 15)
{
goal = VoxelPlayPlayer.instance.transform.position;
goal.y = VoxelPlayEnvironment.instance.GetHeight(goal);
}
if (distance < 5)
{
this.currentstatus = MOBSTATUS.ATTACKING;
}
//MOVEMENT HAPPENS HERE
Vector3 direction = (goal - mobcontroller.transform.position).normalized*2f;
if(mobcontroller.collisionFlags!=CollisionFlags.CollidedBelow)
direction+= Vector3.down;
mobcontroller.Move(direction * Time.fixedDeltaTime);
RotateTowards(direction);
}
}
Edit:
All code: https://pastebin.com/khCmfKGi
Part of your problem is that you are using CollisionFlags incorrectly.
Instead of this:
if(mobcontroller.collisionFlags!=CollisionFlags.CollidedBelow)
You need to do this
if(mobcontroller.collisionFlags & CollisionFlags.CollidedBelow)
Because you are trying to check if the mob is at least colliding below, not if the mob is only colliding below.
Even then, CharacterController.Move should not move you through colliders on its own.
I suspect that RotateTowards(direction) might be rotating the boundaries of mob's collider through the ground in some cases. To prevent that, I recommend creating a lookDirection that keeps the character rotation flat when you do your RotateTowards:
Vector3 direction = (goal - mobcontroller.transform.position).normalized*2f;
if(mobcontroller.collisionFlags & CollisionFlags.CollidedBelow)
direction+= Vector3.down;
mobcontroller.Move(direction * Time.fixedDeltaTime);
Vector3 lookDirection = (goal - mobController.transform.position);
lookDirection.y = mobController.transform.y;
RotateTowards(lookDirection);
This problem happened when using Rigidbody and CharacterController on the mob. Removing Rigidbody from the mob solved this problem.
I have a blockbreaker type game coded using C# that mainly works fine but upon play testing i have found the ball will slow right down in certain situations! i.e if it wedges between to game object bricks the force of the ball rapidly slows down! 8 times out of ten this will not happen but other times it does and im unsure why! i will post the Ball script i think you will need to help solve this but should you need anymore information then please ask.
public class Ball : MonoBehaviour {
private Paddle paddle;
private bool hasStarted = false;
private Vector3 paddleToBallVector;
void Start () {
paddle = GameObject.FindObjectOfType<Paddle> ();
paddleToBallVector = this.transform.position - paddle.transform.position;
}
// Update is called once per frame
void Update () {
if (!hasStarted) {
//lock ball relative to the paddle
this.transform.position = paddle.transform.position + paddleToBallVector;
//wait for mouse press to start
if (Input.GetMouseButtonDown (0)) {
//if (Input.GetTouch(0).phase == TouchPhase.Ended){
hasStarted = true;
this.GetComponent<Rigidbody2D> ().velocity = new Vector2 (2f, 10f);
}
}
}
void OnCollisionEnter2D(Collision2D collision){
Vector2 tweak = new Vector2 (Random.Range(0f,0.2f),Random.Range(0f,0.2f));
if (hasStarted) {
GetComponent<AudioSource> ().Play ();
GetComponent<Rigidbody2D>().velocity += tweak;
}
}
}
You are adding directly to the velocity of the ball. The velocity variable defines a direction and speed, not just a speed as you are thinking here.
So, when the ball collides with a block it has a velocity of (+x, +y). After the collision and bounce is has a velocity of (+x, -y). So by adding a random positive value to the velocity when it has the -y velocity means that it will slow down the ball.
This does not happen every time because you are moving the ball in your Update() method. Change that to 'FixedUpdated()'. Fixed Update handles all physics calculations.
Also, I would recommend having a RigidBody2D on your Ball object. Moving physics objects should always have RigidBodies. With this, you can then use AddForce in the forward direction of your ball and that should solve your problem.
EDIT: I see you have a RigidBody2D already. I missed that the first time. Instead of having GetComponent<RigidBody2D>().velocity try GetComponent<RigidBody2D>().AddForce( transform.forward * impulseAmount, ForceMode.Impluse );