I am making a First Person Shooter Game and am currently working on the jumping. I am using animation to jump as I have struggled with gravity before. However my problem is that my player cannot move if my jump script is attached to the player as well as the movement. However I know that it has nothing to do with the scripts as both scripts work but not simultaneously.
I believe it has something to do with the animation. There are 3 parts to the animation, Base, Running and Jump. If the player is moving the running animation is active, if the player has pressed the space bar then the jump animation is active. Jump animation is prioritised.
Is there any reason why the player cannot move and jump?
Here is the codes and animation controller:
Animation and Jump
public class Jump : MonoBehaviour
{
public bool run;
public float speed;
public GameObject player;
public Animator dgbanim;
public bool jump;
public float rejump;
public float rejumper;
// Start is called before the first frame update
void Start()
{
speed = 50;
run = false;
jump = false;
rejumper = 0;
rejump = 0;
}
// Update is called once per frame
void Update()
{
if (jump == false && Input.GetKey("space")) { dgbanim.SetBool("Jump", true); jump = true; rejump = 21; }
if (rejump > 0) { rejump = rejump - 1; }
if(rejump == 0) { jump = false; dgbanim.SetBool("Jump", false); }
if (Input.GetKey("w") || Input.GetKey("s") || Input.GetKey("a") || Input.GetKey("d") || Input.GetKey("up") || Input.GetKey("down") || Input.GetKey("left") || Input.GetKey("right")) { run = true; }
if (run == true) { dgbanim.SetBool("Running", true); run = false; }
else if (run == false) { dgbanim.SetBool("Running", false); }
}
private void OnCollisionEnter(Collision collision)
{
//if (collision.gameObject.tag != "EnemyBullets") { jump = true; rejump = 0; }
if (collision.gameObject.tag != "EnemyBullets") { jump = false; }
}
}
Movement
public class Movement : MonoBehaviour
{
public GameObject player;
public float speed;
// Start is called before the first frame update
void Start()
{
speed = 50;
}
// Update is called once per frame
void Update()
{
player.transform.Translate(Input.GetAxis("Horizontal") * speed * Time.deltaTime, 0, Input.GetAxis("Vertical") * speed * Time.deltaTime);
}
}
It seems like your animator have ‘apply root motion’ checked and its taking movement from animations rather than code, try unchecking it.
Animation can control some properties of a GameObject, such as the position. If you try to edit a GameObject's position in a script but an animation controls the position, the animation will override the script's changes to the position.
To see if this is the problem, you can open the animation window and select your player. If the player's position (in the inspector) is red, then the animation is controlling it.
I know of 2 ways to fix this:
Method 1 (not recommended): Edit the animation to move the root bone instead of the player
Most character models have a GameObject called "Armature" or "root" as a child. The position of your character should not be in the animation. If you need to move the character up in your animation, you can animate the "Armature" or "root" GameObject. In the screenshot below, note that in the animation window you don't see "PlayerPref: position" but instead see "Armature: position". This allows my movement script to move PlayerPref without the animation overriding the position.
Method 2 (recommended): Use the physics engine to jump
In your animation, don't make the player jump. Instead make the player "jump in place" i.e. maybe swing arms upward as if jumping but don't actually move upward. Just like Method 1, your animation should not set the player's position. The easiest way to do this is to click on "player: position" in the animation window and press delete.
Add a Rigidbody to your character. In your script:
private Rigidbody rig;
...
void Start(){
rig = GetComponent<Rigidbody>();
...
}
In your script when you trigger jumping, you can add rig.AddForce(jumpPower * transform.up);. This will make the physics system handle the jumping. If the player is jumping but there is something above, the physics engine will stop the player. With animation, the player will jump through any obstacles.
Related
I have a moving platform in a 2D Sidescroller built in Unity 2020.1
The Moving Platform translates between two points using the MoveTo method. It does not have a RigidBody2D component.
I attach the Player to the platform by making it the child of the platform using OnCollisionEnter2D and OnCollisionExit2D to parent the Player to the parent and reset to null respectively. Works great.
I'm using the CharacterController from Standard Assets.
The problem:
The player just walks in place when I try to move him back and forth on the platform.
What I've tried so far:
Changing the current velocity of the player by adding a constant to the x dimension of it's move vector.
Works kinda sorta but that constant needs to be huge to get it to move even a little bit. It's a huge kluge that violates every sense of coding propriety.
Put a RigidBody2D on the platform. Make it kinematic so it doesn't fall to the ground when I land on it. Move the platform via "rb.velocity = new Vector2(speed, rb.velocity.y)";
2a) Attempt to make the Player a child of the kinematic platform.
Player is made a child, but it doesn't move with the platform as expected. I believe that this is because both objects have RigidBody2D components, which I gather don't play well together based on what I've read.
2b) Attempt to add the platform's moving vector to the player's movement vector to make him stay in one place. Player stays stationary to make sure he stays fixed on the platform.
No dice.
I'm all out of ideas. Perusing videos on making player's stick to moving platforms all use the platform to move the player from place to place, without expecting that the game may want the player to move back and forth on the platform as the platform is moving.
I can't believe that this isn't a solved problem, but my Google foo isn't getting me any answers.
Thanks.
I'm a fairly newbie to Unity and C# but I wanted to help so I tried simulating your game for a solution and I didn't run into any problems using this script as the Player movement (you can modify variables as u like, add a separate variable for jump speed to make it smoother etc)
public class Player : MonoBehaviour {
Rigidbody2D rb;
float speed = 7f;
Vector3 movement;
public bool isOnGround;
public bool isOnPlatform;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
movement = new Vector3(Input.GetAxis("Horizontal"), 0f, 0f);
transform.position += movement * speed * Time.deltaTime;
Jump();
}
void Jump()
{
if (Input.GetButtonDown("Jump") && isOnGround || Input.GetButtonDown("Jump") && isOnPlatform)
{
rb.AddForce(transform.up * speed, ForceMode2D.Impulse);
}
}
}
Also add an empty child object to your Player gameObject and add a BoxCollider2D at his feet, narrow it down on Y axis like this
also attach this script to that child gameObject to check if player is on the ground(tag ground collider objects with new tag "Ground") so u don't jump infinitely while in the air OR if the player is on the platform(tag platform collider objects with "Platform") so you're still able to jump off it
public class GroundCheck : MonoBehaviour {
Player player;
MovingPlatform mp;
// Start is called before the first frame update
void Start()
{
player = FindObjectOfType<Player>();
mp = FindObjectOfType<MovingPlatform>();
}
private void OnCollisionEnter2D(Collision2D other)
{
if (other.gameObject.tag == "Ground")
{
player.isOnGround = true;
}
if (other.gameObject.tag == "Platform")
{
player.isOnPlatform = true;
transform.parent.SetParent(other.transform);
mp.MoveThePlatform();
}
}
private void OnCollisionExit2D(Collision2D other)
{
if (other.gameObject.tag == "Ground")
{
player.isOnGround = false;
}
if (other.gameObject.tag == "Platform")
{
transform.parent.SetParent(null);
}
}
}
and finally for platform movement (no RigidBody2Ds, just a collider)
public class MovingPlatform : MonoBehaviour {
bool moving;
public Transform moveHere;
// Update is called once per frame
void Update()
{
if (moving)
{
gameObject.transform.position = Vector2.MoveTowards(transform.position, moveHere.position, 2f * Time.deltaTime);
}
}
public void MoveThePlatform()
{
moving = true;
}
}
additional images
Player, Platform
P.s. Forgot to add - on Player's RigidBody2D, under Constraints, check the "Freeze Rotation Z" box.
I am using a Player rigid body object and there are walls around the Player. These walls are restricting the Player to go through. The Player gets collided with these walls and then falls. The Player uses teleport function to jump from one area to next. Is there a way to make the Player jump to a position just outside the collision area after the Player is collided with these walls?
That is, Player A gets collided with the wall and does not jump to last position, but the position before the collision happened?
public GameObject Player;
public Vector3 PlayerPos;
public bool RecordPos = true;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if(RecordPos == true)
{
PlayerPos = Player.transform.position;
}
}
public void OnTriggerEnter(Collider col)
{
if(col.gameObject.name == "Cube(3)" )
{
RecordPos = false;
Player.transform.position = PlayerPos;
}
}
In this script, the Player moves to last position it teleported from.
The "last" position before colliding is 1 frame before the collision. If you capture this position, the character will probably just fall on the obstacle again. Imagine you have a platform ___...___ and an obstacle. The easiest solution is to have 1 trigger at the left side of the obstacles and 1 trigger at the right side. If the player hasn't overcome the obstacle yet, he will be teleported to a chosen destination by you (before the obstacle) and if he's already overcome the obstacle, he will be teleported at the right side. __S_..._S__ (S stands for save/checkpoint trigger)
You need the following script on the gameobject with the Trigger collider. You also need to create a child object to the gameobject with the trigger:
private void OnTriggerEnter2D(Collider2D collision)
{
SaveManager.Instance.LastCheckpointOnHit = transform.GetChild(0).position;
}
And I suppose you have some sort of a singleton for data persistance. Now you can move the child gameobject whereever you want to teleport the player. And BTW I named the property LastCheckpointOnHit, because I was thinking of Holow Knight where if you get hit by spikes it instantly teleports you.
Then you just move the player: Player.transform.position = SaveManager.Instance.LastCheckpointOnHit;
In general when dealing with Rigidbody you shouldn't apply positions through the Transform component but rather through the Rigidbody.
I could imagine something like if you collide with a wall you get pushed away from the wall a bit in the direction where you came from like e.g.
[SerializeField] private float someDistanceThreshold = 0.01f;
[SerializeField] private Rigidbody _rigidbody;
private void Start()
{
if(!_rigidbody) _rigidbody = Player.GetComponent<Rigidbody>();
}
private void FixedUpdate()
{
PlayerPos = _rigidbody.position;
}
public void OnTriggerEnter(Collider col)
{
// instead of the name I would rather use a tag later to cover all obstacles
if(col.gameObject.name == "Cube(3)")
{
_rigidbody.position -= (_rigidbody.position - PlayerPos).normalized * someDistanceThreshold;
// For stopping the player here
_rigidbody.velocity = Vector3.zero;
}
}
I have created a NPC that follows the main player. When the player is in a certain range of the NPC, the NPC is supposed to walk, run, and attack based on the distance between the player and the NPC.The NPC has a C# MutantMovement Script attached, which also contains Animations. The NPC is also a NavMesh Agent. My problem is that the Animations do not run smoothly with the logic. I hope someone can help. Below is the MutantMovement Script and the Animation Controller.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
[RequireComponent (typeof (NavMeshAgent))]
[RequireComponent (typeof (Animator))]
public class MutantMovement : MonoBehaviour {
[SerializeField] private float _range = 10f;
public Transform _goal; // destination
public Transform _player;
private Animator _anim;
private bool _alive;
private float _distance;
private NavMeshAgent _agent;
void Start ()
{
_goal = GameObject.Find("Player").transform;
_player = GameObject.Find("Player").transform;
_alive = true;
// get a reference to the NavMesh Agent component
_agent = GetComponent<NavMeshAgent>();
_anim = GetComponent<Animator>();
}
void Update()
{
Vector3 direction = _goal.position - this.transform.position;
float angle = Vector3.Angle(direction, this.transform.forward);
if(Vector3.Distance(_goal.position, this.transform.position) < 10 &&
angle < 50){
_anim.SetBool("isIdle", false);
if ((_alive && direction.magnitude > 5))
{
_agent.destination = _goal.position;
_anim.SetBool("isWalking", true);
_anim.SetBool("isAttacking", false);
_anim.SetBool("isRunning", false);
if((_alive && direction.magnitude > 6.5)){
_agent.destination = _goal.position;
_anim.SetBool("isRunning", true);
_anim.SetBool("isWalking", false);
_anim.SetBool("isAttacking", false);
}
}
else{
_agent.isStopped = false;
_anim.SetBool("isAttacking", true);
_anim.SetBool("isWalking", false);
_anim.SetBool("isRunning", false);
}
}else{
_anim.SetBool("isIdle", true);
_anim.SetBool("isWalking", false);
_anim.SetBool("isAttacking", false);
}
}
public void SetAlive(bool alive)
{
_alive = alive;
}
}
Animation Controller
I would use Animator triggers rather than booleans in your particular case, so the transitions from one animation to another are triggered only once.
In your state machine, you can then set the transitions so :
walking or attacking ---[trigger run]----> running animation
running or walking -----[trigger attack]---> attack animation
running or attacking ----[trigger walk] ---> walking animation
It makes it overall easier as you don't have to turn the other bools to false when triggering a new animation state.
Another thing you can check to make sure the transitions are fluid, is to have a look at the exit time of your animations - if you turn "Has Exit Time" off, your transitions will be instant. You can find this option if you open your animator controller and click on your state machine transition arrow.
So I want my player to be able to travel up and down ladders without falling off unintentionally. Climbing up is easy enough, just let them walk forward against a wall and they'll collide and stay still but when they descend using the 'back' button they will start descending very briefly but immediately walk off the ladder. Disabling the player script temporarily is a messy process so I wanted to know what a good alternative is.
I'm using transform to raise and lower my player in the ladderScript and to move it in the playerScript.
Script (amended for brevity)
public class LadderScript : MonoBehaviour {
private playerScript pScript; //<--These both contain the colliding players script...
private Collider collided; //<---- ...and collider when Update runs
private bool triggerStay; //used to prevent Update from running while there is no contact
void Start()
{
triggerStay = false;
}
...
void Update() //Runs once per collider that is touching the player every frame
{
if (triggerStay) {
if (collided.CompareTag ("Player")) { //A player is touching the ladder
/*
if (!pScript.getIsGrounded ()) { //Ineffective
pScript.enabled = false;
} else if (pScript.getIsGrounded ()) {
pScript.enabled = true;
}
*/
if (Input.GetAxis ("Vertical") < 0) { // Player should go down
collided.transform.Translate (0, -.1f, 0);
} else if (Input.GetAxis ("Vertical") > 0) //Player should go up
collided.transform.Translate (0, .1f, 0);
}
}
}
...
}
I have this C# script attached to my main camera game object which also has a capsule collider attribute. However, it doesn't seem to do anything. How should I modify/add to this to make the camera "jump" and fall down to the ground again?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Jump : MonoBehaviour {
[HideInInspector] public bool jump = false;
public float jumpForce = 1000f;
public Transform groundCheck;
private bool grounded = false;
private Rigidbody rb;
// Use this for initialization
void Awake ()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update ()
{
grounded = Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Ground"));
if (Input.GetButtonDown("Jump") && grounded)
{
jump = true;
}
}
void FixedUpdate()
{
if (jump)
{
rb.AddForce(new Vector2(0f, jumpForce));
jump = false;
}
}
}
Also, I would like to have the key for this be the spacebar if possible, but whatever key works or is there already is fine. I am still learning C#, so please forgive me if the solution is obvious.
This line is most likely causing the problem:
grounded = Physics2D.Linecast(transform.position, groundCheck.position, 1 << LayerMask.NameToLayer("Ground"));`
There are 2 reason that it wont produce proper results:
You haven't setup your ground tiles or the place where you character moves to the "Ground" layer. You wont have this by default but you can add it from the Project Settings->Tags and Layers menu.
Your colliders are not close enough to the ground thus not causing collision.
Besides that it should work fine.