- Home /
Referencing the Collision of two other objects to allow for an animation to be played on the scripted object.
Hi all,
Will preface this by saying that I am a beginner when it comes to C# and Unity.
So i'm creating a script for animations to play on a weapon model (in an FPS context) when relevant keys are pressed, and this is simple enough for the WASD keys which will not (in my case) require any conditions in order to disable or enable running animations contextually.
However, the jump animation is another story. Currently the animation works fine whenever space is pressed, but I want the animation trigger to be disabled whenever the player is airborne. As it is, spamming space will play the animation regardless of whether the player is in contact with the ground.
The weapon model is not a child of the Player gameobject (because of mesh warping issues when parented), I have a simple script that references the Player object and Camera object and sets the position and rotation of the weapon model accordingly.
I've also managed to disable the actual jump action on the player movement script, by setting up a bool called 'm_Grounded' that is false by default, and becomes true when the player's collider contacts another collider with the tag 'ground', and the jump action can only be performed when space is pressed AND m_grounded = true (pressing space in this case sets m_Grounded back to false after performing the jump).
I've tried to make a similar method in the weapon animation script to dictate when the animation is available to be played, but I can't quite get my head around what kind of function I need to make to reference the collider (and it's collision with another object tagged 'Ground') of an object (the Player) that is completely separate from the object that has this script attached (the weapon).
Here's my current script, I'm fairly certain that OnCollisionEnter is looking for the weapon's collider to be touching the 'Ground', and not the Player GameObject as seen in this code.
using UnityEngine;
public class WeaponAnim : MonoBehaviour
{
public GameObject Player;
private Animator anim;
private bool _Grounded = false;
private Collider playerCol;
// Use this for initialization
void Start()
{
anim = GetComponent<Animator>();
playerCol = Player.GetComponent<Collider>();
}
void OnCollisionEnter(Collision CollisionInfo)
{
if (CollisionInfo.collider.tag == "Ground")
{
_Grounded = true;
}
}
// Update is called once per frame
void Update()
{
if (Input.GetKey("w") || (Input.GetKey("s")) || (Input.GetKey("a")) || (Input.GetKey("d")))
{
anim.SetBool("isRunning", true);
}
else
{
anim.SetBool("isRunning", false);
}
if ((Input.GetKeyDown("space")) && (_Grounded == true))
{
anim.SetTrigger("jump");
_Grounded = false;
}
}
}
Can anyone point me in the right direction? I have a feeling it's a simple one-line solution that I'm just not aware of.
Thanks for any help.
Answer by FoodLover195 · Oct 21, 2018 at 03:03 AM
Hi @TheFeenster
I don't work with models too often however I am positive there should be a better solution to your weapon child/parent issue. Also, for your input, you should try and use Input.GetAxis or Input.GetAxisRaw because it allows players to customise their controls. As for your collision issue, you were correct. You selected an appropiate solution to your issue however the OnCollisionEnter function is checking for collision resulting from the object that the script is attached to. What you could do is place the grounded variable and OnCollisionEnter code into the player control script and use the reference you have to complete your if statement like so:
If (Player.grounded && Input.GetAxisRaw("space") != 0) {
// Do Stuff
}
Notes -
a) You don't need to specify a bool variable as true or false but instead can just leave it alone for a true check, or use the ! operator for a false check. i.e. myBool or !myBool
b) Also the input function I used doesn't take the name of the key pressed, but rather the name of the input profile. You can learn more about this by googling about "Unity Input Manager", or just click the link I provide.
c) Since you're new to unity the best bit of advice I could give is to always use the script reference when you are using in-built functions. It's super useful.
Links -
Input Class - https://docs.unity3d.com/ScriptReference/Input.html
OnCollisionEnter - https://docs.unity3d.com/ScriptReference/Collider.OnCollisionEnter.html
Answer by TheFeenster · Oct 21, 2018 at 01:09 PM
Thanks for the info! Regarding the mesh distortion, I have seen that creating empty GameObjects for each mesh in the model and making the meshes children of their respective empty GameObjects is the right way to sort them to avoid abnormalities, but the brief attempt I made didn't yield any results, but I'm not sure if I made an empty object for every single mesh in the model, come to think of it.
I've changed the if statement regarding WASD to GetAxis Horizontal and Vertical, but I've left the jump as a GetKeyDown for the time being because there's some delay in the execution of it if I convert it to GetAxis Jump. Does it perhaps need to be under FixedUpdate rather than Update?
I'm still having trouble referencing the 'grounded' bool from the PlayerMotor script. Currently the Player reference is a GameObject, does it need to be something different?
I do indeed already have a grounded variable (named m_Grounded specifically) and the OnCollisionEnter method in the PlayerMotor script attached to the actual player, which dictates when the actual movement of the jump action can be done. But whenever I try and input something like: Player.Grounded or Player.m_Grounded into the WeaponAnim script, it tells me; 'GameObject does not contain a definition for 'm_grounded''. I set the m_grounded bool in the PlayerMotor script to public just in case that was the issue, but it still doesn't seem to be able to be referenced.
I assume the code example you wrote is supposed to be part of the WeaponAnim script and not the PlayerMotor, but the same issue prevails even vice versa. Instead of trying to reference the PlayerMotor, i would be trying to reference the weapon's Animator.
I feel there's something fundamental I am misunderstanding to being able to reference the script of another object that has been added as a variable.
For clarity, here's the PlayerMotor script (Most of it is following a tutorial by Brackeys);
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class PlayerMotor : MonoBehaviour
{
[SerializeField]
private Camera cam;
[SerializeField]
private float jumpForce = 500f;
public bool m_Grounded = false;
private Vector3 velocity = Vector3.zero;
private Vector3 rotation = Vector3.zero;
private float cameraRotationX = 0f;
private float currentCameraRotationX = 0f;
[SerializeField]
private float cameraRotationLimit = 90f;
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
}
//Acquires a movement vector
public void Move(Vector3 _velocity)
{
velocity = _velocity;
}
//Acquires a rotational vector
public void Rotate(Vector3 _rotation)
{
rotation = _rotation;
}
//Acquires a camera rotational vector
public void RotateCamera(float _cameraRotationX)
{
cameraRotationX = _cameraRotationX;
}
void OnCollisionEnter(Collision CollisionInfo)
{
if (CollisionInfo.collider.tag == "Ground")
{
m_Grounded = true;
}
}
void Update()
{
if ((Input.GetKeyDown("space")) && (m_Grounded == true))
{
rb.AddForce(0f, jumpForce * Time.fixedDeltaTime, 0f, ForceMode.VelocityChange);
m_Grounded = false;
}
}
//Runs every physics iteration, Remember FixedUpdate instead of Update for physics calculations
void FixedUpdate()
{
PerformMovement();
PerformRotation();
}
//Perform movement based on velocity variable
void PerformMovement()
{
if (velocity != Vector3.zero)
{
rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);
}
}
//Perform rotation
void PerformRotation()
{
rb.MoveRotation(rb.rotation * Quaternion.Euler(rotation));
if (cam != null)
{
//set our rotation and clamp it to the value set by the cameraRotationLimit float value
currentCameraRotationX -= cameraRotationX;
currentCameraRotationX = Mathf.Clamp(currentCameraRotationX, -cameraRotationLimit, cameraRotationLimit);
//Apply our rotation to the transform of our camera
cam.transform.localEulerAngles = new Vector3(currentCameraRotationX, 0f, 0f);
}
}
}
Oh sorry. Yeah you need a reference to the script that is attached to the player. Also any variable that you want to be referenced also needs to be public. So it would be something like this:
public class Player$$anonymous$$otor : $$anonymous$$onoBehaviour {
public bool grounded;
private void Update() {
if(input.Get$$anonymous$$ey("Space") && !grounded) {
// Code for when the player jumps
grounded = false;
}
}
}
public class Weapon : $$anonymous$$onoBehaviour {
[SerializableField] GameObject playerObject;
private Player$$anonymous$$otor playerController;
private void Start() {
playerController = playerObject.GetComponent<Player$$anonymous$$otor>();
}
// Guaranteed to run after the jump part
private void LateUpdate() {
if(!playerController.grounded /*/&& animation isn't already running/*/) {
// Run Animation
}
}
}
The [SerializableField] is handy because it allows you to keep your variable private, but you can still access it in the inspector. Generally speaking you want most of your variables to be private because that's good code practice.
Thanks a lot, I'm getting somewhere!
A new issue has arisen; I'm not sure what to put where you've written "animation isn't already running" in the LateUpdate script, so currently without that when the playerController is not grounded, the animation starts playing every frame during which the Player is not grounded, basically getting caught at frame 0 infinitely until grounded= true again, at which point the animation plays fully.
The animation is tied to a trigger, and so appears as: anim.SetTrigger("jump");
I've tried putting !anim.SetTrigger("jump"); and anim.SetTrigger("jump") = false; after the && but neither appears to be a valid argument.
Is the fact that it is a trigger contributing to the problem?
$$anonymous$$y LateUpdate currently looks like this;
private void LateUpdate()
{
if (!playerController.grounded && // ??? //
{
anim.SetTrigger("jump");
}
}
Oh that makes sense. Sorry I haven't worked with animations for a while. If you have a trigger called "jump" (a bool?) you can use the functions anim.SetTrigger("jump"), and anim.GetTrigger("jump").
if(!playerController.grounded && !anim.GetTrigger("jump")) {
anim.SetTrigger("jump", true);
}
Then when you land you would have to reset the trigger to equal false again.
Hmm, apparently Animator does not contain a definiton for 'GetTrigger', and I believe trigger and Bool are two separate types when creating a parameter in the Animator, because SetTrigger doesn't allow for 2 arguments.
I understand if there isn't much more you can suggest at this point, it seems like animator functions have changed somewhat since you last worked with them, but you've set me on the right path and I thank you profusely for your help so far!