Change material on GameObjects with Unet in Unity - unity3d

I want to change the material of a GameObject on all clients when I click on it in any client. I am new to UNET and I assume I have a conceptional flaw. So basically what I am trying to do is:
Shoot a ray on the NetworkPlayer to an object in the scene
Send a [Command] from the player
In this [Command] call an [ClientRpc] on the object
In the [ClientRpc]change the material of this object
My player:
using UnityEngine;
using UnityEngine.Networking;
// This script is on my Game Player Prefab
// (removed the cam movement part)
public class CamMovement : NetworkBehaviour
{
void Update()
{
if (!isLocalPlayer)
{
return;
}
if (Input.GetMouseButtonDown(0))
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
CmdNextColor(hit.transform.gameObject);
}
}
[Command]
public void CmdNextColor(GameObject hitObject)
{
RPC_ColorChange colorChange = hitObject.GetComponent<RPC_ColorChange>();
if (colorChange != null)
{
colorChange.RpcNextColor();
}
}
}
My object:
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
public class RPC_ColorChange : NetworkBehaviour {
public Material[] material;
[SyncVar]
int curColOfThisObject;
Text text;
private void Start()
{
text = GetComponentInChildren<Text>();
}
[ClientRpc]
public void RpcNextColor()
{
if (!isClient)
return;
if (material.Length > 0)
{
Material curMaterial = this.GetComponent<MeshRenderer>().material;
curColOfThisObject++;
if (curColOfThisObject >= material.Length)
curColOfThisObject = 0;
curMaterial = material[curColOfThisObject];
}
}
private void Update()
{
if (isClient)
{
text.text = "new color of this object: " + curColOfThisObject.ToString();
}
}
}
What happens is:
The text on the object changes to the appropriate color, but the material is never changed. How do I change the material?
Bonus question:
If anyone knows a good tutorial on how to concept a UNET game please let me know.

Your problem is that you calculate the value of curColOfThisObject on the client side but at the same time use [SyncVar] for it.
From the [SyncVar] Docu:
These variables will have their values sychronized from the server to clients
-> Don't change the Values on the clients in RpcNextColor but rather already on the server in the CmdNextColor. Otherwise curColOfThisObject will instantly get overwritten with the deafult value which was never changed on the server. I would than pass the value to the clients as parameter in the [ClientRpc] so technically you wouldn't even need the [SyncVar] at all.
In CamMovement
[Command]
public void CmdNextColor(GameObject hitObject)
{
RPC_ColorChange colorChange = hitObject.GetComponent<RPC_ColorChange>();
if (colorChange != null)
{
colorChange.NextColor();
// after calculating a new curColOfThisObject send it to clients (doesn't require [SyncVar] anymore)
colorChange.RpcNextColor(curColOfThisObject);
}
}
In RPC_ColorChange
// Make the calculation of the value on the server side
[Server]
private void NextColor()
{
if (material.Length > 0)
{
Material curMaterial = this.GetComponent<MeshRenderer>().material;
curColOfThisObject++;
if (curColOfThisObject >= material.Length)
curColOfThisObject = 0;
// set the material also on the server
curMaterial = material[curColOfThisObject];
}
}
[ClientRpc]
public void RpcNextColor(int newValue)
{
if (!isClient) return;
// easier to debug if you keep the curColOfThisObject variable
curColOfThisObject = newValue;
if(newValue=> material.Length)
{
Debug.LogError("index not found in material");
return;
}
// instead of curColOfThisObject you could also just use the newValue
// but this is easier to debug
curMaterial = material[curColOfThisObject];
}
If you want to stick to the [SyncVar] you could also completely skip the ClientRpc and make it a hook for the [SyncVar] instead:
In CamMovement
[Command]
public void CmdNextColor(GameObject hitObject)
{
RPC_ColorChange colorChange = hitObject.GetComponent<RPC_ColorChange>();
if (colorChange != null)
{
colorChange.NextColor();
}
}
In RPC_ColorChange
[SyncVar(hook = "OnNextColor")]
private int curColOfThisObject;
// Make the calculation of the value on the server side
[Server]
private void NextColor()
{
if (material.Length > 0)
{
Material curMaterial = this.GetComponent<MeshRenderer>().material;
curColOfThisObject++;
if (curColOfThisObject >= material.Length)
curColOfThisObject = 0;
// set the material also on the server
curMaterial = material[curColOfThisObject];
}
}
// This method automatically gets called when the value of
// curColOfObject is changed to newValue on the server
private void OnNextColor(int newValue)
{
if (!isClient) return;
// easier to debug if you keep the curColOfThisObject variable
curColOfThisObject = newValue;
if(newValue=> material.Length)
{
Debug.LogError("index not found in material");
return;
}
// instead of curColOfThisObject you could also just use the newValue
// but this is easier to debug
curMaterial = material[curColOfThisObject];
}
Little extra: I would check the existence of the RPC_ColorChange component before I send stuff through the network.
if (Input.GetMouseButtonDown(0))
{
Ray ray = cam.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
if(hit.GetComponent<RPC_ColorChange>()!=null)
{
CmdNextColor(hit.transform.gameObject);
}
}
}
Be aware that you might hit a child or parent instead of the actual object you would like to hit .. so evtl. you'll have to look for the RPC_ColorChange component in the childs or parent using GetComponentInChildren or GetComponentInParent.

It is a bit counter-intuitive that RpcFunctions get invoked after the Command functions have been processed:
On the player prefab:
[Command]
public void CmdNextColor(GameObject hitObject)
{
RPC_ColorChange colorChange = hitObject.GetComponent<RPC_ColorChange>();
if (colorChange != null)
{
int curColor = colorChange.GetCurColor();
// change color +1 on the clients
colorChange.RpcNextColor(curColor);
// assign a SyncVar with the current color
colorChange.SyncColorVar();
}
}
Now the functions are called in the following order:
1) SyncColorVar()
2) RpcNextColor()
Test project: gitlab.com/KlausUllrich/networkTest

Related

(Unity3d) **nullreferenceexception** thrown when *instance.lockpos()* is called; plus how to make an object maintain its starting rotation

I've run up against a little problem, and I can't seem to find the solution.
I'm putting together a game in Unity3d that includes these gauntlets that can control the environment, i.e. raise downed bridges, open doors, etc. First, I'm just trying to allow the gauntlets to access objects from afar, and I found a telekinesis tutorial online that I am trying to pseudo-repurpose.
This script is supposed to raise a bridge (hence BridgeMover) until it hits a specific position and then lock it in place. Unfortunately, I'm throwing a null reference exception when the bridge hits the "lockbox" object, which is a small cube on the edge of the bridge ramp leading up to the bridge. I know this is simple, but I can't quite conceptualize it, and I've just been staring at it for a few days. This is my big issue, and any help would be appreciated.
In other news, I'm also trying to have the bridge rise as a flat plane instead of following the rotation of the cast ray, and would appreciate any advice about that as well, but I would understand if y'all want me to suffer through on that one. I'm pretty sure it has something to do with the Lerp, which I still don't really understand.
In the hierarchy, I have a handhold object, which is a child of the gauntlet object, which is a child of the camera, which is a child of the player. So
Player
Camera
Gauntlet
handHold
The lockbox object is a child of the ramp that it's attached to. So:
Ramp
Lockbox
Also, this isn't a criticism of the tutorial; I'm just swimming slightly out of my depths here. The tutorial, for those interested, can be found here.
public class HandController : MonoBehaviour
{
public static BridgeMover instance;
public Camera mainCamera;
Rigidbody rbOfHeldObject;
GameObject heldObject;
public GameObject handHold;
bool holdsObject = false;
float interactionDistance = 100;
float slideSpeed = 2;
Quaternion currentRotation;
private void Start()
{
mainCamera.GetComponent<Camera>();
}
private void Update()
{
//HandMove();
if (Input.GetMouseButtonDown(0) && !holdsObject)
Raycast();
if (Input.GetMouseButtonUp(0) && holdsObject)
ReleaseObject();
if (holdsObject && instance.LockPos())
LockObject();
if(holdsObject)
{
if (CheckDistance() >= 0.1f)
MoveObjectToPosition();
}
}
private void Raycast()
{
Ray ray = mainCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
RaycastHit hit;
if (Physics.Raycast(ray, out hit, interactionDistance))
{
if(hit.collider.CompareTag("Bridge"))
{
heldObject = hit.collider.gameObject;
heldObject.transform.SetParent(handHold.transform);
holdsObject = true;
rbOfHeldObject = heldObject.GetComponent<Rigidbody>();
rbOfHeldObject.constraints = RigidbodyConstraints.FreezeAll;
}
}
}
public float CheckDistance()
{
return Vector3.Distance(heldObject.transform.position, handHold.transform.position);
}
private void MoveObjectToPosition()
{
currentRotation = heldObject.transform.rotation;
heldObject.transform.position = Vector3.Lerp(heldObject.transform.position,
handHold.transform.position, slideSpeed * Time.deltaTime);
heldObject.transform.rotation = currentRotation;
}
private void ReleaseObject()
{
rbOfHeldObject.constraints = RigidbodyConstraints.None;
heldObject.transform.parent = null;
heldObject = null;
holdsObject = false;
}
private void LockObject()
{
rbOfHeldObject.useGravity = false;
heldObject.transform.parent = null;
heldObject = null;
holdsObject = false;
}
}
And then in the BridgeMover class:
public class BridgeMover : MonoBehaviour
{
public static BridgeMover instance;
bool lockPos = false;
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "LockBox")
{
lockPos = true;
}
}
public bool LockPos()
{
return lockPos;
}
}
Any help would be appreciated. Also, if any further information is needed, let me know and I'll add it.
It looks like you're trying to implement a singleton instance for BridgeMover. Unfortunately, you don't have your instance defined. Here's the standard Unity method for creating a singleton implemented in your BridgeMover code:
public class BridgeMover : MonoBehaviour
{
public static BridgeMover instance;
bool lockPos = false;
void Awake()
{
if(instance == null)
{
instance = this;
}
else
{
Destroy(this.gameObject);
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "LockBox")
{
lockPos = true;
}
}
public bool LockPos()
{
return lockPos;
}
}
Additionally, in your HandController class you should not declare a static BridgeMover. Instead, reference the static instance properly:
public static BridgeMover instance;
if (holdsObject && BridgeMover.instance.LockPos())

Stop and Play Particle System on Networked Player in Unity using Mirror

I am trying to play and stop a particle system which is a child of a player prefab. So far I have gotten the host client's thrust particle effect to update across all clients but I haven't been able to figure out a non-host client. I would like it if every time a player presses the thruster button the game would display the thruster effect across all clients and when they stop the particle system stops and other clients would see it as well.
So far my code is as follows:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
public class PlayerMovement : NetworkBehaviour
{
public float thrustPower = 300f;
Rigidbody2D m_playerRb;
bool m_thrustOn = false;
public ParticleSystem m_playerPs;
[SyncVar(hook = nameof(SetThrusters))]
bool m_thrusterState = false;
private void Start()
{
m_playerRb = GetComponent<Rigidbody2D>();
m_playerPs.Stop();
}
private void Update()
{
if (isLocalPlayer)
{
MovePlayer();
m_thrusterState = m_thrustOn;
}
}
private void FixedUpdate()
{
if (m_thrustOn)
{
m_playerRb.AddForce(gameObject.transform.up * thrustPower * Time.fixedDeltaTime, ForceMode2D.Force);
}
}
private void SetThrusters(bool oldVal, bool newVal)
{
if (newVal) m_playerPs.Play();
else if (newVal == false) m_playerPs.Stop();
}
private void MovePlayer()
{
m_thrustOn = Input.GetKey(KeyCode.W) ? true : false;
float horizontalInput = Input.GetAxisRaw("Horizontal");
transform.Rotate(new Vector3(0, 0, horizontalInput * -180 * Time.deltaTime));
}
}
I cant really find anything online about Mirror in terms of particle systems. Let me know if you need any more info.
Thanks!
SyncVar always only go in the direction HOST -> CLIENT !
For the other way round you would need to use a Command
like e.g.
[Command]
void CmdSetSetThrusters(bool newVal)
{
// the hook would not be executed on the host => do it manually now
SetThrusters(m_thrusterState, newVal);
// set it on the host -> now via synVar automatically forwarded to others
m_thrusterState = newVal;
}
And then you will have to add a check whether you are the host or not like e.g.
if (isLocalPlayer)
{
MovePlayer();
if(isServer)
{
// afaik this you still have to call manually for yourself
// not sure here how SyncVar Hooks are handled on the host itself
SetThrusters(m_thrusterState, m_thrustOn);
// this is now synced to others
m_thrusterState = m_thrustOn;
}
else
{
// if your not Host send the command
CmdSetSetThrusters(m_thrustOn);
}
}
Personally for this reason for me the SyncVar with Hooks was always a little bit confusing and suspect.
You could as well simply implement both directions yourself using e.g.
[Command]
void CmdSetSetThrusters(bool newVal)
{
// set the value on yourself
SetThrusters(newVal);
// Then directly forward it to all clients
RpcSetThrusters(newVal);
}
[ClientRpc]
private void RpcSetThrusters(bool newValue)
{
// set the value on all clients
SetThrusters(newVal);
}
private void SetThrusters(bool newVal)
{
if (newVal)
{
m_playerPs.Play();
}
else
{
m_playerPs.Stop();
}
// if you are the HOST forward to all clients
if(isServer)
{
RpcSetThrusters(newVal);
}
}
and then
if (isLocalPlayer)
{
MovePlayer();
if(isServer)
{
SetThrusters(m_thrusterState);
}
else
{
// if your not Host send the command
CmdSetSetThrusters(m_thrustOn);
}
}

Using a timer in conjunction with 2 push buttons from arduino

So I am using two push buttons (connected to an Arduino Uno) as an input to my game. The player has to push down both buttons at the same time for the character to move in the game. I want the player to hold down the buttons for a different amount of time in each level. I have a working Arduino and a working Unity timer and player script, but am not able to get the code to do what I want. What I basically want is that only when the player presses the buttons down, does the timer start counting down. Right now, the timer starts as soon as the scene begins. I know that I somehow have to reference the timer script to the button object, I have tried this but it still doesn't work. Note that the timer UI does have a Timer tag on it. I have also referenced the Player Controller script in the Timer script. Right now, Its giving me a range of errors. I have attached an image depicting these errors.error image
The Timer script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
//int startTime = 0;
public bool buttonPressed = false;
public int timeLeft;
public Text countdownText;
GameObject Character;
void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
//StartCoroutine("LoseTime");
BeginTimer();
}
void Update()
{
countdownText.text = ("Time Left = " + timeLeft);
if (timeLeft <= 0)
{
//StopCoroutine("LoseTime");
//countdownText.text = "Times Up!";
Invoke("ChangeLevel", 0.1f);
}
}
public void BeginTimer()
{
Character.GetComponent<PlayerController>().Update();
//gameObject.GetComponent<MyScript2>().MyFunction();
if (buttonPressed == true )
{
StartCoroutine("LoseTime");
}
else if (buttonPressed == false)
{
StopCoroutine("LoseTime");
}
}
IEnumerator LoseTime()
{
while (true)
{
yield return new WaitForSeconds(1);
timeLeft--;
}
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
The Player Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
public class PlayerController : MonoBehaviour
{
SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
//player == GameObject.FindWithTag("Player").GetComponent<>();
public float Speed;
public Vector2 height;
public float xMin, xMax, yMin, yMax;
public bool buttonPressed = false;
GameObject Character;
public void Awake()
{
Character = GameObject.FindWithTag("Player");
}
public void Start()
{
if (!sp.IsOpen)
{ // If the erial port is not open
sp.Open(); // Open
}
sp.ReadTimeout = 1; // Timeout for reading
}
public void Update()
{
if (sp.IsOpen)
{ // Check to see if the serial port is open
try
{
string value = sp.ReadLine();//To("Button"); //Read the information
int button = int.Parse(value);
//float amount = float.Parse(value);
//transform.Translate(Speed * Time.deltaTime, 0f, 0f); //walk
if (button == 0) //*Input.GetKeyDown(KeyCode.Space*/) //jump
{
buttonPressed = true;
Character.GetComponent<Rigidbody2D>().AddForce(height, ForceMode2D.Impulse);
Character.GetComponent<Rigidbody2D>().position = new Vector3
(
Mathf.Clamp(GetComponent<Rigidbody2D>().position.x, xMin, xMax),
Mathf.Clamp(GetComponent<Rigidbody2D>().position.y, yMin, yMax)
);
Timer tmr = GameObject.Find("Timer").GetComponent<Timer>();
tmr.BeginTimer();
}
}
catch (System.Exception)
{
}
}
void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
}
I think the problem may be with how I am referencing the scripts in each other.
In your timer you have a quite strange mixup of Update and Coroutine. Also note that BeginTimer is called exactly once! You also shouldn't "manually" call Update of another component.
I wouldn't use Update at all here. Simply start and stop a Coroutine.
The Timer script should only do the countdown. It doesn't have to know more:
public class Timer : MonoBehaviour
{
public int timeLeft;
public Text countdownText;
private bool timerStarted;
public void BeginTimer(int seconds)
{
// Here you have to decide whether you want to restart a timer
timeLeft = seconds;
// or if you rather want to continue counting down
//if(!timerStarted) timeLeft = seconds;
StartCoroutine(LoseTime());
}
public void StopTimer()
{
StopAllCoroutines();
}
private IEnumerator LoseTime()
{
timerStarted = true;
while (timeLeft > 0)
{
yield return new WaitForSeconds(1);
timeLeft --;
countdownText.text = $"Time Left = {timeLeft}";
}
// Only reached after the timer finished and wasn't interrupted meanwhile
// Using Invoke here is a very good idea since we don't want to interrupt anymore
// if the user lets go of the button(s) now
Invoke(nameof(ChangeLevel), 0.1f);
}
void ChangeLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
}
In general avoid to use Find at all. If anyhow possible already reference things in the Inspector! If needed you can use Find but only once! What you never want to do is use any of the Find and GetComponent variants repeatedly - rather store the reference the first time and re-use it - and especially not in Update no a per frame basis. They are very expensive calls!
public class PlayerController : MonoBehaviour
{
public float Speed;
public Vector2 height;
// I prefer to use Vector2 for such things
public Vector2 Min;
public Vector2 Max;
public bool buttonPressed = false;
// Already reference these via the Inspector if possible!
public Rigidbody2D Character;
public Timer timer;
public Rigidbody2D _rigidbody;
private SerialPort sp = new SerialPort("\\\\.\\COM4", 9600);
private void Awake()
{
FetchReferences();
}
// This is my "secret" tip for you! Go to the component in the Inspector
// open the ContextMenu and hit FetchReferences
// This already stores the references in the according fields ;)
[ContextMenu("FetchReferences")]
private void FetchReferences()
{
if(!Character)Character = GameObject.FindWithTag("Player"). GetComponent<Rigidbody2D>();
if(!timer) timer = GameObject.Find("Timer").GetComponent<Timer>();
}
private void Start()
{
if (!sp.IsOpen)
{
sp.Open(); // Open
}
sp.ReadTimeout = 1;
}
private void Update()
{
// I wouldn't do the serialport open check here
// your if block simply silently hides the fact that your application
// doesn't work correctly! Rather throw an error!
try
{
string value = sp.ReadLine(); //Read the information
int button = int.Parse(value);
//TODO: Since it isn't clear in your question how you get TWO buttons
//TODO: You will have to change this condition in order to only fire if both
//TODO: buttons are currently pressed!
buttonPressed = button == 0;
if (buttonPressed)
{
Character.AddForce(height, ForceMode2D.Impulse);
// The clamping of a rigidbody should always be done ine FixedUpdate!
// Pass in how many seconds as parameter or make the method
// parameterless and configure a fixed duration via the Inspector of the Timer
timer.BeginTimer(3.0f);
}
else
{
// Maybe stop the timer if condition is not fulfilled ?
timer.StopTimer();
}
}
catch (System.Exception)
{
// You should do something here! At least a Log ...
}
}
private void FixedUpdate()
{
// Here I wasn't sure: Are there actually two different
// Rigidbody2D involved? I would assume you rather wanted to use the Character rigidbody again!
Character.position = new Vector3(Mathf.Clamp(Character.position.x, Min.x, Max.x), Mathf.Clamp(Character.position.y, Min.y, Max.y));
}
// Did you mean OnApplicationQuit here?
private void ApplicationQuit()
{
if (sp != null)
{
{
sp.Close();
}
}
}
}
Typed on smartphone but I hope the idea gets clear

Unity network transform problem - Client spawning objects at wrong location

I'm working on a Fortnite-esque game for Unity.
Player spawns in, has ability to spawn cubes to make a "base".
Everything works perfectly well in mono but I'm having a strange issue with networking. On the server, my player can spawn the cubes perfectly according to the Raycast hitpoint while on the client, even though the player is a clone of the Player prefab, the spawned objects always either end up at the world origin, rather than Raycast hitpoint -or- if I remove if (!isPlayLocal) {return;} from the player's script containing Raycast info, the cube spawns inaccurately and without its corresponding material.
I'll try and pinpoint the code so I can place it here but I imagine it could be a number of things.
Local Player Auth is checked off on spawn prefabs and all prefabs have been registered in Network Manager.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class BuildingSystemNew : NetworkBehaviour
{
[SerializeField] private Camera playerCamera;
[SerializeField] private GameObject blockTemplatePrefab;
[SerializeField] private GameObject blockPrefab;
[SerializeField] private Material templateMaterial;
[SerializeField] private LayerMask buildableSurfacesLayer;
private bool buildModeOn = false;
private bool canBuild = false;
private bool crossHairOn = false;
private BlockSystem bSys;
public Texture2D crosshairImage;
private int blockSelectCounter = 0;
private GameObject weapon;
private Vector3 buildPos;
private GameObject currentTemplateBlock;
private void Start()
{
bSys = GetComponent<BlockSystem>();
}
private void Update()
{
if (isLocalPlayer == false)
return;
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
if (Input.GetKeyDown("e"))
{
buildModeOn = !buildModeOn;
if (buildModeOn)
{
// weapon.SetActive(false);
crossHairOn = true;
}
else
{
// weapon.SetActive(true);
crossHairOn = false;
}
}
if (Input.GetKeyDown("r"))
{
blockSelectCounter++;
if (blockSelectCounter >= bSys.allBlocks.Count) blockSelectCounter = 0;
}
if (buildModeOn)
{
RaycastHit buildPosHit;
if (Physics.Raycast(playerCamera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0)), out buildPosHit, 10, buildableSurfacesLayer))
{
Vector3 point = buildPosHit.point;
buildPos = new Vector3(Mathf.Round(point.x), Mathf.Round(point.y), Mathf.Round(point.z));
canBuild = true;
}
else
{
Destroy(currentTemplateBlock.gameObject);
canBuild = false;
}
}
if (!buildModeOn && currentTemplateBlock != null)
{
Destroy(currentTemplateBlock.gameObject);
canBuild = false;
}
if (canBuild && currentTemplateBlock == null)
{
currentTemplateBlock = Instantiate(blockTemplatePrefab, buildPos, Quaternion.identity);
currentTemplateBlock.GetComponent<MeshRenderer>().material = templateMaterial;
}
if (canBuild && currentTemplateBlock != null)
{
currentTemplateBlock.transform.position = buildPos;
if (Input.GetMouseButtonDown(0))
{
CmdPlaceBlock();
}
else if (Input.GetMouseButtonDown(1))
{
CmdDestroyBlock();
}
}
}
[Command]
public void CmdPlaceBlock()
{
GameObject newBlock = Instantiate(blockPrefab, buildPos, Quaternion.identity);
Block tempBlock = bSys.allBlocks[blockSelectCounter];
newBlock.name = tempBlock.blockName + "-Block";
newBlock.GetComponent<MeshRenderer>().material = tempBlock.blockMaterial;
NetworkServer.SpawnWithClientAuthority(newBlock, connectionToClient);
}
[Command]
private void CmdDestroyBlock()
{
RaycastHit hit;
Ray ray = playerCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit))
{
var objectHit = hit.collider.gameObject;
if (hit.collider.gameObject.tag == "Block")
{
Destroy(objectHit);
}
}
}
void OnGUI()
{
if (crossHairOn == true)
{
float xMin = (Screen.width / 2) - (crosshairImage.width / 2);
float yMin = (Screen.height / 2) - (crosshairImage.height / 2);
GUI.DrawTexture(new Rect(xMin, yMin, crosshairImage.width, crosshairImage.height), crosshairImage);
}
}
}
The Problem
You are calling
[Command]
public void CmdPlaceBlock()
{
GameObject newBlock = Instantiate(blockPrefab, buildPos, Quaternion.identity);
Block tempBlock = bSys.allBlocks[blockSelectCounter];
newBlock.name = tempBlock.blockName + "-Block";
newBlock.GetComponent<MeshRenderer>().material = tempBlock.blockMaterial;
NetworkServer.SpawnWithClientAuthority(newBlock, connectionToClient);
}
without a parameter.
A Command is called on the client but executed on the Server
=> with the local variables of the Server!
So e.g. buildPos will always have the default value 0,0,0 on the server since due to
if(!isLocalPlayer) return;
later the line
buildPos = new Vector3(Mathf.Round(point.x), Mathf.Round(point.y), Mathf.Round(point.z));
is never executed on the server. The same also applies e.g. to blockSelectCounter and probably other values your CmdPlaceBlock depends on.
Solution
You should pass your client's buildPos value (and also all other values that are different on client and server) to the server command so the server knows at which correct position the new object should be placed:
//...
if (Input.GetMouseButtonDown(0))
{
CmdPlaceBlock(buildPos);
}
//...
[Command]
public void CmdPlaceBlock(Vector3 spawnPosition)
{
GameObject newBlock = Instantiate(blockPrefab, spawnPosition, Quaternion.identity);
Block tempBlock = bSys.allBlocks[blockSelectCounter];
newBlock.name = tempBlock.blockName + "-Block";
newBlock.GetComponent<MeshRenderer>().material = tempBlock.blockMaterial;
NetworkServer.SpawnWithClientAuthority(newBlock, connectionToClient);
}
I added only an example for the position knowing it will always be different on client and server. But it also might apply to your other values like e.g. blockSelectCounter.
Do this change for all values that have to be the ones of the client and not the ones of the server.
Note that the types that can be passed between networking methods are limited! You can't pass e.g. any component references.
The allowed argument types are;
Basic type (byte, int, float, string, UInt64, etc)
Built-in Unity math type (Vector3, Quaternion, etc),
Arrays of basic types
Structs containing allowable types
NetworkIdentity
NetworkInstanceId
NetworkHash128
GameObject with a NetworkIdentity component attached.
Additional Hints
For readability and amount of lines you should change things like e.g.
if (buildModeOn)
{
// weapon.SetActive(false);
crossHairOn = true;
}
else
{
// weapon.SetActive(true);
crossHairOn = false;
}
to simply
// weapon.SetActive(!buildModeOn);
crossHairOn = buildModeOn;
and check bools not like
if (isLocalPlayer == false)
but rather
if(!isLocalPlayer)
It simply reads/writes easier ;)

Teleport script not working as intended

I would like to make a gameobject in my game, which when enter trigger, then it will teleport my character to an another position (otherTeleport.transform.position), however when I use the script what I have written, my character won't teleport there, while the enemy will. If I put a Debug.Log in the bounce script which will log the character's position I see that my character has been at the otherTeleport's position, but only for one debuglog, and after that it's back to the original teleport's position. The teleport's would be working two-way so if the user go to one it will port it to the second, and if he goes to the second, then it will be ported to the first.
Here is my code:
public class TeleportScript : MonoBehaviour {
public GameObject otherTeleport;
private Collider otherTeleportColl;
public bool isNeedToTeleport;
private TeleportScript otherTeleportScript;
private GameObject player;
private Bounce bounceScript;
// Use this for initialization
void Start () {
isNeedToTeleport = true;
otherTeleportColl = otherTeleport.GetComponent<Collider>();
otherTeleportScript = otherTeleport.GetComponent<TeleportScript>();
player = GameObject.Find("Player");
bounceScript = player.GetComponent<Bounce>();
}
// Update is called once per frame
void Update () {
}
void OnTriggerEnter(Collider collider) {
if (isNeedToTeleport == true) {
if (collider.tag == "Enemy") {
otherTeleportScript.setNeedToTeleport(false);
collider.transform.position = otherTeleport.transform.position;
}
if (collider.tag == "Player") {
otherTeleportScript.setNeedToTeleport(false);
bounceScript.player.transform.position = otherTeleport.transform.position;
collider.transform.position = otherTeleport.transform.position;
bounceScript.playerTransform.position = otherTeleport.transform.position;
}
}
}
void OnTriggerExit(Collider collider) {
Debug.Log ("TELEPORTED OUT");
setNeedToTeleport(true);
otherTeleportScript.setNeedToTeleport(true);
}
public void setNeedToTeleport(bool value) {
if (value == true) {
isNeedToTeleport = true;
}
else {
isNeedToTeleport = false;
}
}
}
Any help would be appreciated!
I did some changes to your code, can't test it because I don't have access to unity right now, let me know if it works...
public class TeleportScript : MonoBehaviour {
public GameObject otherTeleport;
private Collider otherTeleportColl;
public bool isNeedToTeleport;
private TeleportScript otherTeleportScript;
private GameObject player;
private Bounce bounceScript;
// Use this for initialization
void Start () {
isNeedToTeleport = true;
otherTeleportColl = otherTeleport.GetComponent<Collider>();
otherTeleportScript = otherTeleport.GetComponent<TeleportScript>();
player = GameObject.Find("Player");
bounceScript = player.GetComponent<Bounce>();
}
// Update is called once per frame
void Update () {
}
void OnTriggerEnter(Collider collider) {
if(collider.tag == "Player" && isNeedToTeleport)
{
otherTeleportScript.setNeedToTeleport(false);
collider.transform.position = otherTeleport.transform.position;
bounceScript.player.transform.position = otherTeleport.transform.position; //not sure what is this for....
}
}
void OnTriggerExit(Collider collider) {
Debug.Log ("TELEPORTED OUT");
setNeedToTeleport(true);
otherTeleportScript.setNeedToTeleport(true);
}
public void setNeedToTeleport(bool value) {
if (value == true)
isNeedToTeleport = true;
else
isNeedToTeleport = false;
}
}
Your character teleports to the other place
after that you move the collider over there aswell, notice that the collider's object of other script of teleportation has been initialized with the isNeedToTeleport = true;
The character now also bounces there, hits the collider of the other object, it checks that isNeedToTelport is in fact true in this other script and it goes into the same If condition and teleports back!
Take this line:
otherTeleportScript.setNeedToTeleport(true);
out of your OnTriggerExit, or set it to false. Actually setting it to false might be better. You want to tell the other script that someone is incoming and it shouldn't immediately teleport them back.
As an aside, you should change your setNeedToTeleport function to this:
public void setNeedToTeleport(bool value) {
isNeedToTeleport = value;
}
Those ifs are redundant.