Unity3D: How to move an object in relation to another object? - c#

I have a physics enabled sphere in my scene and a character that i can move around using WASD. now what i want is that as soon as player hits the ball it should not be physics enabled anymore and move along with player as like the player is holding it in the hands.
What i have tried so far:
i was able to do it but not at all perfect how i want it to be. i tried OnCollisionEnter to detect the collision, i made the sphere a child of the player, i used isKinematic = true as soon as collision is detected and i used Vector3.MoveTowards for the object to follow the position as the player.
I have attached two reference videos below
Reference Clip(The style i am aiming for):https://www.youtube.com/watch?v=fi-GB0RwLr0
MyVersion(This is what i am able to do):https://www.youtube.com/watch?v=JtV11KHY4pU
overall, if you watch the clips you can see how my version is very rigid, stucky, buggy, not so light weight feeling at all. Cut me a slack as well if i did anything way wrong, i started unity like just in the previous month.
This is my Player script through which i am controlling it all
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Player : MonoBehaviour
{
public float MoveSpeed;
[SerializeField]private Rigidbody playerRig;
[SerializeField] private Rigidbody ballRig;
[SerializeField] private GameObject Ball;
[SerializeField] private GameObject heldObj;
private bool isTouching = false;
private Vector3 offset = new Vector3(0, 1, 3);
private Vector3 force;
public float jump;
private bool isGrounded = true;
private void Start()
{
Ball = GameObject.FindGameObjectWithTag("Ball");
ballRig.GetComponent<Rigidbody>();
playerRig.GetComponent<Rigidbody>();
}
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
playerRig.velocity = new Vector3(x*MoveSpeed, playerRig.velocity.y,z*MoveSpeed);
Vector3 vel = playerRig.velocity;
vel.y = 0;
if (vel.x != 0 || vel.z != 0)
{
transform.forward = vel;
}
if(transform.position.y < -10)
{
GameOver();
}
ReleaseTheBall();
if (isTouching == true)
{
ballRig.transform.position = Vector3.MoveTowards(ballRig.transform.position, playerRig.transform.position, 5f);
}
}
void Jump()
{
force = new Vector3(0, jump, 0);
if (Input.GetKeyDown(KeyCode.Space) && isGrounded == true)
{
isGrounded = false;
playerRig.AddForce(force, ForceMode.Impulse);
}
}
private void OnCollisionEnter(Collision collision)
{
if(collision.gameObject.tag == "Ball" && heldObj == null)
{
ballRig.isKinematic = true;
// ballRig.constraints = RigidbodyConstraints.FreezeRotation;
ballRig.transform.parent = playerRig.transform;
isTouching = true;
Debug.Log(" BALL PICKED UP AND MOVING ");
heldObj = Ball;
}
}
public void ReleaseTheBall()
{
if (Input.GetKeyDown(KeyCode.Space) && heldObj != null)
{
isTouching = false;
heldObj = null;
ballRig.transform.parent = null;
//ballRig.constraints = RigidbodyConstraints.None;
ballRig.isKinematic = false;
//Vector3 forceDirection = new Vector3(playerRig.transform.position.x, 0, playerRig.transform.position.z);
Vector3 rotationDirection = new Vector3(playerRig.transform.rotation.x, playerRig.transform.rotation.y, playerRig.transform.rotation.z);
ballRig.AddForce(new Vector3(transform.position.x,0,transform.position.z) * 5, ForceMode.Impulse);
ballRig.AddTorque(rotationDirection * 1);
Debug.Log("BALL RELEASED");
}
}
public void GameOver()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
i know there could be some very inefficient lines of code here, feel free to call out those lines as well.
So, basically what i want is that i want my player to pick up the ball as soon as it hits it and the ball should move exactly like player as if he is holding the ball in his hands and when i hit the spacebar i want the ball to be released into the direction i am looking(this part is also very tricky for me), what i am trying to do is i want to do exactly like in the refrence clip. Thanks a lot in advance.
one more thing, when i stop pressing any key to move(WASD), my player transform keeps changing values up and down and it slightly starts moving. i mean when i dont press any key, it still kinda moves.

First of all, you have indeed quite a lot of lines that aren't useful in here and that could be optimized.
Anyway, I hope this will do what you want it to do:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
[RequireComponent(typeof(Rigidbody))]
public class Player : MonoBehaviour {
[Header("Movement Setup")]
[Tooltip("Translation speed in m/s")]
[SerializeField] float _TranslationSpeed;
[Tooltip("Rotation speed in °/s")]
[SerializeField] float _RotationSpeed;
[Tooltip("Interpolation speed [0;1]")]
[SerializeField] float _InterpolationSpeed;
[Tooltip("Strength for jumps")]
[SerializeField] float _JumpingStrength;
[Tooltip("Strength for shoots")]
[SerializeField] float _ShootingStrength;
[Header("Other Settings")]
[Tooltip("Where the helded item will be placed")]
[SerializeField] Transform _ContactPoint;
[Tooltip("Which layers should be considered as ground")]
[SerializeField] LayerMask _GroundLayer;
// The player's rigid body
Rigidbody _Rigidbody;
// Is the player on the ground
bool _IsGrounded;
// Rigidbody of what's the player is helding. Null if the player isn't holding anything
Rigidbody _HeldedItemRigidbody;
// Getter to _IsGrounded, return true if the player is grounded
public bool IsGrounded { get { return _IsGrounded; } }
private void Start() {
_Rigidbody = GetComponent<Rigidbody>(); // The player's rigidbody could be serialized
}
void FixedUpdate() {
float horizontalInput, verticalInput;
// Getting axis
horizontalInput = Input.GetAxis("Horizontal");
verticalInput = Input.GetAxis("Vertical");
// Moving toward the x
Vector3 moveVect = transform.forward * _TranslationSpeed * Time.fixedDeltaTime * verticalInput;
_Rigidbody.MovePosition(_Rigidbody.position + moveVect);
// Rotating the player based on the horizontal input
float rotAngle = horizontalInput * _RotationSpeed * Time.fixedDeltaTime;
Quaternion qRot = Quaternion.AngleAxis(rotAngle, transform.up);
Quaternion qRotUpRight = Quaternion.FromToRotation(transform.up, Vector3.up);
Quaternion qOrientationUpRightTarget = qRotUpRight * _Rigidbody.rotation;
Quaternion qNewUpRightOrientation = Quaternion.Slerp(_Rigidbody.rotation, qOrientationUpRightTarget, Time.fixedDeltaTime * _InterpolationSpeed); // We're using a slerp for a smooth movement
_Rigidbody.MoveRotation(qRot * qNewUpRightOrientation);
// This prevents the player from falling/keep moving if there is no input
if (_IsGrounded) _Rigidbody.velocity = Vector3.zero;
_Rigidbody.angularVelocity = Vector3.zero;
if (transform.position.y < -10) GameOver();
if (Input.GetKey(KeyCode.Mouse0) && _HeldedItemRigidbody != null) ShootTheBall();
if (!_IsGrounded) return; // What's below this won't be executed while jumping
if (Input.GetKey(KeyCode.Space)) Jump();
}
void Jump() {
_IsGrounded = false;
_Rigidbody.AddForce(_JumpingStrength * transform.up, ForceMode.Impulse);
}
private void OnCollisionEnter(Collision collision) {
// Checking if the player encountered a ground object
if ((_GroundLayer & (1 << collision.gameObject.layer)) > 0) _IsGrounded = true;
if (collision.gameObject.tag == "Ball") { // this can be improved as tags are error prone
// Setting the item position to contact point
collision.gameObject.transform.position = _ContactPoint.position;
// Getting the rigidbody
_HeldedItemRigidbody = collision.gameObject.GetComponent<Rigidbody>();
// Setting the parent
_HeldedItemRigidbody.transform.parent = transform;
// Setting the body to be kinematic
_HeldedItemRigidbody.isKinematic = true;
// Stopping any movement or rotation
_HeldedItemRigidbody.velocity = Vector3.zero;
_HeldedItemRigidbody.angularVelocity = Vector3.zero;
}
}
public void ShootTheBall() {
// Reverting what's done in OnCollisionEnter
_HeldedItemRigidbody.transform.parent = null;
_HeldedItemRigidbody.isKinematic = false;
// Adding force for the shoot
_HeldedItemRigidbody.AddForce(transform.forward * _ShootingStrength, ForceMode.Impulse);
// Resetting helding item to null
_HeldedItemRigidbody = null;
}
public void GameOver() {
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
You have to add a layer, and set the ground to this new layer.
Set up the variable as you like, don't hesitate to mess up with it a little bit to find what suits you (and don't forget to set up the player's mass (in rigidbody) as well!!)
Exemple of setup for the player script
Last things, you have to add an empty game object to your player which will be where the ball will be placed. I know it's a little bit dirty but place it a bit far from the player (more than the ball's radius) or it will bug. We could do something programatically to fix the ball at a spot and adapt this spot to the ball's radius.
This is an example of player
As you can see, I have the empty "player" gameobject which has the rigidbody, the collider and the player script.
The GFX item which is just the capsule.
If you don't know about this: the player game object is actually on the floor (at the player's feet) so it's better for movements. If you do this, don't forget to adjust the player's capsule collider to fit the GFX.
I also have the contact point.
Forget about the nose stuff, it's just a way to see where the player is facing when you just have a capsule.
If you have any question, feel free to ask.

Related

My player is getting stuck inside the wall in the 2D platforming game I am making in unity

I am making a 2D platforming game in unity. For some reason, the player is getting stuck in the wall. I have used a Tilemap for the wall, then used tilemap Collider 2D with composite collider. The same thing works fine with the ground.
When I go left facing the wall, it behaves as expected:
When I release the left arrow button, it also shows the expected result:
But when I click right arrow next, the player gets stuck inside the wall:
and cannot get out:
Here is my player movement code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class playerScript : MonoBehaviour
{
// Start is called before the first frame update
[SerializeField] private float movement_speed = 2;
[SerializeField] private float jump_power = 5;
[SerializeField] private LayerMask groundLayer;
[SerializeField] private LayerMask wallLayer;
private Rigidbody2D rb_player;
private BoxCollider2D boxCollider;
private Animator anim;
private void Start()
{
}
private void Awake()
{
rb_player = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
boxCollider = GetComponent<BoxCollider2D>();
groundLayer = LayerMask.GetMask("ground");
wallLayer = LayerMask.GetMask("wall");
}
// Update is called once per frame
private void Update()
{
float x = Input.GetAxis("Horizontal");
rb_player.velocity = new Vector2(x * movement_speed, rb_player.velocity.y);
//flip the player
if(x > 0.01f)
{
transform.localScale = new Vector3(1, 1, 1);
}
else if(x < -0.01f)
{
transform.localScale = new Vector3(-1, 1, 1);
}
if(Input.GetKeyDown(KeyCode.Space) && isGrounded())
{
jump();
}
anim.SetBool("run", x != 0);
anim.SetBool("jump", !isGrounded());
print("wall: "+isTouchingWall());
print("ground: "+isGrounded());
}
private void jump()
{
rb_player.velocity = new Vector2(rb_player.velocity.x, jump_power);
anim.SetTrigger("jump_trigger");
}
private void OnCollisionEnter2D(Collision2D collision)
{
// if(collision.gameObject.tag == "ground")
// {
// grounded = true;
// }
}
private bool isGrounded()
{
RaycastHit2D raycastHit = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, 0, Vector2.down, .2f, groundLayer);
return raycastHit.collider != null;
}
private bool isTouchingWall()
{
RaycastHit2D raycastHit = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, 0, new Vector2(transform.localScale.x,0), .1f, wallLayer);
return raycastHit.collider != null;
}
}
It would be nice if anyone can help me as I am a complete beginner in game development with unity.
The problem
Your pivot point of your player (where your player script is attached) isn't centered like your collider. Because of this, your box collider is being flipped like a page from a book and it lands inside the wall.
To fix it
Make sure that your player pivot point is the same as the center of your BoxCollider2D. This way, your collider won't "teleport" from right to left and bypass walls.
Other tips
I see some beginner's mistakes in your code, so even if you manage to fix your issue, I highly recommend to follow the next tips:
All physic based code (jump, movement, etc.) should go inside FixedUpdate() method and use Time.deltaTime or Time.fixedDeltaTime to keep the same speed (for movement and jump) among computers.
Use the proper naming conventions, your variables should be camel case, no underscores. Methods (and Classes) should always start with a capital letter in C# (PascalCase).
Hopefully this will help you.

How do I keep my player from going over the collider?

I'm making a 2D game where the player moves left and right while dodging falling objects, I put triggers so it doesn't leave the screen but sometimes when it collides with these triggers it passes this collider. I sent an image of the colliders, the Player inspector and also a collider.
Here my Player code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ball : MonoBehaviour
{
public bool isRight;
public float speed;
public Transform pointR;
public Transform pointL;
Rigidbody2D rb;
private SpriteRenderer sprite;
private float maxSpeed;
[Header("PowerUps")]
public float force;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
sprite = GetComponent<SpriteRenderer>();
maxSpeed = 600f;
}
void Update()
{
if(Input.GetMouseButtonDown(0))
{
isRight = !isRight;
}
}
void FixedUpdate()
{
if(isRight)
{
rb.velocity = new Vector2(speed, 0);
rb.AddForce(rb.velocity,ForceMode2D.Force );
sprite.flipX = true;
if(rb.velocity.x >= maxSpeed)
{
rb.velocity = new Vector2(maxSpeed, 0);
}
}
else
{
rb.velocity = new Vector2(-speed, 0 );
rb.AddForce(rb.velocity, ForceMode2D.Force );
sprite.flipX = false;
}
}
void OnTriggerEnter2D(Collider2D collider)
{
if(collider.gameObject.tag =="ColliderL")
{
isRight = !isRight;
}
if(collider.gameObject.tag == "ColliderR")
{
isRight = !isRight;
}
// For PowerUps
if(collider.gameObject.tag == "SpeedUp")
{
StartCoroutine(Seconds());
}
}
IEnumerator Seconds()
{
rb.AddForce(rb.velocity * force);
yield return new WaitForSeconds(5f);
rb.AddForce(rb.velocity * -force);
}
}
I would ideally need to see your scene setup to further pinpoint your mistakes. But these are a few common mistakes that are made:
Tags are case-sensitive. Also, make sure to assign them.
Your collider on one of the object is not 2D; There is Collider, then there is Collider2D.
Your collider is not marked as trigger.
Though there are a few non-related problems with your setup:
rb.velocity.collider doesn't exist. You are looking for rb.velocity.
Ideally you want to set physics changes (velocity, in your case) to FixedUpdate() instead.
If you do want to get colliders from RigidBody, use GetAttachedColliders.
Assuming you are setting up a border which the player can never pass through, you should use a full collider instead of a trigger. This uses OnCollisionEnter2D.
new Vector2(speed, 0* Time.deltaTime);
0 * Time.deltaTime does nothing, since you are multiplying by 0.
What you are looking for is probably new Vector2(speed * Time.deltaTime, 0);. But you don't need to multiply the speed by deltaTime since you are not moving the ball manually via it's Transform.
rb.velocity = new Vector2(speed, 0); should be your result.

FPS Projectile firing from the wrong place

I'm trying to make a basic FPS game in Unity and I'm having an issue where the projectiles I shoot won't instantiate in the right place. From what I can tell, the projectile instantiates in roughly the same position relative to the player regardless of where I look (that position being a little to the left of the starting angle of the player).
Here's a 20 second video demonstration of what I'm talking about.
https://youtu.be/WLVuqUtMqd0
Even when I'm facing the exact opposite direction of where the projectile usually instantiates it still spawns in the same place, which means the projectile ends up spawning behind the player and hitting the player.
I tried using Debug.DrawRay() to see if maybe the firepoint itself is wrong (which would be shown by the ray starting somewhere other than the gun barrel). However it seems like the starting point of the ray is correct every time.
I'm not sure if this is related to the issue above, but I have noticed that the ray direction is wrong if I'm looking a little below the horizon. It seems like the projectile's direction is correct regardless though.
Ray directions when looking slightly above the horizon vs. lower
Here's my code for intantiating/shooting the projectile. I think the most relevant parts are probably shootProjectile() and instantiateProjectile(). This script is attached to the player character.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shooting : MonoBehaviour
{
public Camera cam;
public GameObject projectile;
public Transform firePoint;//firePoint is set to the end of the barrel.
public float projectileSpeed = 40;
//private var ray;
private Vector3 destination;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
//var ray = cam.ViewportPointToRay(new Vector3(0.5f,0.5f,0));
//Debug.DrawRay(ray.origin, ray.direction);
if(Input.GetButtonDown("Fire1")) {
//player is shooting
ShootProjectile();
}
}
void ShootProjectile() {
Ray ray1 = cam.ScreenPointToRay(Input.mousePosition);
//Debug.Log(ray.direction);
RaycastHit hit;
if(Physics.Raycast(ray1, out hit))//checking whether the player is going to hit something
{
destination = hit.point;
}
else {
destination = ray1.GetPoint(1000);
}
Debug.DrawRay(firePoint.position, destination, Color.white, 10f);
InstantiateProjectile(firePoint);
}
void InstantiateProjectile(Transform firePoint) {
var projectileObj = Instantiate (projectile, firePoint.position, Quaternion.identity) as GameObject;//projectile is instantiated
projectileObj.GetComponent<Rigidbody>().velocity = (destination - firePoint.position).normalized * projectileSpeed;//projectile is set in motion
}
}
Here's the location of firePoint.
firePoint (i.e. where the projectile should instantiate)
I would appreciate any help on this, as I've been trying to fix it (on and off) for several days, and really have no idea what the problem is.
Edit: Here's my player movement script(s) as well. The first one is PlayerController.cs, and it converts the player's inputs into the appropriate movement vectors and camera rotation. It then calls methods from PlayerMotor.cs, which actually performs the movements.
PlayerController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(PlayerMotor))]
public class PlayerController : MonoBehaviour
{
[SerializeField]
public float speed = 10f;
[SerializeField]
private float lookSens = 3f;
private PlayerMotor motor;
void Start() {
motor = GetComponent<PlayerMotor>();
//Debug.Log("PlayerControllerStart");
Cursor.lockState = CursorLockMode.Locked;
}
void Update() {
//Debug.Log("PlayerControllerUpdate");
//calculate movement velocity as 3D vector.
float xMov = Input.GetAxisRaw("Horizontal");
float zMov = Input.GetAxisRaw("Vertical");
Vector3 movHorizontal = transform.right * xMov;
Vector3 movVertical = transform.forward * zMov;
Vector3 velocity = (movHorizontal + movVertical).normalized * speed;
motor.move(velocity);
//speed*=(float)1.15;
//rotation
float yRot = Input.GetAxisRaw("Mouse X");
Vector3 rotation = new Vector3 (0f, yRot, 0f) * lookSens;
motor.rotate(rotation);
float xRot = Input.GetAxisRaw("Mouse Y");
Vector3 cameraRotation = new Vector3 (xRot, 0f, 0f) * lookSens;
motor.rotateCamera(cameraRotation);
if (Input.GetKeyDown(KeyCode.Space) == true && motor.isGrounded()) {
motor.canJump=true;
}
if (Input.GetKey(KeyCode.W) == true) {
motor.accel=true;
}
else{
motor.accel=false;
}
}
}
PlayerMotor:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class PlayerMotor : MonoBehaviour
{
[SerializeField]
private Camera cam;
private Vector3 velocity = Vector3.zero;
private Vector3 rotation = Vector3.zero;
private Vector3 cameraRotation = Vector3.zero;
private Vector3 jumpVector = new Vector3 (0f, 5f, 0f);
private PlayerController pc;
private Rigidbody rb;
public bool canJump;
public bool accel;
public float acceleration;
int jumpCount;
void Start() {
rb = GetComponent<Rigidbody>();
pc = GetComponent<PlayerController>();
canJump=false;
jumpCount=0;
accel=false;
//acceleration = 1.0;
//distToGround = collider.bounds.extents.y;
//Debug.Log("PlayerMotorStart");
}
//sets velocity to a given movement vector.
public void move(Vector3 _velocity) {
velocity = _velocity;
}
public void rotate(Vector3 _rotation) {
rotation = _rotation;
}
public void rotateCamera(Vector3 _cameraRotation) {
cameraRotation = _cameraRotation;
}
public bool isGrounded() {
return Physics.Raycast(transform.position, -Vector3.up, (float)2.5);
}
public void jump() {
rb.AddForce(transform.up * 250f);
//Debug.Log("Jump"+jumpCount);
jumpCount++;
canJump=false;
}
void FixedUpdate() {
performMovement();
performRotation();
if (canJump) {
jump();
}
//Debug.Log("PlayerMotorUpdate");
if(accel&&pc.speed<20f&&isGrounded())
{
//Debug.Log("W Pressed");
pc.speed*=(float)1.005;
}
else if(!accel) {
pc.speed=7f;
}
}
void performMovement() {
if(velocity != Vector3.zero) {
rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
//Debug.Log("Movement");
}
}
void performRotation() {
rb.MoveRotation(rb.rotation * Quaternion.Euler(rotation));
if(cam != null) {
cam.transform.Rotate(-cameraRotation);
}
}
}
Here are some pictures of my projectile prefab as well.
To solve one first confusion: The method Debug.DrawRay takes as paramters
a start position
a ray direction(!)
You are passing in another position which is not what you want.
This might only work "accidentally" as long as you stand on 0,0,0 so maybe it wasn't that notable but the debt ray definitely points into a wrong direction.
Rather do e.g.
Debug.DrawRay(firePoint.position, destination - firePoint.position, Color.white, 10f);
Or instead rather use Debug.DrawLine which rather actually takes
start position
end position
Then you don't need to calculate the direction first
Debug.DrawLine(firePoint.position, destination, Color.white, 10f);
Then I suspect the following is happening: The spawned projectileObj actually is placed correctly but it is the visuals inside that projectile prefab that have a local offset against the parent prefab root.
Since you always spawn the projectile in world rotation Quaternion.identity that offset stays the same no matter where you look.
I guess a simple fix would already be to also apply the same rotation like
var projectileObj = Instantiate (projectile, firePoint.position, firePoint.rotation);

player stutters when moving unity 2d

whenever the player uses its jetpack or when there's a lot of velocity, the player stutters. I tried using interpolate and other things like that but the only outcome was even more stutter. If anyone knows what the cause for the stuttering is, please tell me and if you want to, maybe even explain your answer :)
here is what i mean by the player stuttering :
https://www.youtube.com/watch?v=k6q3vvQtwjM
here is my player code :
using UnityEngine;
using UnityEngine.SceneManagement;
public class Player : MonoBehaviour
{
//basic variables
[SerializeField]
private float speed = 5f;
[SerializeField]
private float Jumpforce = 5f;
[SerializeField]
private float JetPackForce = 5f;
[SerializeField]
public bool canUseJetpack = true;
[SerializeField]
private Rigidbody2D rb;
[SerializeField]
private Animator animator;
private bool isFacingRight = true;
//runs when game starts
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
//runs every frame
void Update()
{
//applies force thus making player to move left or right
var move = Input.GetAxis("Horizontal");
transform.position = transform.position + new Vector3(move * speed * Time.deltaTime, 0, 0);
//changes player animation state
animator.SetFloat("Speed", Mathf.Abs(move));
//flip the player if facing right or left
if (move < 0 && isFacingRight)
{
flip();
}
else if (move > 0 && !isFacingRight)
{
flip();
}
//checks if the space key is pressed and that the player velocity on the y axis is smaller than 0.001
if(Input.GetKeyDown("space") && Mathf.Abs(rb.velocity.y) < 0.001f)
{
//adds force from below the player thus making the player jump
rb.AddForce(new Vector2(0, Jumpforce), ForceMode2D.Impulse);
}
//checks for the key 'Q' to be pressed and that there is enough fuel in the jetpack
if (Input.GetKey(KeyCode.Q) && canUseJetpack == true)
{
//ads force from below the player thus making the player fly using its jetpack
rb.AddForce(Vector2.up * JetPackForce);
//decreases the fuel in the jetpack
JetpackBar.instance.UseFuel(0.1f);
//makes the player run the using jetpack animation
animator.SetBool("UsingJetpack", true);
}
else
{
//if the player isn't using the jetpack, switch to the idle animation or the run animation depending if the player is moving or not
animator.SetBool("UsingJetpack", false);
}
//checks if the player health is less than 0
if (HealthBar.instance.currentHealth <= 0)
{
//if so, restart the game
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
//checks if someting collided with the player
private void OnCollisionEnter2D(Collision2D collision)
{
//if the thing collided with the player has a tag of 'Enemy'
if (collision.gameObject.CompareTag("Enemy"))
{
//then, make the player take damage
HealthBar.instance.takeDamage(25);
}
}
//flip the player
void flip()
{
isFacingRight = !isFacingRight;
transform.Rotate(0f, 180f, 0f);
}
}
Thanks :D
First of all whenever dealing with Rigidbodies you do not want to use the Transform component to move an object. You rather want to only move it via the Rigidbody/Rigidbody2D component!
Then there is a general issue. You can either move your object hard based on the input or use the physics and forces. Both at the same time is not possible because both transform.position or the actually "correct" Rigidbody2D.MovePosition will overrule the forces so these two systems are clashing all the time.
Therefore I would rather simply overwrite the velocity directly like e.g.
void Update()
{
// Store the current velocity
var velocity = rb.velocity;
var move = Input.GetAxis("Horizontal");
// Only overwrite the X component
// We don't need Time.deltaTime since a velocity already
// moves the object over time
velocity.x = move * speed;
animator.SetFloat("Speed", Mathf.Abs(move));
if (move < 0 && isFacingRight)
{
flip();
}
else if (move > 0 && !isFacingRight)
{
flip();
}
if(Input.GetKeyDown("space") && Mathf.Abs(rb.velocity.y) < 0.001f)
{
// simply overwrite the Y velocity
// You'll have to adjust that value of course
// and might want to rename it ;)
velocity.y = Jumpforce;
}
if (Input.GetKey(KeyCode.Q) && canUseJetpack)
{
// Here we want to use Time.deltaTime since the jetpack
// shall increase the Y velocity over time
// again you'll have to adjust/rename that value
velocity.y += JetPackForce * Time.deltaTime;
// Also adjust this value!
// To be frame-rate independent you want to use Time.deltaTime here
JetpackBar.instance.UseFuel(0.1f * Time.deltaTime);
animator.SetBool("UsingJetpack", true);
}
else
{
animator.SetBool("UsingJetpack", false);
}
// After having calculated all changes in the velocity now set it again
rb.velocity = velocity;
if (HealthBar.instance.currentHealth <= 0)
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}

Collider does not always detect OnTriggerEnter

I stumbled across a problem while working with the book "Unity in Action". At the end of chapter 3 you'd end up with the basics for a simple fps game. It's basically a player (camera attached to it) in a simple and small level which only exists out of a number of cubes forming walls and the floor etc. These cubes all have box collider on them. Now the player is also able to shoot at moving enemies which are also able to shoot. This was done by Raycast/RaycastHit. All this worked perfectly so I wanted to add something that reassembles bullet holes, simply by instantiating a black sphere object on the wall where the fireball (this is the object the enemy and the player shoot) hits it. This works BUT sometimes the fireball object just goes through the wall. In case there is another wall behind it, the fireball object gets destroyed by this second wall and the desired sphere is created on the second wall instead of on the first wall.
I changed the speed in the fireball from 20 to 10 and then back to 20 and at a speed of 10, the success rate is around 19/20, while at a speed of 20 it's around 6/10.
Here is the code of the fireball which is supposed to check whether it hits the player (then health is deducted, that works fine) or hits the enemy (then the enemy falls over, also works fine) or hits the wall in which case the sphere should be created.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fireball : MonoBehaviour {
public float speed = 10.0f;
public int damage = 1;
[SerializeField] private GameObject wallHit;
private GameObject _wallHit;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
transform.Translate(0,0, speed * Time.deltaTime);
}
void OnTriggerEnter(Collider other){
RaycastHit hit;
PlayerCharacter player = other.GetComponent<PlayerCharacter>();
ReactiveTarget target = other.GetComponent<ReactiveTarget>();
WallBehavior wall = other.GetComponent<WallBehavior>();
if(player != null){
player.Hurt(damage);
}
if(target != null){
target.ReactToHit();
}
if(wall != null){
if(Physics.Raycast(transform.position, transform.forward, out hit)){
wall.WallWasHit(hit);
}
}
Destroy(this.gameObject);
}
}
As you can see one thing I tried was to give each wall a WallBehavior script, which looks like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WallBehavior : MonoBehaviour {
[SerializeField] private GameObject wallHit;
private GameObject _wallHit;
static int count = 0;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public void WallWasHit(RaycastHit hit){
count++;
Debug.Log("Wall was hit: " + count);
_wallHit = Instantiate(wallHit) as GameObject;
_wallHit.transform.position = hit.point;
}
}
But none of my attempts to understand why this happens so infrequently was successful so far and I hope someone can help me with this because I feel like might be crucial for my learning before I continue with the book! Thanks in advance. if further information are needed, I am happy to provide them. See the following picture for a better visualization of the issue.
EDIT:
As mentioned in the answers, I replaced the fireball script accordingly but I am not sure how to change the following script likewise. This script is on my camera which is on my player object. In the Update function the Fireball is instantiated and also given a movement, so I guess this causes the problems?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RayShooter : MonoBehaviour {
private Camera _camera;
[SerializeField] private GameObject fireballPrefab;
private GameObject _fireball;
void Start () {
_camera = GetComponent<Camera>();
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
}
void OnGUI(){
int size = 12;
float posX = _camera.pixelWidth/2 - size/4;
float posY = _camera.pixelHeight/2 - size/2;
GUI.Label(new Rect(posX, posY, size, size), "X");
}
// Update is called once per frame
void Update () {
if(Input.GetMouseButtonDown(0)){
Vector3 point = new Vector3(_camera.pixelWidth/2, _camera.pixelHeight/2, 0);
_fireball = Instantiate(fireballPrefab) as GameObject;
_fireball.transform.position = transform.TransformPoint(Vector3.forward * 1.5f);
_fireball.transform.rotation = transform.rotation;
Ray ray2 = _camera.ScreenPointToRay(point);
RaycastHit hit;
if(Physics.Raycast(ray2, out hit)){
GameObject hitObject = hit.transform.gameObject;
ReactiveTarget target = hitObject.GetComponent<ReactiveTarget>();
if(target !=null){
target.ReactToHit();
}
}
}
}
}
This is not how to move a Rigidbody Object and moving it like this could cause so many issues including the one mentioned in your question. Objects with Rigidbody should be moved with the Rigidbody component and with the functions like Rigidbody.MovePosition, Rigidbody.AddForce and Rigidbody.velocity not by the transform or transform.Translate.
Also, you should move the Rigidbody Object in the FixedUpdate function instead of the Update function. If you are moving your other Rigidbody Objects by their transform then you must fix them too. The example below replaces transform.Translate with Rigidbody.MovePosition in the Fireball script:
public float speed = 10.0f;
public int damage = 1;
[SerializeField]
private GameObject wallHit;
private GameObject _wallHit;
public Rigidbody rb;
// Use this for initialization
void Start()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void FixedUpdate()
{
//Move to towards Z-axis
Vector3 pos = new Vector3(0, 0, 1);
pos = pos.normalized * speed * Time.deltaTime;
rb.MovePosition(rb.transform.position + pos);
}
void OnTriggerEnter(Collider other)
{
RaycastHit hit;
PlayerCharacter player = other.GetComponent<PlayerCharacter>();
ReactiveTarget target = other.GetComponent<ReactiveTarget>();
WallBehavior wall = other.GetComponent<WallBehavior>();
if (player != null)
{
player.Hurt(damage);
}
if (target != null)
{
target.ReactToHit();
}
if (wall != null)
{
if (Physics.Raycast(transform.position, transform.forward, out hit))
{
wall.WallWasHit(hit);
}
}
Destroy(this.gameObject);
}
If you still run into issues, use Rigidbody.velocity instead:
void FixedUpdate()
{
Vector3 pos = Vector3.zero;
pos.z = speed * Time.deltaTime;
rb.velocity = pos;
}
Sometimes, depending on the size of the object and the speed it is moving by, you many need to set its Rigidbody Interpolate from None to Interpolate and Collision Detection to Continuous.
Don't forget to remove the code in the Update function.
EDIT:
I looked at your project and found new problems:
1.You enabled IsTrigger on your "Fireball" GameObject. Please uncheck the IsTrigger on the collider.
2.You just want to shoot a bullet. The force should be added once only not every FixedUpdate. Add the force in the Start function instead of the FixedUpdate function. Use Rigidbody.velocity instead of Rigidbody.MovePosition.
Remove the FixedUpdate function and the code inside it.
This should be your new Start function:
void Start()
{
rb = GetComponent<Rigidbody>();
Vector3 pos = transform.forward * speed * Time.deltaTime;
rb.velocity = pos;
}

Categories

Resources