I'm currently trying to smoothly change the camera's position using a pre-defined array of camera positions.
It should go like this:
I press space, and the camera should smoothly change to the position of camera 0 in the camera array.
I press space again, and the camera should smoothly change to the position of camera 1 in the camera array
etc.
public class CameraWaypoints : MonoBehaviour {
public Camera[] CamPOVs;
private Camera _main;
private int _indexCurrentCamera;
private int _indexTargetCamera;
private float _speed = 0.1f;
void Start () {
_indexTargetCamera = 0;
_main = Camera.main;
//disable all camera's
for (int i = 0; i < CamPOVs.Length; i++)
{
CamPOVs[i].enabled = false;
}
_indexCurrentCamera = 0;
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.Space))
{
if (_indexTargetCamera < CamPOVs.Length)
{
_indexCurrentCamera = _indexTargetCamera;
_indexTargetCamera++;
}
}
//prevent array out of bounds
if (_indexTargetCamera >= CamPOVs.Length)
{
_indexTargetCamera = 0;
}
_main.transform.position = Vector3.Lerp(CamPOVs[_indexCurrentCamera].transform.position, CamPOVs[_indexTargetCamera].transform.position, Time.time * _speed);
_main.transform.rotation = CamPOVs[_indexTargetCamera].transform.rotation;
}
}
With my current solution, the camera actually smoothly moves to the target.
HOWEVER, once that target is reached, on pressing space afterwards, the camera just changes to the target, without smoothing.
It's as if once the lerp has succesfully been completed once, it won't lerp ever again, unless I restart ofcourse.
EDIT: To clarify: when pressing space, the camera target actually changes, and the camera follows this target. The problem is that once the camera position has reached the target position once, it won't smoothly lerp to the target anymore, but rather change its position to the target immediately.
on pressing space afterwards, the camera just changes to the target,
without smoothing.
When you press space both source and target are changed so you're setting the transform's position immediately.
once the lerp has succesfully been completed once, it won't lerp ever
again, unless I restart ofcourse.
From the doc:
1. Time.time
This is the time in seconds since the start of the game
2. Vector3.Lerp
The parameter t (time) is clamped to the range [0, 1]
So, Time.time will continue to rise but Lerp will clamp to a maximum of 1. When you calculate Time.time * 0.1 (your speed) it'll take 10 seconds to reach your target. Anything over 10 seconds will be clamped to 1, which will result in an instant jump to your destination. Use Time.deltaTime instead.
Additionally, you don't need multiple cameras just to act as positional targets. An array of transforms will be just fine.
With all that said, here's how it should look:
public class CameraWaypoints : MonoBehaviour
{
public Transform[] CamPOVs;
public float _speed = 0.1f;
private Camera _main;
private int _indexTargetCamera;
void Awake ()
{
_main = Camera.main;
_indexTargetCamera = 0;
}
void Update ()
{
if (Input.GetKeyDown(KeyCode.Space))
{
_indexTargetCamera = ++_indexTargetCamera % CamPOVs.Length;
}
var time = Time.deltaTime * _speed;
// Alternatives to Lerp are MoveTowards and Slerp (for rotation)
var position = Vector3.Lerp(_main.transform.position, CamPOVs[_indexTargetCamera].position, time);
var rotation = Quaternion.Lerp(_main.transform.rotation, CamPOVs[_indexTargetCamera].rotation, time);
_main.transform.position = position;
_main.transform.rotation = rotation;
}
}
Related
I have a character selection screen, when a certain button is pressed the assigned character will slide into view. Unfortunately for one of my character's they transform position seems to change putting them higher, etc making them not appear on the game screen. I'm not sure why as both character's have the same components and no where in code tells them to change position. The only script they have works as slide animation to make seem as though they glide into view instead of just appear. (attached below)
If I change the character's avatar int he animator to the same as the other character's then their position will be correct but then they will not do any animations.
Winter is the working GameObject.
Eunha is the one that changes position. Before the game starts Eunha's transform position is the same as Winter's shown in the Inspector.
private Vector3 startPosition;
private void Awake()
{
finalPosition = transform.position;
startPosition = finalPosition - transform.right * 5.0f;
}
private void Update()
{
transform.position = Vector3.Lerp(transform.position, finalPosition, 0.1f);
}
private void OnEnable()
{
transform.position = startPosition;
} ```
The codeing order seems to be problem. I recommend using 2 empty objects to determine location. This script is more efficient and reliable.
public Transform startPosition; // place on start position
public Transform endPosition; // place on shifted position
public float duration = 1f;
public void OnEnable() => StartCoroutine(Shift());
public IEnumerator Shift()
{
var shiftProgress = 0f;
while (shiftProgress < 1)
{
shiftProgress += Time.deltaTime/duration;
transform.position = Vector3.Lerp(startPosition.position, endPosition.position, shiftProgress);
yield return new WaitForEndOfFrame();
}
}
This is my current script it already makes the camera follow the players object. But the players object will get bigger as the game goes on and when it gets to big. It becomes very difficult to see. There are no errors I just don't know how to fix it, and I tried to find it online but I can't.
using UnityEngine;
using System.Collections;
public class CameraController : MonoBehaviour {
public GameObject player;
private Vector3 offset;
void Start ()
{
offset = transform.position - player.transform.position;
}
void LateUpdate ()
{
transform.position = player.transform.position + offset;
}
}
Depends ofcourse on how exactly your player "grows" but you could do something like e.g.
void Start ()
{
offset = (transform.position - player.transform.position) / player.transform.lossyScale.x;
}
void LateUpdate ()
{
transform.position = player.transform.position + offset * player.transform.lossyScale.x * someAdditionalFactor;
}
So the offset grows linear together with the size of the player object. Via the someAdditonalFactor you could e.g. say if the player grows to double size the offset should be multiplied by 4 (set the factor 2) or only 1.5 (set the factor 0.5) .. or just use 1 or leave it out.
Alternatively you could directly store and add the offset in the local space of the player using InverseTransformPoint and TransformPoint
void Start ()
{
offset = player.transform.InverseTransformPoint(transform.position);
}
void LateUpdate ()
{
transform.position = player.transform.TransformPoint(offset);
}
This way the offset is calculated automatically when the player changes its scale, rotation or position.
One approach you could use is to scale your offset vector by some value relative to the size of your player character.
void LateUpdate ()
{
transform.position = player.transform.position + (offset * GetAdjustmentValue());
}
private float GetAdjustmentValue() {
// Some code to determine the amount you want to shift the camera
// For example, if your player character is 50% bigger you might
// want to move the offset 10% further out so you would return 1.1
// By default, you would return 1, so that no scaling occurs.
// The details of this calculation really depend on how you are changing
// the size of your character and what specific effect you want that to have
// on your camera position
}
Another approach could be to have fixed positions for your camera depending on the character size. These could be empty GameObjects that are children of your player character. Then, when your player grows, you could set the camera transform.position = bigCharacterPositionObject.transform.position.
I have a spawner object. Every time a gameobject is spawned, I want that object to move randomly (wandering). The problem in my script is that the gameobject movement is very random (jittery). How can I solve this?
void Start ()
{
InvokeRepeating("SpawnNPC", Spawntime, Spawntime);
}
// Update is called once per frame
void Update () {
population = GameObject.FindGameObjectsWithTag("NPCobject");
for (int i = 0; i < population.Length; i++)
{
getNewPosition();
if (population[i].transform.position != pos)
{
population[i].transform.position = Vector3.MoveTowards(population[i].transform.position, pos, .1f);
}
}
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
I made the New randomize vector in different method, because I plan to change it with pathfinder function and make it in different thread/task.
You are choosing a new direction every single frame. That will always be very jittery. You wan't to only change direction after, at least, a small interval. Here is a link to one way to do that from Unity's website. https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html
What about using Navigation? As you said wandering, I thought it would give you a nice result and also make your code simple.
The following screenshot is a sample with Navigation. The moving game objects are also changing their direction nicely, although it cannot be seen in the sample because the game object is capsule...
Ground game object in the sample program has NavMesh. See here to build NavMesh.
Agent game object has NavMeshAgent Component. See here to set it up.
Th Behaviour class below is for Agent game object.
using UnityEngine;
using UnityEngine.AI;
public class NavAgentBehaviour : MonoBehaviour {
public Transform[] Destinations { get; set; }
// Use this for initialization
void Start ()
{
InvokeRepeating("changeDestination", 0f, 3f);
}
void changeDestination()
{
var agent = GetComponent<NavMeshAgent>();
agent.destination = Destinations[Random.Range(0, Destinations.Length)].position;
}
}
The next Behaviour class is just for spawning the Agent and setting up the destinations. On Unity, set it to whatever game object in the scene, and allocate game objects to the fields.
using UnityEngine;
public class GameBehaviour : MonoBehaviour {
public GameObject Agent;
public Transform SpawnPoint;
public Transform Destination1;
public Transform Destination2;
public Transform Destination3;
// Use this for initialization
void Start()
{
Agent.SetActive(false);
InvokeRepeating("Spawn", 0f, 2f);
}
void Spawn() {
var newAgent = Instantiate(Agent);
newAgent.GetComponent<NavAgentBehaviour>().Destinations = new[] { Destination1, Destination2, Destination3 };
newAgent.transform.position = SpawnPoint.position;
newAgent.SetActive(true);
}
}
Increase the number of destination, to make the moves look more random. By the way, the destinations do not need to be specified by game objects, which is only for making it easy to see the sample program's behaviour.
The source of the jitteriness comes from the fact that you are updating the position to move every frame so your objects never have a consistent location to move to. I would instead suggest attaching a new script to each of your objects that individually handles their movement. In that script you could do something like the following, which has a delay to keep the target position for more than 1 frame.
float delaytimer;
Vector3 pos;
void Start () {
getNewPosition(); // get initial targetpos
}
void Update () {
delaytimer += Time.deltaTime;
if (delaytimer > 1) // time to wait
{
getNewPosition(); //get new position every 1 second
delaytimer = 0f; // reset timer
}
transform.position = Vector3.MoveTowards(transform.position, pos, .1f);
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
You are changing the direction they are moving in every frame, thats what is causing the jitter.
You could wait a few moments before you change the direction, perhaps something like this.
// Outside update
float betweenChanges = 2;
float lastChange = 0;
// Inside update
if(Time.realtimeSinceStartup > lastChange)
{
// Change directions
// ...
lastChange = Time.realTimeSinceStart + betweenChanges;
}
You could also solve this by using InvokeRepeating or a Coroutine.
If you dont want all the NPC's to change direction at the same time and still plan on controlling every NPC from the same class like in your example, you should perhaps add a timer for each NPC instance instead and use that to decide when to change its direction.
A better idea would be to let each NPC have its own Movement-script.
The game object I'm scaling is simple. It shows the thrust for the ship being used (this last for 2 seconds as the bar decreases). Then for 3 seconds, the object scales back up to almost exactly where it was, but it needs to be exact or over time the object will not look correct after several uses (the scaling would get exponentially worse, and we know this is no good). I've researched this for the past hour or 2 with no avail in anything I try. This is the closest thing I've been able to mash up.
Overall problem: It needs to be EXACTLY 5 every time it scales back. I realize if I take off the last else if statement it works (poorly), but no matter what number I put in for the X it doesn't always scale to the exact same number, which is a problem, and why I need to return it's original scale.
tl;dr My object's X value scales from 5 to near 0, when trying to return it to its original scale (of 5) it scales up to near 5 then goes to 1,1,1.
using UnityEngine;
using System.Collections;
public class ShipPlayerController : MonoBehaviour {
[Header ("Camera(s)")]
public Camera CameraObject;
[Header ("Float(s)")]
public float fireRate = 0.3f;
public float fireTimer = 0.0f;
public float moveSpeedX = 12.0f;
public float moveSpeedY = 8.0f;
//counts from 0 to 5, so I can use the thrust again (starts at 5 so they can use it initially, this resets to 0 every time they press T and its been over 5 seconds)
public float thrusterTimer = 5.0f;
//the rate at which I can thrust, the lower the number the more often you can use it
public float thrusterRate = 5.0f;
//how long the player is actually thrusting
public float thrusterActive = 2.0f;
private Vector3 movement = Vector3.zero;
//supposed to be the original scale i've made for the game object???
private Vector3 originalScale; //(also have tried adding = Vector3.zero;)
[Header ("Script(s)")]
public Projectile bullet;
public HUD hudScript;
// Update is called once per frame
void Update () {
updatePosition ();
updateFiring ();
updateThrust ();
//calling the method when I push "t" for thrust
updateThrusterBar ();
}
//Skipping a bunch of unneeded code
........................................
//Gets called once I press "T"
public void updateThrusterBar () {
//Supposed to be the original scale???
originalScale = transform.lossyScale;
//When I press "T" the bar shrinks showing the player is using their thrust
if (thrusterActive > thrusterTimer) {
hudScript.thrustBar.transform.localScale += new Vector3 (-0.04187f, 0, 0);
//Fills the thrust back up so they can see when its full
} else if (thrusterTimer < thrusterRate) {
hudScript.thrustBar.transform.localScale += new Vector3 (0.029f, 0, 0);
//(Doesn't work): Logically, if the X scale isn't 5, then go back to your original scale i've made for you, which in the game is (5,0.3,1)
//What happens: defaults to (1,1,1) in the game, which is a square...
} else if (hudScript.thrustBar.transform.localScale.x != 5.0f) {
hudScript.thrustBar.transform.localScale = originalScale;
}
}
}
I'm making a 2D game. I have a Cube Object falling down from the top of the screen, and I have pipes which the cube should pass by reversing gravity.
Well my object is falling down from the top of the screen. I tap on the screen to reverse gravity but it's not going immediately up: it takes time to change the gravity orientation. When i tap the screen my object continues falling and then goes up. My movement is forming the shape of a U when I tap the screen. The same thing happens when it goes up: I tap it to go down and in that case my movement forms the shape of a ∩.
What I want to achieve is that when I tap the screen my object's movement has instant response.
In addition, I want some sort of attenuation, damping, or smoothing.
I've tried these examples without success:
http://docs.unity3d.com/ScriptReference/Vector2.Lerp.html
http://docs.unity3d.com/Documentation/ScriptReference/Vector3.SmoothDamp.html
This is my code:
public class PlayerControls : MonoBehaviour
{
public GameObject playerObject = null;
Rigidbody2D player;
public float moveSpeed;
public float up;
Vector2 targetVelocity;
void Start()
{
player = playerObject.GetComponent<Rigidbody2D>();
// Set the initial target velocity y component to the desired up velocity.
targetVelocity.y = up;
}
void Update()
{
for (int i = 0; i < Input.touchCount; i++)
{
Touch touch = Input.GetTouch(i);
if (touch.phase == TouchPhase.Ended && touch.tapCount == 1)
{
// Flip the target velocity y component.
targetVelocity.y = -targetVelocity.y;
}
}
}
void FixedUpdate()
{
// Ensure if moveSpeed changes, the target velocity does too.
targetVelocity.x = moveSpeed;
// Change the player's velocity to be closer to the target.
player.velocity = Vector2.Lerp(player.velocity, targetVelocity, 0.01f);
}
}
This script is attached to the falling cube.
When affected by a constant force (in your code, gravity), an object will move in a parabola. You're observing a parabola: that U shape. To avoid the parabola and get instant response (a V shape), don't use forces. Replace the gravityScale code with something like:
Vector2 velocity = player.velocity;
velocity.y = -velocity.y;
player.velocity = velocity;
This inverts the velocity's y component, making the player move upwards.
You also mentioned you want to smooth this out. I suggest adding another variable: the target velocity.
To implement this, add Vector2 targetVelocity to your component. Then, during each FixedUpdate you can interpolate from the current velocity towards the target velocity to gradually change speed. Replace this line in your current code:
player.velocity = new Vector2(moveSpeed, player.velocity.y);
With this:
player.velocity = Vector2.Lerp(player.velocity, targetVelocity, 0.01f);
This will slowly change the velocity to be the same as the target velocity, smoothing out the movement. 0.01f is the place between the old velocity and the new velocity that the player's velocity is set to. Choose a larger number to interpolate more quickly, and a smaller number to change direction more slowly.
Then, instead of changing velocity when the screen is tapped, change targetVelocity. Make sure that the x component of targetVelocity is equal to moveSpeed, or the object won't move horizontally at all!
Combining these two changes, the Start and FixedUpdate methods should look similar to this:
void Start()
{
player = playerObject.GetComponent<Rigidbody2D>();
// Set the initial target velocity y component to the desired up velocity.
targetVelocity.y = up;
}
void Update()
{
for (int i = 0; i < Input.touchCount; i++)
{
Touch touch = Input.GetTouch(i);
if (touch.phase == TouchPhase.Ended && touch.tapCount == 1)
{
// Flip the target velocity y component.
targetVelocity.y = -targetVelocity.y;
}
}
}
void FixedUpdate()
{
// Ensure if moveSpeed changes, the target velocity does too.
targetVelocity.x = moveSpeed;
// Change the player's velocity to be closer to the target.
player.velocity = Vector2.Lerp(player.velocity, targetVelocity, 0.01f);
}
Note that you shouldn't use Input in FixedUpdate, because inputs are updated every frame and FixedUpdate may run multiple times per frame. This could cause the input to be read twice, especially if the frame rate gets slower and the fixed update is more likely to need to run multiple times per frame.