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);
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 get an object to rotate smoothly on the z axis when it comes into contact with a collider. However it just seems to snap really quickly and I can't find a way to smooth this out.
I've split the rotation out into 2 scenarios - one for when the player enters the collider (which needs to be smooth), and one for when the player is already in the collider (which needs to be snappy). this is so that when the player enters the objects gravity (collider) he'll rotate smoothly before falling to the ground. However, if the player is already within the objects gravitational pull, its snappy so they can run on the ground (a circle) without any wierd floating or rolling occuring.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GravitationalBody : MonoBehaviour
{
[Range(0, 0.3f)] [SerializeField] private float m_OrientationSmoothing = 0.3f; // How much to smooth out the movement
void OnTriggerEnter2D(Collider2D obj) {
// Define direction of gravity
Vector3 gravityDirection = transform.position - obj.transform.position;
// apply gravity to all objects within range
obj.GetComponent<Rigidbody2D>().AddForce(gravityDirection);
orientObjects(obj, gravityDirection, true);
}
void OnTriggerStay2D(Collider2D obj)
{
// Define direction of gravity
Vector3 gravityDirection = transform.position - obj.transform.position;
// apply gravity to all objects within range
obj.GetComponent<Rigidbody2D>().AddForce(gravityDirection);
orientObjects(obj, gravityDirection, false);
}
// Control players Z axis rotation to point toward center of planets
void orientObjects(Collider2D obj, Vector3 gravityDirection, bool lerp)
{
float orientation = 90 + Mathf.Atan2(gravityDirection.y, gravityDirection.x) * Mathf.Rad2Deg;
if (lerp)
{
Quaternion q = Quaternion.AngleAxis(orientation, Vector3.forward);
obj.transform.rotation = Quaternion.Lerp(obj.transform.rotation, q, m_OrientationSmoothing * Time.deltaTime);
}
else obj.transform.rotation = Quaternion.Euler(0f, 0f, orientation);
}
}
I don't know if it will help but if you want a smooth rotation look at this code, I'm sure you can find inspiration here:
[SerializeField] float _degreesPerSecond = 30f;
[SerializeField] Vector3 _axis = Vector3.forward;
void Update ()
{
transform.Rotate( _axis.normalized * _degreesPerSecond * Time.deltaTime );
}
I'm attempting to make the ability to slide in an FPS game I'm working on and I've tried rotating the player object but when tested, it does nothing. Pressing the slide key I have assigned does nothing but rotate the player object in the Y and Z axis (I'm trying to rotate it on the X axis)
Here's the code I'm using (only relevant parts have been kept in):
public class SlideMovementScript : MonoBehaviour
{
public Rigidbody rig;
public CapsuleCollider capcol;
public GameObject player;
public float originalHeight;
public float reducedHeight;
public float slideSpeed = 10f;
// Start is called before the first frame update
void Start()
{
capcol = GetComponent<CapsuleCollider>();
rig = GetComponent<Rigidbody>();
originalHeight = capcol.height;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftControl) && Input.GetKey(KeyCode.W))
Sliding();
else if (Input.GetKeyUp(KeyCode.LeftControl))
GoUp();
}
private void Sliding()
{
capcol.height = reducedHeight;
player.transform.localRotation = Quaternion.Euler(1.599f, 0f, 0f);
rig.AddForce(transform.forward * slideSpeed, ForceMode.VelocityChange);
}
private void GoUp()
{
GetComponent<CapsuleCollider>().height = originalHeight;
player.transform.localRotation = Quaternion.Euler(0f, 0f, 0f);
}
}
I've gotten ZERO error messages from this but I haven't managed to find the problem
Another problem I've also had is when you start sliding, it just instantly rotates the Player object to X 1.599 Y 0 Z 0. I want it to rotate to where the player is facing but I haven't found a way to do it, even using transform.foward or other references won't work.
The Explanation (skip to script if you want) (read notes)
I reccomend using Quaternion's methods. You can use Quaternion.Slerp which can smooth out the rotation towards the player.
Slerp means spherical linear interpolation
Basically, Slerp is a 3 argumented method. the first argument is the current rotation, the second argument is the target rotation, and the third is at which point between those two you want to output.
More simply, Lerp is linear interpolation. Lerps arguments are the same, except it handles floats and not Quaternions. If you Mathf.Lerp(0,1,1); you will get 1, because 1 is all the ay to the second argument. If you do Mathf(0, 3, 0.5); you will get 1.5 because 1.5 is halfway (0.5) between them both.
You can imagine slerp like that, if you give rotation (90, 0, 0) and alerp it to (0, 0, 0) by 0.5 you will get (45, 0, 0).
On this example, I slerp from the current rotation, to the target rotation, by delta time. This way, as time goes on, it slightly increases the current rotation to be the target.
I can create the target as 1.599 rotated on the x.
To make this relative Make a parent game object to the player containing the mesh and rigidbody and maybe the collider (depends if you want the collider upright). I will call this "slidePlayer" slidePlayer must be a child of the player gameObject
Notes
Make sure to have slidePlayer to a gameObject that is a child of player, but must have the player mesh or sliding won't be visible.
Change rot to the target rotation.
Change speed to speed to rotate to the target rotation.
Here is your script I changed:
public class SlideMovementScript : MonoBehaviour
{
public Rigidbody rig;
public CapsuleCollider capcol;
public GameObject player;
public GameObject slidePlayer;
public float originalHeight;
public float reducedHeight;
public float slideSpeed = 10f;
// Start is called before the first frame update
void Start()
{
capcol = GetComponent<CapsuleCollider>();
rig = GetComponent<Rigidbody>();
originalHeight = capcol.height;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftControl) && Input.GetKey(KeyCode.W))
Sliding();
else if (Input.GetKeyUp(KeyCode.LeftControl))
GoUp();
}
private void Sliding()
{
capcol.height = reducedHeight;
Quaternion rot = Quaternion.Euler(1.599f, 0f, 0f);
float speed = 1f;
Quaternion curRot = slidePlayer.transform.localRotation;
slidePlayer.transform.localRotation = Quaternion.Slerp(curRot, rot, Time.deltaTime * speed);
rig.AddForce(transform.forward * slideSpeed, ForceMode.VelocityChange);
}
private void GoUp()
{
GetComponent<CapsuleCollider>().height = originalHeight;
Quaternion rot = Quaternion.Euler(0f, 0f, 0f);
float speed = 3f;
Quaternion curRot = slidePlayer.transform.localRotation;
slidePlayer.transform.localRotation = Quaternion.Slerp(curRot, rot, Time.deltaTime * speed);
}
}
Let me know if there are any problems in coments. Thanks.
I have a platform that must rotate simultaneously in x, y & z directions in a random order, the maximum degree measure of rotation of vectors x & z is 10 degrees, and rotation in the "y" direction is 360 degrees.
Here is the code:
using UnityEngine;
public class PlaneRotating : MonoBehaviour
{
void Start() {
}
void Update()
{
Vector3 euler = transform.eulerAngles;
euler.z = Random.Range(-10f, 10f);
euler.y = Random.Range(0f, 360f);
euler.x = Random.Range(-10f, 10f);
transform.eulerAngles = euler;
}
}
Everything works perfectly, except for a certain rotation speed, since I don't know how to set it.
Can you tell me how to set the speed?
You could lerp between positions, its not the best way to control the speed because large rotations will be faster and smaller rotations will be slower but you can have a stable rotations, time wise.
using UnityEngine;
public class PlaneRotating : MonoBehaviour
{
public Transform transform;
Vector3 lastPos
Vector3 newPos
float t;
void Start()
{
lastPos = transform.eulerAngles;
NewAngle();
}
void Update()
{
transform.eulerAngles = Vector3.Lerp(lastPos, newPos, t);
t+=0.01f;
if(t>1)
NewAngle();
}
void NewAngle()
{
lastPos = newPos;
newPos = new Vector3(
Random.Range(-10f, 10f),
Random.Range(0f, 360f),
Random.Range(-10f, 10f));
t = 0;
}
}
you can then play with Time.deltaTime to make it consistent this is only an example.
If you want to control speed
you can just add two vectors and you do the randomization periodically every x seconds or x frames. you change the direction. there are plenty of good videos on youtube but you need to be more clear if you want to have a good answer. my first answer doewnt let you control the speed with random numbers, only makes smooth transitions and you can choose how fast they change. for constant speeds you have to do something like the bellow
void Update()
{
transform.eulerAngles += new Vector3(0,1,0);
}
let me know if I can be of any further help.
I want my enemy to move back to starting position. He follows me until I get out of his range and then he just stops.
Also i want my skeleton to stop for like 5 sec, and then go back to starting point, any ideas ? I never did anything involving time, exept stopping it.
Here is my script for enemy:
Also here is a screenshoot of inspector on the skeleton: enemy
Here is my script for enemy:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class chase : MonoBehaviour
{
public Transform player;
private Animator anim;
public float LookRadius = 15f;
public Transform enemyStartPos;
// Use this for initialization
void Start()
{
anim = GetComponent<Animator>();
this.enemyStartPos.position = this.transform.position;
}
// Update is called once per frame
void Update()
{
if (!PauseMenu.GameIsPaused)
{
if (Vector3.Distance(player.position, this.transform.position) < 15)
{
Vector3 direction = player.position - this.transform.position;
direction.y = 0;
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(direction), 0.1f);
anim.SetBool("isIdle", false);
if (direction.magnitude > 3)
{
this.transform.Translate(0, 0, 0.05f);
anim.SetBool("isWalking", true);
anim.SetBool("isAttacking", false);
}
else
{
anim.SetBool("isAttacking", true);
anim.SetBool("isWalking", false);
}
}
else
{
if (Vector3.Distance(this.enemyStartPos.position, this.transform.position) >1)
{
Vector3 direction = this.enemyStartPos.position - this.transform.position;
direction.y = 0;
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(direction), 0.1f);
anim.SetBool("isIdle", false);
if (direction.magnitude > 1)
{
this.transform.Translate(0, 0, 0.05f);
anim.SetBool("isWalking", true);
anim.SetBool("isAttacking", false);
}
}
else
anim.SetBool("isIdle", true);
anim.SetBool("isAttacking", false);
anim.SetBool("isWalking", false);
}
}
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, LookRadius);
}
}
Before you try to "code" this, think about it in terms of your program design. You have designed and implemented behavior that says "if the bot's position is within X units of the player's position, turn the bot and move it toward the player."
What you describe wanting to do next can be thought of in the same way (which should lead to similar code).. "else, if the bot's position is NOT within X units of the player's position, turn the bot and move it toward [origin]." Note, this means you need to define what [origin] is for the bot. Point being, it makes no difference in the code whether you are moving toward a player or some arbitrary fixed point. The code to move one transform toward another transform is the same.
For "wandering" it's essentially the same thought process: If bot is within X units of [origin] and not following player, pick a random direction and move that way. (better yet, pick a random direction and move that way for some amount of time so your bot doesn't just jitter around origin).