Player movement is working (at least somewhat) now, however one issue remains, and that's the insane numbers the y velocity of the Rigidbody2D on the player. Since the isGrounded check I plan to add will use velocity for the sake of stability, this needs to be fixed.
It confuses me, considering the velocity is 0 normally, but whenever moving left or right it changes to said high numbers.
Movement code:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerM : MonoBehaviour
{
private PControls control;
Rigidbody2D rb;
SpriteRenderer sp;
Transform tr;
public float speed = 0f;
public float speedC;
public float heightSpeed = 5f;
public float heightC;
public bool grounded;
void Start()
{
control = new PControls();
control.Enable();
rb = GetComponent<Rigidbody2D>();
sp = GetComponent<SpriteRenderer>();
tr = GetComponent<Transform>();
}
// Update is called once per frame
void FixedUpdate()
{
RaycastHit2D hit = Physics2D.Raycast(rb.position, -Vector2.up);
Color color = new Color(0, 0, 1.0f);
Debug.DrawRay(transform.position, Vector2.down);
speedC = rb.velocity.magnitude;
var pos = control.Movement.Move.ReadValue<float>();
float GetVerticalSpeed() => rb.velocity.y;
if(pos == -1)
{
sp.flipX = true;
}
else if(pos == 1)
{
sp.flipX = false;
}
if((pos == 0) && (speed > 0.1f))
{
speed -= 3f * Time.deltaTime;
}
else if(speed < 1.4f)
{
speed += Mathf.Abs(pos) * 8 * Time.deltaTime;
}
if(speedC < 7f)
{
rb.AddForce(new Vector3((pos * 5), 0f) * speed * Time.deltaTime, ForceMode2D.Impulse);
}
var jump = control.Movement.Jump.ReadValue<float>();
Debug.Log(GetVerticalSpeed());
Vector3 v = rb.velocity;
v.y = 10;
if(jump == 1)
{
rb.velocity = v;
}
}
}
Seems the main issue was fixed by converting the height velocity to an integer using System.Math.Round():
float vel = rb.velocity.y;
heightC = (int)System.Math.Round(vel);
Not sure it's the best solution, but it's something..
Related
I'm writing some code to create a Minecraft Quake like game but I have an issue with the jump mecanic. When I'm stuck to a wall the player jump to high (see the video).
I use a Rigidbody for the physics and I modify that velocity to move the player. There is a Physic Material on the player's Collider with no consideration for friction or bouncing.
If you have ideas to fix the bug or an alternative to work around the problem, I'm interested.
How it looks like
Here is my code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum PlayerMovementState {
Sneak,
Walk,
Run
}
public class PlayerControls : MonoBehaviour
{
Rigidbody rb;
Vector3 velocity, desiredVelocity;
PlayerMovementState moveState;
float groundDistance;
[SerializeField]
bool forwardAir, backAir, rightAir, leftAir;
[SerializeField]
LayerMask groundLayer;
[SerializeField]
bool onGround;
bool desiredJump;
float jumpHeight = 1.0f;
private void Awake() {
rb = GetComponent<Rigidbody>();
moveState = PlayerMovementState.Walk;
groundDistance = GetComponentInChildren<Collider>().bounds.extents.y;
}
private void Update() {
Vector2 playerInputs = Vector2.ClampMagnitude(
new Vector2(
Input.GetAxis("Horizontal"),
Input.GetAxis("Vertical")
), 1.0f
);
if (Input.GetKey(KeyCode.LeftShift)) moveState = PlayerMovementState.Sneak;
else if (Input.GetKey(KeyCode.LeftControl)) moveState = PlayerMovementState.Run;
else moveState = PlayerMovementState.Walk;
float speed = moveState == PlayerMovementState.Run ? 10f : (
moveState == PlayerMovementState.Sneak ? 2f : 5f
);
RaycastGround();
onGround = !forwardAir && !backAir && !rightAir && !leftAir;
if (Input.GetButtonDown("Jump")) desiredJump = true;
if (moveState == PlayerMovementState.Sneak)
{
if (forwardAir && playerInputs.y > 0) playerInputs.y = 0f;
if (backAir && playerInputs.y < 0) playerInputs.y = 0f;
if (rightAir && playerInputs.x > 0) playerInputs.x = 0f;
if (leftAir && playerInputs.x < 0) playerInputs.x = 0f;
}
desiredVelocity =
(transform.forward * playerInputs.y + transform.right * playerInputs.x) * speed;
}
private void FixedUpdate() {
velocity = rb.velocity;
float acceleration = 10;
velocity.x = Mathf.MoveTowards(velocity.x, desiredVelocity.x, acceleration);
velocity.z = Mathf.MoveTowards(velocity.z, desiredVelocity.z, acceleration);
if (desiredJump && onGround)
{
desiredJump = false;
float jumpSpeed = Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight);
velocity.y += jumpSpeed;
}
rb.velocity = velocity;
desiredJump = false;
}
void RaycastGround()
{
forwardAir = !(Physics.Raycast(
transform.position + Vector3.forward * 0.1f,
-Vector3.up,
groundDistance + 0.1f,
groundLayer
));
backAir = !(Physics.Raycast(
transform.position - Vector3.forward * 0.1f,
-Vector3.up,
groundDistance + 0.1f,
groundLayer
));
rightAir = !(Physics.Raycast(
transform.position + Vector3.right * 0.1f,
-Vector3.up,
groundDistance + 0.1f,
groundLayer
));
leftAir = !(Physics.Raycast(
transform.position - Vector3.right * 0.1f,
-Vector3.up,
groundDistance + 0.1f,
groundLayer
));
}
}
Very likely the problem is that the script thinks it's still grounded while it is jumping upwards along the wall.
Depending on what feeling you want, either fix the raycasts such that they only trigger when you are standing directly on top of an object, or you check if the y part of your velocity is <= 0 for your onGround variable.
I did not find a solution to my problem but I found a workaround anyway.
By detecting the walls around the player, I prevent him from moving in the direction of the wall which prevents him from being stuck on it and having this bug when he jumps.
(It means that my problem is not resolved and that I am still looking for a solution)
Video
...
float wallDistance;
...
[SerializeField]
bool forwardWall, backWall, rightWall, leftWall;
...
SpherecastWall();
...
if (forwardWall && playerInputs.y > 0) playerInputs.y = 0f;
if (backWall && playerInputs.y < 0) playerInputs.y = 0f;
if (rightWall && playerInputs.x > 0) playerInputs.x = 0f;
if (leftWall && playerInputs.x < 0) playerInputs.x = 0f;
void SpherecastWall() {
forwardWall = (Physics.SphereCast(
new Ray(transform.position, Vector3.forward),
wallDistance,
.2f,
groundLayer
));
backWall = (Physics.SphereCast(
new Ray(transform.position, -Vector3.forward),
wallDistance,
.2f,
groundLayer
));
rightWall = (Physics.SphereCast(
new Ray(transform.position, Vector3.right),
wallDistance,
.2f,
groundLayer
));
leftWall = (Physics.SphereCast(
new Ray(transform.position, -Vector3.right),
wallDistance,
.2f,
groundLayer
));
}
I think that's because the spheres center gets over the corner of the wall. So when you apply a force the sphere will be pushed over it.
Maybe you could replace the sphere collider of your player with a capsule or a square collider.
I have a fish which is swimming across the screen. When it gets within 10% of the edge of the screen, I want it to turn begin turning around until it has completely reversed and is now swimming in the opposite direction. This should be more gradual like a fish would swim. I don't want it to rotate on its own axis.
I'm not having any luck getting it to turn around. It only turns partially.
Update
Here's the fish
public class FishSwim : MonoBehaviour
{
public enum Direction { LeftToRight, RightToLeft };
public Direction moveDirection;
[SerializeField]
private float speedMin = 0.5f;
[SerializeField]
private float speedMax = 1f;
[SerializeField]
private bool useOnlySpeedMax = false;
private float speed;
[HideInInspector]
public float removeBeyond;
private void Start()
{
var dist = (transform.position - Camera.main.transform.position).z;
if (moveDirection == Direction.RightToLeft)
removeBeyond = Camera.main.ViewportToWorldPoint(new Vector3(1, 0, dist)).x;
else
removeBeyond = Camera.main.ViewportToWorldPoint(new Vector3(1, 0, dist)).x + FindObjectOfType<SkinnedMeshRenderer>().bounds.size.x;
}
private void OnEnable()
{
speed = Random.Range(speedMin, speedMax);
if (useOnlySpeedMax)
{
speed = speedMax;
}
if (moveDirection == Direction.RightToLeft)
{
speed = -speed;
}
}
// Update is called once per frame
void Update()
{
float realSpeed = speed * Time.deltaTime;
transform.position += Vector3.right * realSpeed;
if (moveDirection == Direction.RightToLeft && transform.position.x < -Mathf.Abs(removeBeyond))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.right), Time.deltaTime * 40f);
moveDirection = Direction.LeftToRight;
}
else if (moveDirection == Direction.LeftToRight && transform.position.x > Mathf.Abs(removeBeyond))
{
}
}
}
I would use Coroutines to do this. Explanation in comments:
bool isTurning = false;
[SerializeField] float turnPeriod = 0.5f; // how long it takes to turn, smaller=faster
private void OnEnable()
{
speed = Random.Range(speedMin, speedMax);
if (useOnlySpeedMax)
{
speed = speedMax;
}
// Get rid of negative speed
}
void Update()
{
float realDistance = speed * Time.deltaTime;
transform.position += transform.right * realDistance;
// Surely there is a better way than calculating
// Mathf.Abs(removeBeyond) every frame... but that is off-topic
if (!isTurning && (
(moveDirection == Direction.RightToLeft
&& transform.position.x < -Mathf.Abs(removeBeyond)
) || (moveDirection == Direction.LeftToRight
&& transform.position.x > Mathf.Abs(removeBeyond)
) ) )
{
// If we aren't already turning and we should be, start turning:
StartCoroutine(Turn());
}
}
IEnumerator Turn()
{
isTurning = true;
Vector3 startForward = transform.forward;
// find end speed and direction
Direction endDirection = moveDirection == Direction.RightToLeft ?
Direction.LeftToRight:Direction.RightToLeft;
// keep track of how much of our turning time has elapsed
float elapsedTimePortion = Time.deltaTime/turnPeriod;
// turn until you've spent enough time turning
while (elapsedTimePortion < 1f)
{
// by whatever portion we've spent time turning, turn starting forward
// 180 degrees around up axis
float angle = 180f * elapsedTimePortion;
Vector3 newForward = Quaternion.AngleAxis(angle, Vector3.up) * startForward;
transform.rotation = Quaternion.LookRotation(newForward);
yield return null;
// next frame - update how long we've been turning
float newElapsedTimePortion = elapsedTimePortion + Time.deltaTime/turnPeriod;
if (newElapsedTimePortion >= 0.5f && elapsedTimePortion < 0.5f)
{
// If we've just passed 50% of the turn,
// make fish move opposite direction
moveDirection = endDirection;
}
elapsedTimePortion = newElapsedTimePortion;
}
// we're done turning, just set the rotation to the end rotation.
isTurning = false;
transform.rotation = Quaternion.LookRotation(-startForward);
// Does removeBeyond need to be updated here? There are a few lines in Start()
// which would suggest that.
}
The grounded state for my character controller flickers on and off constantly at what seems to be every frame. From what I know, it's supposed to check if the player is grounded through player.isGrounded, but something else is moving it back up.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCharacterController: MonoBehaviour {
static Animator anim;
public bool walking;
public GameObject playerModel, Hero;
//Transforms
public Transform playerCam, character, centerPoint;
private Vector3 moveDirection;
//character controller declaration
CharacterController player;
//Mouse Rotation
private float rotX, rotY;
//Mouse Y Position
public float mouseYPosition = 1f;
//Mouse Sensitivity
public float Sensitivity = 10f;
//Mouse Zoom
private float zoom;
public float zoomSpeed = 2;
//Clamping Zoom
public float zoomMin = -2f;
public float zoomMax = -10f;
public float rotationSpeed = 5f;
//Move Front Back left & Right
private float moveFB, moveLR;
//Movement Speed
public float Speed = 2f;
//Velocity of Gravity
public float verticalVelocity;
//Jump Distance
public float jumpDist = 5f;
//Multiple Jumps
int jumpTimes;
//To use with Dialogue Manager
public DialogueManager DiagM;
public AudioClip jumpSound;
public AudioClip HurtSound;
public AudioClip PunchSound;
AudioSource audioSource;
//knockback
public float knockBackForce;
public float knockBackTime;
private float knockBackCounter;
// Use this for initialization
void Start ()
{
//character controller
player = GameObject.Find("Player").GetComponent<CharacterController> ();
StartCoroutine(MyCoroutine(character));
anim = GetComponent<Animator>();
//mouse zoom
zoom = -3;
centerPoint.transform.position = playerCam.transform.position;
centerPoint.transform.parent = null;
audioSource = GetComponent<AudioSource>();
}
IEnumerator MyCoroutine (Transform character)
{
if (player.isGrounded == true)
{
anim.SetBool("isFalling",false);
//anim.SetBool("isIdling", true);
yield return new WaitForSeconds(0);
}
}
// Update is called once per frame
void Update ()
{
//Mouse Zoom Input
zoom += Input.GetAxis ("Mouse ScrollWheel") * zoomSpeed;
if (zoom > zoomMin)
zoom = zoomMin;
if (zoom < zoomMax)
zoom = zoomMax;
//Mouse Camera Input
playerCam.transform.localPosition = new Vector3 (0, 0, zoom);
//Mouse Rotation
rotX += Input.GetAxis ("Mouse X") * Sensitivity;
rotY -= Input.GetAxis ("Mouse Y") * Sensitivity;
//Clamp Camera
rotY = Mathf.Clamp (rotY, -60f, 60f);
playerCam.LookAt (centerPoint);
centerPoint.localRotation = Quaternion.Euler (rotY, rotX, 0);
//Movement Speed
if (knockBackCounter <= 0)
{
moveDirection = (transform.forward * Input.GetAxis("Vertical")) + (transform.right * Input.GetAxis("Horizontal"));
moveDirection = moveDirection * Speed;
moveDirection.y = verticalVelocity;
player.Move(moveDirection * Time.deltaTime);
//Movement Rotation
centerPoint.position = new Vector3 (character.position.x, character.position.y + mouseYPosition, character.position.z);
//knockback disable
//Movement Input
if (Input.GetAxis("Vertical") != 0 || Input.GetAxis("Horizontal") != 0)
{
transform.rotation = Quaternion.Euler(0f, centerPoint.rotation.eulerAngles.y, 0f);
Quaternion turnAngle = Quaternion.LookRotation(new Vector3(moveDirection.x, 0f, moveDirection.z));
playerModel.transform.rotation = Quaternion.Slerp(playerModel.transform.rotation, turnAngle, Time.deltaTime * rotationSpeed);
if (player.isGrounded == true)
{
anim.SetBool("isWalking", true);
anim.Play("Running");
}
}
else
{
StartCoroutine(MyCoroutine(character));
}
if (Input.GetButtonDown("LHand"))
{
audioSource.PlayOneShot(PunchSound, 1F);
anim.Play("RPunch");
}
if (player.isGrounded == true)
{
jumpTimes = 0;
//verticalVelocity = -Physics.gravity.y * Time.deltaTime;
verticalVelocity = 0;
}
else
{
verticalVelocity += Physics.gravity.y * Time.deltaTime;
anim.SetBool("isWalking", false);
anim.SetBool("isFalling", true);
}
if (jumpTimes < 1)
{
if (Input.GetButtonDown("Jump"))
{
verticalVelocity += jumpDist;
anim.Play("Jump");
audioSource.PlayOneShot(jumpSound, 1F);
jumpTimes += 1;
}
}
}
else
{
knockBackCounter -= Time.deltaTime;
}
}
public void Knockback(Vector3 direction)
{
knockBackCounter = knockBackTime;
anim.Play("Jump");
audioSource.PlayOneShot(HurtSound, 50F);
moveDirection = direction * knockBackForce;
moveDirection.y = knockBackForce;
}
}
It looks like it has to do with the verticalVelocity lines, but so far I have only tried setting verticalVelocity = 0 and that works until I have actually moved the character. What could I change to stop the flickering?
Probably it is already solved, but the reason for that is that if you are using Character Controller you should apply gravity ALL the time to the character.
When the character collides with a object, it actually enters a little bit inside this object, then Unity pushes the character back away from the object, until it is no longer touching it. At this point, your gravity starts acting again, and re initiziling the cycle.
You need to apply gravity 100% of the time to create enough force to "balance" this fight with the floor. Could be a smaller "gravity" like 1. No need to be your gravity variable.
Also, on top of that, I like to add a "Coyote time", and make my on IsGrounded() method, as follows:
public bool IsGrounded()
{
return CoyoteTime < CoyoteTimeMax;
}
public void CoyoteControl()
{
if (CharController.isGrounded)
{
CoyoteTime = 0;
}
else
{
CoyoteTime += Time.deltaTime;
}
}
And then I call the CoyoteControl() on Update(), and I can call IsGrounded() whenever I need.
On the inspector I usually set the CoyoteTimeMax to 0.1 and it makes falls more smooth.
As per you comment. You should not determine if your player is grounded by checking an animation parameter. The best way is to use a RayCast(). So what you have to do:
Create a Layer named Ground, and add all the platforms in your
scene to that layer.
Create a bool variable
i.e
bool isGrounded;
Create a function to check if the character is grounded
Something like:
bool checkGrounded(){
return Physics.Raycast(transform.position, Vector3.down, 2f, 1 << LayerMask.NameToLayer("Ground")));
}
In this answer you can read about the involved parameters in the Raycast
Finally inside the update check if the player is grounded or not
Something like:
void Update(){
isGrounded = checkGrounded();
}
I have found that the isGrounded check can change over the course of the Update() function if you are checking it multiple times. Assigning it to a variable at the beginning of the function may solve the flickering issue.
void Update()
{
bool isGrounded = characterController.isGrounded;
...
I'm trying to programming movements of a main FPS character with a Rigidbody.
Camera and ZQSD displacements works well but jumping while moving is being very restricted :
Immobile jumping : Y (min) = 1; Y (max) = 2.7;
Mobile jumping : Y (min) = 1; Y (max) = 1.9;
It's very frustrating, especially for a platform game. Personnaly, I'd like to get same result for both actions.
Here's my C# code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FPSMovement : MonoBehaviour {
public float walkAcceleration = 150f;
public float maxWalkSpeed = 10f;
public float jumpVelocity = 500f;
public float maxSlope = 45f;
private Rigidbody rb;
private Vector2 horizontalMovement;
private bool isGrounded = false;
private bool doubleJumped = false;
// Use this for initialization
void Start () {
rb = GetComponent<Rigidbody> ();
}
// Update is called once per frame
void Update () {
if(rb.velocity.magnitude > maxWalkSpeed){
rb.velocity = Vector3.ClampMagnitude(rb.velocity, maxWalkSpeed);
}
transform.rotation = Quaternion.Euler (0, GetComponentInChildren<FPSCamera>().currentYRotation, 0);
// Entrées clavier ZQSD déplaçant le joueur
float x = 0, y = 0, z = 0;
if (Input.GetKey (KeyCode.Z)) {
x += 1f;
}
if (Input.GetKey (KeyCode.S)) {
x -= 1f;
}
if (Input.GetKey (KeyCode.Q)) {
z -= 1f;
}
if (Input.GetKey (KeyCode.D)) {
z += 1f;
}
// Arrêt prompt du glissement
if ((!Input.GetKey (KeyCode.Z) && !Input.GetKey (KeyCode.S) && !Input.GetKey (KeyCode.Q) && !Input.GetKey (KeyCode.D))
&& isGrounded) {
rb.velocity /= 1.1f;
}
// Saut du joueur
if (Input.GetKey (KeyCode.Space) && isGrounded) {
rb.AddForce (0, jumpVelocity, 0);
// Deuxième saut
}
if (Input.GetKey (KeyCode.Space) && !doubleJumped) {
rb.AddForce (0, jumpVelocity, 0);
doubleJumped = true;
}
rb.AddRelativeForce (z * walkAcceleration, 0, x * walkAcceleration);
}
void OnCollisionStay(Collision other) {
foreach (ContactPoint contact in other.contacts) {
if (Vector3.Angle (contact.normal, Vector3.up) < maxSlope) {
isGrounded = true;
doubleJumped = false;
}
}
}
void OnCollisionExit() {
isGrounded = false;
}
}
I also have impression that player hovers when moving in the airs, but this is not the main question and above all just an impression.
A simple issue with few words to explain, but a real problem to resolve for this type of gaming !
May I ask you, Stackoverflow people to gently help me, a thousand thanks by advance mates.
PS: My native language is the french, so please, don't lose too much time on grammar or other english subtlety :)
It is because you clamp the total velocity of the player where you should only clamp the horizontal speed. If you walk at maximum speed and jump, your jump speed is added to the total velocity and your character should actually be travelling at a higher speed than the maximum walking speed. To fix it, you do this:
// Isolate the horizontal component
Vector3 horizontalVelocity = rb.velocity;
horizontalVelocity.y = 0;
if (horizontalVelocity.magnitude > maxWalkSpeed) {
// Clamp the horizontal component
Vector3 newVelocity = Vector3.ClampMagnitude(horizontalVelocity, maxWalkSpeed);
// Keep the original vertical velocity (jump speed)
newVelocity.y = rb.velocity.y;
rb.velocity = newVelocity;
}
I'm working on a project where I'm trying to make my character move by jumping at an angle. Right now during the frame updates, the character will pivot back and forth and when the right key is pressed, they will jump. This code causes them to jump at an angle, but they always return to their original position.
Additionally, I have two characters who start on opposite sides of the stage, but when I start the game they teleport to the same position. I've spent a lot of time reviewing my code but I can't seem to get this to work. Any help you can provide?
using UnityEngine;
using System.Collections;
public class Freg : MonoBehaviour {
public GameObject Tounge;
public float gravity;
public float tempScale = 1;
public float MaxJump = 8f;
public float MinJump = 0.1f;
static float yVector = 0;
static float xVector = 0;
static bool grounded = true;
bool isleft = false;
Vector3 farthestleft;
Vector3 farthestright;
// Use this for initialization
void Start () {
farthestleft = new Vector3 (-33.7f, 50.2f, 24.8f);
farthestright = new Vector3 (22.56f, 54.83f, -15.12f);
}
void OnTriggerEnter (Collider other) {
if (other.GetComponent<Collider> ().tag == "Ground") {
grounded = true;
yVector = 0;
//xVector = 0;
Vector3 onGround = new Vector3 (transform.position.x, -4.86f, transform.position.z);
transform.position = onGround;
} else
grounded = false;
}
// Update is called once per frame
void Update () {
/*if (Input.GetKey (KeyCode.UpArrow) == true) {
Tounge.transform.localScale.Set (1, 0.5f, 1);
} else {
Tounge.transform.localScale.Set (1, 1, 1);
}*/
if (grounded == false) {
yVector -= gravity;
}
if (Input.GetKeyDown (KeyCode.UpArrow) == true && grounded == true) {
MinJump += 0.5f;
} else if (MinJump > 0.1f){
yVector += MinJump;
xVector += MinJump;
MinJump = 0.1f;
grounded = false;
}
Vector3 stuff = new Vector3 (transform.localPosition.y + xVector, transform.position.y + yVector, transform.position.z);
transform.position = stuff;
float t = Mathf.PingPong (Time.time * 0.5f * 2.0f, 1.0f);
transform.eulerAngles = Vector3.Lerp (farthestright, farthestleft, t);
}
}
it looks like you should update the current position during the if statements, rather than after that way on each update, the actual position is moving based on the decision rather than just the end of the loop.