2D game platform camera goes up when player touches edges - c#

I'm doing a 2D platform game where the player only climbs (y-axis).
What I'm trying to do is when I reach near to the top edge, my camera goes up a little bit so I can see more of the level.
I have this, but it's not working:
Public Transform player;
Void Start() { }
Void Update()
{
if (player.transform.position > Screen.height - 50)
{
transform.position.y += speed * Time.deltaTime;
}
}
Other Way is like this But not working more than once, i might need to set move = false; but dont know how without interupting Lerp:
float moveTime = 1f; // In seconds
float moveTimer = 0.0f; // Used for keeping track of time
bool moving = false; // Flag letting us know if we're moving
public float heightChange = 7.0f; // This is the delta
// These will be assigned when a collision occurs
Vector3 target; // our target position
Vector3 startPos; // our starting position
void Start()
{
}
void Update()
{
// If we're currently moving and the movement hasn't finished
if (moving && moveTimer < moveTime)
{
// Accumulate the frame time, making the timer tick up
moveTimer += Time.deltaTime;
// calculate our ratio ("t")
float t = moveTimer / moveTime;
transform.position = Vector3.Lerp(startPos, target, t);
}
else
{
// We either haven't started moving, or have finished moving
}
}
void OnTriggerEnter2D(Collider2D other)
{
if (!moving)
{
// We set the target to be ten units above our current position
target = transform.position + Vector3.up * heightChange;
// And save our start position (because our actual position will be changing)
startPos = transform.position;
// Set the flag so that the movement starts
moving = true;
}
}
}

you are comparing two different things here. Screen.height is in pixels while player position is in world units. so lets assume your screen is 1024x768 you are checking if your player is above 718 which is a huge amount in world units.
What you have to do is convert one to the others unit and compare then for example with http://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.html
Another thing i noticed is that this script is named player so i assume this is controlling your player object in which case
transform.position.y += speed * Time.deltaTime;
would only change your players position. To change the position of the main camera you could use:
Camera.main.transform.position += new Vector3(0f, speed * Time.deltaTime, 0f);
And lastly, always post the errors you get when asking questions, explain what you expect to happen and what actually happens.

You could try lerping the between positions eg.
transform.position = new trasform.lerp(transform.position, player.transform.position, speed*Time.DeltaTime);
just use the if statement to trigger a bool that then does the lerp, so the camera will move to what you need it to and not just when the player hits a certain point. Then when the camera is finished mooving, reset the bool ready for next time

Related

Is there a better way to calculate the direction of a rolling ball in order to make the camera follow it?

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 ();
}
}

How to make a thrown object rotate automatically based on the direction its thrown in?

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.

How to implement lerp for smooth movement

I want achieve moving object on x axis only with lerp to get smoothly movement.
this is picture what i need
I don't know how i can implement lerp to this code to get smooth movement between these values, it now works but it teleport the player and that is not smooth movement what i want to achieve
This is my working code that teleports player:
void Start()
{
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 desiredPos = new Vector3(transform.position.x + 1.5f, transform.position.y, transform.position.z);
transform.position = desiredPos;
}
if (Input.GetMouseButtonDown(1))
{
Vector3 desiredPos = new Vector3(transform.position.x -1.5f, transform.position.y, transform.position.z);
transform.position = desiredPos;
}
}
I want to implement this but i don't understand how to do it .. When i put all code into update the player don't even move.. It only works for me when i copy paste all the code from docs, but how i can move the time from start method to update and always do the same to achieve to get smooth movement for player when going left and right i don't know really please help me guys..
This is the code that works but i don't know how to change it for my example..
https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
There are multiple ways. I would not use Translate as this gives you little control here but rather e.g. MoveTowards which makes sure you have no over shooting at the end. Use this for a linear movement with a given moveSpeed:
// set move speed in Units/seconds in the Inspector
public float moveSpeed = 1f;
private Vector3 desiredPos;
private bool isMoving;
private void Update()
{
if (!isMoving && Input.GetMouseButtonDown(0))
{
desiredPos = transform.position + Vector3.right * 1.5f;
isMoving = true;
}
if (!isMoving && Input.GetMouseButtonDown(1))
{
desiredPos = transform.position - Vector3.right * 1.5f;
isMoving = true;
}
if(isMoving)
{
transform.position = Vector3.MoveTowards(transform.position, desiredPos, moveSpeed * Time.deltaTime);
// this == is true if the difference between both
// vectors is smaller than 0.00001
if(transform.position == desiredPos)
{
isMoving = false;
// So in order to eliminate any remaining difference
// make sure to set it to the correct target position
transform.position = desiredPos;
}
}
}
Or as you asked use Vector3.Lerp like e.g.
// a factor between 0 and 1
[Range(0, 1)] public float lerpFactor;
...
transform.position = Vector3.Lerp(transform.position, desiredPos, lerpFactor);
lerpFactor has to be a value between 0 and 1 where in our case 0 would meen the object never moves and 1 it directly jumps to the target position. In other words the closer you set it to 0 the slower it will reach the target, the closer you set it to 1 the faster it will reach the target.
a lot of people do this to get "smooth" movements but what actually happens is e.g. if you set 0.5 for lerpFactor then every frame the object is placed in the middle between current position and target position.
That looks somehow smooth, moves very fast at the beginning and very very slow at the end ... but: It actually never really reaches the target position but just gets very slow.
For your case that is fine since anyway we compare the current and target position using == with a precision of 0.00001. One just has to have in mind how Lerp works.
But with this you won't have any control neither over the move speed nor the duration.
If you want overall more controll (as I do) I would recommend to use a Coroutine (it is not absolutely neccessary and you could do the same in Update as well but in my eyes Coroutines are better to maintain and keep track of).
Than you could also make a smooth eased-in and eased-out movement with an always fixed duration regardless how far the distance is
// set desired move duration in seconds
public float moveDuration = 1;
private bool isMoving;
privtae void Update()
{
if (!isMoving && Input.GetMouseButtonDown(0))
{
StartCoroutine(transform.position + Vector3.right * 1.5f, moveDuration);
}
if (!isMoving && Input.GetMouseButtonDown(1))
{
StartCoroutine(transform.position - Vector3.right * 1.5f, moveDuration);
}
}
private IEnumerator Move(Vector3 targetPosition, float duration)
{
if(isMoving) yield break;
isMoving = true;
var startPosition = transform.position;
var passedTime = 0f;
do
{
// This would move the object with a linear speed
var lerpfactor = passedTime / duration;
// This is a cool trick to simply ease-in and ease-out
// the move speed
var smoothLerpfactor = Mathf.SmoothStep(0, 1, lerpfactor);
transform.position = Vector3.Lerp(startPosition, targetPosition, smoothLerpfactor);
// increase the passedTime by the time
// that passed since the last frame
passedTime += Time.deltaTime;
// Return to the main thread, render this frame and go on
// from here in the next frame
yield return null;
} while (passedTime < duration);
// just like before set the target position just to avoid any
// under shooting
transform.position = targetPosition;
isMoving = false;
}
and you could still extend this example to also take the dtsnace to move into account like e.g.
var actualDuration = duration * Vector3.Distance(startPosition, targetPosition);
and then later everywhere use actualDuration.
Use transform.Translate instead:
public float moveSpeed = 3f;
void Update ()
{
//Moves Left and right along x Axis
transform.Translate(Vector3.right * Time.deltaTime * Input.GetAxis("Horizontal")* moveSpeed);
}

How can I flip a sprite of an AI enemy character to face the direction its moving in?

I'm trying to make a patrolling AI character that will move from point to point.
The patrol part works perfectly. However, the problem is that the sprite only faces right. When it turned the sprite stays facing the same direction.
I have tried to change the transform rotation using transform.rotate, transform.rotation, transform.Quaternion and making a variable to store the rotation value yet they all kick errors back. The errors are usually made from the rotate/rotation functions not being compatible with any of the attempts I have tried.
Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// To do:
/// - make rotation of enemy sprite work when reaching the end of patrol area
/// - create attack function
/// </summary>
public class Enemy : MonoBehaviour
{
public int health;
public float speed;
public GameObject bloodEffect;
public Transform[] moveSpots; //patrol spots
public float startWaitTime; //start countdown till move to next spot
private Rigidbody2D rb;
private Animator anim;
private int randomSpot; //number of patrol spots
private float waitTime; //how long enemy stays at patrol spot for
// Start is called before the first frame update
void Start()
{
waitTime = startWaitTime; //make waittime equal to startwaittime
anim = GetComponent<Animator>();
randomSpot = Random.Range(0, moveSpots.Length); //choose a random first spot
}
// Update is called once per frame
void Update()
{
Vector3 spriteRotation = new Vector3(0, randomSpot, 0);
transform.position = Vector2.MoveTowards(transform.position, moveSpots[randomSpot].position, speed * Time.deltaTime); //move toward first patrol area
transform.eulerAngles = spriteRotation;
if (Vector2.Distance(transform.position, moveSpots[randomSpot].position) < 0.5f) //asks if patrol point is further that .5f away from enemy
{
if (waitTime <= 0) //if waitTime less than or equal to 0
{
randomSpot = Random.Range(0, moveSpots.Length); //picks new patrol point
waitTime = startWaitTime; //restarts countdown clock
}
else
{
waitTime -= Time.deltaTime; //counts down clock till next point
}
}
if (health <= 0)
{
Destroy(gameObject);
}
}
public void TakeDamage(int damage)
{
Instantiate(bloodEffect, transform.position, Quaternion.identity);
Debug.Log("Blood effect played");
health -= damage;
Debug.Log("Damage Taken");
}
}
The expected results for this code is that a random point will be chosen and the AI will move toward that chosen point. Once there it will stay idle for a specified amount of time before turning and moving to a new spot.
The actual result is mostly the same as expected only the sprite does not turn around but instead continues to face to the right even when the AI is moving left.
Image of area
the enemy is the dark red cube, the movepoints are the points that the enemy patrols between. when it reaches the left point, he should turn to the right and go back but this is not what happens, instead he just moves back and forth with no rotation. ive tried the SpriteRenderer.flipX route and it only works one time and then sticks with that direction.
The SpriteRenderer Component has a Flip attribute you could use for this.
You can access it in code
SpriteRenderer.flipX = true;
It will only flip the sprite and won't change anything else, so double check if your colliders are still in the right space :) See more in the documentation
Good luck
randomSpot is an index not an angle. So using
transform.eulerAngles = new Vector3(0, randomSpot, 0);
doens't make any sense to me ...
Instead of rotating you could also flip the sprite/Image by using a negative scale like e.g.
// Update is called once per frame
private void Update()
{
// however you determin if facing left or right
// you e.g. simply check whether the target position
// is left or right of you
var difference = moveSpots[randomSpot].position - transform.position;
var isFacingRight = difference.x > 0;
if (isFacingRight && transform.localScale.x < 0
|| !isFacingRight && transform.localScale.x > 0)
{
FlipSprite();
}
}
private void FlipSprite()
{
// invert the local X-axis scale
transform.localScale = new Vector3(-spriteTransform.localScale.x, spriteTransform.localScale.y, spriteTransform.localScale.z);
}
Script used for the example
private void Update()
{
// works only in a ScreenOverlay Canvas
var targetPosition = Input.mousePosition;
var difference = targetPosition - transform.position;
var isFacingRight = difference.x > 0 ? true : false;
if (isFacingRight && transform.localScale.x < 0
|| !isFacingRight && transform.localScale.x > 0)
{
FlipSprite();
}
// simply only move left or right on the x-axis towards the mouse
transform.position = Vector3.MoveTowards(transform.position, new Vector3(targetPosition.x, 218, 0), Time.deltaTime * 100);
}
private void FlipSprite()
{
// invert the local X-axis scale
transform.localScale = new Vector3(-transform.localScale.x, transform.localScale.y, transform.localScale.z);
}
You can try:
if (moveSpot[randomSpot].transform.position.x > transform.position.x) {
transform.localScale = new Vector3(-1, 1, 1);
}
else {
transform.localScale = new Vector3(1, 1, 1);
}

Unity - How to animate platform move up and down on collision enter and exit using Lerp?

I am trying to animate a platform prefab that is spawned (the platform object itself is within an empty because its position changes) DOWN when a Runner object collides with it (onCollisonEnter) and UP on collision exit.
I have followed answers given to my old question here verbatim- Unity, C# - cannot make object move down from current y position ONCE on collision enter? but can't get animations using the Animator to work despite having this in my animator as directed:
One person who answered suggested I use Lerp to animate the platform prefab entirely using code. I have researched Lerp but as I need 2 separate states for Down/Idle Down (to make platform stay down) and the same for up/idle up I do not know how to do this.
How can I achieve this effect using Lerp programmatically? Is it possible to achieve the effect I am going for?
ERROR:
EDIT:
how i spawn (dequeue then enqueue) my platforms:
nextPosition += new Vector3 (
Random.Range (minGap.x, maxGap.x) + scale.x,
Random.Range (minGap.y, maxGap.y),
Random.Range (minGap.z, maxGap.z));
Transform o = objectQueue.Dequeue();
o.localScale = scale;
o.localPosition = position;
//o.localEulerAngles = rotation;
o.gameObject.SetActive (true);
int materialIndex = Random.Range(0, materials.Length);
o.GetComponent<Renderer>().material = materials[materialIndex];
o.GetComponent<Collider> ().material = noFrictionMaterial;
platform newScript = new o.GetComponent<platform>(); //getting an error here when I tried to implement your code
objectQueue.Enqueue (o);
if(nextPosition.y < minY){
nextPosition.y = minY + maxGap.y;
}
else if(nextPosition.y > maxY){
nextPosition.y = maxY - maxGap.y;
}
This is what the vectors should ALWAYS be and what I set them at originally in start():
up = new Vector3 (transform.position.x, transform.position.y, transform.position.z);
down = new Vector3 (transform.position.x, transform.position.y-2f, transform.position.z);
Since the platforms aren't actually spawning, are you sure that's the error? Can you help me?
This should work for you:
using UnityEngine;
using System.Collections;
public class Collision : MonoBehaviour {
public Vector3 up, down;
public bool isUp = false;
public float speed = 5f;
float startTime,
length;
bool isMoving = false;
void Start () {
//initialise the platform to a position
if (isUp) transform.position = up;
else transform.position = down;
length = Vector3.Distance(up, down);
}
void Update()
{
if (isUp)
{
//move Down
transform.position = Vector3.Lerp(up, down, ((Time.time - startTime) * speed) / length);
}
else
{
//move Up
transform.position = Vector3.Lerp(down, up, ((Time.time - startTime) * speed) / length);
}
}
//move down
void OnCollisionEnter(Collision col)
{
if (!isMoving)
{
startTime = Time.time;
isMoving = true;
}
}
//move up
void OnCollisionExit(Collision col)
{
if (!isMoving)
{
startTime = Time.time;
isMoving = true;
}
}
}
The thing you have to watch out for is making sure that the "runner" stays on the platform until it's on the bottom so that it can move up again. You could add a check so that it moves back up regardless with another bool it it doesn't work for you. Hope it helps :)
EDIT
What lerp (short for linear interpolation) does is pick points between two vectors based on the progression of the time component (0 to 1). So in order for the platform to go up and down it needs a starting point and an end point, in this case it's up or down (up to the upper position down for the other).
If you want to create a new platform without causing an error you need to set these values after creating it so that everything works correctly (if you leap from any position it will jump aggressively in the first step of the lerp). For example:
//after instantiating the new platform object
scriptName newObject = newGameobject.GetComponent<scriptName>();
newObject.up = (up Vector);
newObject.down = (down Vector);

Categories

Resources