I am trying to make an endless runner game where the player stands stationary on a platform, and objects are instantiated in different patterns further down the platform with an initial speed. I want those objects to increase their speed after a certain time interval is met. For example, if the time interval was 30 seconds and the baseline speed was 10, I would want the speed to go to 20 after 30 seconds have passed, then increase to 30 after another 30 seconds have past, etc. I have a spawner spawning in obstacle patterns with spawn points in various areas.
As of right now, the obstacles spawn in with the same speed no matter what, but increase their speed once they've been spawned in for the duration of the time interval. For example, object 1 is spawned in, moves for 5 seconds at the initial speed, then increases speed, followed by object 2 which does the exact same thing as object 1 upon spawning. Here is my object movement code:
using UnityEngine;
public class ObstacleMovement : MonoBehaviour
{
public Rigidbody rb;
// Keeps track of the time and increases speed based off the time
float time;
int seconds;
public int timeInterval; // Time interval where speed is increased
// Speed stuff
public Vector3 speedInc; // Speed increase each time interval
public float initialSpeed; // Initial speed in the Z direction
Vector3 currentSpeed; // Current speed of the object
// Vars for locking the object in place so no rotation/sliding occurs
float startPositionX;
Quaternion startRotation;
// Start is called before the first frame update
void Start()
{
startPositionX = transform.position.x;
startRotation = transform.rotation;
currentSpeed = new Vector3(0, 0, initialSpeed);
rb.velocity = currentSpeed;
}
// Update is called once per frame
void Update()
{
TrackTime();
if (seconds == timeInterval) {
currentSpeed = SpeedUp(currentSpeed);
time = 0;
}
rb.velocity = currentSpeed;
Debug.Log("Speed is: " + currentSpeed);
LockPos();
}
Vector3 SpeedUp(Vector3 iSpeed) {
Vector3 newSpeed = iSpeed + speedInc;
Debug.Log("Speeding up");
return newSpeed;
}
void TrackTime() {
time += Time.deltaTime;
seconds = (int)time % 60;
Debug.Log(seconds + " seconds have past");
}
void LockPos() {
Vector3 pos = transform.position;
pos.x = startPositionX;
transform.position = pos;
transform.rotation = startRotation;
}
}
In the code above I am creating a timer, keeping track of the seconds, and then increasing the speed after XXX seconds have passed. What am I doing wrong? I can post the code of the spawner and spawn points if that's necessary. Is there a more appropriate place for this to be executed? Thanks!!
If I understood you correctly, the problem is that your new spawned obstacles do not have the "Game current speed", right?
This is happening cause you're controlling the speed of every obstacles separated from the game state.
So what you do right now is: spawn obstacles always with the same speed, and then every spawned obstacle check if it's his turn to increase the speed.
But what you wan't is to have one object in charge of that spawning and time control, then, when the spawner spawns the obstacle, It assigns the velocity.
Something like:
public class ObstacleSpawner : MonoBehaviour
{
// Keeps track of the time and increases speed based off the time
float time = 0;
int seconds = 0;
public int timeInterval; // Time interval where speed is increased
// Speed stuff
public Vector3 speedInc; // Speed increase each time interval
public float initialSpeed; // Initial speed in the Z direction
Vector3 currentSpeed; // Current speed of the object
//Prefab of the object
GameObject obstaclePrefab = null;
//Tracking all the objects
List<GameObject> obstacleList = new List<GameObject>();
void InstantiateNewObstacle()
{
GameObject newObstacle = Instantiate(obstaclePrefab);
newObstacle.GetComponent<RigidBody>().velocity = currentSpeed;
//If you want to Lock the position (that should not be necessary, do it here too)
obstacleList.Add(newObstacle);
}
// Update is called once per frame
void Update()
{
TrackTime();
if (seconds == timeInterval) {
currentSpeed = SpeedUp(currentSpeed);
time = 0;
}
Debug.Log("Speed is: " + currentSpeed);
LockPos();
}
Vector3 SpeedUp(Vector3 iSpeed) {
Vector3 newSpeed = iSpeed + speedInc;
Debug.Log("Speeding up");
return newSpeed;
}
void TrackTime() {
time += Time.deltaTime;
seconds = (int)time % 60;
Debug.Log(seconds + " seconds have past");
}
}
Now every time you want to spawn one new Obstacle you only need to call ObstacleSpawner.InstantiateNewObject();. Remember, the obstacleList is not necessary but I'm pretty sure that would be useful for your type of game.
Related
Rotating an object around the same pivot gives different results
So I'm trying to make a door that opens when clicked on, and I have a door that works good as is, but for some reason when I try to make it work for another door using RotateAround, it moves along the Z axis when closing. The code is identical to the other door that works and works when opening this door, so I have no clue as to why it's having trouble closing for this one. The code I'm using to rotate them is as follows
IEnumerator CloseDoor()
{
float timer = 1f;
float speed = 30f;
Vector3 pivot = new Vector3(door.transform.position.x, door.transform.position.y,
door.transform.position.z + 1);
while (timer > 0)
{
door.transform.RotateAround(pivot, Vector3.down, speed * Time.deltaTime);
yield return new WaitForSeconds(0.001f);
timer -= Time.deltaTime;
}
}
IEnumerator OpenDoor()
{
float timer = 1f;
float speed = 30f;
Vector3 pivot = new Vector3(door.transform.position.x, door.transform.position.y,
door.transform.position.z + 1);
while (timer > 0)
{
door.transform.RotateAround(pivot, Vector3.up, speed * Time.deltaTime);
yield return new WaitForSeconds(0.001f);
timer -= Time.deltaTime;
}
}
I'll start with some overall advices, regarding your code.
Don't use Time.deltaTime with new WaitForSeconds(...). Time.deltaTime is an amout of time, that passed between Update() calls. But your logic is not inside Update(). You use own time inervals with new WaitForSecons(...), but if the specified amout of time is less then Time.deltaTime, then it will be executed every Time.deltaTime after all Update() calls. It works good for you only because your time interval 0.001f is low enough to be executed every Update(). When your argument in new WaitForSeconts(...) becomes more them Time.deltaTime, the rotation speed of the door becomes too low to door be opened completely. To made your code more clear and safe, return null with yield instruction. In this case, you can be shure, that coroutine will be executed every Update(). More info here.
Your code works with transform positioning, and probably you have physics in your game. All changes with physical object supposed to be done in the FixedUpdate(). In your case your cant return new WaitForFixedUpdate() in yield instruction and use Time.fixedDeltaTime with it.
So speaking about main question. In your code, you are doing pretty unclear and unsafe thing, like hardcoding pivot with global position offset here:
Vector3 pivot = new Vector3(door.transform.position.x, door.transform.position.y,
door.transform.position.z + 1);
Probably, not all doors will have same rotation, and for some of them offset, just with z coordinate will be wrong. Also it becomes wrong after door opening, because the position and rotation of the door changed when you rotating it around some point, that is not the center of the door. So you should base on local transform point, like this:
public class Door : MonoBehaviour
{
private bool _doorOpened = false;
private bool _doorOpening = false;
[SerializeField] // to see in the inspector.
private Vector3 _localDoorRotatePoint = new Vector3(0.5f, 0f, 0f);
// Update is called once per frame
void Update()
{
if (!_doorOpening && Input.GetKeyDown(KeyCode.E))
{
if (!_doorOpened)
{
StartCoroutine(OpenDoor());
}
else
{
StartCoroutine(CloseDoor());
}
}
}
IEnumerator CloseDoor()
{
_doorOpening = true;
var timer = 1f;
var speed = 30f;
// in my case _localDoorRotate point is (0.5f, 0f, 0). In your case it will be like (0, 0, 1f) or something like this.
// remember, that this point is in local transform coordinates, and in scales with this transform scale vector.
var pivot = transform.TransformPoint(_localDoorRotatePoint);
while (timer > 0)
{
transform.RotateAround(pivot, Vector3.down, speed * Time.fixedDeltaTime);
yield return new WaitForFixedUpdate();
timer -= Time.fixedDeltaTime;
}
_doorOpening = false;
_doorOpened = false;
}
IEnumerator OpenDoor()
{
_doorOpening = true;
var timer = 1f;
var speed = 30f;
// in my case _localDoorRotate point is (0.5f, 0f, 0f). In your case it will be like (0f, 0f, 1f) or something like this.
// remember, that this point is in local transform coordinates, and in scales with this transform scale vector.
var pivot = transform.TransformPoint(_localDoorRotatePoint);
while (timer > 0)
{
transform.RotateAround(pivot, Vector3.up, speed * Time.fixedDeltaTime);
yield return new WaitForFixedUpdate();
timer -= Time.fixedDeltaTime;
}
_doorOpening = false;
_doorOpened = true;
}
}
Helpfull links, that can help you with understanding this code:
Transform.TransformPoint
Transform.up
I'm trying to implement my own gravity for a character.
But for some unknown to me reason the character's landing speed is higher landing than when jumping, by quite a lot (~15 initial jump speed, ~24 final land speed). I'm ultimately trying to replicate behavior shown on the gif below.
https://imgur.com/a/q77w5kS
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Movement : MonoBehaviour {
public float speed = 3;
public float jumpSpeed = 15;
public float gravity = -1f;
private float ySpeed = 0;
private float jumpTime = 0;
private bool direction = false; //true for going up, false for falling down - gravity
private bool previousDirection = false; //to keep track of changing direction
private CharacterController _characterController;
// Start is called before the first frame update
void Start() {
_characterController = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update() {
float dx = Input.GetAxis("Horizontal");
float dz = Input.GetAxis("Vertical");
float dy = Input.GetAxis("Jump");
// move around
Vector3 movement = new Vector3(dx, 0, dz);
// limit diagonal movement
movement = Vector3.ClampMagnitude(movement, 1);
// speed
movement *= speed;
// change of direction
if(ySpeed>0 && direction!=true)
direction = true;
else if(ySpeed<0 && direction!=false)
direction = false;
// if changed direction - peak of the jump
if(direction!=previousDirection){
Debug.Log("Jump Time = " + jumpTime);
jumpTime = Time.deltaTime;
previousDirection = direction;
}
// jump/on ground
if(_characterController.isGrounded) {
ySpeed = -0.5f;
jumpTime = 0;
if(dy > 0f){
ySpeed = jumpSpeed;
}
}
// gravity - when not on ground
else{
// dv/dt
ySpeed += gravity*jumpTime;
// add jump time
jumpTime += Time.deltaTime;
// Debug.Log(jumpTime);
Debug.Log(ySpeed);
}
movement.y = ySpeed;
// direction adjustment
movement = transform.TransformDirection(movement);
movement *= Time.deltaTime;
_characterController.Move(movement);
}
}
I think I figured out what was wrong with my idea.
ySpeed += gravity*jumpTime
So every frame I'm adding more and more acceleration downwards. This should just be: ySpeed += gravity *Time.deltaTime
(Acceleration due to gravity is then constant, not getting greater as time passes)
It is being integrated over many steps, so each Update() cycle is a slice of time that adds some velocity based on acceleration due to gravity, multiplied by the amount of time taken by that little slice.
In another words...
Gravity is a constant acceleration. I've made it a linear function of the time spent in the air. So, I start the jump with no gravity and end the jump with very high gravity.
Keeping my Photon project under given 500 msg/s is really tricky. Even with 10 players in room each updating position 10 times per second 10(players) * 10(sent msg) * 10(received msg) = 1000 msg/s is generated. That is just the player movement. Next I need to move bullets around which will increase the amount of messages once more.
At the moment I have bullets instantiated across network but only local player is able to move it since I don't yet sync movement of bullets. I was wondering could I have all of the clients start moving the bullets on their local device once it's instantiated instead of passing position through network? This would save a lot of messages since I would never have to send bullet positions over network.
Hacking and cheating is not a problem in my game.
EDIT: this is the script I'm using at the moment to move bullets. This only works locally on the device that bullet was instantiated on. How would I run this script locally on each client?
public class Network_Bullet : Photon.MonoBehaviour {
private Rigidbody2D rb;
[HideInInspector]
public float speed = 0;
[HideInInspector]
public Vector2 direction = Vector2.zero;
public void SetValues(float _speed, Vector2 _direction)
{
rb = GetComponent<Rigidbody2D> ();
this.speed = _speed + 150f; // bullet has 150 more speed than player
this.direction = _direction;
}
private void Update()
{
if (speed != 0)
{
rb.velocity = direction * speed * Time.fixedDeltaTime;
}
}
}
And here is how to bullet is instantiated:
private void OnClick_Shoot()
{
if (photonView.isMine == true)
{
if (timeSinceLastBullet >= spawnTime)
{
GameObject newBullet = PhotonNetwork.Instantiate (Path.Combine ("prefabs", "Network Bullet"), transform.position, transform.rotation, 0);
newBullet.GetComponent<Network_Bullet> ().SetValues (owner.speed, new Vector2(owner.last_horizontal, owner.last_vertical));
timeSinceLastBullet = 0f;
}
else
{
Debug.Log ("loading...");
}
}
}
First, 10 players with 10 movement messages per second generates 100 messages per second per room.
To the actual question - for bullets, all you need to do is instantiate and give it initial rotation and speed. Because bullets usually don't change their rotation or speed (at least in video games), you only need to do that and only update the bullets in everyone's local machines.
You can try this:
private void OnClick_Shoot()
{
if (timeSinceLastBullet >= spawnTime)
{
int level = 2; //it is not need for you, but you can setup some params on object
// for example i set level for increase damage per level.
string someMsg = "hi i'm bullet";
object[] parametres = new object[] {
level,
someMsg
}
PhotonNetwork.Instantiate (Path.Combine ("prefabs", "Network Bullet"), transform.position, transform.rotation, 0, parametres );
timeSinceLastBullet = 0f;
}
else
{
Debug.Log ("loading...");
}
}
And bullet, just add PhotonView component on your prefab and it will be work
public class Bullet : MonoBehaviour {
private Rigidbody2D rb;
[HideInInspector]
public float speed = 0;
[HideInInspector]
public Vector3 direction = Vector3.zero;
void Start()
{
rb = GetComponent<Rigidbody2D> ();
this.speed = _speed + 150f; // bullet has 150 more speed than player
this.direction = _direction;
PhotonView pView = gameObject.GetPhotonView();
// or
//PhotonView pView = gameObject.GetComponent<PhotonView>();
damage = 10 * pView[0]; // increase damage per level
Debug.Log(pView[1]);
}
void Update()
{
if (speed != 0)
{
rb.velocity = direction * speed * Time.fixedDeltaTime;
}
}
}
So, that methods will be work localy on all clients. I'm using that on my bullets.
I am trying to make an object scale from zero to it's normal size when I instantiate it, so it will look like it popped to the screen.
So when the object start I get it's normal size then update it to zero, and then in update I am scaling it.
This is my code:
void Start()
{
normalScale = transform.localScale;
transform.localScale *= 0.1f;
}
void Update()
{
transform.localScale = Vector3.Lerp(transform.localScale * 0.1f, transform.localScale, 5f * Time.deltaTime);
// destroy item
if (transform.localScale == normalScale)
{
transform.localScale = transform.localScale * 0.1f;
}
}
Thank you.
With this you're always changing from it's current scale, which of course you changed last update
transform.localScale = Vector3.Lerp(transform.localScale * 0.1f, transform.localScale, 5f * Time.deltaTime);
What you need to do is create two Vector3 outside of the update function, one for the start size, one for the final size
Vector3 start = Vector3.zero;
Vector3 end = new Vector3(1,1,1);
You'll also need a timer:
float lerpTime = 0;
Altogether you get
transform.localScale = Vector3.Lerp(start, end, lerpTime);
lerpTime += Time.deltaTime // times whatever multiplier you want for the speed
There are a couple of issues with your code that are likely to be causing problems. The first is the start/end values you're passing in to the lerp:
Vector3.Lerp(transform.localScale * 0.1f, transform.localScale, 5f * Time.deltaTime);
On your second frame, .localScale is roughly (0.1, 0.1, 0.1). The max value of the lerp on your second frame is the value from your first frame. That means that your current code is endlessly shrinking the target - the opposite of what you wanted.
The other problem is the way you're handling the time. You're passing 5f * Time.deltaTime, which is (probably) always going to be less than 1. This means you will never reach the maximum value.
So, to fix these, you need two things: first, you need to make sure your min/max values are actually min/max values, not arbitrary values in between. Second, you need to make sure your third parameter progresses smoothly from 0 to 1 over a defined time.
Something like this:
public float ScaleTime = 5f; // the time it'll take to grow, settable in the inspector
public float ScaleTime = 5f; // the time it'll take to grow, settable in the inspector
Vector3 _targetScale;
Vector3 _startScale;
float _currentLerp = 0f;
void Start()
{
_targetScale = this.localScale;
_startScale = _targetScale * 0.1f;
}
void Update()
{
_currentLerp += Time.deltaTime * ScaleTime;
if (_currentLerp < 1)
{
transform.localScale = Vector3.Lerp(_startScale, _targetScale, _currentLerp);
}
else
{
transform.localScale = _targetScale; // make sure we definitely hit the target size
... do whatever else you need to do here...
}
}
As per https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html your 3rd parameter in
transform.localScale = Vector3.Lerp(transform.localScale * 0.1f, transform.localScale, 5f * Time.deltaTime);
is supposed to indicate the "fracJourney", in other words it should change from 0f to 1f to indicate the progress of your animation, but Time.deltaTime will give you the time since the last frame so that you probably see it jump around 0.005 (or whatever your frame rate is).
You need to add another variable to indicate the progress of your animation:
public float speed = 1.0F;
private float startTime;
void Start() {
startTime = Time.time;
normalScale = transform.localScale;
}
void Update()
{
float distCovered = (Time.time - startTime) * speed;
transform.localScale = Vector3.Lerp(Vector3.zero, normalScale, distCovered);
// destroy item
if (transform.localScale >= normalScale)
{
startTime = Time.time; // Reset the animation
}
}
I have a 2D multiplayer game using Photon where all the movements are made by RigidBody 2d. When both players are connected I can see my opponent movements but they are not smooth. Last Update Time variable is set to 0.25.
using UnityEngine;
using System.Collections;
public class NetworkCharacter : Photon.MonoBehaviour {
Vector3 realPosition = Vector3.zero;
Quaternion realRotation = Quaternion.identity;
public float lastUpdateTime = 0.1f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void FixedUpdate () {
if(photonView.isMine){
//Do nothing -- we are moving
}
else {
transform.position = Vector3.Lerp(transform.position, realPosition, lastUpdateTime);
transform.rotation = Quaternion.Lerp(transform.rotation, realRotation, lastUpdateTime);
}
}
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){
if(stream.isWriting){
//This is OUR player.We need to send our actual position to the network
stream.SendNext(transform.position);
stream.SendNext(transform.rotation);
}else{
//This is someone else's player.We need to receive their positin (as of a few millisecond ago, and update our version of that player.
realPosition = (Vector3)stream.ReceiveNext();
realRotation = (Quaternion)stream.ReceiveNext();
}
}
}
From the unity script reference
[Lerp] Interpolates between from and to by the fraction t. This is most commonly used to find a point some fraction of the way along a line between two endpoints (eg, to move an object gradually between those points). This fraction is clamped to the range [0...1]. When t = 0 returns from. When t = 1 returns to. When t = 0.5 returns the point midway between from and to.
So basically Vector3.Lerp returns this:
from + ((to - from) * t)
As you can see, by calling Lerp with the same values, it's always returning the same value, what you should use instead are MoveTowards and RotateTowards.
transform.position = Vector3.MoveTowards(transform.position, realPosition, lastUpdateTime * Time.deltaTime);
transform.rotation = Quaternion.RotateTowards(transform.rotation, realRotation, lastUpdateTime * Time.deltaTime);