Unity3D: How to move an object in relation to another object?

Viewed 61

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.

1 Answers

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