Unity mirror sync between clients - unity3d

Im trying a basic multiplayer card game and want to sync the ammount of cards between the player. If a player draws a card the main deck loses one and depending on who drawed the card myDeck or the enmy deck gets one added.
At runtime if one player draws a card it works as intended but if the second player also draws a card the counter gets resetted.
public class Player : NetworkBehaviour{
private GameObject myHandText;
private GameObject enemyText;
private GameObject deckText;
private int deckCount;
private int myDeck;
private int enemyDeck;
private void Update()
{
if (Input.GetKeyDown("e"))
{
CmdDrawCard();
}
}
public override void OnStartClient()
{
base.OnStartClient();
myHandText = GameObject.Find("Me");
enemyText = GameObject.Find("Emeny");
deckText = GameObject.Find("Deck");
deckCount = 50;
myDeck = 0;
enemyDeck = 0;
RpcSetText();
}
[Command]
public void CmdDrawCard()
{
RpcSetDeck();
}
[ClientRpc]
void RpcSetDeck()
{
if (deckCount > 0)
{
deckCount--;
if (hasAuthority)
{
myDeck++;
}
else
{
enemyDeck++;
}
}
RpcSetText();
}
[ClientRpc]
void RpcSetText()
{
enemyText.GetComponent<Text>().text = "Enemy: " + enemyDeck;
myHandText.GetComponent<Text>().text = "My Deck: " + myDeck;
deckText.GetComponent<Text>().text = "Deck: " + deckCount;
}
}
I already tried too SyncVars but it changes nothing. It seems like each Player got two different counters for each variable.

Related

How can I check FindGameObjectsWithTag during runtime?

I'm having troubles with my enemy NavMeshAgent AI, First it searches for GameObjects tagged with "Defenses", then I set the first destination based in how close the defense is by my enemy, when the defense is destroyed, the array value is increased by one and it changes the destination to the next defense.
My problem is, my player can create (Instance) new defenses during the game, when all my defenses are destroyed, my enemy turns crazy, so, i need a way to add those new defenses to my array, below is my enemy script.
public class NavMeshEnemy : MonoBehaviour
{
[Header("References", order = 0)]
[SerializeField] NavMeshAgent enemyAgent;
[Space(10)]
[SerializeField] GameObject[] destinations;
[Space(10)]
[SerializeField] float distanceObjects;
[SerializeField] int arrayElements = 0;
void Awake()
{
enemyAgent = GetComponent<NavMeshAgent>();
destinations = GameObject.FindGameObjectsWithTag("Defenses");
}
void Start()
{
destinations = destinations.OrderBy((d) => (d.transform.position - this.transform.position).sqrMagnitude).ToArray();
}
void Update()
{
distanceObjects = Vector3.Distance(this.transform.position, destinations[arrayElements].transform.position);
enemyAgent.destination = destinations[arrayElements].transform.position;
CheckArray();
}
void CheckArray()
{
if (destinations[arrayElements].gameObject.activeInHierarchy == false)
{
if (arrayElements < destinations.Length - 1)
{
arrayElements++;
enemyAgent.destination = destinations[arrayElements].transform.position;
}
else
{
arrayElements = 0;
}
}
}
}
Thank you for reading! :)
I would rather implement a component on your prefab with a static list like e.g.
public class NavMeshDestination : MonoBehaviour
{
public static HashSet<Transform> existingDestinations = new HashSet<Transform>();
private void Awake()
{
if(!existingDestinations.Contains(this)) existingDestinations.Add(this);
}
private void OnDestroy()
{
if(existingDestinations.Contains(this)) existingDestinations.Remove(this);
}
}
And then don't even go by tag but simply do
public class NavMeshEnemy : MonoBehaviour
{
[Header("References", order = 0)]
[SerializeField] NavMeshAgent enemyAgent;
[Space(10)]
[SerializeField] float distanceObjects;
[SerializeField] int arrayElements = 0;
void Awake()
{
if(!enemyAgent) enemyAgent = GetComponent<NavMeshAgent>();
}
void Update()
{
destinations = NavMeshDestination.existingDestinations.OrderBy(d => (d.transform.position - this.transform.position).sqrMagnitude).ToArray();
distanceObjects = Vector3.Distance(this.transform.position, destinations[arrayElements].transform.position);
enemyAgent.destination = destinations[arrayElements].transform.position;
CheckArray();
}
void CheckArray()
{
if (destinations[arrayElements].gameObject.activeInHierarchy == false)
{
if (arrayElements < destinations.Length - 1)
{
arrayElements++;
enemyAgent.destination = destinos[arrayElements].transform.position;
}
else
{
arrayElements = 0;
}
}
}
}
Note however that this still won't fix your issues with no available destination. So you probably should stop the execution if the array is empty like
if(NavMeshDestination.existingDestinations.Count == 0)
{
// Stop the enemyAgent
return;
}

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

How to display win panel once the item left equal to zero?

I want to display win panel in the game once itemLeft = 0. But still cant figure out how and what is the error about. Below shows my getScore coding:-
public GameObject scoretext;
public GameObject itemlefttext;
public GameObject finalScore;
public static float score = 0;
public GameObject winPanel;
private void Start()
{
scoretext.GetComponent<Text>().text = "0";
setscore(0);
}
private void Update()
{
itemlefttext.GetComponent<Text>().text = "" + GameObject.FindGameObjectsWithTag("draggableobject").Length;
if (GameObject.FindGameObjectsWithTag("draggableobject").Length == 0)
{
winPanel.SetActive(true);
}
}
public void setscore(float scoretoadd)
{
score += scoretoadd;
scoretext.GetComponent<Text>().text = score.ToString("F0");
finalScore.GetComponent<Text>().text = score.ToString("F0");
}
There are many ways to implement this.
With your current code structure:
private void Update()
{
itemlefttext.GetComponent<Text>().text = ""+GameObject.FindGameObjectsWithTag("draggableobject").Length;
//itemLeftTxt = GameObject.FindGameObjectWithTag("Text").GetComponent<Text>();
itemLeftTxt.text = gameObject.GetComponent<Text>().text;
if (itemLeftTxt.text == "0")
{
winPanel.SetActive(true);
}
}
Minor Improvement:
private void Update()
{
itemlefttext.GetComponent<Text>().text = "" + GameObject.FindGameObjectsWithTag("draggableobject").Length;
//itemLeftTxt = GameObject.FindGameObjectWithTag("Text").GetComponent<Text>();
itemLeftTxt.text = gameObject.GetComponent<Text>().text;
if (GameObject.FindGameObjectsWithTag("draggableobject").Length == 0)
{
winPanel.SetActive(true);
}
}
If those draggable objects are not spawned on runtime then you can create a public variable and assign a reference to them through the inspector OR
New way:
public GameObject[] DraggableObjects;
Add this to the start function:
DraggableObjects = GameObject.FindGameObjectsWithTag("draggableobject");
itemLeftTxt = gameObject.GetComponent<Text>();
You can delete extra line of codes:
Final Update function:
private void Update()
{
itemlefttext.text = "" + DraggableObjects.Length;
if (DraggableObjects.Length == 0)
{
winPanel.SetActive(true);
}
}
Final Start Function:
private void Start()
{
DraggableObjects = GameObject.FindGameObjectsWithTag("draggableobject");
itemLeftTxt = GetComponent<Text>();
}
PS: Calling Gameobject.FindGameObjectsWithTag inside the update would be heavy on processor. Let me know if it helps.

want to get screen point on render texture camera unity3d

I am making sniper game, I have 2 cameras, one is rendering whole environment, and second one is in scope of the gun, which has high zoom quality.
I am able to show, enemy point on screen through main camera. and it shows proper on main camera, but in scope camera(2nd camera), it not showing properly. I want to show these points exactly same as enemy is.
here is my script to show point so far. I am attaching screen shots, first one is for scope camera and does not showing proper place, and 2nd one is for main camera, showing at proper place (Red circle is pointing)
using UnityEngine;
using UnityEngine.UI;
public class identity_shower : MonoBehaviour {
public Texture tex;
public GameObject helt;
public bool target_is_high =false;
private int counter_for_high = -1;
private int counter_for_low = -1;
private int counter_value = 50;
private bool bool_for_high = false;
private bool bool_for_low = false;
private Camera scope_cam_active;
private RectTransform rectTransform;
private Vector2 uiOFFset;
// Update is called once per frame
void OnGUI()
{
GameObject[] objs = GameObject.FindGameObjectsWithTag("enemy_soldier");
if( objs.Length<=0 )
{
return;
}
if (bool_for_high)
{
Vector2 pos_to_display = Camera.main.WorldToScreenPoint (this.transform.position);
GUI.DrawTexture (new Rect(pos_to_display.x-10.0f,(Screen.height- pos_to_display.y)-40.0f,15,15),tex);
}
}
public void show_your_identity()
{
if (target_is_high) {
if (counter_for_high >= counter_value)
{
return;
}
counter_for_high = counter_for_high + 1;
if (counter_for_high >= counter_value)
{
bool_for_high = true;
}
} else if(!target_is_high)
{
if (counter_for_low >= counter_value)
{
return;
}
counter_for_low = counter_for_low + 1;
if (counter_for_low >= counter_value)
{
bool_for_low = true;
}
}
}
}

Reference to a particular Prefab clone

I am developing a simple 2D game. In game, I've created a prefab for charcaters. and I am changing sprite of prefab runtime. This all execute fine. Now I want to apply click event on a particular prefab clone and want to increase scale of prefab. I am attaching a c# script what I have did till now.
public class ShoppingManager : MonoBehaviour {
public static ShoppingManager instance;
[System.Serializable]
public class Shopping
{
public string CharacterName;
public Sprite CharacterSprite;
}
public GameObject CharacterPrefab;
public Transform CharacterSpacer;
public List<Shopping> ShoppingList;
private CharacterScript NewCharacterScript;
/*********************************************Awake()******************************************/
void Awake()
{
MakeSingleton ();
}
/******************************Create object of the script**********************************/
void MakeSingleton()
{
instance = this;
DontDestroyOnLoad (gameObject);
}
// Use this for initialization
void Start () {
LoadCharacters ();
}
void LoadCharacters()
{
foreach (var characters in ShoppingList) {
GameObject NewCharacter = Instantiate (CharacterPrefab) as GameObject;
NewCharacterScript = NewCharacter.GetComponent<CharacterScript> ();
NewCharacterScript.CharacterName = characters.CharacterName;
NewCharacterScript.Charcater.GetComponent<Image> ().sprite = characters.CharacterSprite;
NewCharacterScript.GetComponent<Button> ().onClick.AddListener (() => CharacterClicked (NewCharacterScript.CharacterName, NewCharacterScript.Charcater));
NewCharacter.transform.SetParent (CharacterSpacer, false);
}
}
void CharacterClicked(string CharacterName, GameObject Char)
{
StartCoroutine (IncreaseScale (Char));
}
IEnumerator IncreaseScale(GameObject TempCharacter)
{
int i = 5;
while (i > 0) {
yield return new WaitForSeconds (0.1f);
Vector3 TempVector = TempCharacter.GetComponent<RectTransform> ().localScale;
TempVector.x = TempVector.x + 0.2f;
TempVector.y = TempVector.y + 0.2f;
TempCharacter.GetComponent<RectTransform> ().localScale = TempVector;
i--;
}
}
}
This code triggers click event and also it increases scale but of last clone, not of clicked prefab clone. What I am missing, I can't understand. What should I correct in this. and Yeah! I am also attaching code of a script that I've added on prefab.
public class CharacterScript : MonoBehaviour {
public string CharacterName;
public GameObject Charcater;
}
create collider for your object attach the script below to it this way each object is responsible for handling its own functionalities like increasing its own size
public class characterFunctionalities: MonoBehaviour{
void OnMouseDown()
{
StartCoroutine (IncreaseScale (this.gameobject));
}
IEnumerator IncreaseScale(GameObject TempCharacter)
{
int i = 5;
while (i > 0) {
yield return new WaitForSeconds (0.1f);
Vector3 TempVector = TempCharacter.GetComponent<RectTransform> ().localScale;
TempVector.x = TempVector.x + 0.2f;
TempVector.y = TempVector.y + 0.2f;
TempCharacter.GetComponent<RectTransform> ().localScale = TempVector;
i--;
}
}
}