I'm working with a CharacterController and added the ability to push Rigidbodies. The problem is however that the pushing is frame-rate dependent. How would I be able to make it frame-rate independent? I have tried adding Time.deltatime, but this makes pushing not possible, I might be adding it wrong though.
Here's the code that adds force to rigidbodies;
void OnControllerColliderHit(ControllerColliderHit hit)
{
Rigidbody body = hit.collider.attachedRigidbody;
if (body == null || body.isKinematic)
return;
if (hit.moveDirection.y < -.3f)
return;
Vector3 pushDirection = new Vector3(hit.moveDirection.x, 0, hit.moveDirection.z);
body.velocity = pushForce * pushDirection;
}
As far as I know it has something to do with the last 2 lines of code.
Edit(The code for pushing):
public void PushStates() {
// Creating the raycast origin Vector3's
Vector3 forward = transform.TransformDirection(Vector3.forward) * distanceForPush;
Vector3 middle = controller.transform.position - new Vector3(0, -controller.height / 2, 0);
// Inspector bool
if (pushRay)
{
Debug.DrawRay(middle, forward, Color.cyan);
}
// Force the pushForce and movementSpeed to normal when the player is not pushing
pushForce = 0f;
movementSpeed = walkSpeed;
// Draws a raycast in front of the player to check if the object in front of the player is a pushable object
if (Physics.Raycast(middle, forward, out hit, distanceForPush))
{
if (InputManager.BButton() && playerIsInPushingTrigger)
{
PushableInfo();
playerIsPushing = true;
anim.SetBool("isPushing", true);
if (hit.collider.tag == "PushableLight")
{
pushForce = playerPushForceLight;
movementSpeed = pushSpeedLight;
}
else if (hit.collider.tag == "PushableHeavy")
{
pushForce = playerPushForceHeavy;
movementSpeed = pushSpeedHeavy;
}
// Checks the players speed now instead off movement. This is neccesary when the player is pushing a pushable into a collider.
// The player and pushable never stop moving because of force.
if (currentSpeed < 0.15f)
{
//Removes all remaining velocity, when the player stops pushing
pushableObjectRB.velocity = Vector3.zero;
pushableObjectRB.angularVelocity = Vector3.zero;
anim.SetFloat("pushSpeedAnim", 0f);
}
else
{
// Calls a rotation method
PushingRot();
if (hit.collider.tag == "PushableLight")
{
anim.SetFloat("pushSpeedAnim", pushSpeedAnimLight);
}
else if (hit.collider.tag == "PushableHeavy")
{
anim.SetFloat("pushSpeedAnim", pushSpeedAnimHeavy);
}
}
}
else
{
anim.SetBool("isPushing", false);
pushForce = 0f;
movementSpeed = walkSpeed;
playerIsPushing = false;
}
}
else
{
anim.SetBool("isPushing", false);
playerIsPushing = false;
}
// Setting the time it takes to rotate when pushing
AnimatorStateInfo stateInfo = anim.GetCurrentAnimatorStateInfo(0);
if (stateInfo.fullPathHash == pushStateHash)
{
turnSmoothTime = maxTurnSmoothTimePushing;
}
else
{
turnSmoothTime = 0.1f;
}
}
You were right, you need to multiply by Time.deltaTime which is the time elapsed since last rendered frame.
As this number is really small (has you have 60+ fps) you'll also need to increase the value (multiply it by 100, 1000...)
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'm creating a Mario clone and I need to keep the player's forward momentum into the jump and then onward into the landing. I can't figure out how to achieve this. Every time I land, the player has to build up momentum again. Any Idea how to fix this? I've tried several solutions to no avail. I'm thinking it has something to do with how I'm adding force and acceleration to the player when holding left or right. Not sure though any help would be much appreciated. thanks in advance.
Here's my code:
Animator animator;
Rigidbody2D rb;
bool isGrounded;
public float moveSpeed;
public Vector2 acceleration;
public float jumpHeight;
public float lowjumpMultiplier;
public Transform groundCheckM;
public Transform groundCheckL;
public Transform groundCheckR;
public float storedValue;
void Start()
{
animator = GetComponent<Animator>();
rb = GetComponent<Rigidbody2D>();
transform.eulerAngles = new Vector3(0, 0, 0);
}
private void Update()
{
if
((Physics2D.Linecast(transform.position,groundCheckM.position, 1 << LayerMask.NameToLayer("Floor/Platforms"))) || //Check if grounded
(Physics2D.Linecast(transform.position, groundCheckL.position, 1 << LayerMask.NameToLayer("Floor/Platforms"))) ||
(Physics2D.Linecast(transform.position, groundCheckR.position, 1 << LayerMask.NameToLayer("Floor/Platforms"))))
{
isGrounded = true;
animator.SetBool("Jump", false);
}
else
{
isGrounded = false;
}
animator.SetFloat("Walk", rb.velocity.x); //Set animation float to x velocity
if (rb.velocity.x <= 0.03f && rb.velocity.x >= -0.03f && isGrounded) //Play "Idle" animation
{
animator.Play("Mario_Idle");
}
if (rb.velocity.x >=4 || rb.velocity.x <=-4)
{
animator.speed = Mathf.Abs(rb.velocity.x / 5.5f); //Increase speed of walking animation with player's walking speed
}
}
void FixedUpdate()
{
if (Input.GetKey("d") || Input.GetKey("right")) //Move player to the right
{
rb.AddForce(acceleration * rb.mass);
transform.rotation = Quaternion.Euler (0, 0, 0);
}
else if (Input.GetKey("a") || Input.GetKey("left")) //Move player to the left
{
rb.AddForce(-acceleration * rb.mass);
transform.rotation = Quaternion.Euler(0, 180, 0);
}
if (rb.velocity.x >= 10)
{
rb.velocity = new Vector2(10, rb.velocity.y); //Cap player speed at 10 when moving right
}
else if (rb.velocity.x <= -10)
{
rb.velocity = new Vector2(-10, rb.velocity.y); //Cap player speed at 10 when moving left
}
if (Input.GetKey("space") && isGrounded) //Player jump
{
rb.velocity += new Vector2(rb.velocity.x, jumpHeight);
animator.SetBool("Jump", true);
}
}
}
I feel dumb but the answer was in the physics material. Once I lowered the friction, it allowed momentum from the jump to carry into the player's run speed. I guess it's a good reminder to tinker directly inside Unity and its built in physics system.
If you want to keep your momentum when jumping, you could store it in a variable that will increase until it reaches the max, or you let go of the key.
float acceleration;
float accelFactor = 0.6f;
float deAccelFactor = 1f;
bool jumping; //you should set it to true when jumping and false, when not.
Rigidbody2D rb;
void Start(){
rb = GetComponent<Rigidbody2D>();
}
void Update(){
if (Input.GetKeyDown(KeyCode.D))
{
Accelerate();
rb.AddForce(acceleration * rb.Mass);
}
else if (jumping)
{
rb.AddForce(acceleration * rb.Mass);
}
else
{
DeAccel();
}
}
void Accelerate(){
acceleration += accelFactor * Time.deltaTime;
acceleration = Mathf.Clamp(acceleration, 0, maxAccel);
}
void DeAccel(){
acceleration -= deAccelFactor * Time.deltaTime;
acceleration = Mathf.Clamp(acceleration, 0, maxAccel);
}
This is what I would recommend using, that is, if I understand what you mean.
here's my jump code it updates in void Update
void Jump()
{
if(isgrounded == true)
{
amountofjumps = jumps;
}
if(Input.GetKeyDown(KeyCode.UpArrow) && amountofjumps > 0)
{
rb2d.velocity = Vector2.up * jump * Time.deltaTime;
amountofjumps--;
}
else if(Input.GetKeyDown(KeyCode.UpArrow) && amountofjumps == 0 && isgrounded == true)
{
rb2d.velocity = Vector2.up * jump * Time.deltaTime;
}
}
here are the variables I use for my jump code
bool isgrounded;
public float groundcheckradius;
public LayerMask whatisground;
public float jump;
private int amountofjumps;
public int jumps;
here's how I detect the ground
void checkforground()
{
isgrounded = Physics2D.Raycast(transform.position,Vector2.down, groundcheckradius,whatisground);
}
void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawLine(transform.position, transform.position + Vector3.down * groundcheckradius);
}
thanks in advance
Velocity is often used for moving object try rigidbody2d.AddForce() instead.
void Jump()
{
if (isgrounded == true)
{
amountofjumps = jumps;
}
if (Input.GetKeyDown(KeyCode.UpArrow) && amountofjumps > 0 && isgrounded == true)
{
rb2d.AddForce(transform.up * jump, ForceMode2D.Impulse);
amountofjumps--;
}
}
First of all Time.deltaTime makes no sense when setting a velocity. A velocity already is frame-rate independent so when you multiply a velocity by Time.deltaTime (about 0.017 for 60 fps) it becomes extremely small/slow.
Secondly currently you overwrite the entire velocity so if your player is moving forward it will completely stop any movement on the X axis.
And finally when you are grounded you want always be able to jump ... not only if amountofjumps == 0 which will never be the case since right before you have set it to amountofjumps = jumps;! You don't want to check the amountofjumps at all when jumping from the ground!
You would probably rather use e.g.
// get the current velocoty of the rigidbody
var velocity = rb2d.velocity;
// Only overwrite the Y velocity with the jump
velocity.y = jump;
// re-assign the changed vector to the rgidbody
rb2d.velocity = velocity;
And then I would change the logic to something like e.g.
private void Jump()
{
if(isgrounded)
{
amountofjumps = jumps;
// when you are grounded you can always jump!
// Not only when the amountofjumps == 0
// actually when you are grounded the amountofjumps shouldn't matter at all
if(Input.GetKeyDown(KeyCode.UpArrow))
{
DoJump();
}
}
// As before while not grounded the amountofjumps is taken into account
else if(amountofjumps > 0)
{
if(Input.GetKeyDown(KeyCode.UpArrow))
{
DoJump();
}
}
}
private void DoJump()
{
// get the current velocoty of the rigidbody
var velocity = rb2d.velocity;
// Only overwrite the Y velocity with the jump
velocity.y = jump;
// re-assign the changed vector to the rgidbody
rb2d.velocity = velocity;
// Always reduce the amount of jumps also when jumping from the ground
amountofjumps--;
}
I have written a script to move an object via ray cast to positions on a terrain, however I can’t for the life of me seem to make this movement smooth. I have tried all manner of things trying to figure this out (as you can probably tell by the amount of commented-out code below); disabling various rigidbody variables, however to no avail.
One approach that (kind of) worked was disabling the collider attached to the object, however this lead to the object inadvertently sinking below the terrain.
Can anyone please advise me as to the best approach to go about this would be? I feel like this should be very simple but I am over complicating it.
//Move
if (Input.GetKey(KeyCode.E)) {
// if (Input.GetKeyDown(KeyCode.E))
// {
// modObj.GetComponent(BoxCollider).enabled = false;
initPos = modObj.transform.position;
var initRotation = modObj.transform.rotation;
// }
//
// modObj.GetComponent(Rigidbody).isKinematic = true;
// modObj.GetComponent(Rigidbody).useGravity = false;
moveObject(modObj, initPos, initRotation);
} else {
// modObj.GetComponent(BoxCollider).enabled = true;
// modObj.GetComponent(Rigidbody).isKinematic = false;
// modObj.GetComponent(Rigidbody).useGravity = true;
}
function moveObject(modObj: GameObject, initPos: Vector3, initRotation: Quaternion) {
//Debug.Log("Moving Object");
var hit: RaycastHit;
var foundHit: boolean = false;
foundHit = Physics.Raycast(transform.position, transform.forward, hit);
//Debug.DrawRay(transform.position, transform.forward, Color.blue);
if (foundHit && hit.transform.tag == "Terrain") {
modifyObjGUIscript.activateMoveDisplay(initPos, hit.point);
// var meshHalfHeight = modObj.GetComponent.<MeshRenderer>().bounds.size.y /2; //helps account for large and small objects
modObj.transform.position = hit.point; //***method 01***
// modObj.transform.position = Vector3.Lerp(initPos, hit.point, speed); //***method 02***
// modObj.transform.position = Vector3.SmoothDamp(initPos, hit.point, velocity, smoothTime); //***method 02***
// modObj.transform.position.y = modObj.transform.position.y + meshHalfHeight + hoverHeight;
modObj.transform.rotation = initRotation;
}
}
You need to call the moveObject to recompute the position very frequently - every frame, if possible. For example from Update or from coroutine:
void Update()
{
modObj.transform.position = Vector3.Lerp(initPos, hit.point, speed);
}
IEnumerator MoveObject(Vector3 initPos, Vector3 endPos, float speed)
{
while (initPos != endPos)
{
modObj.transform.position = Vector3.Lerp(initPos, endPos, speed);
yield return null;
}
}
I have a VR Scene with a C# Script on the camera that allows the user to Click Once to move and again to stop.
public float speed = 1.0f;
public bool startedMoving = true;
public GameObject myCam;
// Update is called once per frame
void Update () {
if (startedMoving) {
transform.position += myCam.transform.forward * speed * Time.deltaTime;
}
// if(Input.GetButtonDown("Fire1")){
if (Input.GetMouseButton(0)){
startedMoving = !startedMoving;
}
}
What I want to know is how I can CLICK & HOLD to move Backwards..?
Thank you!
~ b
Use enum the represent the status of the mouse instead of startedMoving or multiple booleans that will make everything easier to implement. The comment in the code describes how it works.
using UnityEngine;
using System.Collections;
public class ClickAndHeld : MonoBehaviour
{
public GameObject myCam;
CLICK_MODE clickMode = CLICK_MODE.NO_CLICK;
MOVE_DIRECTION moveDir = MOVE_DIRECTION.IDLE;
public float speed = 1.0f;
//If down for 0.5 secods the it is considered Click and Hold instead of Click
float clickHoldDetectTime = 0.5f;
float clickCounter = 0; //Dont not change
void Start()
{
StartCoroutine(mover());
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButton(0))
{
//If in NO_CLICK, set to CLICKED
if (clickMode == CLICK_MODE.NO_CLICK)
{
//Change the mode to CLICKED
clickMode = CLICK_MODE.CLICKED;
}
//If in CLICKED mode, start counting to clickHoldDetectTime
if (clickMode == CLICK_MODE.CLICKED)
{
clickCounter += Time.deltaTime; //Increment counter
//Check if we have reached the clickHoldDetectTime time
if (clickCounter > clickHoldDetectTime)
{
//Reset Counter
clickCounter = 0;
//Change the mode to CLICK_AND_HELD
clickMode = CLICK_MODE.CLICK_AND_HELD;
}
}
//If STILL down and the the current mode is CLICK_AND_HELD then do clickedAndHeldDown stuff
if (clickMode == CLICK_MODE.CLICK_AND_HELD)
{
clickedAndHeldDown();
}
}
else
{
//If released and the current mode is CLICKED then do clicked stuff
if (clickMode == CLICK_MODE.CLICKED)
{
clicked();
}
//If released and the current mode is CLICK_AND_HELD, change to RELEASED then do relased stuff
if (clickMode == CLICK_MODE.CLICK_AND_HELD)
{
//Change the mode to RELEASED
clickMode = CLICK_MODE.RELEASED;
mouseReleasedAfterBeingHeld();
}
//Reset each time mouse button is released
reset();
}
}
IEnumerator mover()
{
while (true)
{
if (moveDir == MOVE_DIRECTION.IDLE)
{
}
//Move Forward
if (moveDir == MOVE_DIRECTION.FORWARD)
{
transform.position += myCam.transform.forward * speed * Time.deltaTime;
}
//Move Backward
if (moveDir == MOVE_DIRECTION.BACKWARD)
{
transform.position -= myCam.transform.forward * speed * Time.deltaTime;
}
yield return null;
}
}
private void clicked()
{
Debug.Log("CLICKED");
//If Idle, become Forward
if (moveDir == MOVE_DIRECTION.IDLE)
{
moveDir = MOVE_DIRECTION.FORWARD;
}
//If forward, moves become idle
else if (moveDir == MOVE_DIRECTION.FORWARD)
{
moveDir = MOVE_DIRECTION.IDLE;
}
//--------------------------------------------------
//If backward, moves become idle
else if (moveDir == MOVE_DIRECTION.BACKWARD)
{
moveDir = MOVE_DIRECTION.IDLE;
}
}
private void clickedAndHeldDown()
{
Debug.Log("CLICKED AND HELD");
//If Idle, becomes backward
if (moveDir == MOVE_DIRECTION.IDLE)
{
moveDir = MOVE_DIRECTION.BACKWARD;
}
}
//Called when released after being RELEASED from CLICKED_HELD
private void mouseReleasedAfterBeingHeld()
{
Debug.Log("RELEASED AFTER CLICKED AND HELD");
//If backward, move becomes idle
if (moveDir == MOVE_DIRECTION.BACKWARD)
{
moveDir = MOVE_DIRECTION.IDLE;
}
//--------------------------------------------------
//If forward, move becomes idle
else if (moveDir == MOVE_DIRECTION.FORWARD)
{
moveDir = MOVE_DIRECTION.IDLE;
}
}
void reset()
{
clickMode = CLICK_MODE.NO_CLICK;
clickCounter = 0;
}
}
public enum CLICK_MODE
{
NO_CLICK, CLICKED, CLICK_AND_HELD, RELEASED
}
public enum MOVE_DIRECTION
{
IDLE, FORWARD, BACKWARD
}
Because you only have one trigger action you're going to have to implement something time based for toggling forward/backwards movement. For example, if you press and release the trigger quickly then forward walk could be toggled, but if you're holding the trigger after N length of time then walk backwards.
He's a practical example to get you starting.
Pressing and releasing the trigger within 300ms will toggle forward movement
Pressing and holding the trigger for longer than 300ms will begin backwards movement, releasing the trigger then will stop backwards movement
This is a theoretical example
public float speed = 1.0f;
bool triggerPressed = false;
float triggerHeldTime = 0f;
public bool movingForwards = false;
public bool movingBackwards = false;
void Update ()
{
// increment hold time if we're still holding trigger
if (Input.GetMouseButton(0) && triggerPressed)
triggerHeldTime += Time.deltaTime;
if (Input.GetMouseButton(0) && !triggerPressed)
{
// reset everything when trigger initially pressed
movingForards = false;
movingBackwards = false;
triggerHeldTime = 0f;
triggerPressed = true;
}
else if (!Input.GetMouseButton(0) && triggerPressed)
{
// upon trigger release
triggerPressed = false;
// if we are not moving backwards, toggle forwards movement
if(!movingBackwards)
movingForwards = !movingForwards;
// always reset backwards movement when we release the trigger
movingBackwards = false;
triggerHeldTime = 0f;
}
// if the trigger has been held for 300ms then move backwards
if(triggerHeldTime > 0.3f)
{
moveForwards = false;
moveBackwards = true;
}
// actually perform the movement
if (moveForwards)
{
transform.position += myCam.transform.forward * speed * Time.deltaTime;
}
else if(moveBackwards)
{
transform.position -= myCam.transform.forward * speed * Time.deltaTime;
}
}