I'm some hours already trying to solve this, I tried many ways but none of them worked...
So I have an enemy that follows the player using an AI and I need the sprite to flip when the enemy turn left or turn right.
Here's a part of my code (A lot of the code is about the AI so I'll post just part of it)
Vector3 dir = ( path.vectorPath[currentWaypoint] - transform.position ).normalized;
dir *= speed * Time.fixedDeltaTime;
//Move the AI
rb.AddForce (dir, fMode);
void Update () {
if (rb.AddForce > 0) {
sr.flipX = false;
} else if (rb.AddForce < 0)
sr.flipX = true;
animate.SetFloat ("pMove", Mathf.Abs(rb.AddForce));
}
The easiest way to flip a sprite is using localScale.x *= -1; And instead of checking the AddForce you should check the velocity of the rigidbody in the x axis (or y axis if you flip as well the sprite depending on if it is jumping or falling)
Basically in Update() you can do something like this: vx = rigidbody.velocity.x; to store the velocity in the x axis of the sprite. Then in LastUpdate() you check if it is necessary to flip the sprite or not:
if (vx > 0) {
facingRight = true;
} else if (vx < 0) {
facingRight = false;
}
if (((facingRight) && (localScale.x<0)) || ((!facingRight) && (localScale.x>0))) {
localScale.x *= -1;
}
Here you have a whole example where the sprite moves based on the player's inputs. You will need to addapt it for your AI.
//store references to components on the gameObject
Transform transform;
Rigidbody2D rigidbody;
public float MoveSpeed = 3f;
// hold player motion in this timestep
float vx;
float vy;
Awake () {
// get a reference to the components we are going to be changing and store a reference for efficiency purposes
transform = GetComponent<Transform> ();
rigidbody = GetComponent<Rigidbody2D> ();
}
void Update()
{
// determine horizontal velocity change based on the horizontal input
vx = Input.GetAxisRaw ("Horizontal");
//Change in case you are jumping or falling
vy = rigidbody.velocity.y;
// Change the actual velocity on the rigidbody
rigidbody.velocity = new Vector2(_vx * MoveSpeed, _vy);
}
// Checking to see if the sprite should be flipped
// this is done in LateUpdate since the Animator may override the localScale
// this code will flip the player even if the animator is controlling scale
void LateUpdate()
{
// get the current scale
Vector3 localScale = transform.localScale;
if (vx > 0) // moving right so face right
{
facingRight = true;
} else if (vx < 0) { // moving left so face left
facingRight = false;
}
// check to see if scale x is right for the player
// if not, multiple by -1 which is an easy way to flip a sprite
if (((facingRight) && (localScale.x<0)) || ((!facingRight) && (localScale.x>0))) {
localScale.x *= -1;
}
// update the scale
transform.localScale = localScale;
}
Assuming, sr is a SpriteRenderer component, sr.flipX approach is fine. However, your assessment of the force on the Rigidbody is not right. rb.AddForce has the return type of void, and a proper way of reading whether the Rigidbody had an applied force on it is to read rb.velocity.magnitude. Also, rb.velocity would give you the direction of the GameObject's velocity as a Vector3. Assuming that you are working on the X-axis, putting this in your LateUpdate method:
sr.flipX = rb.velocity.magnitude > 0 && rb.velocity.x < 0 ? true : false;
instead of the if-else block at your Update method would flip the sprite along the X-axis if the Rigidbody is moving (rb.velocity.magnitude > 0) and it is moving towards the left-hand side (rb.velocity.x < 0).
In your question, you have asked only to flip the sprite: As Unity documentation states, flipX only affects rendering, not the other components (such as colliders and animators).
Related
Currently I'm using a normalized rb velocity to get the x and z velocity directions in order to compare it with eulerAngles of the camera in order to tell the camera which way to spin. But I'm doing this inside a bunch of nested if statements and it just feels.... wrong.
For actually following the ball I just have parent child relationships with Player -> RotatorObject -> Camera, so rotating the RotatorObject rotates around the center of the player.
Here is an example of what I'm currently doing.
// Velocity direction of the ball
Vector3 VelNormed = Vector3.Normalize(rb.velocity);
xVelNormed = VelNormed.x;
zVelNormed = VelNormed.z;
// Current CameraRotationObject angle
yEuler = transform.eulerAngles.y;
zEuler = transform.eulerAngles.z;
// If Moving forwards.
if (zVelNormed >= 0.5)
{
// If camera is within hoped for range. (270 is camera = perfectly forwards)
if (yEuler >= 260 && yEuler <= 280)
{
// Move camera to the right.
if (yEuler >= 260 && yEuler <= 270)
{
// Rotate camera right
transform.Rotate(0, (speed * Time.deltaTime) * 1, 0);
}
else if (yEuler <= 280 && yEuler >= 270)
{
// Rotate camera left
transform.Rotate(0, (speed * Time.deltaTime) * -1, 0);
}
else
{
}
}
}
// If Moving backwards.
else if (zVelNormed <= -0.5)
{
//etc.
}
Like I said before, using all of these if statements like this makes me feel like a monster! IE. I'm almost sure I'm doing this the old computer programming for dummies way
First of all the eulerAngles are very rarely what you want to use
When you read the .eulerAngles property, Unity converts the
Quaternion's internal representation of the rotation to Euler angles.
Because, there is more than one way to represent any given rotation
using Euler angles, the values you read back out may be quite
different from the values you assigned. This can cause confusion if
you are trying to gradually increment the values to produce animation.
Why do you actually need to make the camera spin at all? It sounds to me like your main issue actually origins in the camera being somewhere nested as child below the rolling ball.
You should rather extract the camera into scene root level and make it follow the ball via code like e.g.
public class CameraFollow : MonoBehaviour
{
// Interpolation factors for the position and rotation
[Range(0, 1)] public float moveInterpolationFactor = 5f;
[Range(0, 1)] public float rotateInterpolationFactor = 5f;
// The target this object shall follow
[SerializeField] Rigidbody targetObject;
// Positional offset in the movement direction of the target object
// e.g. (0,0, -1) will keep the camera 1 world space unit behind the move direction
public Vector3 positionOffset = new Vector3(0, 0.25f, -1);
private Vector3 targetPosition;
private Quaternion targetRotation;
private void Awake ()
{
if(!targetObject) return;
UpdateTargetValues();
}
private void UpdateTargetValues ()
{
targetPosition = targetObject.position + targetObject.rotation * positionOffset;
targetRotation = Quaternion.LookRotation(targetObject.velocity);
}
private void LateUpdate ()
{
// If there is no target stay where you are
if(!targetObject) return;
// Get the move velocity since we want to follow based
// on the move direction, not the objects orientation
var velocity = targetObject.velocity;
// If not moving simply continue moving to the last set position
// otherwise update the target values
if(velocity.sqrMagnitude > 0)
{
UpdateTargetValues ();
}
transform.position = Vector3.Slerp(transform.position, targetPosition, moveInterpolationFactor * Time.deltaTime);
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, rotateInterpolationFactor * Time.deltaTime);
}
public void SetTarget(Rigidbody target)
{
targetObject = target;
if(!targetObject) return;
UpdateTargetValues ();
}
}
whenever the player uses its jetpack or when there's a lot of velocity, the player stutters. I tried using interpolate and other things like that but the only outcome was even more stutter. If anyone knows what the cause for the stuttering is, please tell me and if you want to, maybe even explain your answer :)
here is what i mean by the player stuttering :
https://www.youtube.com/watch?v=k6q3vvQtwjM
here is my player code :
using UnityEngine;
using UnityEngine.SceneManagement;
public class Player : MonoBehaviour
{
//basic variables
[SerializeField]
private float speed = 5f;
[SerializeField]
private float Jumpforce = 5f;
[SerializeField]
private float JetPackForce = 5f;
[SerializeField]
public bool canUseJetpack = true;
[SerializeField]
private Rigidbody2D rb;
[SerializeField]
private Animator animator;
private bool isFacingRight = true;
//runs when game starts
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
//runs every frame
void Update()
{
//applies force thus making player to move left or right
var move = Input.GetAxis("Horizontal");
transform.position = transform.position + new Vector3(move * speed * Time.deltaTime, 0, 0);
//changes player animation state
animator.SetFloat("Speed", Mathf.Abs(move));
//flip the player if facing right or left
if (move < 0 && isFacingRight)
{
flip();
}
else if (move > 0 && !isFacingRight)
{
flip();
}
//checks if the space key is pressed and that the player velocity on the y axis is smaller than 0.001
if(Input.GetKeyDown("space") && Mathf.Abs(rb.velocity.y) < 0.001f)
{
//adds force from below the player thus making the player jump
rb.AddForce(new Vector2(0, Jumpforce), ForceMode2D.Impulse);
}
//checks for the key 'Q' to be pressed and that there is enough fuel in the jetpack
if (Input.GetKey(KeyCode.Q) && canUseJetpack == true)
{
//ads force from below the player thus making the player fly using its jetpack
rb.AddForce(Vector2.up * JetPackForce);
//decreases the fuel in the jetpack
JetpackBar.instance.UseFuel(0.1f);
//makes the player run the using jetpack animation
animator.SetBool("UsingJetpack", true);
}
else
{
//if the player isn't using the jetpack, switch to the idle animation or the run animation depending if the player is moving or not
animator.SetBool("UsingJetpack", false);
}
//checks if the player health is less than 0
if (HealthBar.instance.currentHealth <= 0)
{
//if so, restart the game
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
//checks if someting collided with the player
private void OnCollisionEnter2D(Collision2D collision)
{
//if the thing collided with the player has a tag of 'Enemy'
if (collision.gameObject.CompareTag("Enemy"))
{
//then, make the player take damage
HealthBar.instance.takeDamage(25);
}
}
//flip the player
void flip()
{
isFacingRight = !isFacingRight;
transform.Rotate(0f, 180f, 0f);
}
}
Thanks :D
First of all whenever dealing with Rigidbodies you do not want to use the Transform component to move an object. You rather want to only move it via the Rigidbody/Rigidbody2D component!
Then there is a general issue. You can either move your object hard based on the input or use the physics and forces. Both at the same time is not possible because both transform.position or the actually "correct" Rigidbody2D.MovePosition will overrule the forces so these two systems are clashing all the time.
Therefore I would rather simply overwrite the velocity directly like e.g.
void Update()
{
// Store the current velocity
var velocity = rb.velocity;
var move = Input.GetAxis("Horizontal");
// Only overwrite the X component
// We don't need Time.deltaTime since a velocity already
// moves the object over time
velocity.x = move * speed;
animator.SetFloat("Speed", Mathf.Abs(move));
if (move < 0 && isFacingRight)
{
flip();
}
else if (move > 0 && !isFacingRight)
{
flip();
}
if(Input.GetKeyDown("space") && Mathf.Abs(rb.velocity.y) < 0.001f)
{
// simply overwrite the Y velocity
// You'll have to adjust that value of course
// and might want to rename it ;)
velocity.y = Jumpforce;
}
if (Input.GetKey(KeyCode.Q) && canUseJetpack)
{
// Here we want to use Time.deltaTime since the jetpack
// shall increase the Y velocity over time
// again you'll have to adjust/rename that value
velocity.y += JetPackForce * Time.deltaTime;
// Also adjust this value!
// To be frame-rate independent you want to use Time.deltaTime here
JetpackBar.instance.UseFuel(0.1f * Time.deltaTime);
animator.SetBool("UsingJetpack", true);
}
else
{
animator.SetBool("UsingJetpack", false);
}
// After having calculated all changes in the velocity now set it again
rb.velocity = velocity;
if (HealthBar.instance.currentHealth <= 0)
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
so what I am trying to do is pull a spear back(like a slingshot) and send it flying, all the while making it rotate while it's flying in the air to mimic a real thrown spear. So far, I have only been able to make it rotate after a few seconds to a certain position but I can already tell that this isn't really a permanent fix and will definitely lead to problems down the line. I have tried looking for better ways of rotating the object but I can't really find anybody else who had a problem like this and it's hard to understand the unity documentation regarding rotation as it's for 3D objects.
Here is a gif showing how it looks like:
https://gfycat.com/chiefglasshorsemouse
This is the code that I have attached to my spear object that's responsible for letting me pull it back and launch it with the mouse:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Scripting.APIUpdating;
public class spear : MonoBehaviour
{
Rigidbody2D rb;
Vector3 velocity;
private Vector3 _initialPosition;
[SerializeField] private float _launchPower = 500;
bool rotating = false;
public GameObject objectToRotate;
private float _timeSittingAround;
private bool _spearWasLaunched;
[SerializeField] private float _spearRotation = 360;
private void Awake()
{
_initialPosition = transform.position;
rb = GetComponent<Rigidbody2D>();
}
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
//rotates the spear
IEnumerator rotateObject(GameObject gameObjectToMove, Quaternion newRot, float duration)
{
if(rotating)
{
yield break;
}
rotating = true;
Quaternion currentRot = gameObjectToMove.transform.rotation;
float counter = 0;
while(counter < duration)
{
counter += Time.deltaTime;
gameObjectToMove.transform.rotation = Quaternion.Lerp(currentRot, newRot, counter / duration);
yield return null;
}
rotating = false;
}
//reloads the scene if spear goes out of bounds or lays dormant for 2 seconds
void Update()
{
GetComponent<LineRenderer>().SetPosition(0, transform.position);
GetComponent<LineRenderer>().SetPosition(1, _initialPosition);
if (_spearWasLaunched &&
GetComponent<Rigidbody2D>().velocity.magnitude <= 0.1)
{
_timeSittingAround += Time.deltaTime;
}
if(transform.position.y > 30 ||
transform.position.y < -12.5 ||
transform.position.x > 40 ||
transform.position.x < -20 ||
_timeSittingAround > 2)
{
string currentSceneName = SceneManager.GetActiveScene().name;
SceneManager.LoadScene(currentSceneName);
}
}
//slingshot mechanic
private void OnMouseDrag()
{
Vector3 newPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = new Vector3(newPosition.x, newPosition.y);
}
private void OnMouseDown()
{
GetComponent<SpriteRenderer>().color = Color.red;
GetComponent<LineRenderer>().enabled = true;
}
//launches the spear when mouse is released as well as begins the rotating mechanic
private void OnMouseUp()
{
Vector2 directionToInitialPosition = _initialPosition - transform.position;
GetComponent<Rigidbody2D>().AddForce(directionToInitialPosition * _launchPower);
GetComponent<Rigidbody2D>().gravityScale = 1;
_spearWasLaunched = true;
GetComponent<LineRenderer>().enabled = false;
Quaternion rotation2 = Quaternion.Euler(new Vector3(0, 0, _spearRotation));
StartCoroutine(rotateObject(objectToRotate, rotation2, 3f));
}
}
There are a few ways to do it. You could try setting up rigidbodies and maybe joints and weighting different parts so that the tip of the spear naturally rotates the spear. More info on that here: https://answers.unity.com/questions/14899/realistic-rotation-of-flying-arrow.html
There's also another solution in that link for doing it with one line of code, making it rotate in the direction of movement.
In the top of your class, add this:
public Rigidbody2D mySpear;
Then in the inspector, drag the spear object into that slot to assign it. Then in your code, remove all your rotation code. In Update() add this:
mySpear.transform.right =
Vector2.Slerp(mySpear.transform.right, mySpear.rigidbody.velocity.normalized, Time.deltaTime)
A third way to do it is...
Figure out the rotation (on the z axis) of the object when it's pointing directly down. Put that down as a constant called downRotation or something.
Set a minimum velocity and maximum velocity that will be used by the rotation.
I recommend also adding an AnimationCurve variable.
Instead of having a special function for rotation, just put it in FixedUpdate. If the spear's velocity.x is not 0, then rotate.
The speed of the rotation should be based on the speed of the object. If the object's x velocity is very slow then it would probably fall (rotate) more quickly. If it's going very fast, the rotation would not change much.
Don't use this exact code, but it'd look something like this:
// This part figures out how fast rotation should be based on how fast the object is moving
var curve = speedCurve.Evaluate(1 - ((rigidBody.velocity.x - minVelocity) / (maxVelocity - minVelocity)));
// Rotates the spear
var rotate = rigidBody.rotation.eulerAngles;
var speed = curve * rotateSpeed * Time.fixedDeltaTime;
rotate.z = Mathf.MoveTowards(rotate.z, downRotation, speed);
transform.rotation = Quaternion.Euler(rotate);
Side note: You should use rigidbody rotation like I am here, and you should be assigning components to variables in the inspector instead of calling GetComponent every frame.
this is the code for movement when touch on an IOS device in a 2D game i am creating in unity. but when i run it the game loads but when i touch to the right he moves to the left and when i touch to the left he also moves to the left.
i have used ScreenToWorldPoint as told to use but it dont seem to work he will only move one direction
void PlayerWalkMobile()
{
// force by which we will push the player
float force = 0.0f;
// the players velocity
float velocity = Mathf.Abs(GetComponent<Rigidbody2D>().velocity.x);
if (Input.touchCount > 0)
{
countTouches++;
Touch h = Input.touches[0];
Vector2 position = Camera.main.ScreenToWorldPoint(new Vector2(h.position.x, h.position.y));
Debug.Log("the postion of the touch is " + h.position);
if (position.x > 0)
{
// if the velocity of the player is less than the maxVelocity
if (velocity < maxVelocity)
force = speed;
// turn the player to face right
Vector3 scale = transform.localScale;
scale.x = 1;
transform.localScale = scale;
// animate the walk
animator.SetInteger("walk", 1);
}
else if (position.x < 0)
{
// if the velocity of the player is less than the maxVelocity
if (velocity < maxVelocity)
force = -speed;
// turn the player to face right
Vector3 scale = transform.localScale;
scale.x = -1;
transform.localScale = scale;
// animate the walk
animator.SetInteger("walk", 1);
}
// add force to rigid body to move the player
GetComponent<Rigidbody2D>().AddForce(new Vector2(force, 0));
// set the idle animation
if (h.phase == TouchPhase.Ended)
animator.SetInteger("walk", 0);
} // if Input.touchCount > 0
}
in the debug the touches are all over the place the 0.0 is the far left of the screen everywhere else is a minus
the position of the touch is (-1093.7, 1048.3)
i thought
Vector2 position = Camera.main.ScreenToWorldPoint(new Vector2(h.position.x, h.position.y));
was supposed to make 0.0 the centre
I've got a quick question regarding 2D Sprite animations that I haven't been able to find specifically answered anywhere:
I have a sprite with walk animations to the right. However, I obviously want to flip the animation to the left when he walks left (2D side-scroller).
I can easily flip the sprite itself, using transform.localscale.x, however, that only flips the sprite. Not the animation clip. (This no longer happens in Unity)
So, while the sprite flips, the minute the animation clip begins playing, it flips back right (as the only animation clip I have is for the right facing sprite).
Is the only way to do this to flip the sprites in Photoshop, or is there a way to do it in Unity?
Thanks!
UPDATE: With the actual versions of unity if you scale the transform by multiplying it by -1, the animation frames are also scaled.
I finally figured it out by doing this:
void Flip()
{
// Switch the way the player is labelled as facing
facingRight = !facingRight;
// Multiply the player's x local scale by -1
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
This is from Unity's 2D Platformer example.
To implement some sort of checking which makes use of the Flip method, you can do something similar to the below example which is basic movement code. facingRight is set as a value on the class so that the other methods can use it, and it is defaulted to false.
void Update()
{
//On X axis: -1f is left, 1f is right
//Player Movement. Check for horizontal movement
if (Input.GetAxisRaw ("Horizontal") > 0.5f || Input.GetAxisRaw("Horizontal") < -0.5f)
{
transform.Translate (new Vector3 (Input.GetAxisRaw ("Horizontal") * moveSpeed * Time.deltaTime, 0f, 0f));
if (Input.GetAxisRaw ("Horizontal") > 0.5f && !facingRight)
{
//If we're moving right but not facing right, flip the sprite and set facingRight to true.
Flip ();
facingRight = true;
} else if (Input.GetAxisRaw("Horizontal") < 0.5f && facingRight)
{
//If we're moving left but not facing left, flip the sprite and set facingRight to false.
Flip ();
facingRight = false;
}
//If we're not moving horizontally, check for vertical movement. The "else if" stops diagonal movement. Change to "if" to allow diagonal movement.
} else if (Input.GetAxisRaw ("Vertical") > 0.5f || Input.GetAxisRaw("Vertical") < -0.5f)
{
transform.Translate (new Vector3 (0f, Input.GetAxisRaw ("Vertical") * moveSpeed * Time.deltaTime, 0f));
}
//Variables for the animator to use as params
anim.SetFloat ("MoveX", Input.GetAxisRaw ("Horizontal"));
anim.SetFloat ("MoveY", Input.GetAxisRaw ("Vertical"));
}
void FlipHorizontal()
{
animator.transform.Rotate(0, 180, 0);
}
You could also do that on transform itself (without animator). But in that case rotation value can be overriden by animator
This is how I did it - almost the same as the other technique by Jestus with unity script.
var facing : String = "right";
function updateFacing(curr : String){
if(curr != facing){
facing = curr;
var theScale : Vector3 = gameObject.transform.localScale;
theScale.x *= -1;
gameObject.transform.localScale = theScale;
}
}
//put to use
function controls(){
if(Input.GetKey (KeyCode.LeftArrow)){
updateFacing("left");
} else if(Input.GetKey (KeyCode.RightArrow)){
updateFacing("right");
}
}
If you're animating in Unity:
Copy all frames (sprites) of the animation that you want to flip over.
Paste those frames into your new animation and select everything on the first frame.
Change the x scale of the first frame from 1 to -1.
Do the same thing with the very last frame of your animation.
Now it should play facing the other direction!
This is my C# implementation. It uses a string as the direction facing to make it a little easier to debug.
public string facing = "right";
public string previousFacing;
private void Awake()
{
previousFacing = facing;
}
void Update()
{
// store movement from horizontal axis of controller
Vector2 move = Vector2.zero;
move.x = Input.GetAxis("Horizontal");
// call function
DetermineFacing(move);
}
// determine direction of character
void DetermineFacing(Vector2 move)
{
if (move.x < -0.01f)
{
facing = "left";
}
else if (move.x > 0.01f)
{
facing = "right";
}
// if there is a change in direction
if (previousFacing != facing)
{
// update direction
previousFacing = facing;
// change transform
gameObject.transform.Rotate(0, 180, 0);
}
}