I'm fairly new to Unity, and I've begun learning how to use Fishnet networking. I've created a basic player movement script that syncs player position far faster than a Network Transform would. But I'm running into a bizarre problem I don't know how to solve.
In my scene, I have a Network Manager which, upon connection, spawns my Player prefab--a simple sprite with a player script and a network object. I haven't added a network transform, since I'll be syncing each player's position manually to reduce delay between clients. Here's the player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using FishNet.Object;
public class Player : NetworkBehaviour
{
private void Update()
{
if (IsOwner) //only the client that owns this object will run this code
{
//get input, send it to server
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticalInput = Input.GetAxisRaw("Vertical");
RpcMoveCharacter(horizontalInput, verticalInput);
}
//since this is an observers rpc, only the server will call it
RpcSendCharacterPosition(transform.position.x, transform.position.y);
}
[ServerRpc]
public void RpcMoveCharacter(float x, float y)
{
//change the position of the server's instance of the player
transform.Translate(x * 10f * Time.deltaTime * Vector3.right);
transform.Translate(y * 10f * Time.deltaTime * Vector3.up);
}
[ObserversRpc]
public void RpcSendCharacterPosition(float x, float y)
{
if (IsClientOnly)
{
//ensure clients' instance of the player match the server's' position
transform.position = new Vector2(x, y);
}
}
}
The script works perfectly...except for one problem: the movement speed of the player isn't consistent for both players. The issues only occur when I build and run my game, then have the two versions of the game connect.
When either player is a host (server + client) their player object moves at medium speed on both screens. This is the intended speed.
When the version of my game running from my unity editor window is only a client, the player moves at fast speed on both screen--many times faster than intended.
When the version of my game I created using 'build and run' is only a client, the player moves at slow speed on both screens--many times slower than intended.
I've tested everything I can think of. One test I did was to prevent the network manager from spawning the player prefab, place the player object in scene ahead of time, and convert this:
private void Update()
{
if (IsOwner)
{
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticalInput = Input.GetAxisRaw("Vertical");
RpcMoveCharacter(horizontalInput, verticalInput);
}
RpcSendCharacterPosition(transform.position.x, transform.position.y);
}
[ServerRpc]
to this:
private void Update()
{
//now anyone can control the player object
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticalInput = Input.GetAxisRaw("Vertical");
RpcMoveCharacter(horizontalInput, verticalInput);
RpcSendCharacterPosition(transform.position.x, transform.position.y);
}
//same effect as note above
[ServerRpc (RequireOwnership = false)]
in order to see if there was something about the player spawning feature was bugged. My changes had zero effect whatsoever--nothing changed at all. If my editor was a client only it still moved the player too quickly, and if my build was a client only it still moved the player too slowly.
Another thing I tried was to make a brand new project in case I had toggled a setting weirdly or something in the last one. Once I had created a new project, all I did was import fishnet, add fishnet's default NetworkManager object to my scene, create a simple prefab called player, add a network object and the original player script to the player prefab, set the network manager to spawn the player prefab, and tried again. No luck--everything was exactly the same.
Any ideas? I'm super stuck here--I don't know what else to try, since everything in the code/scene seems to be working perfectly. I can't figure out why my build would be behaving differently than my editor's play mode, regardless of which is the server (or host) and which is the client only.
Thanks!
Creator of FishNet here. The only possible way you can update a transforms data quicker than the built-in NetworkTransform is by sending a RPC and snapping the transform every time data arrives.
Otherwise setting the interpolation and interval to 1 on NetworkTransform will send every tick, and smooth over only 1 tick. It's literally the fastest possible way to update transforms without snapping them.
If you want the entire network to move faster then add a TimeManager component to your network manager and increase the tick rate.
So I don't really know how exactly this Fishnet works.
But as said in general in any networking you can never rely on
All your devices running at the same FPS (frames per second)
Your networked messages arriving at the server/other clients immediately
Your networked messages arriving at the server/other clients in the exact same intervals as you ended them
So what I would rather do as mentioned is
First of all do not send network messages every frame but rather some fixed time intervals (like e.g. often used every 0.2 seconds)
Rather handle all local movement locally and immediately
It would be very bad for the UX if you send your user inputs to the server and have to wait until it is applied by receiving back the resulting position. This causes a 2-times network delay which would be extremely uncanny for the local user.
Instead of the delta rather synchronize the resulting position value.
This way you can be sure all players are in sync with the actual resulting positions and it works immediately also for players who joined the session later or potentially missed a few input messages due to network lag.
So I would do something like
public class Player : NetworkBehaviour
{
// Interval in seconds how often to send your position to the server/clients
[SerializeField] private float sendInterval = 0.2f;
// How fast you can move in units per second
[SerializeField] private float moveSpeed = 10f;
// Use this to adjust your input sensitivities
[SerializeField] [Min(0)] private float inputSensitivityX = 1f;
[SerializeField] [Min(0)] private float inputSensitivityY = 1f;
// Might have to play a bit with this value to make smooth interpolation faster or slower
// 5 is an arbitrary value but works quite good from experience
// depends on your sendInterval and movespeed as well
[SerializeField] privte float interpolation = 5f;
// keeps track of passed time
private float sendTimer;
private Vector2 receivedTargetPosition;
private void Start()
{
if(!IsOwner)
{
receivedTargetPosition = transform.position;
}
}
private void Update()
{
//only the client that owns this object will run this code
if (IsOwner)
{
//get input
var horizontalInput = Input.GetAxisRaw("Horizontal");
var verticalInput = Input.GetAxisRaw("Vertical");
var input = new Vector2(horizontalInput * inputSensitivityX, verticalInput * inputSensitivityY);
// Makes sure that you always have maximum 1 magnitude for the input
input = Vector2.ClampMagnitude(input, 1f);
// use the rotation to already rotate this vector from local into world space
input = trasform.rotation * input;
// Here you want the deltaTime of THIS DEVICE
var movement = moveSpeed * Time.deltaTime * input;
// Move your player LOCALLY
transform.position += (Vector3)movement;
}
// If you are not the owner you rather apply the received position
else
{
// I would e.g. smoothly interpolate somewhat like
transform.position = Vector3.Lerp(transform.position, receivedTargetPosition, interpolation * Time.deltaTime);
}
// Check if next send time interval has passed
sendTimer += Time.deltaTime;
if(sendTimer >= sendInterval)
{
sendTimer = 0;
if(IsServer)
{
RpcSendPositionToClients(transform.position.x, transform.position.y);
}
else
{
RpcSendPositionToServer(transform.position.x, transform.position.y);
}
}
}
[ServerRpc]
public void RpcSendPositionToServer(float x, float y)
{
// just in case
// the owner already gets its position in Update so nothing to do
if(IsOwner) return;
//change the position of the server's instance of the player
receivedTargetPosition = new Vector2(x, y);
}
[ClientRpc]
public void RpcSendPositionToClients(float x, float y)
{
// Owner and server already know the positions
if(IsOwner || IsServer) return;
receivedTargetPosition = new Vector2(x, y);
}
}
Related
I am using the new input system in my unity project. I also use Cinemachine. I use Cinemachine Input Provider to change the input from the old to the new system.
When I change max speed to Input Value Gain in speed field of virtual camera's settings (I did it because it is the most comfortable way to control camera) I face a problem.
My problem: When my character moves after some time the camera speed changes. If I start to move in the opposite direction, the camera speed returns to normal.
This is independent of the other components in the scene. My scene has only plane, cube, camera and my character.
Here's my character control code (ignore the awful calculation of movement direction):
private Rigidbody _rb;
private Vector2 _moveDirection;
private float speed = 5f;
private void Awake()
{
_rb = GetComponent<Rigidbody>();
Cursor.lockState = CursorLockMode.Locked;
}
public void OnMove(InputAction.CallbackContext context)
{
_moveDirection = context.ReadValue<Vector2>();
}
private void FixedUpdate()
{
Move(_moveDirection);
}
private void Move(Vector3 moveDirection)
{
float scaledMoveSpeed = speed * Time.deltaTime;
moveDirection = new Vector3(Camera.main.transform.forward.x, 0, Camera.main.transform.forward.z).normalized * moveDirection.y + new Vector3(Camera.main.transform.right.x, 0, Camera.main.transform.right.z).normalized * moveDirection.x;
_rb.MovePosition(transform.position + moveDirection * scaledMoveSpeed);
}
Here's a screenshot of the camera settings and Cinemachine Input Provider:
And screenshots of the Input Actions settings:
I found a solution for those who will ever face this problem! On your main camera, in the CinemachineBrain component, change the Update Method from Smart Update to Late Update. That should help
When I try to use Vector2.Lerp in unity, I run into a problem. The object flies downwards at a very high speed.
I am trying to make a moving platform in a 2D game. It moves from minimum x value to maximum x value. I want to use Vector2.Lerp to make the speed in both directions the same, but when I apply transform.Translate, and pass Vector2.Lerp as argument, the object flies down with very high speed.
That's the problem, because when I pass in Vector 3 with coordinates divided by 100, everything works fine. But different speeds appear in different directions.
The object has a box collider 2D and a script that moves it. It has no rigidbody 2D.
What am I doing wrong?
Here's my function that moves the object in one direction (it's called in FixedUpdate):
Vector2 target = new Vector3(xMin, 0);
Vector2 moving = Vector2.Lerp(transform.position, target, speed * Time.fixedDeltaTime);
transform.Translate(moving);
Lerp(a, b, t) interpolates between a and b.
t must be in the range [0,1], so that Lerp(a, b, 0) = a and Lerp(a, b, 1) = b. Using speed * Time.fixedDeltaTime in the place of t doesn't make sense.
If you want to move your object from a to b with a constant speed of speed (units per second), you can do:
IEnumerator Move(Vector2 a, Vector2 b, float speed){
float movementDuration = Vector2.Distance(a, b) / speed;
for(float t = 0; t < 1; t += Time.deltaTime / movementDuration){
transform.position = Vector2.Lerp(a, b, t);
yield return null;
}
transform.position = b;
}
And you can call this method using StartCoroutine(Move(a, b, speed));
Be careful not to call this every frame. Call only once and see what happens.
As explained here Lerp interpolates linear from the given start value to he target value using a relative factor from 0 to 1.
You can go with a Coroutine but would need to start it and make sure it is running only once at a time.
Or if you simply want to continuously move towards the target you would want to rather use
private void Update()
{
transform.position = Vector2.MoveTowards(transform.position, new Vector2(xMin, 0), speed * Time.deltaTime);
}
Another note for your usecase: If you use Rigibody/Rigidbody2D or any other component form the physics engines you do not want to move anything via Transform as this breaks with the physics, usually looks weird and breaks collision detection etc.
You rather want to go though the according component - in your case e.g.
[SerializeField] private Rigidbody2D _rigidbody;
pivate void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody2D>();
}
private void FixedUpdate()
{
var position = Vector2.MoveTowards(_rigidbody.position, new Vector2(xMin, 0), speed * Time.deltaTime);
_rigidbody.MovePosition(position);
}
I am moving a rigidbody using rb.AddForce(force,ForceMode.Impulse) where force is the target position the rigidbody have to reach.
Now the speed it goes directly depends on the distance it has to cover.
Let's say the time taken to reach the target position is 3sec. I need the rigidbody to cover the same target pos in 5sec.
I dont want to change the timescale as it affects my gameflow
On Changing the velocity of rigidbody it fails to reach the target position
Some basic physics/math:
velocity = change-in-position / travel-time
force = mass * change-in-velocity / acceleration-time
For ease, we're going to call change-in-position as distance, and change-in-velocity/acceleration-time as acceleration
Now, since the acceleration-time component is effectively zero because you're using Impulse, we're going to remove it from the equation (in math terms, we set it at '1')
force = mass * change-in-velocity
Assuming your object starts at zero velocity, we can simplify change-in-velocity to just velocity
force = mass * velocity
force = mass * distance / travel-time
To bring that back into Unity code:
var mass = rb.mass;
var distance = destination.position - transform.position;
var travelTime = 5f; // seconds
var force = mass * distance / travelTime;
rb.AddForce(force, ForceMode.Impulse);
Note that this assumes a frictionless transfer and constant velocity.
If you ignore gravity, this code solves the problem, here I changed the drag according to weight and distance, it may be a little bit away from the destination at the end, the reason should be higher drag friction.
public void ForceToTarget(Transform target, float time = 1f)
{
var rb = GetComponent<Rigidbody>();
var vector = target.position - transform.position;
var distance = vector.magnitude;
rb.drag = distance/time;
rb.AddForce(vector*rb.mass*distance/time, ForceMode.Impulse);
}
If you want precise control over your speed, then stop using ForceMode.Impulse because other physics effects like drag will make your answers wrong. Instead, just set the speed yourself. You can do this with a Coroutine to control timing and ForceMode.VelocityChange to control the speed. Basically, just look at where you are, where the target is, how much time is left, and apply the speed directly.
private bool canMove = true;
public void MoveTo(Vector3 targetPosition, float targetTime)
{
if(canMove)
{
StartCoroutine(MoveToCoroutine(targetPosition,targetTime));
}
}
private IEnumerator MoveToCoroutine(Vector3 targetPosition, float time)
{
canMove = false;
while(time > 0)
{
var positionDelta = transform.position - targetPosition;
var targetSpeed = positionDelta / time;
var speedDelta = targetSpeed - rb.velocity;
rb.AddForce(speedDelta , ForceMode.VelocityChange);
yield return null;
time -= Time.deltaTime;
}
// Bring the object to a stop before fully releasing the coroutine
rb.AddForce(-rb.velocity, ForceMode.VelocityChange);
canMove = true;
}
I wrote this here into the text editor, no IDE and haven't tested it, but I'm pretty sure this'll do what you want.
Assuming you're using the target position as-is then larger vectors will cause larger force to be applied than smaller vectors. Similarly, if using a direction vector as-is then as the rb gets closer to the target the magnitute of the vector gets smaller and thus less force is applied.
To get a constant speed use the direction to the target and Normalise it instead. Regardless of the distance the direction vector will always have a magnitude of 1 so you can multiply it by any value to accurately control the speed of the object:
Rigidbody rb;
public Transform target;
public float dist;
public float speed = 2f; // or whatever
public float targetDistance = 40f; // or whatever
private void Start()
{
rb = GetComponent<Rigidbody>();
StartCoroutine("IMove");
}
IEnumerator IMove()
{
dist = Vector3.Distance(transform.position, target.position);
while (dist > targetDistance)
{
dist = Vector3.Distance(transform.position, target.position);
rb.AddForce(Vector3.Normalize(target.position - transform.position) * speed, ForceMode.Impulse);
yield return new WaitForFixedUpdate();
}
}
Without getting too much into the physics and maths, if you want it to travel slower but the same distance you need to reduce the gravity on it and the initial force.
Note in this example I am assuming the weight is 1 to make the calculation a bit easier for force.
public class TrevelSpeedAdjusted
{
public float speedFactor = 1;
void FixedUpdate()
{
// Reduce the gravity on the object
rb.AddForce(-Physics.gravity * rigidbody.mass * (1 - speedFactor));
}
public float AddAdjustedForce(Vector3 force, ForceMode forceMode)
{
rb.AddForce(force * speedFactor, forceMode);
}
}
So you can try DoTween package to do this pretty easily and its very convenient to use a package instead of using Unity's inbuilt system.
With doTween use this:
DOMove(Vector3 to, float duration, bool snapping) condition to tween your physics Gameobject to a given target position in the duration you require.
Here's documentation you can refer to if you want: http://dotween.demigiant.com/documentation.php
Let me give you an example:
Install the doTween Package. http://dotween.demigiant.com/download
Import it to unity.
Go to your script where you want to achieve the functionality you mentioned on your question and add this header "using DG.Tweening".
Now get access of your RigidBody.
For Example Lets say: I have a cube gameobject with rigidbidy and this script attached.
The Cube Initial Position is at 0,0,0.
And I want it to move to 5,5,5 in 3 seconds or 5 seconds as per your questions request. And lets say I want this to happen when I click SpaceBar on keyboard.
So I would simply do.
Rigidbody rb;
void Start()
{
rb= GetComponent<Rigibody>();
}
void Update()
{
if(Input.GetButtonDown(Keycode.Space))
{
MoveCube(new Vector3(5,5,5),5);
}
}
void MoveCube(Vector3 inTargetPosition , float durationToReachTheTarget)
{
//What this line does is first take in the target position you want your physics object to reach as it first parameter, Then takes in the duration in which you want it to reach there.
rb.DoMove(inTargetPosition,durationToReachTheTarget);
}
This should help you. But remember this is only if you okay with adding an extra package. Personally this package is very good and I would recommend you this.
I'm working on an Augmented Reality app for Android without tracking images/objects. The user stands at a predefined position and virtual objects are placed into the real world. when the user turns around or moves the phone, the objects are fixed at their respective places. I do this by applying the gyroscope data to the camera.
My problem: I want the objects positions to be always fixed to the same places regardless of the users viewing direction when he starts up the app. Right now, on starting the app, the objects are positioned depending on the camera. After that, they are fixed to their places, when the user changes his viewing direction.
I drew an image of what the exact problem is to better elaborate:
I want to know which sensors are relevant to solve this problem. Since Google Maps accurately determines the viewing direction of a user, I assume there are built in sensors to find out in which direction the user is looking in order to apply this information to the camera's rotation at the start.
This is the code I use to apply the phones rotation to the camera (I'm using Unity and C#):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Gyrotransform : MonoBehaviour
{
// STATE
private float _initialYAngle = 0f;
private float _appliedGyroYAngle = 0f;
private float _calibrationYAngle = 0f;
private Transform _rawGyroRotation;
private float _tempSmoothing;
// SETTINGS
[SerializeField] private float _smoothing = 0.1f;
private IEnumerator Start()
{
Input.gyro.enabled = true;
Application.targetFrameRate = 60;
_initialYAngle = transform.eulerAngles.y;
_rawGyroRotation = new GameObject("GyroRaw").transform;
// _rawGyroRotation.parent = Core.Instance.transform;
_rawGyroRotation.position = transform.position;
_rawGyroRotation.rotation = transform.rotation;
// Wait until gyro is active, then calibrate to reset starting rotation.
yield return new WaitForSeconds(1);
StartCoroutine(CalibrateYAngle());
}
private void Update()
{
ApplyGyroRotation();
ApplyCalibration();
transform.rotation = Quaternion.Slerp(transform.rotation, _rawGyroRotation.rotation, _smoothing);
}
private IEnumerator CalibrateYAngle()
{
_tempSmoothing = _smoothing;
_smoothing = 1;
_calibrationYAngle = _appliedGyroYAngle - _initialYAngle; // Offsets the y angle in case it wasn't 0 at edit time.
yield return null;
_smoothing = _tempSmoothing;
}
private void ApplyGyroRotation()
{
_rawGyroRotation.rotation = Input.gyro.attitude;
_rawGyroRotation.Rotate(0f, 0f, 180f, Space.Self); // Swap "handedness" of quaternion from gyro.
_rawGyroRotation.Rotate(90f, 180f, 0f, Space.World); // Rotate to make sense as a camera pointing out the back of your device.
_appliedGyroYAngle = _rawGyroRotation.eulerAngles.y; // Save the angle around y axis for use in calibration.
}
private void ApplyCalibration()
{
_rawGyroRotation.Rotate(0f, -_calibrationYAngle, 0f, Space.World); // Rotates y angle back however much it deviated when calibrationYAngle was saved.
}
public void SetEnabled(bool value)
{
enabled = true;
StartCoroutine(CalibrateYAngle());
}
}
As far as I understand it the Gyroskope returns the rotational difference since it was started.
That's why your objects appear in the direction you are facing during start.
I guess what you rather want might be Compass.magneticHeading at least for setting the correct rotation once at gamestart
// Orient an object to point to magnetic north.
transform.rotation = Quaternion.Euler(0, -Input.compass.magneticHeading, 0);
You could do this once at start on the parent of all the objects you want to show in order to orient them correctly on the GPS north.
In mini game what I'm working on in Unity I want to loop movetowards. I want to move object and back to 1st position but not only 1 time. Now I have this code what give me working "move to postition and back system". How can I change this code to make loop in moving?
public class TestMovement : MonoBehaviour
{
public float speed = 3;
public Vector3 target = Vector3.zero;
private Vector3 origin;
void Start(){
origin = transform.position;
}
void Update(){
TestTransform ();
}
void TestTransform (){
transform.position = Vector3.MoveTowards (transform.position, target, speed * Time.deltaTime);
if (transform.position == target) target = origin;
}
}
TestTransform is getting called from Update, meaning it gets called once for every frame. So, you are essentially already in a loop (an endless loop as long as your game is running). Let's look at your existing code and your desired outcome:
Your existing code starts immediately moving toward the target. Upon reaching the target, it switches the target back to the origin, and starts moving back toward this new target.
Your code is changing the "target" to origin on the first pass. After that, once it has returned to the new "target" (which is now "origin"), it's just sitting there, testing that transform.position == target, and setting target = origin over and over on every frame.
If you want the object to bounce back and forth between origin and target, there are a couple of changes you need to make:
You need a third Vector3 (let's call it "currentTarget").
On start, set currentTarget = target.
In TestTransform, change where you're testing against and changing "target" to operate on "currentTarget" instead. When you change it, you'll need to consider whether currentTarget is set to target or origin, and pick the other as your next target.
End result should be "currentTarget" changes back and forth between "target" and "origin".
Side note: as someone else mentioned in comment, testing for exact equality of vectors won't always work -- you should be checking the distance between vectors and waiting for it to be less than some very small value.
this alow you to have as many positions to move as you want, just create empty GameObjects for each target and add them to list in inspector. Your transform will move in loop, of course if you want only 2 positions add 2 elements to list. (ps. i added everything in your script just to make it easy to understand, i would recomend to make another method CheckNextPosition() with the logic that manages targets)
public class TestMovement : MonoBehaviour
{
public float speed = 3;
public List<Transform> targets= new List<Transform();
public Transform target;
protected int currentTargetIndex=0;
void Start(){
origin = transform.position;
target= targets[currentTargetIndex];
}
void Update(){
TestTransform ();
}
void TestTransform (){
transform.position = Vector3.MoveTowards (transform.position, target.position, speed * Time.deltaTime);
if (transform.position == target.position)//check if you reached the target
{
if(currentTargetIndex >= targets.Count)//check if you reached the last position in your targets list
{
currentTargetIndex=0;//go to first target in your target list
}
else
{
currentTargetIndex++;// go to next target in your target list
}
target=targets[currentTargetIndex];// set the next target
}
}
}
Use Mathf.PingPong to easily do oscillation.
float distanceBetweenPosts = Vector3.Distance(origin, target);
float distanceTowardTarget = Mathf.PingPong(Time.time * speed, distanceBetweenPosts);
float percentageDistance = distanceTowardTarget / distanceBetweenPosts;
Vector3 currentOffset = (target-origin ) * percentageDistance;
Vector3 currentPosition = origin + currentOffset;
transform.position = currentPosition;
If you want to set a specific time startTime for the target starting at origin, then you can use :
float distanceTowardTarget = Mathf.PingPong((Time.time-startTime) * speed,
distanceBetweenPosts);