I'm trying to make special zone located on ground that accelerates the player, entering which the player's speed increases. When he gets out of it, the speed smoothly returns to the original. But now, when the player leaves the zone, the speed instantly becomes the original. Don't know how to realize it. Please help:)
My code:
PlayerMovement:
[SerializeField] private Rigidbody _rigidoby;
public bool inZone = false;
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
}
}
SpecialZone:
private Vector3 _cachedVelocity;
private Rigidbody _collisionRigidbody;
[SerializeField] private PlayerMovement _player;
private void OnTriggerStay(Collider other)
{
_player.inZone = true;
if (_collisionRigidbody == null)
{
_collisionRigidbody = other.GetComponent<Rigidbody>();
_cachedVelocity = _collisionRigidbody.velocity;
}
_collisionRigidbody.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 1000;
}
private void OnTriggerExit(Collider other)
{
_collisionRigidbody.velocity = _cachedVelocity;
_player.inZone = false;
}
A simple solution can be to gradually decrease the speed in the update once you exit the trigger like this:
private void OnTriggerExit(Collider other)
{
//_collisionRigidbody.velocity = _cachedVelocity;
_player.inZone = false;
}
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
} else {
If (_collisionRigidbody.velocity > _cachedVelocity;)
_collisionRigidbody.velocity -= 0.01;
}
}
Another could be to launch a coroutine when you exit the trigger.
Alternatively, you could use Vector3.Lerp and set the percent of the speed change in the update like this:
Vector3 _startingSpeedBeforeDeceleration = Vector3.zero;
private void OnTriggerExit(Collider other)
{
//_collisionRigidbody.velocity = _cachedVelocity;
_startingSpeedBeforeDeceleration = _collisionRigidbody.velocity;
_player.inZone = false;
}
float percent = 0f; //from 0 to 1
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
} else {
If (_collisionRigidbody.velocity > _cachedVelocity;) {
_collisionRigidbody.velocity = Vector3.Lerp(_startingSpeedBeforeDeceleration, _cachedVelocity, percent);
percent += 0,01f;
}
}
}
Not debugged code. Note that I kept the departing speed in the variable _startingSpeedBeforeDeceleration, that is optional, and depends on the movement behaviour you want.
You can play around with Vector3.Lerp for example changing the first argument _startingSpeedBeforeDeceleration as a fixed value, and put there rigidbody speed every time it changes like this Vector3.Lerp(_collisionRigidbody.velocity, _cachedVelocity, percent);, that way you have a smooth start and smooth end, and a top speed change at the middle of the path. Also the percent change can be handled to set the movement behaviour.
Related
I want to press the key once
My cube moves to the right and rotates 90 degrees
The rotation is well done
But it does not move well
using UnityEngine;
using System.Collections;
public class Player : MonoBehaviour
{
public float speed;
public float time;
public GameObject contactPoint;
private Rigidbody rig;
private void Start()
{
rig = GetComponent<Rigidbody>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
StartCoroutine(RotatePlayer(Vector3.forward * 90, Vector3.right, time));
}
if (Input.GetKeyDown(KeyCode.A))
{
StartCoroutine(RotatePlayer(Vector3.back * 90, Vector3.left, time));
}
}
private IEnumerator RotatePlayer(Vector3 byAngle, Vector3 dir, float inTime)
{
Quaternion fromAngle = contactPoint.transform.rotation;
Quaternion toAngle = Quaternion.Euler(transform.eulerAngles - byAngle);
for (float t = 0; t < 1; t += Time.deltaTime / inTime)
{
rig.MovePosition(transform.position + (dir * speed * Time.deltaTime));
rig.MoveRotation(Quaternion.Slerp(fromAngle, toAngle, t));
yield return null;
}
}
}
Your main issue is: You should do Physics related things like movement of a Rigidbody only in FixedUpdate.
For Coroutines Unity provides WaitForFixedUpdate which does exactly what the name says, making sure the code after it is executed in a FixedUpdate physics call.
private IEnumerator RotatePlayer(Vector3 byAngle, Vector3 dir, float inTime)
{
yield return new WaitForFixedUpdate();
// here I would also use "rig" instead of "transform"
Quaternion fromAngle = contactPoint.transform.rotation;
Quaternion toAngle = Quaternion.Euler(rig.eulerAngles - byAngle);
for (float t = 0; t < 1; t += Time.deltaTime / inTime)
{
yield return new WaitForFixedUpdate();
// here I would also use "rig" instead of "transform"
rig.MovePosition(rig.position + (dir * speed * Time.deltaTime));
rig.MoveRotation(Quaternion.Slerp(fromAngle, toAngle, t));
}
}
beyond that it is a bit unclear what exactly you define as not move well. You also should somehow make sure that only one routine is running at a time. Either by terminating already running routines like
private void Update()
{
if (Input.GetKeyDown(KeyCode.D))
{
StopAllCoroutines();
StartCoroutine(RotatePlayer(Vector3.forward * 90, Vector3.right, time));
}
if (Input.GetKeyDown(KeyCode.A))
{
StopAllCoroutines();
StartCoroutine(RotatePlayer(Vector3.back * 90, Vector3.left, time));
}
}
or preventing new routines from starting until the current one is finished using a flag like
private bool alreadyRotating;
private IEnumerator RotatePlayer(Vector3 byAngle, Vector3 dir, float inTime)
{
if(alreadyRotating) yield break;
alreadyRotating = true;
......
alreadyRotating = false;
}
Recently I started working with Unity, this is the first time I'm trying to build a 2d platformer.
For some reason, when I press the jump button, there is a random chance that it will actually make the player jump. Probably around 1 in 50 that it actually jumps.
I just can't figure out why its doing that. Do you know what I'm doing wrong here?
using UnityEngine;
public class Player : MonoBehaviour
{
public float movespeed = 5f;
public float jumpforce = 5f;
public Rigidbody2D player;
public LayerMask layerMaskPlatforms;
private float movementHorizontalInput;
private bool jumpInput;
private float lastTimeOnGroundInSeconds = 0f;
private float lastTimePressedJump = 0f;
void OnBecameInvisible()
{
// todo: restart game
}
void Update()
{
movementHorizontalInput = Input.GetAxisRaw("Horizontal");
jumpInput = Input.GetButtonDown("Jump");
}
void FixedUpdate()
{
if (jumpInput)
{
AttemptJump();
lastTimePressedJump = 0.2f;
}
else if (lastTimePressedJump > 0)
{
AttemptJump();
lastTimePressedJump -= Time.deltaTime;
}
if (IsOnGround())
{
lastTimeOnGroundInSeconds = 0.2f;
}
else if (lastTimeOnGroundInSeconds > 0)
{
lastTimeOnGroundInSeconds -= Time.deltaTime;
}
player.velocity = new Vector2(movementHorizontalInput * movespeed * Time.deltaTime * 50f, player.velocity.y);
}
private void AttemptJump()
{
if (lastTimeOnGroundInSeconds > 0)
{
player.AddForce(new Vector2(0, jumpforce), ForceMode2D.Impulse);
lastTimeOnGroundInSeconds = 0;
}
}
private bool IsOnGround()
{
Vector2 groundedCheckPosition = (Vector2)transform.position + new Vector2(0, -0.01f);
var overlapBox = Physics2D.OverlapBox(groundedCheckPosition, transform.localScale, 0, layerMaskPlatforms);
return overlapBox;
}
}
One major issue is that the jump input is being polled every frame (Update), but the jump code is done every several frames (FixedUpdate), so if you press jump, it is most likely the FixedUpdate method will never see that jump, explaining why it happens so rarely.
You'd need to save the jump state (maybe in a boolean), so by the time Fixed Update happens, it knows that that a jump occurred. Then set that jump to false, and do the jump logic.
I can write somehow this code for optimization?
If not use coroutines, when I click on space the next jump has more force and so on.
If use rb.MovePosition, the character will move as if 15 fps. I know, change Time in settings. But I want to know if exist another method...
private void Update() {
if(Input.GetKeyDown(KeyCode.Space)) {
StopAllCoroutines();
StartCoroutine(Jump());
}
}
private IEnumerator Jump() {
if(rb.bodyType != RigidbodyType2D.Dynamic) {
rb.bodyType = RigidbodyType2D.Dynamic;
}
rb.constraints = RigidbodyConstraints2D.FreezePositionY;
_pos = transform.position;
for (float t = 0; t < 1; t += Time.deltaTime * 4f)
{
transform.position = Vector3.Lerp(transform.position, new Vector3(transform.position.x, _pos.y + .35f, transform.position.z), t);
yield return null;
}
rb.constraints = RigidbodyConstraints2D.None;
}
Rigidbodies exist so you don't need to directly adjust an object's transform. Since you have a Rigidbody2d you can just set the velocity directly:
public float jumpSpeed = 5f; // Whatever feels right
private void FixedUpdate() {
if(Input.GetKeyDown(KeyCode.Space)) {
rb.velocity = Vector2.up * jumpSpeed;
}
}
(Edited to use velocity instead of AddForce)
I am new to unity and don't know a lot of stuff. I've been watching tutorials and I saw one in which the guy created a replica of famous 'Chilly Snow'. The game is complete but the movement of ball isn't like the one in chilly snow. The ball starts orbiting continuously when I press mouse button. I wanted to know how to create that kind of movement, so that the ball turns left and right in a curve but doesn't go in to an orbit. I googled a lot but wasn't able to find my required result. I would really appreciate if anyone could point me in the right direction. Images are attached.Chilly Snow | Movement of my ball
public class movement : MonoBehaviour {
private float points;
public float playerSpeed;
private float rotationSpeed;
public Text score;
private bool isMovingLeft;
public GameObject player;
public bool isDead;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
transform.Translate (Vector3.down * playerSpeed * Time.deltaTime);
if (Input.GetMouseButtonDown (0))
{
Time.timeScale = 1;
isMovingLeft = !isMovingLeft;
rotationSpeed += 0.5f * Time.deltaTime;
}
if (Input.GetMouseButton (0))
{
rotationSpeed = 1f;
}
if (isMovingLeft) {
rotationSpeed += 1.5f * Time.deltaTime;
transform.Rotate(0,0,rotationSpeed);
} else
transform.Rotate(0,0, - rotationSpeed);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
Here is how I would approach it without doing a rotation... using your code.
public class movement : MonoBehaviour {
private float points;
public Text score;
public GameObject player;
public bool isDead;
private float currentXSpeed;
private float targetSpeed;
public float maxXSpeed;
public float speedChange;
void Start () {
Time.timeScale = 0;
isDead = false;
isMovingLeft = true;
points = 0;
targetSpeed = maxXSpeed;
}
void Update ()
{
if (isDead == false)
{
points += Time.deltaTime;
}
if(Input.GetMouseButtonDown(0))
{
Time.timeScale = 1;
targetSpeed = -targetSpeed;
}
currentSpeed = mathf.MoveTowards(currentSpeed, targetSpeed, speedChange * Time.deltaTime);
Vector3 movementDirection = new Vector3(currentSpeed, Vector3.down.y * playerSpeed, 0.0f);
transform.Translate (movementDirection * Time.deltaTime);
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle") {
Die ();
}
}
public void Die()
{
playerSpeed = 0f;
isDead = true;
Invoke ("Restart", 2f);
}
void Restart(){
SceneManager.LoadScene ("Ski_scene_1");
}
void FixedUpdate()
{
score.GetComponent<Text>().text = points.ToString("0");
}
}
You need something like sinusoidal movement or any other graph you fancy.
An example would be for this is like;
gameObject.transform.Translate(Vector3.right * Time.deltaTime*cubeSpeed);
gameObject.transform.position += transform.up * Mathf.Sin (Time.fixedTime * 3.0f ) * 0.1f;
Above pseudo is for 2D graph simulation, can be adapted to your situation.
The object is always moving to right and going up and down while making a sinusoidal movement. Because the up and down speed is not fixed hence you get the sinusoidal or like sinusoidal movement.
In your case, while the object is always going down it will make the sinusoidal movement to left and right.
Your movement is based on the rotation so, if you give this sinusoidal speed as your rotation speed, you can achieve this.
Another aproach can be lerp or slerp
Lerp allows you to make kinda smooth transactions between 2 vectors.
Like moving from pointA to pointB in X seconds.
For rotation you will need Quaternion.Lerp There is a great answer on Unity Answers you can check that if you haven't before.
Hope this helps! Cheers!
I would like to make some lifting platforms in my game, so if the platform went down, the characters can't go over it. I have written a script for it, but for some reason the "lifting up" is not working as intended. It won't go back to its starting place, but it will go a bit below. And for some reason it won't go smoothly to the place where it should, just "teleport" there and done. I thougt multiplying Time.deltaTime with a const will help, but it is the same.
Here is my code, any help would be appreciated:
public class LiftingPlatform : MonoBehaviour {
private Transform lift;
private bool isCanBeLifted;
private float timeToLift;
public float timeNeededToLift = 5f;
private Vector3 startPos;
private Vector3 downPos;
private Vector3 shouldPos;
private bool isDown;
public GameObject[] collidingWalls;
// Use this for initialization
void Start () {
lift = transform;
isCanBeLifted = true;
timeToLift = 0f;
isDown = false;
startPos = transform.position;
downPos = new Vector3(startPos.x, startPos.y - 5f, startPos.z);
}
// Update is called once per frame
void Update () {
timeToLift += Time.deltaTime;
if (timeToLift >= timeNeededToLift) {
if (isCanBeLifted) {
if (isDown) {
shouldPos = Vector3.Lerp(startPos, downPos, Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = true;
}
else if (!isDown) {
shouldPos = Vector3.Lerp(downPos, new Vector3(startPos.x, startPos.y, startPos.z), Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = false;
}
}
timeToLift = 0;
}
if (!isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(true);
}
}
else if (isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(false);
}
}
}
void OnTriggerEnter(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = false;
}
}
void OnTriggerExit(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = true;
}
}
}
These lifting platforms are a child of another Platforms object.
It doesn't look like you are updating the object's position every frame. You are only checking if the total time passed is greater than the time needed to lift, and then updating the position to a value that is dependent on the delta time (using the Vector3.Lerp function).
What I would do is in the update step, if timeToLift is greater then timeNeededToLift, subtract the latter from the former and invert the value of isDown. Then, in your Vector3.Lerp, make the third argument (timeToLift / timeNeededToLift) instead of (Time.deltaTime * 10). Can you try that and see if it works?
The third argument for Vector3.Lerp is the "blending factor" between the two vectors, 0 is the first vector, 1 is the second, and 0.5 is in between. If the total time is greater than the time needed to lift, but the delta time is not greater than 1, it will get the position of the platform using a blending factor of less than 1, resulting in a platform that didn't move fully.