I am having some odd behavior with a game I am making. The main idea is that the user will travel down a path and can press space bar to invert the gravity and avoid obstacles. The problem I am having is that when I press space bar the gravity inverts most of the time, but sometimes it will not work correctly.
I did the key detection in the Update method and changed the gravity in the FixedUpdate method. I wasn’t sure if it was technically a physics calculation, so I just put it in there.
Here is my code:
public class BallMove : MonoBehaviour{
bool jump = false;
float grav = -9.81f;
bool changeGravity = false;
void Start()
{
rb = GetComponent<Rigidbody>();
Physics.gravity = new Vector3(0, grav, 0);
}
private void Update()
{
if (Input.GetKeyDown("space"))
changeGravity = true;
}
void FixedUpdate()
{
if ( Input.GetKeyDown("space") && changeGravity )
{
changeGravity = false;
grav *= -1;
Physics.gravity = new Vector3(0, grav, 0);
}
}
}
The document of GetKeyDown says
You need to call this function from the Update function, since the state gets reset each frame.
So it's wrong to detect key in FixedUpdate
void FixedUpdate()
{
if (changeGravity)
{
changeGravity = false;
grav *= -1;
Physics.gravity = new Vector3(0, grav, 0);
}
}
Related
Have tried a few things but they didn't seem to work, so I was hoping that you guys could help me.
I've trying to make this demo for a little time now, but I can't seem to get the jumping to work.
When I try to jump while running, I can't. But I can however jump forever when i get up in the air, which is something that I would like to remove from the game.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class movement : MonoBehaviour
{
Rigidbody2D rb2d;
public float moveVelocity = 8f;
public float jumpVelocity = 15f;
public float fallMultiplier = 2.5f;
public float lowJumpMultiplier = 2f;
public const string RIGHT = "right";
public const string LEFT = "left";
public const string UP = "up";
string buttonPressed;
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
}
void Update()
{
if (rb2d.velocity.y < 0)
{
rb2d.velocity += Vector2.up * Physics2D.gravity.y * (fallMultiplier - 1) *
Time.deltaTime;
}
else if (rb2d.velocity.y > 0 && !Input.GetButton ("Jump"))
{
rb2d.velocity += Vector2.up * Physics2D.gravity.y * (lowJumpMultiplier - 1) *
Time.deltaTime;
}
if (Input.GetKey(KeyCode.RightArrow))
{
buttonPressed = RIGHT;
}
else if (Input.GetKey(KeyCode.LeftArrow))
{
buttonPressed = LEFT;
}
else if (Input.GetKey(KeyCode.UpArrow))
{
buttonPressed = UP;
}
else
{
buttonPressed = null;
}
}
private void FixedUpdate()
{
if (buttonPressed == RIGHT)
{
rb2d.velocity = new Vector2(moveVelocity, rb2d.velocity.y);
}
else if (buttonPressed == LEFT)
{
rb2d.velocity = new Vector2(-moveVelocity, rb2d.velocity.y);
}
else if (buttonPressed == UP)
{
rb2d.velocity = Vector2.up * jumpVelocity;
}
else
{
rb2d.velocity = new Vector2(0, rb2d.velocity.y);
}
}
}
I am not sure why you are trying to control the gravity of the player when gravity is already applied to any gameObject with the RigidBody or RigidBody2D component attached to it. It is good that you are reading input in the Update() function and applying the motion inside of the FixedUpdate(). Here is a slight tweak to your current code, let me know how this goes.
Rigidbody2D rb2d;
public float moveVelocity = 8f;
public float jumpVelocity = 15f;
private float horizontalInput = 0.0f;
// did the player just try to jump
private bool justJumped = false;
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
}
void Update()
{
// grab how the player is moving - this value is mapped to your arrow keys
// so horizontal is left / right arrow and Vertical is up/down arrow
// the value is between [-1, 1] respectively
horizontalInput = Input.GetAxis("Horizontal");
// player tried to jump, we are not currently jumping and the player is grounded
if(IsGrounded() && Input.GetKeyDown(KeyCode.UpArrow))
{
justJumped = true;
}
}
private void FixedUpdate()
{
// handle our jump
if(justJumped)
{
// we just applied a jump so do not apply it again
justJumped = false;
// you can either apply the jump by setting velocity, but I would recommend using AddForce instead
rb2d.AddForce(Vector2.up * jumpVelocity);
}
// we are moving in either the left or right direction
if(horizontalInput != 0)
{
// move in the horizontal axis, but keep our Y component of velocity the same in case we jumped
rb2d.velocity = new Vector2 (horizontalInput * moveVelocity, rigidBody.velocity.y);
}
else
{
// just in case we are not moving, assure this by setting the horizontal component of velocity to 0
rb2d.velocity = new Vector2 (0f, rigidBody.velocity.y);
}
}
// determines if this object is currently touching another object that is marked as ground
private bool IsGrounded() {
// our current position (the player)
Vector2 position = transform.position;
// the direction we are going to aim the raycast (down as that is where the ground is)
Vector2 direction = Vector2.down;
// the ground should be close, so check it very close to the player
float distance = 1.0f;
// check if we hit anything that is on the layer of just ground - ignore all other layers
// IMPORTANT:: Make sure to make a Layer and set all of your ground to this layer
RaycastHit2D hit = Physics2D.Raycast(position, direction, distance, LayerMask.GetMask("GroundLayer"));
// we hit a ground collider, so we are grounded
if (hit.collider != null) {
return true;
}
return false;
}
Let me know if this works for you. I changed a few things around such as using GetAxis() instead of using the arrow keys as in Unity, these are the same. I also changed your jump to use an AddForce2D instead of setting velocity. The one addition was I added a IsGrounded() which will detect if whatever object this script is on (I assume it is on your player object) is near or touching objects below them that are marked as GroundLayer. If you have questions comment below.
I need help with my code where I can only jump once but then it's locked for me I think I'm onto something.
Here is my code:
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Vector3 v = transform.position;
v.x += speed * Input.GetAxis("Horizontal") * Time.deltaTime;
v.z += speed * Input.GetAxis("Vertical") * Time.deltaTime;
transform.position = v;
if (Input.GetKeyDown(KeyCode.Space) && isGrounded==true)
{
GetComponent<Rigidbody2D>().velocity = Vector2.up * jumpVelocity;
isGrounded = false;
}
void OnCollisionEnter(Collision col)
{
if (col.gameObject.tag == ("Ground") && isGrounded == false)
{
isGrounded = true;
}
}
}
}
You nested the OnCollisionEnter method inside the Update.
This way it is a local function and not found by the Unity Physics / Message System -> it will never be called.
Another issue in your code: Whenever there is a Rigibody/Rigibody2D involved you do not want to set or access stuff via the Transform component. This will break the physics and collision detection!
And finally note that Physics and Physics2D are two completely separate engines! If you want to be moving and detect collisions in 3D space then you have to use a Rigidbody, not a Rigibody2D!
It should rather be
[Header("References")]
[SerializeField] private Rigidbody _rigidbody;
[Header("Debugging")]
[SerializeField] private bool isGrounded;
private void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
}
// Update is called once per frame
private void Update()
{
var x = Input.GetAxis("Horizontal");
var y = Input.GetAxis("Vertical");
// Clamp the vector so it has a maximum magnitude of 1
// this prevents that diagonal movement is overall faster
var moveInput = Vector2.ClampMagnitude(new Vector2(x,y), 1) * speed;
// instead of using the Transform simply change the rigidbody velocity accordingly
var velocity = _rigidbody.velocity;
velocity.x = moveInput.x;
velocity.z = moveInput.y;
_rigidbody.velocity = velocity;
// it is cheaper to first check a simple bool flag
// if this already fails we can skip the more expensive "GetKeyDown"
if (isGrounded && Input.GetKeyDown(KeyCode.Space))
{
_rigidbody.velocity = Vector2.up * jumpVelocity;
isGrounded = false;
}
}
// This method should be on class level and not nested under Update
private void OnCollisionEnter(Collision col)
{
// if already "isGrounded" we can skip the more expensive "CompareTag"
if (!isGrounded && col.gameObject.CompareTag("Ground"))
{
isGrounded = true;
}
}
I am building a game where the player can be controlled using the mouse input, using a click to move logic via a navmesh agent.
In order to let the player jump, I started using a CharacterController as well which should help managing the player. My issue is that I can't figure out where to put the jump logic. All references I found are related using the character controller without the navmesh agent.
I can get rid of the CharacterController if needed, but the NavMeshAgent has to stay.
Here it is a working code which allows to walk. Can you please help me with the jumping logic?
private NavMeshAgent _agent;
private CharacterController _characterController;
private Vector3 _desVelocity;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo))
{
_agent.destination = hitInfo.point;
}
}
var currMovementDirection = _desVelocity.normalized * currentSpeed;
if (_agent.remainingDistance > _agent.stoppingDistance)
{
_desVelocity = _agent.desiredVelocity;
_characterController.Move(currMovementDirection * Time.deltaTime);
}
}
You can achieve this using a Rigidbody instead of a CharacterController. The trick is that you need to disable the NavMeshAgent in order to jump.
Optionally, you set the destination to where you are at the time of the jump, so that the agent doesn't continue the simulation while the jump is happening.
Using collision detection, you turn the NavMeshAgent back on again once you land.
public class PlayerMovement : MonoBehaviour
{
private Camera cam;
private NavMeshAgent agent;
private Rigidbody rigidbody;
public bool grounded = true;
void Start()
{
cam = Camera.main;
agent = GetComponent<NavMeshAgent>();
rigidbody = GetComponent<Rigidbody>();
}
void Update()
{
// clicking on the nav mesh, sets the destination of the agent and off he goes
if (Input.GetMouseButtonDown(0) && (!agent.isStopped))
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
agent.SetDestination(hit.point);
}
}
// when you want to jump
if (Input.GetKeyDown(KeyCode.Space) && grounded)
{
grounded = false;
if (agent.enabled)
{
// set the agents target to where you are before the jump
// this stops her before she jumps. Alternatively, you could
// cache this value, and set it again once the jump is complete
// to continue the original move
agent.SetDestination(transform.position);
// disable the agent
agent.updatePosition = false;
agent.updateRotation = false;
agent.isStopped = true;
}
// make the jump
rigidbody.isKinematic = false;
rigidbody.useGravity = true;
rigidbody.AddRelativeForce(new Vector3(0, 5f, 0), ForceMode.Impulse);
}
}
/// <summary>
/// Check for collision back to the ground, and re-enable the NavMeshAgent
/// </summary>
private void OnCollisionEnter(Collision collision)
{
if (collision.collider != null && collision.collider.tag == "Ground")
{
if (!grounded)
{
if (agent.enabled)
{
agent.updatePosition = true;
agent.updateRotation = true;
agent.isStopped = false;
}
rigidbody.isKinematic = true;
rigidbody.useGravity = false;
grounded = true;
}
}
}
}
The jump logic should be inside the Update() method since we want the height to be calculated every frame.
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo))
{
_agent.destination = hitInfo.point;
}
}
var currMovementDirection = _desVelocity.normalized * currentSpeed;
groundedPlayer = _characterController.isGrounded;
if (groundedPlayer && currMovementDirection.y < 0)
{
currMovementDirection.y = 0f;
}
// Changes the height position of the player..
if (Input.GetButtonDown("Jump") && groundedPlayer)
{
currMovementDirection.y += Mathf.Sqrt(jumpHeight * -3.0f * gravityValue);
}
currMovementDirection.y += gravityValue * Time.deltaTime;
if (_agent.remainingDistance > _agent.stoppingDistance)
{
_desVelocity = _agent.desiredVelocity;
_characterController.Move(currMovementDirection * Time.deltaTime);
}
}
Please see the official docs here
I am making a map game in unity. I want to drag the mouse on the map resulting in the player viewing different areas of the map (yes, like Hears of Iron IV). I have tried using the 'onDrag' method but I can not find the proper code for it. Can someone please link some documentation or required code for this method?
Thank you.
If you want to change camera position you can simply do it with this code:
public float dragSpeed = 2;
private Vector3 dragOrigin;
void Update()
{
if (Input.GetMouseButtonDown(0))
{
dragOrigin = Input.mousePosition;
return;
}
if (!Input.GetMouseButton(0)) return;
Vector3 pos = Camera.main.ScreenToViewportPoint(dragOrigin - Input.mousePosition);
Vector3 move = new Vector3(pos.x * dragSpeed, 0, pos.y * dragSpeed);
transform.Translate(move, Space.World);
}
With this, you can start and do other things.
If you want to move the map instead of the camera you can also try :
private bool isDragging;
public void OnMouseDown()
{
isDragging = true;
}
public void OnMouseUp()
{
isDragging = false;
}
void Update()
{
if (isDragging) {
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
transform.Translate(mousePosition);
}
}
this work as Drag and Drop any 2d objects in unity.
the other answer does not work since the map should also move while button is held.. here is the fix
public float dragSpeed = 2;
private Vector3 dragOrigin;
private bool dragStarted = false;
if (Input.GetMouseButtonDown(1))
{
dragOrigin = Input.mousePosition;
dragStarted = true;
}
if (Input.GetMouseButtonUp(1))
{
dragStarted = false;
return;
}
if (dragStarted)
{
Vector3 pos = main_camera.ScreenToViewportPoint(dragOrigin - Input.mousePosition);
Vector3 move = new Vector3(pos.x * dragSpeed, 0, pos.y * dragSpeed);
main_camera.transform.Translate(move, Space.World);
}
this also assumes your camera is pointed downwards or facing north..
So i have a simple scene in unity, a player with a parallax background and a Tilemap as a ground, as well as some very simple post processing. I know this isn't a The minute i move, there is a consistent stutter just under ever second. I'm not sure whether it's to do with my player movement code, camera movement or anything else. I'm also using a Cinemachine virtual camera. My rigidbody interpolation is set to interpolate and collision detection set to continuous. Here's my player movement if this helps. Here is a sample of what it looks like, if you look at the background or the tilemap it's quite noticeable. https://youtu.be/h2rSheZWtKs
[SerializeField] private LayerMask groundLayerMask;
public float speed;
public float Jump;
public sword swordScript;
public GameObject swordSprite;
private float move;
private Rigidbody2D rb;
private BoxCollider2D boxCollider2d;
private bool facingRight;
public SpriteRenderer spr;
public Animator PlayerAnims;
public bool movementAllowed;
void Awake()
{
Application.targetFrameRate = 60;
Application.targetFrameRate = Screen.currentResolution.refreshRate;
boxCollider2d = GetComponent<BoxCollider2D>();
rb = GetComponent<Rigidbody2D>();
facingRight = true;
spr = GetComponent<SpriteRenderer>();
}
// Start is called before the first frame update
void Start()
{
boxCollider2d = GetComponent<BoxCollider2D>();
rb = GetComponent<Rigidbody2D>();
facingRight = true;
spr = GetComponent<SpriteRenderer>();
}
// Update is called once per frame
void FixedUpdate()
{
if(movementAllowed == true)
{
rb.velocity = new Vector2(move * speed, rb.velocity.y);
if (isGrounded() && Input.GetButtonDown("Jump"))
{
rb.AddForce(new Vector2(rb.velocity.x, Jump));
}
}
}
void Update()
{
move = Input.GetAxis("Horizontal");
rb.velocity = new Vector2(move * speed, rb.velocity.y);
if (movementAllowed == true)
{
Flip(move);
if (move == 0)
{
PlayerAnims.SetBool("isRunning", false);
}
else
{
PlayerAnims.SetBool("isRunning", true);
}
}
}
private bool isGrounded()
{
float extraHeightText = .1f;
RaycastHit2D raycasthit2d = Physics2D.BoxCast(boxCollider2d.bounds.center, boxCollider2d.bounds.size, 0f, Vector2.down, extraHeightText, groundLayerMask);
Color rayColour;
if (raycasthit2d.collider != null)
{
rayColour = Color.green;
PlayerAnims.SetBool("isJumping", false);
}
else
{
rayColour = Color.red;
PlayerAnims.SetBool("isJumping", true);
}
Debug.DrawRay(boxCollider2d.bounds.center + new Vector3(boxCollider2d.bounds.extents.x, 0), Vector2.down * (boxCollider2d.bounds.extents.y + extraHeightText), rayColour);
Debug.DrawRay(boxCollider2d.bounds.center - new Vector3(boxCollider2d.bounds.extents.x, 0), Vector2.down * (boxCollider2d.bounds.extents.y + extraHeightText), rayColour);
Debug.DrawRay(boxCollider2d.bounds.center - new Vector3(boxCollider2d.bounds.extents.x, boxCollider2d.bounds.extents.y + extraHeightText), Vector2.right * (boxCollider2d.bounds.extents.x), rayColour);
return raycasthit2d.collider != null;
}
private void Flip(float move)
{
if (move > 0 && !facingRight || move < 0 && facingRight)
{
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
if (swordScript.isFollowing == true)
{
Vector3 swordScale = swordSprite.transform.localScale;
swordScale.x *= -1;
swordSprite.transform.localScale = swordScale;
}
}
}
You are setting rb.velocity in both the Update() and FixedUpdate() methods. I would try only using one of those.
On top of that, your jump check also re-applies the X velocity along with the upward force. So if you're jumping the player will be pushed forward at double speed.
You also have an error being outputted in the console about an Index being out of range... I would look into that.
Can you also post your code for the parallax background?
FixedUpdate is a method where you want to do everything physics, player-controller and simulation related.
Update is just for rendering-related fluff i.e. code of no real consequence other than making things look correctly.
Hence:
Move all your player/physics controller code to FixedUpdate.
Move bg parallax code to Update.
Use Time.deltaTime (in Update) and Time.fixedDeltaTime (in FixedUpdate) time steps when animating or interpolating between values.
Ad #3.: Although - as #Menyus noted (below) - Time.deltaTime is all you really need.
Time.fixedDeltaTime is for that extra intent explicitness (but was necessary in earlier unity versions).