I am trying to configure this type of game where I have 6 players connected to the same room. On game start, the Master Client (first player turn) starts where the UI button should be enabled only to him and it should be disabled to all other players and after the first player click the button it will get disabled and for the 2nd player alone it should be enabled and so on to all other players.
So i have this idea where a loop should iterate through the list of players when a button is clicked by the first or master client then getNext() that is the next player as per the actor number. The problem is how do i put in code?
I was also recommended to use Custom properties but it has been a huge problem to me since i don't understand them. Watched some of the tutorials and also some documentation but I seem not to understand.
I am currently not using photonview component.
let me just write a short code of what i mean;
public class GameManager : MonoBehaviourPunCallbacks
{
private ExitGames.Client.Photon.Hashtable _myCustomProperties = new ExitGames.Client.Photon.Hashtable();
private void Start()
{
//onstart the master client should only be the one t view the button and click it
//his name should be displayed when it his turn
if (PhotonNetwork.IsMasterClient)
{
Player_Name.text = PhotonNetwork.MasterClient.NickName + " " + "it's your turn";
button.SetActive(true);
}
}
//onclcik the Button
public void TurnButton()
{
for(int i = 0; i<PhotonNetwork.Playerlist.length; i++)
{
//so every click of the button a loop should iterate and check the player if is the next to see the button and click it
//a name should also be displayed on his turn as per his nickname
}
}
}
There is no need for custom player properties here.
I would rather use room properties for that
public class GameManager : MonoBehaviourPunCallbacks
{
private const string ACTIVE_PLAYER_KEY = "ActivePlayer";
private const string ACTIVE_ME_FORMAT = "{0}, you it's your turn!!";
private const string ACTIVE_OTHER_FORMAT = "Please wait, it's {0}'s turn ...";
private Room room;
[SerializeField] private Button button;
[SerializeField] private Text Player_Name;
#region MonoBehaviourPunCallbacks
private void Awake()
{
button.onClick.AddListener(TurnButton):
button.gameObject.SetActive(false);
Player_Name.text = "Connecting ...";
// Store the current room
room = PhotonNetwork.CurrentRoom;
if (PhotonNetwork.IsMasterClient)
{
// As master go active since usually this means you are the first player in this room
// Get your own ID
var myId = PhotonNetwork.LocalPlayer.ActorNumber;
// and se it to active
SetActivePlayer(myId);
}
else
{
// Otherwise fetch the active player from the room properties
OnRoomPropertiesUpdate(room.CustomProperties);
}
}
// listen to changed room properties - this is always called if someone used "SetCustomProperties" for this room
// This is basically the RECEIVER and counter part to SetActivePlayer below
public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
{
// Maybe another property was changed but not the one we are interested in
if(!propertiesThatChanged.TryGetValue(ACTIVE_PLAYER_KEY, out var newActiveID)) return;
// if we got a value but it's not an int something is wrong in general
if(!(newActiveID is int newActvieIDValue))
{
Debug.LogError("For some reason \"ACTIVE_PLAYER_KEY\" is not an int!?");
return;
}
// otherwise apply the ID
ApplyActivePlayer(newActvieIDValue);
}
// Optional but might be important (?)
// In the rare case that the currently active player suddenly leaves for whatever reason
// as the master client you might want to handle this and go to the next player then
//public override void OnPlayerLeftRoom (Player otherPlayer)
//{
// if(!PhotonNetwork.IsMasterClient) return;
//
// if(!propertiesThatChanged.TryGetValue(ACTIVE_PLAYER_KEY, out var currentActiveID) && currentActiveID is int currentActiveIDValue) return;
//
// if(currentActiveIDValue != otherPlayer.ActorNumber) return;
//
// var nextPlayer = Player.GetNextFor(currentActiveIDValue);
// var nextPlayerID = nextPlayer.ActorNumber;
// SetActivePlayer(nextPlayerID);
//}
#endregion MonoBehaviourPunCallbacks
// Called via onClick
private void TurnButton()
{
// this gets the next player after you sorted by the actor number (=> order they joined the room)
// wraps around at the end
var nextPlayer = Player.GetNext();
// Get the id
var nextPlayerID = nextPlayer.ActorNumber;
// and tell everyone via the room properties that this is the new active player
SetActivePlayer(nextPlayerID);
}
// This writes the new active player ID into the room properties
// You can see this as kind of SENDER since the room properties will be updated for everyone
private void SetActivePlayer(int id)
{
var hash = new ExitGames.Client.Photon.Hashtable();
hash[ACTIVE_PLAYER_KEY] = id;
room.SetCustomProperties(hash);
}
// this applies all local changes according to the active player
private void ApplyActivePlayer(int id)
{
// get the according player
var activePlayer = Player.Get(id);
// Am I this player?
var iAmActive = PhotonNetwork.LocalPlayer.ActorNumber == id;
// Set the button active/inactive accordingly
button.gameObject.SetActive(iAmActive);
// Set the text accordingly
Player_Name.text = string.Format(iAmActive ? ACTIVE_ME_FORMAT : ACTIVE_OTHER_FORMAT, activePlayer.NickName):
}
}
Related
So am currently developing a way where players in a current room are in a waiting room entering or selecting their specific item. When each player has completed a button is clicked which is 'I am Ready!'...When a player clicks I am ready then an Int variable ReadyPlayers Updates according to how many have clicked the I am ready Button. If the readyplayers (int) match the number of players in the room then a countdown starts. The problem I am facing is configuring the custom properties. I wrote this code hoping that it would solve this problem but I end up getting logical errors where the readyplayers (int) are not counted from the players.
using Hashtable = ExitGames.Client.Photon.Hashtable;
//Variables
public int ReadyPlayers = 0;
//added this to the button on the inspector : onclick event
public void OnClickIamReady()
{
ReadyPlayers = (int)PhotonNetwork.LocalPlayer.CustomProperties["PlayerReady"];
ReadyPlayers++;
Hashtable hash = new Hashtable() { { "PlayerReady", ReadyPlayers } };
PhotonNetwork.LocalPlayer.SetCustomProperties(hash);
//when a player clicks this button deactivate it
IamReadyButton.gameObject.SetActive(false);
foreach (Player player in PhotonNetwork.PlayerList)
{
Debug.Log(player.NickName.ToString() + " " + " is Ready. " + " " + " Players Who are Ready : " + player.CustomProperties["PlayerReady"].ToString());
}
}
It makes no sense that every player uses his own custom properties in order to store how many ready players he is aware of in total.
Instead you rather want to set only yourself to ready.
Then check of everyone is ready and start the match. But you don't want to make this check on the clients themselves. What about network lag? What if two clients press the button at the same time and the properties are not synced yet?
-> I would rather only check on the Master client within OnPlayerPropertiesUpdate
So something like
using System.Linq;
...
public void OnClickIamReady()
{
// Do not use a new hashtable everytime but rather the existing
// in order to not loose any other properties you might have later
var hash = PhotonNetwork.LocalPlayer.CustomProperties;
hash["Ready"] = true;
PhotonNetwork.LocalPlayer.SetCustomProperties(hash);
IamReadyButton.gameObject.SetActive(false);
if(!PhotonNetwork.IsMasterClient) return;
CheckAllPlayersReady ();
}
public override void OnPlayerPropertiesUpdate (Player targetPlayer, Hashtable changedProps)
{
if(!PhotonNetwork.IsMasterClient) return;
if(!changedProps.ContainsKey("Ready")) return;
CheckAllPlayersReady();
}
public override void OnMasterClientSwitched(Player newMasterClient)
{
if(newMasterClient != PhotoNework.LocalPlayer) return;
CheckAllPlayersReady ();
}
private void CheckAllPlayersReady ()
{
var players = PhotonNetwork.PlayerList;
// This is just using a shorthand via Linq instead of having a loop with a counter
// for checking whether all players in the list have the key "Ready" in their custom properties
if(players.All(p => p.CustomProperties.ContainsKey("Ready") && (bool)p.CustomProperties["Ready"]))
{
Debug.Log("All players are ready!");
}
}
i'm currently working on a Unity project in which i need to update 8 Slider values from a JSON file. I tested the loading part and everything works but when i apply the values to the sliders, they dont behave like i thought they would.
I have a OnButton function that: 1. Triggers JSON Loading -> 2. Passes the loaded JSON values to static variables -> 3. Calls a function to update the sliders.
Now here is the problem: On Button press, only one slider at a time gets updated, top to bottom and I do not understand why. So in order for every time you load a json file, you have to press the LOAD button up to 8 times, before every slider is updated. I use the same LoadSliders function for when the UI loads and it works there.
Thanks for your help!
using UnityEngine;
using UnityEngine.UI;
.
.
.
public void UpdateSettings(BoidPresetSaveData loadedSave)
{
maxSpeed = loadedSave.maxSpeed;
maxForce = loadedSave.maxForce;
arriveRadius = loadedSave.arriveRadius;
desiredSeparation = loadedSave.desiredSeparation;
neighbourDistance = loadedSave.neighbourDistance;
//Weights
separation = loadedSave.separation;
alignment = loadedSave.alignment;
cohesion = loadedSave.cohesion;
wander = loadedSave.wander;
avoidWalls = loadedSave.avoidWalls;
}
public void LoadSliders()
{
maxSpeedSlider.value = maxSpeed;
maxForceSlider.value = maxForce;
arriveRadiusSlider.value = arriveRadius;
desiredSeperationSlider.value = desiredSeparation;
neighbourDistanceSlider.value = neighbourDistance;
separationSlider.value = separation;
alignmentSlider.value = alignment;
cohesionSlider.value = cohesion;
wanderSlider.value = wander;
avoidWallsSlider.value = avoidWalls;
}
And the OnButton function isnt very fancy either:
public void LoadPreset()
{
var loadKey = presetDropdown.captionText.text;
if (!SaveManager.SaveExists(loadKey,folderKey))
{
Debug.Log("Preset file does not exist.");
return;
}
BoidPresetSaveData loadedSave = SaveManager.Load<BoidPresetSaveData>(loadKey, folderKey);
BoidSettings.instance.UpdateSettings(loadedSave);
BoidSettings.instance.LoadSliders();
}
Ok, i found out, that the slider.value call also invokes the OnSliderChange event. With this in mind, the code above is not functional because you created a kind of a loop there.
Also I lied that this works on Gui load, i just did not test enough.
I had to split up the UpdateSettings function, so that every slider gets its own OnSliderChange method. So the functional code looks like this:
public void UpdateMaxSpeed()
{
maxSpeed = maxSpeedSlider.value;
}
public void UpdateMaxForce()
{
maxForce = maxForceSlider.value;
}
public void UpdateArriveRadius()
{
arriveRadius = arriveRadiusSlider.value;
}public void UpdateDesiredSeparation()
{
desiredSeparation = desiredSeperationSlider.value;
}
public void UpdateNeighbourDistance()
{
neighbourDistance = neighbourDistanceSlider.value;
}
public void UpdateSeparation()
{
separation = separationSlider.value;
}
public void UpdateAlignment()
{
alignment = alignmentSlider.value;
}
public void UpdateCohesion()
{
cohesion = cohesionSlider.value;
}
public void UpdateWander()
{
wander = wanderSlider.value;
}
public void UpdateAvoidWalls()
{
avoidWalls = avoidWallsSlider.value;
}
There may be a cleaner method for this, but I do not know enough about C# and Unity to give an educated guess about this.
I created a script where by clicking on the smartphone display, an object was activated. I used Input.GetMouseButtonDown(0) to check if a tap was made on the display. The script works fine, but I have a problem: if I insert a button in the scene, clicking on the button I receive both the click of the button and that of the Input.GetMouseButtonDown function. How could I "divide" the two touches?
// Example
void Update()
{
if (Input.GetMouseButtonDown(0))
myObject.SetActive(true);
}
public void PauseButton()
{
// Open Pause Panel
}
I was thinking of deleting "Input.GetMouseButtonDown (0)" and using an invisible button and resizing it on the portion of the display I needed.
Add Tag to your UI button like: "UI"
Below function tells whether a click was Over a UI object tagged with a UI tag, so you can check in your screen tap function if you should fire your event or not
public static class InputHandler
{
public const string CompareTagForUIElements = "UI";
public static bool IsPointerOverUIObject(int touchId)
{
var eventDataCurrentPosition = new PointerEventData(EventSystem.current);
// I'm using touch since you are talking about smartphones,
// if you still need click use Input.mousePosition
eventDataCurrentPosition.position = new Vector2(GetTouch(touchId).position.x, GetTouch(touchId).position.y);
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
for (int i = 0; i < results.Count; i++)
{
if (results[i].gameObject.CompareTag(CompareTagForUIElements))
return true;
}
return false;
}
}
So rather than
public void ITappedOnScreen()
{
if(Input.GetMouseButtonDown(0))
Debug.Log("Screen tapped");
}
use
public void ITappedOnScreen()
{
if(Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Begin &&
!InputHandler.IsPointerOverUIObject(0))
Debug.Log("Screen tapped");
}
I am new to Unity, and many of these principals I am just blanking on.
I build a dialogue system and its working great, but I want to make a confirmation box.
I have managed to get the confirmation box to pull up, but I cant seem to figure out how to wait for one of the Yes / No buttons are pushed before the code is returned to the Dialogue manager with the proper bool.
Right now the code pushes on behind the dialogue box. Which is expected.
I tried an enumerator but I must have been doing it wrong because it did not matter.
I just cleaned up the code to try to start from scratch.
public bool AskForTakingMoney(){
takingMoneyBox.SetActive(true);
goldText.text = "Current Gold: " + GameManager.instance.currentGold.ToString() + "G";
questionText.text = "Pay the " + DialogManager.instance.goldAmount + "G?";
//Wait here until yes or no button pushed
return saysYes;
}
public void SaysYes(){
saysYes = true;
selectedAnswer = true;
takingMoneyBox.SetActive(false);
}
public void SaysNo(){
saysYes = false;
selectedAnswer = true;
takingMoneyBox.SetActive(false);
}
I really just need the return function to not go back until the yes or no button is pushed.
I am at a complete loss.
If you want to show a popup, then the popup should be responsible for what happens next.
So your AskForTakingMoney should never return a value. Instead you need to assign the onclick events for the Yes and No button.
Your code should simply be:
public void AskForTakingMoney(){
takingMoneyBox.SetActive(true);
goldText.text = "Current Gold: " + GameManager.instance.currentGold.ToString() + "G";
questionText.text = "Pay the " + DialogManager.instance.goldAmount + "G?";
}
public void SaysYes(){
takingMoneyBox.SetActive(false);
// Your withdraw money code here.
}
public void SaysNo(){
takingMoneyBox.SetActive(false);
}
Then from within the Editor, click on your Yes Button.
You should see an onclick field like the one in the picture. Click on the plus sign, then drag in your MonoBehaviour object which is holding the code with above functions.
After dragging it in, You should be able to select SaysYes from the dropdown that appears under 'YourScriptName' -> 'SaysYes()'
Do the same for your No Button and you should be all set.
You would either need the function to not care about returning what they clicked, and each frame check (in the update method, or in a coroutine) if the button has been pressed by checking your selectedAnswer variable.
Or you could run some code in a separate thread, and use WaitHandle's, to cause the thread to pause until the button has been pressed.
Example using your code:
using System.Threading;
using UnityEngine;
public class Manager : MonoBehaviour
{
EventWaitHandle handle = new EventWaitHandle(false,EventResetMode.AutoReset);
Thread _thread;
public void StartThread()
{
_thread = new Thread(BackgroundThreadFunction);
_thread.Start();
}
public void BackgroundThreadFunction()
{
if (AskForTakingMoney())
{
//Yes
}
else
{
//No
}
}
public bool AskForTakingMoney()
{
takingMoneyBox.SetActive(true);
goldText.text = "Current Gold: " + GameManager.instance.currentGold.ToString() + "G";
questionText.text = "Pay the " + DialogManager.instance.goldAmount + "G?";
//Wait here until yes or no button pushed
handle.WaitOne();
return saysYes;
}
public void SaysYes()
{
saysYes = true;
selectedAnswer = true;
takingMoneyBox.SetActive(false);
handle.Set();
}
public void SaysNo()
{
saysYes = false;
selectedAnswer = true;
takingMoneyBox.SetActive(false);
handle.Set();
}
}
Make Two Buttons, Set them to active at the end of AskForTakingMoney(), Make those buttons lead to SaysYes(); and SaysNo();
Edit: Comment if you need clarification. I may not be understanding you clear enough. Do you even have buttons set up?
Hey guys I have been building a shop menu that is activated when the button is pressed. I have four buttons in the menu that correspond to several buy able items in a grid. The buttons and text and Image attached are inside a GameObject. I'm trying to switch between menus with SetActive. I've successfully used SetActive to open and close the menu but when I try with the other portions they stay inactive regardless. Even when the button supposedly activating is pressed. The picture I uploaded shows the hierarchy of the shop. The buttons are under ShopSelection and the actual menu is Under Shop
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour {
private static UIManager instance;
public static UIManager Instance;
public GameObject shopMenu, motherShips, resources, fleets, research;
bool menuIsOpen = false;
bool motherShipOpen = false;
bool resourcesOpen = false;
bool fleetOpen = false;
public Button shop, motherShipButton, resourceButton, fleetButton, exitButton;
void Start () {
shopMenu.SetActive(false);
//Open Shop Menu
Button btn = shop.GetComponent<Button>();
btn.onClick.AddListener(TaskOnClick);
//Open Motherships portion of shop
Button btn1 = shop.GetComponent<Button>();
btn1.onClick.AddListener(TaskOnClick3);
//Open resources portion of shop
Button btn2 = shop.GetComponent<Button>();
btn2.onClick.AddListener(TaskOnClick4);
//Open fleet portion of shop
Button btn3 = shop.GetComponent<Button>();
btn3.onClick.AddListener(TaskOnClick5);
//Open Research portion of shop
Button btn4 = shop.GetComponent<Button>();
btn4.onClick.AddListener(TaskOnClick6);
//Exit Menu
Button bttn = exitButton.GetComponent<Button>();
bttn.onClick.AddListener(TaskOnClick2);
}
// Update is called once per frame
void Update () {
}
void TaskOnClick()
{
shopMenu.SetActive(true);
menuIsOpen = true;
motherShips.SetActive(true);
}
void TaskOnClick2()
{
if (menuIsOpen == true)
{
shopMenu.SetActive(false);
menuIsOpen = false;
}
}
void TaskOnClick3()
{
if(menuIsOpen == true)
{
//Open Motherships Shop
motherShips.SetActive(true);
resources.SetActive(false);
fleets.SetActive(false);
}
}
void TaskOnClick4()
{
if (menuIsOpen == true)
{
//Open resources Shop
motherShips.SetActive(false);
resources.SetActive(true);
fleets.SetActive(false);
}
}
void TaskOnClick5()
{
if(menuIsOpen == true)
{
//Open Fleet Shop
motherShips.SetActive(false);
resources.SetActive(false);
fleets.SetActive(true);
}
}
void TaskOnClick6()
{
if (menuIsOpen == true)
{
//Open Research Page
motherShips.SetActive(false);
resources.SetActive(false);
fleets.SetActive(false);
}
}
}
You added all callbacks allways to the shop button e.g.
Button btn2 = shop.GetComponent<Button>();
btn2.onClick.AddListener(TaskOnClick4);
not to the supposed buttons motherShipButton, resourceButton, fleetButton
Note that you don't have to call GetComponent again if you declare the variable as Button it will automatically reference the Button component of the drag&dropped GameObject in the Inspector so instead it should simply be
shop.onClick.AddListener(TaskOnClick);
//Open Motherships portion of shop
motherShipButton.onClick.AddListener(TaskOnClick3);
//Open resources portion of shop
resourcesButton.onClick.AddListener(TaskOnClick4);
//Open fleet portion of shop
fleetButton.onClick.AddListener(TaskOnClick5);
//Open Research portion of shop
// Whatever button this belongs to
???.onClick.AddListener(TaskOnClick6);
//Exit Menu
exitButton.onClick.AddListener(TaskOnClick2);
Now also name the methods accordingly e.g. OpenMenu, CloseMenu etc. that would make it a lot easier for you and everyone else to understand the code.
I also doubt that you need the check for if(menuIsOpen). Since the other GameObjects are children of the menu anyway they will simply stay invisible even if SetActive was called. Instead I would make sure that only one panel is active by using a method with e.g. an enum instead like
enum MenuPanel
{
MotherShips,
Resources,
Fleets,
Research
}
private void ShowPanel(MenuPanel panel)
{
// First disable all
motherShips.SetActive(false);
fleets.SetActive(false);
resources.SetActive(false);
// Than only activate according panel
switch(panel)
{
case MenuPanel.MotherShips:
motherShips.SetActive(true);
break;
case MenuPanel.Reaources:
resources.SetActive(true);
break;
case MenuPanel.Fleets:
fleets.SetActive(true);
break;
}
}
And than I would still use dedicated methods for each panel like e.g.
void Start()
{
//...
motherShipButton.onClick.AddListener(ShowMotherShips);
//...
}
private void ShowMotherShips()
{
ShowPanel(MenuPanel.MotherShips);
}
you could do it also directly as lambda expression
void Start()
{
//...
motherShipButton.onClick.AddListener(() => {
ShowPanel(MenuPanel.MotherShips);
});
//...
}
I usually try to avoid it since 1. you can not reuse this expression but you might want to call it from else where as well and 2. you will not be able to use RemoveListener to eventually get rid of that call.