Unity NetworkTransform, force update every Frame? - unity3d

On a purposed LAN setup, I want Unity's NetworkTransform to simply send every single frame. (Mode is "Sync Transform", not rigidbody, etc.)
I'm wondering how to do this .. if you just set the send rate to 0.00001 will it in fact do it every frame that the object is moving?
(The Inspector interface only allows up to 29Hz, simply set it in code if you want smaller values.)
I was also thinking something like on a script......
void Update() { .. SetDirtyBit( ? ); }
would that do it?
(And, what do you use as a bitmask there?)
It really takes forever to determine this sort of thing experimentally. Obviously Unity's doco is a joke. And I couldn't google anyone who'd already figured it. Any experience in this?
Secondly...
Further information. We did some experiments and indeed if you set it to a very small value, it does seem to sync the NetworkTransform every single frame.
(Of course, it doesn't bother sending if it has not moved, as #Programmer points out below.)
H O W E V E R this does not produce smooth results for kinematic objects or camera positions. I don't know why; it has to do with the Update loop and exactly when the change takes place. But what you get is a "very fine jitter".
Thirdly...
I did an experiment, simply using Commands and Rpcs, to send some information every frame (actually from one client to another).
I specifically used a purpose solo ethernet network and the apps do little or nothing other than send the info every frame (ie "manually" just using Command/Rpc), ie on Update().
In fact, it still drops many frames, it's often not sent. (Let's say, it misses 10 or 20 in 10 seconds worth.)
Good grief!
Do not bother doing this if you need some info sent "every frame", say on a LAN system.

if you just set the send rate to 0.00001 will it in fact do it every
frame that the object is moving?
Yes, but when the Object's transform is not moving or rotating, it's not updated over the network regardless of the value of NetworkTransform.sendInterval.
I was also thinking something like on a script......
void Update() { .. SetDirtyBit( ? ); }
Yes. You need to call SetDirtyBit every frame if you need the network update to be sent for the object.
And, what do you use as a bitmask there?
The SetDirtyBit function is technically the function version of the SyncVar attribute. The argument is not really documented that much but if SyncVar marked variable is changed, its dirty mask is changed to 1. This also means that you should pass 1 to the SetDirtyBit function if you want network update to be sent for the object.
You should also use it from a function marked with the [Command] attribute instead of directly from the Update function. Something like below:
void Update()
{
if (isLocalPlayer)
{
CmdUpdateTransform();
}
}
[Command]
void CmdUpdateTransform()
{
SetDirtyBit(1u);
}
While Unity claims you can update the transform by simply using the SetDirtyBit function. I don't think this is true. The statement is missing lots of information. You will need OnSerialize to serialize and send the data and OnDeserialize to receive and de-serialize it. The SetDirtyBit function is simply used to determine which data to send. It's used to reduce bandwidth issues that comes from using uNET.
The code below shows what the use of SetDirtyBit should look like. It might need few changes to work on your side.
public class Example : NetworkBehaviour
{
private const uint SEND_POSITION = 1u;
private const uint SEND_ROTATION = 2u;
private const uint SEND_SCALE = 3u;
void Update()
{
uint dirtyBit = syncVarDirtyBits;
dirtyBit |= SEND_POSITION;
dirtyBit |= SEND_ROTATION;
dirtyBit |= SEND_SCALE;
if (isLocalPlayer)
{
CmdUpdateTransform(dirtyBit);
}
}
[Command]
void CmdUpdateTransform(uint dirtyBit)
{
SetDirtyBit(dirtyBit);
}
public override bool OnSerialize(NetworkWriter writer, bool initialState)
{
if ((this.syncVarDirtyBits & SEND_POSITION) != 0u)
{
writer.Write(this.transform.position);
}
if ((this.syncVarDirtyBits & SEND_ROTATION) != 0u)
{
writer.Write(this.transform.rotation);
}
if ((this.syncVarDirtyBits & SEND_SCALE) != 0u)
{
writer.Write(this.transform.localScale);
}
return true;
}
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
if ((this.syncVarDirtyBits & SEND_POSITION) != 0u)
{
Vector3 pos = reader.ReadVector3();
}
if ((this.syncVarDirtyBits & SEND_ROTATION) != 0u)
{
Quaternion rot = reader.ReadQuaternion();
}
if ((this.syncVarDirtyBits & SEND_SCALE) != 0u)
{
Vector3 scale = reader.ReadVector3();
}
}
}

In the Unity Docs It says
If sendInterval is zero, updates will be sent at the end of the frame when dirty bits are set for that script. Note that setting the value of a SyncVar will automatically set dirty bits.
So you could use your own script using NetworkSettings
using UnityEngine.Networking;
[NetworkSettings(channel = 1, sendInterval = 0.0f)]
class MyScript : NetworkBehaviour
{
[SyncVar]
int value;
}
However SyncVar only works Server -> Client.
But you can use SetDirtyBit to manually set the behaviour dirty.
Or you probably could also use InvokeSyncEvent to synchronize some values directly.
Here I found a bit more information on Dirty bits.

Related

Unity3D New Input System: Is it really so hard to stop UI clickthroughs (or figure out if cursor is over a UI object)?

Even the official documentation has borderline insane recommendations to solve what is probably one of the most common UI/3D interaction issues:
If I click while the cursor is over a UI button, both the button (via the graphics raycaster) and the 3D world (via the physics raycaster) will receive the event.
The official manual:
https://docs.unity3d.com/Packages/com.unity.inputsystem#1.2/manual/UISupport.html#handling-ambiguities-for-pointer-type-input essentially says "how about you design your game so you don't need 3D and UI at the same time?".
I cannot believe this is not a solved problem. But everything I've tried failed. EventSystem.current.currentSelectedGameObject is sticky, not hover. PointerData is protected and thus not accessible (and one guy offered a workaround via deriving your own class from Standalone Input Module to get around that, but that workaround apparently doesn't work anymore). The old IsPointerOverGameObject throws a warning if you query it in the callback and is always true if you query it in Update().
That's all just mental. Please someone tell me there's a simple, obvious solution to this common, trivial problem that I'm just missing. The graphics raycaster certainly stores somewhere if it's over a UI element, right? Please?
I've looked into this a fair bit and in the end, the easiest solution seems to be to do what the manual says and put it in the Update function.
bool pointerOverUI = false;
void Update()
{
pointerOverUI = EventSystem.current.IsPointerOverGameObject();
}
Your frustration is well founded: there are NO examples of making UI work with NewInput which I've found. I can share a more robust version of the Raycaster workaround, from Youtube:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.UI;
/* Danndx 2021 (youtube.com/danndx)
From video: youtu.be/7h1cnGggY2M
thanks - delete me! :) */
public class SCR_UiInteraction : MonoBehaviour
{
public GameObject ui_canvas;
GraphicRaycaster ui_raycaster;
PointerEventData click_data;
List<RaycastResult> click_results;
void Start()
{
ui_raycaster = ui_canvas.GetComponent<GraphicRaycaster>();
click_data = new PointerEventData(EventSystem.current);
click_results = new List<RaycastResult>();
}
void Update()
{
// use isPressed if you wish to ray cast every frame:
//if(Mouse.current.leftButton.isPressed)
// use wasReleasedThisFrame if you wish to ray cast just once per click:
if(Mouse.current.leftButton.wasReleasedThisFrame)
{
GetUiElementsClicked();
}
}
void GetUiElementsClicked()
{
/** Get all the UI elements clicked, using the current mouse position and raycasting. **/
click_data.position = Mouse.current.position.ReadValue();
click_results.Clear();
ui_raycaster.Raycast(click_data, click_results);
foreach(RaycastResult result in click_results)
{
GameObject ui_element = result.gameObject;
Debug.Log(ui_element.name);
}
}
}
So, just drop into my "Menusscript.cs"?
But as a pattern, this is terrible for separating UI concerns. I'm currently rewiring EVERY separately-concerned PointerEventData click I had already working, and my question is, Why? I can't even find how it's supposed to work: to your point there IS no official guide at all around clicking UI, and it does NOT just drop-on-top.
Anyway, I haven't found anything yet which makes new input work easily on UI, and definitely not found how I'm going to sensibly separate Menuclicks from Activityclicks while keeping game & ui assemblies separate.
Good luck to us all.
Unity documentation for this issue with regard to Unity.InputSystem can be found at https://docs.unity3d.com/Packages/com.unity.inputsystem#1.3/manual/UISupport.html#handling-ambiguities-for-pointer-type-input.
IsPointerOverGameObject() can always return true if the extent of your canvas covers the camera's entire field of view.
For clarity, here is the solution which I found worked best (accumulated from several other posts across the web).
Attach this script to your UI Canvas object:
public class CanvasHitDetector : MonoBehaviour {
private GraphicRaycaster _graphicRaycaster;
private void Start()
{
// This instance is needed to compare between UI interactions and
// game interactions with the mouse.
_graphicRaycaster = GetComponent<GraphicRaycaster>();
}
public bool IsPointerOverUI()
{
// Obtain the current mouse position.
var mousePosition = Mouse.current.position.ReadValue();
// Create a pointer event data structure with the current mouse position.
var pointerEventData = new PointerEventData(EventSystem.current);
pointerEventData.position = mousePosition;
// Use the GraphicRaycaster instance to determine how many UI items
// the pointer event hits. If this value is greater-than zero, skip
// further processing.
var results = new List<RaycastResult>();
_graphicRaycaster.Raycast(pointerEventData, results);
return results.Count > 0;
}
}
In class containing the method which is handling the mouse clicks, obtain the reference to the Canvas UI either using GameObject.Find() or a public exposed variable, and call IsPointerOverUI() to filter clicks when over UI.
Reply to #Milad Qasemi's answer
From the docs you have attached in your answer, I have tried the following to check if the user clicked on a UI element or not.
// gets called in the Update method
if(Input.GetMouseButton(0) {
int layerMask = 1 << 5;
// raycast in the UI layer
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero, Mathf.Infinity, layerMask);
// if the ray hit any UI element, return
// don't handle player movement
if (hit.collider) { return; }
Debug.Log("Touched not on UI");
playerController.HandlePlayerMovement(x);
}
The raycast doesn't seem to detect collisions on UI elements. Below is a picture of the Graphics Raycaster component of the Canvas:
Reply to #Lowelltech
Your solution worked for me except that instead of Mouse I used Touchscreen
// Obtain the current touch position.
var pointerPosition = Touchscreen.current.position.ReadValue();
An InputSytem is a way to receive new inputs provided by Unity. You can't use existing scripts there, and you'll run into problems like the original questioner. Answers with code like "if(Input.GetMouseButton(0)" are invalid because they use the old system.

Unet Syncing animator layer weight Unity 3D

I'm currently working on a multiplayer fps to learn UNET and Unity. in this project I'm using Network Animator Component to sync my animations.
I have one layer that runs the normal states like walking and jumping and another that just controls the upper body for scoping. This layer gets activated when scoping.
The parameters a syncing fine but not the layer weight.
The script that controls the layer weight gets disabled on the remote player as it is part of the same script that takes care of shooting and we don't want to shoot from the remote player.
So I placed that code on a different script and made Layerweight a Syncvar, still no results.
this is the snippet that sets the weight and fades it back out.
if (isScoped)
{
animator.SetLayerWeight(1, 1);
layerWeight = 1;
}
else
{
layerWeight = Mathf.Lerp(layerWeight, 0, 0.1f);
animator.SetLayerWeight(1, layerWeight);
}
animator.SetBool("Scoped", isScoped);
how can I sync a variable that is updated on a disabled script?
And what are the different custom attributes like [Command] and [ClientRpc] I should use to get this working?
I have read the documentation and manuals of UNET, but struggle at this relatively easy task as I'm having a hard time understanding the difference of these calls.
In general the main problem with [SyncVar] is that they are
synchronized from the server to clients
so not the other way round!
Then
[Command] is a method that is called by any client but only executed on the server
[ClientRpc] is kind of the opposite, it is called by the server and then executed on all clients
so what I would do is have a setter method for the layerweight like
private void SetLayerWeight(float value)
{
// only player with authority may do so
if(!isLocalPlayer) return;
// Tell the server to update the value
CmdSetLayerWeight(value);
}
[Command]
private void CmdSetLayerWeight(float value)
{
// Server may now tell it to all clients
RpcSetLayerWeight(value);
}
[ClientRpc]
private void RpcSetLayerWeight(float value)
{
// called on all clients including server
layerweight = value;
animator.SetLayerWeight(1, layerweight);
}
and call it like
if (isScoped)
{
SetLayerWeight(1);
}
else
{
SetLayerWeight(Mathf.Lerp(layerWeight, 0, 0.1f));
}
Note though that passing this forth and back between server and client will cause some uncanny lag!
You would probably be better syncing the isScoped bool instead and assign the value directly on the calling client:
private void SetScoped(bool value)
{
// only player with authority may do so
if(!isLocalPlayer) return;
// for yourself already apply the value directly so you don't have
// to wait until it comes back from the server
scoped = value;
// Tell the server to update the value and who you are
CmdSetLayerWeight(value);
}
// Executed only on the server
[Command]
private void CmdSetLayerWeight(bool value)
{
// Server may now tell it to all clients
RpcSetLayerWeight(value);
}
// Executed on all clients
[ClientRpc]
private void RpcSetLayerWeight(bool value)
{
// called on all clients including server
// since setting a bool is not expensive it doesn't matter
// if this is done again also on the client who originally initialized this
scoped = value;
}

Unity3D: Custom UnityEvent AddListener not firing

I have my own custom UnityEvent and am trying to add a listener.
I have used AddListener on numerous other UI objects, such as buttons, dropdowns, toggles, etc. so I understand the process. However, when I Invoke my UnityEvent, it simply doesn't fire.
I'm receiving no error messages, and after doing reading and research, everything looks correct. So, not sure what to do further.
This is an object that emits when it's rotated.
This is the basics of my code:
using UnityEngine.Events;
public class Rotator: MonoBehaviour
{
public UnityEvent OnRotate;
int angle = 0;
int newAngle = 0;
void Start()
{
OnRotate = new UnityEvent();
}
void Update()
{
newAngle = (int)transform.rotation.eulersAngles.z;
if (newAngle != angle)
{
print ("Actual Instance ID: " + GetInstanceID());
print ("Invoking!");
OnRotate.Invoke();
angle = newAngle;
}
}
}
and
public class Owner: MonoBehaviour
{
public Rotator rotator;
void Start()
{
print ("Rotator Instance ID: " + rotator.GetInstanceID());
rotator.OnRotate.AddListener(
() => UpdateRotation()
);
}
void UpdateRotation()
{
print ("Invoked!");
}
}
When the Rotator has it's angle changed, I get this in the console:
Actual Instance ID: 11234
Rotator Instance ID: 11234
Invoking!
The instance ID is to make sure I'm working with the same objects and not going in circles for nothing. They match, so I'm listening to the object that's firing.
However, the listener isn't firing. I've tried different combinations with delegates, etc. but it's all the same. No errors. It just doesn't invoke.
Obviously, I'm doing something wrong, but what is it?
Thanks for any help.
Somehow your answered your new edited version of the question with exactly the code you previously provided in the First Version of your Question!
As I tried to tell you ... if you anywhere in your code do OnRotate = new UnityEvent() of course you thereby erase any persistent callbacks and any runtime callbacks added before that moment!
In short
Simply leave it as
public UnityEvent OnRotate;
and you don't even have to think about it anymore.
For understanding why it also works if you put it in Awake please simply have a look at the Order of Execution for Event Functions
&rightarrow; First Awake and OnEnabled is called for every GameObject/Component. Then all Start methods are called as soon as the GameObject/Component is active.
Within each of these blocks (Awake + OnEnable) and (Start) the order of execution between different component types is not guaranteed unless you explicitly configure it via the Script Execution Order Settings where you could define that Owner is simply run before Rotator .. then having both in Start would also work again.
Why does it also work if you do it on the public field?
&rightarrow; Because this field is serialized. That means it is initialized automatically in the Inspector and then stored together with the Scene or prefab asset including any persistent callbacks.
And then Later Unity re-uses the serialized Version of the field so actually you can completely remove the new UnityEvent(); since it doesn't have any effect on a serialized field! It will always be initialized automatically anyway!
Ok, I found out what the issue was.
My question now is "why?".
I changed my code from:
public UnityEvent OnRotate;
void Start() {
OnRotate = new UnityEvent();
}
to
public UnityEvent OnRotate = new UnityEvent();
void Start() {
}
And now it works.
Although, now that I think about it, Awake() is the method where they all fire before initialization, whereas Start() is when the object is created. So the Start() of the Rotator is probably getting called after the Owner is adding a listener.

How to properly use movement components in unreal engine 4?

I'm trying to set up a very simple RTS-like camera that moves around at the pressure of wasd or the arrow keys. But I can't manage to make it work.
I have a player controller that handles input like so:
void AMyPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAxis("CameraForward", this, &AMyPlayerController::CameraForward);
}
void AMyPlayerController::CameraForward(float Amount)
{
MyRtsCameraReference->CameraForward(Amount);
}
In my camera actor, which inherits from APawn, and I initialize like this:
ARtsCamera::ARtsCamera()
{
PrimaryActorTick.bCanEverTick = true;
CameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
// I tried to use a static mesh in place of this but nothing changes.
SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
MovementComponent = CreateDefaultSubobject<UFloatingPawnMovement>(TEXT("MovementComponent"));
SpringArm->TargetArmLength = 500.f;
SetRootComponent(SceneRoot);
SpringArm->SetupAttachment(SceneRoot);
CameraComponent->SetupAttachment(SpringArm);
SpringArm->AddLocalRotation(FRotator(0.f, -50.f, 0.f));
}
I try to handle the movement like this:
void ARtsCamera::CameraForward(float Amount)
{
if (Amount != 0)
{
// Here speed is a float used to control the actual speed of this movement.
// If Amount is printed here, I get the correct value of 1/-1 at the pressure of w/s.
MovementComponent->AddInputVector(GetActorForwardVector() * Amount * Speed);
}
}
In my set up i then create a blueprint which inherits from this to expose to the level design phase a few parameters like Speed or the spring arm length and so on, but this shouldn't be the issue as if I use the C++ class the behaviour is the same.
The following works as expected (except for the fact that when i then rotate the camera, vectors get messed up) but i would like to use a movement component.
void ARtsCamera::CameraForward(float Amount)
{
if (Amount != 0)
{
AddActorWorldOffset(GetActorForwardVector() * Amount * CameraMovingSpeed);
}
}
The mobility is set to movable. And so far everything seems to be simple enough, no compile errors, but the actor doesn't move. What am I missing?
Also would you use the FloatingPawnMovement for this or would you go for another movement component?
Thanks in advance.

Part of my PlayerExpChangeEvent is being overridden by vanilla

I'm making a spigot plugin (version 1.8.8) that has an function that I know works because it fires flawlessly through my command. However, when I call it at the end of a PlayerExpChangeEvent, it seems like vanilla leveling overrides the bar, making it go up way more that it is supposed to. Running the command/function after this happens makes the bar go back to how it is supposed to be. I've tried setting my event's priority to highest (and when that didn't work, to lowest) but no matter what my function appears to be completely ignored when called inside the event.
Here is some code:
#EventHandler(priority=EventPriority.HIGHEST)
public void onXpGain(PlayerExpChangeEvent event)
{
// Load custom levels from config
ArrayList<String> levelList = new ArrayList<String>(plugin.getConfig().getStringList("levels"));
if (!((String)levelList.get(0)).equals("none"))
{
Player player = event.getPlayer();
Iterator<String> var4 = levelList.iterator();
while (var4.hasNext())
{
String s = (String)var4.next();
String[] splits = s.split(" ");
int levelCompare = Integer.parseInt(splits[0]);
int playerLvl = player.getLevel();
// Detect if on correct tier, else continue iteration
if (playerLvl == levelCompare - 1)
{
// Calculate the player's new XP amount
int totalXp = player.getTotalExperience() + event.getAmount();
player.setTotalExperience(totalXp);
updateBar(event.getPlayer()); // <-- THIS IS THE FUNCTION
return;
}
}
// At max level
player.setTotalExperience(player.getTotalExperience() + event.getAmount());
player.setLevel(getHighestLevel(levelList));
player.setExp(1.0f);
}
}
And here is the function itself. Keep in mind that it works fine when called through a command and not an event. It's purpose is to use the player's total XP to set the level and bar. Neither set correctly in the event; it instead embraces vanilla leveling.
public static void updateBar(Player player) {
ArrayList<String> levelList = new ArrayList<String>(plugin.getConfig().getStringList("levels"));
int totalXp = player.getTotalExperience();
player.setLevel(getHighestLevelForXp(totalXp, levelList));
if (player.getLevel() < getHighestLevel(levelList)) {
int lvlDiff = getTotalXpToLevel(player.getLevel() + 1,levelList) - getTotalXpToLevel(player.getLevel(),levelList);
int xpDiff = totalXp - getTotalXpToLevel(player.getLevel(),levelList);
player.setExp((float)xpDiff/lvlDiff);
} else {
player.setLevel(getHighestLevel(levelList));
player.setExp(0.0f);
}
return;
}
The command where the function works correctly is a bare-bones call to the function and doesn't need a mention here. Does anyone know how to get my event to override vanilla xp gain? The update works through the command, just not natural xp gain. It is already confirmed that the event DOES fire, as the rest of the event changes the internal xp amount, but the visual effects are overridden by vanilla. Can anyone help? Thanks.
Only setting the Player's EXP won't be enough for your desired behaviour. The Vanilla behaviour will still complete, as you're not changing how the event will add EXP to the player.
Currently, your event is working like this:
And PlayerExpGainEvent isn't cancellable, so you cannot undo it's addition of EXP.
What you can do instead is to set the EXP the event will add to 0, therefore not changing the player's EXP after your interception.
event.setAmount(0); //Cancelling the EXP addition
I would recommend to set your event to a high priority, so that other events that depend on Experience gain won't trigger when you set the amount gained to 0.