I want to create a multiplayer application using Photon (Pun2). After a player joins the room, I want to hide some buttons and display others for all the players in that room. At the moment I have this function:
public override void OnPlayerEnteredRoom(Player newPlayer)
{
print(newPlayer); // #02
if (PhotonNetwork.CurrentRoom.PlayerCount == 2 && PhotonNetwork.IsMasterClient)
{
print(PhotonNetwork.CurrentRoom.PlayerCount + "/2 Starting game...");
searchingBtn.SetActive(false); // hide this button
findMatchBtn.SetActive(false); // hide this button
scanningBtn.SetActive(true); // display this button
}
}
But this won't work, because only for 1 of the 2 players the buttons will change, for the other one they will stay the same.
I saw some examples on the Internet, where in this function OnPlayerEnteredRoom it's called PhotonNetwork.LoadLevel(scene_index), but at that moment, I don't want to load another scene, I want to stay in the current scene and just hide/display some buttons. Is this even possible?
EDIT: Here is the final version
public override void OnJoinedRoom()
{
Debug.Log("You joined the room.");
Debug.Log(PhotonNetwork.CurrentRoom.PlayerCount);
Debug.Log(PhotonNetwork.IsMasterClient);
UpdateButtons();
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
Debug.Log("Other players joined the room.");
if (PhotonNetwork.CurrentRoom.PlayerCount > 1 && PhotonNetwork.IsMasterClient)
{
Debug.Log(PhotonNetwork.CurrentRoom.PlayerCount + "/2 Starting game...");
UpdateButtons();
}
}
private void UpdateButtons()
{
searchingBtn.SetActive(false); // hide this button
findMatchBtn.SetActive(false); // hide this button
scanningBtn.SetActive(true); // display this button
}
From Photon Documentation:
virtual void OnJoinedRoom ()
//Called when the LoadBalancingClient entered a room, no matter if this client
//created it or simply joined.
virtual void OnPlayerEnteredRoom (Player newPlayer)
//Called when a remote player entered the room. This Player is already added to
//the playerlist.
With this knowledge, OnPlayerEnteredRoom is only called when a remote player joins the room. So in order to solve your problem, you'll need to call your code in the OnJoinedRoom callback for the local client and OnPlayerEnteredRoom when a remote player joins.
In summary, your new code should look like this:
public override void OnPlayerEnteredRoom(Player newPlayer)
{
UpdateButtons(newPlayer);
}
public override void OnJoinedRoom(Player player)
{
UpdateButtons(player);
}
private void UpdateButtons(Player player)
{
if (PhotonNetwork.CurrentRoom.PlayerCount > 1 && PhotonNetwork.IsMasterClient)
{
print(PhotonNetwork.CurrentRoom.PlayerCount + "/2 Starting game...");
searchingBtn.SetActive(false); // hide this button
findMatchBtn.SetActive(false); // hide this button
scanningBtn.SetActive(true); // display this button
}
}
Related
I have created a simple custom editor tool, which allows me to keep mouse position in a straight line. I require this to draw texture on a terrain in a straight line. Unfortunately, when I enable "Paint texture" tool in the terrain editor in inspector, my custom tool gets disabled and vice-versa. How can I keep both my custom tool and terrain paint tool enabled at once?
Custom tool selected but paint texture is deactivated-
Custom tool got deselected on paint texture selection-
Following is the OnToolGUI method
public override void OnToolGUI(EditorWindow window)
{
HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
Event e = Event.current;
if (!(window is SceneView))
return;
if (!ToolManager.IsActiveTool(this))
return;
if (e.shift)
{
if (e.type == EventType.MouseDown)
{
if (e.button == 0)
{
downY = e.mousePosition.y;
}
}
if (e.type == EventType.MouseDrag)
{
if (e.button == 0)
{
e.mousePosition = new Vector2(e.mousePosition.x, downY);
Debug.Log("Mouse Position: " + e.mousePosition);
}
}
}
As mentioned in the comments I guess it simply is the nature of the tools that they are exclusive and you can only have one active at a time.
As alternative I would rather simply
enable/disable this via a general header menu entry
(optionally) store that decision persistent in EditorPrefs (pretty much like PlayerPrefs but for the editor itself)
and accordingly attach a listener to SceneView.duringSceneGui
This could look somewhat like e.g.
public static class StraightLineTool
{
// Used for the displayed menu labels
// and also simply (ab)used as the unique key for the EditorPrefs
private const string k_MenuName = "My Tools/Straight Line Tool";
private static float downY;
// Property for simplifying access and setting more centralized
private static bool IsEnabled
{
get => EditorPrefs.GetBool(k_MenuName, false);
set => EditorPrefs.SetBool(k_MenuName, value);
}
// method to be called when clicking the menu button
[MenuItem(k_MenuName)]
private static void ToggleEnabled()
{
IsEnabled = !IsEnabled;
ApplySettings();
}
// adding a checkmark when is enabled and simply always allow to click it
[MenuItem(k_MenuName, true)]
private static bool ToggleEnabledValidate()
{
Menu.SetChecked(k_MenuName, IsEnabled);
return true;
}
// Called on every project loading or code recompilation
[InitializeOnLoadMethod]
private static void Initialize()
{
EditorApplication.delayCall -= ApplySettings;
EditorApplication.delayCall += ApplySettings;
}
private static void ApplySettings()
{
// remove so only happening once
EditorApplication.delayCall -= ApplySettings;
SceneView.duringSceneGui -= OnSceneGUI;
if (IsEnabled)
{
// if enabled start listening
SceneView.duringSceneGui += OnSceneGUI;
}
}
// Callback listening to any SceneView.duringSceneGui
private static void OnSceneGUI(SceneView sceneView)
{
// Not sure tbh what this does or if you need it still in this approach
// HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
var currentEvent = Event.current;
if (currentEvent.shift)
{
if (currentEvent.type == EventType.MouseDown)
{
if (currentEvent.button == 0)
{
downY = currentEvent.mousePosition.y;
}
}
if (currentEvent.type == EventType.MouseDrag)
{
if (currentEvent.button == 0)
{
currentEvent.mousePosition = new Vector2(currentEvent.mousePosition.x, downY);
Debug.Log("Mouse Position: " + currentEvent.mousePosition);
}
}
}
}
}
=> using this the tool will be enabled/disabled persistent even when restarting Unity
Then using this as start point you can probably still try to somehow integrate this somewhere more nicely into the SceneView menus - but maybe that is also overkill ;)
This question already has answers here:
How to make the script wait/sleep in a simple way in unity
(7 answers)
Closed 1 year ago.
I am trying to make it so that a button in Unity that can only be pressed and activated every few seconds. I have an array with famous quotes and when I press my button it does display these quotes, but if you press the button continuously then it will cycle through all of the quotes without the ability to be thoughtful and read through them. Now, I have tried something like below, but it did nothing (as in the button still was able to be pressed and no effect was apparent):
Bool pressed;
void Function () {
if (pressed) {
//code to call array
}
}
I thought that a simple combination of bool and an if loop would be sufficient, but this does not work. Am I close to this concept? What would need to be changed to get this operating?
There are a few things you can do to achieve this. Your current code is never toggling the bool pressed. You can just catch the unwanted button presses with a localized bool like you are doing now, but there might be a better approach.
I would consider changing the intractability of the button to also show the end-user that they can not press the button for a short amount of time.
using UnityEngine.UI;
// button that will be pressed
[SerializeField] private Button btn = null;
// time until the button is re-enabled after being pressed
private float timeToWait = 5f;
// reference to the coroutine that toggles our button
private Coroutine buttonDisabled = null;
private void Start()
{
// you can either add the OnClick in the inspector, or you can programmatically add it here
btn.onClick.AddListener(ClickButton);
}
public void ClickButton()
{
// if we have an ongoing coroutine do not start another
if(buttonDisabled != null)
return;
// start our coroutine to re-enable the button
buttonDisabled = StartCoroutine(DisableButtonForSeconds(timeToWait));
// put any other code needed here such as displaying a new quote
}
private IEnumerator DisableButtonForSeconds(float seconds)
{
// disable the button
btn.interactable = false;
// wait for our amount of time to re-enable
yield return new WaitForSeconds(seconds);
// re-enable the button
btn.interactable = true;
// reset our reference to be called again
buttonDisabled = null;
}
Assign the button in the editor to btn and set whatever time you want to wait between button presses for timeToWait. You will also need to assign the button's OnClick to ClickButton, either in code or in the inspector. With this solution the function will not be able to be called as the button itself will be grayed out and not be able to be clicked. After the timeToWait duration is over, the button will reactivate. Place whatever other code you need in the ClickButton method that drives your quote cycling.
Edit: Here is similar code but using an Invoke instead of Coroutine as requested
using UnityEngine.UI;
// button that will be pressed
[SerializeField] private Button btn = null;
// time until the button is re-enabled after being pressed
private float timeToWait = 5f;
private void Start()
{
// you can either add the OnClick in the inspector, or you can programmatically add it here
btn.onClick.AddListener(ClickButton);
}
public void ClickButton()
{
// do not start the function if we are already in the process
if (IsInvoking("ReEnableButton"))
return;
// disable our button interactability
btn.interactable = false;
// call our function ReenableButton in timeToWait seconds
Invoke("ReEnableButton", timeToWait);
}
private void ReEnableButton()
{
// re-enable the button
btn.interactable = true;
}
Edit 2: There are a few ways you can go about having one button disable all four buttons. In general practice, you do not want these buttons knowing about each other, so it would be a good idea to make a button manager that handles the OnClick now and sends the event to all Buttons it knows about.
using UnityEngine.UI;
public class ButtonManager : MonoBehaviour
{
// assign these button references in the inspector by dragging them into the list
[SerializeField] private List<YourButtonScript> YourButtons = new List<YourButtonScript>();
private void Start()
{
// assign the onClick of each button here INSTEAD of the Start() in the button class
// you can still have an onClick in the Button itself, but do NOT have it do the disabling
foreach(YourButtonScript btn in YourButtons)
{
// assign our onClick to callback to disable all buttons
btn.SetDisableOnclick(DisableAllButtons);
}
}
public void DisableAllButtons()
{
foreach(YourButtonScript btn in YourButtons)
btn.DisableButton();
}
}
public class YourButtonScript : MonoBehaviour
{
using UnityEngine.UI;
// button that will be pressed
[SerializeField] private Button btn = null;
// time until the button is re-enabled after being pressed
private float timeToWait = 5f;
// reference to the coroutine that toggles our button
private Coroutine buttonDisabled = null;
public delegate void DisableButtonCallback();
public void SetDisableOnclick(DisableButtonCallback callback)
{
// add the callback to the manager
btn.onClick.AddListener(delegate{callback();});
}
private void Start()
{
// do NOT call the ClickButton anymore from here as the manager handles it
// if you have other button specific data put it in HandleSpecificButtonQuoteData() now
btn.onClick.AddListener(HandleSpecificButtonQuoteData);
}
private void HandleSpecificButtonQuoteData()
{
// put whatever code here that is specific to JUST this button
// if it handles the quotes or what not, display it here
}
public void DisableButton()
{
// if we have an ongoing coroutine do not start another
if(buttonDisabled != null)
return;
// start our coroutine to re-enable the button
buttonDisabled = StartCoroutine(DisableButtonForSeconds(timeToWait));
}
private IEnumerator DisableButtonForSeconds(float seconds)
{
// disable the button
btn.interactable = false;
// wait for our amount of time to re-enable
yield return new WaitForSeconds(seconds);
// re-enable the button
btn.interactable = true;
// reset our reference to be called again
buttonDisabled = null;
}
}
Let me know if you have issues with this.
There are a lot of solutions to this problem floating around, but none of them seem to work properly for me. I have a button that opens a list of selections in a UI element, and I want it to close when clicked outside of it. I currently have this:
private void OnEnable()
{
EventSystem.current.SetSelectedGameObject(gameObject);
}
public void OnDeselect(BaseEventData eventData)
{
//Close the Window on Deselect only if a click occurred outside this panel
if (!mouseIsOver)
gameObject.SetActive(false);
}
public void OnPointerEnter(PointerEventData eventData)
{
mouseIsOver = true;
EventSystem.current.SetSelectedGameObject(gameObject);
}
public void OnPointerExit(PointerEventData eventData)
{
mouseIsOver = false;
EventSystem.current.SetSelectedGameObject(gameObject);
}
Which works fine on a PC, but unfortunately due to there not being an actual pointer on mobile, it closes the panel even if clicked inside. I have tried using something like this:
foreach (Touch touch in Input.touches)
{
int id = touch.fingerId;
if (EventSystem.current.IsPointerOverGameObject(id))
{
isClicked = true;
}
if (Input.GetMouseButtonDown(0))
{
// Check if the mouse was clicked over a UI element
if (EventSystem.current.IsPointerOverGameObject())
{
isClicked = true;
}
}
}
But that has not worked either. This seems like an incredibly simple problem and I don't understand why I can't find a simple solution to it.
I recomend using Unity technic when dropdown is created.
Look at this link
The main idea is to create blocker behind your button and the other ui elements.
Your open panel funcion should look something like this:
private RectTransform gameObjectTransform;
public void OpenPanel()
{
CreateBlocker();
gameObjectTransform = transform.parent;
transform.SetParent(GetComponentInParent<Canvas>());
transform.SetAsLastSibling();
}
Create blocker method like in docs:
private GameObject currentBlocker;
public void CreateBlocker()
{
GameObject gameObject = new GameObject("Blocker");
RectTransform rectTransform = gameObject.AddComponent<RectTransform>();
rectTransform.SetParent(ChatGraphics.Instance.mainCanvas, false);
rectTransform.anchorMin = (Vector2)Vector3.zero;
rectTransform.anchorMax = (Vector2)Vector3.one;
rectTransform.sizeDelta = Vector2.zero;
Canvas canvas = gameObject.AddComponent<Canvas>();
canvas.overrideSorting = true;
canvas.sortingLayerID = component.sortingLayerID;
canvas.sortingOrder = component.sortingOrder - 1;
gameObject.AddComponent<GraphicRaycaster>();
gameObject.AddComponent<Image>().color = Color.clear;
gameObject.AddComponent<Button>().onClick.AddListener(delegate { Hide(); });
currentBlocker = gameObject;
}
And last is Hide method which should be called every time (even if blocker does not triggered)
public void Hide()
{
if (currentBlocker != null)
Destroy(currentBlocker);
if (gameObjectTransform != null)
transform.SetParent(gameObjectTransform);
// Your code to hide your panel
}
I'm making a system to balance calls inside of the OnGUI method of a EditorWindow.
I'm doing the following:
public void Update()
{
Repaint();
}
Inside of my OnGUI method I'm calling this Balancer. I have one list with the callbacks (List).
So the idea is simple, some callvaxc
I'm skipping some repaint frames for the callback that has the complete GUI, and calling on each repaint for other callbacks (for example, a marquee label or dispalying gifs).
By some reason, this error happens "Getting control 0's position in a group with only 0 controls when doing repaint"
private int m_repaintCounter;
public void Draw()
{
Event e = Event.current;
try
{
foreach (var action in m_actions)
{
try
{
// Test 1
// MainAction is a class that inherits from Action (class MainAction : Action)
if (action is MainAction)
{
bool isDesignedType = e.rawType == EventType.Repaint || e.rawType == EventType.Layout;
if (isDesignedType)
++m_repaintCounter;
if (!(m_repaintCounter == 20 && isDesignedType))
continue;
else
m_repaintCounter = 0;
}
// Test 2
action.Value();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
}
catch
{
// Due to recompile the collection will modified, so we need to avoid the exception
}
}
But if I comment the "Test 1" everythings works fine.
On the ctor of the class we need to specify a callback to a GUI method, for example:
public Balancer(Action drawAction)
{
m_actions = new List<Action>();
m_actions.Add(drawAction);
}
So we could do easily (inside the EditorWindow):
private Balancer m_balancer;
public void OnEnable()
{
m_balancer = new Balancer(Draw);
}
public void Draw()
{
// This block will be called every 20 repaints as specified on the if statment
GUILayout.BeginHorizontal("box");
{
GUILayout.Button("I'm the first button");
GUILayout.Button("I'm to the right");
// This marquee will be called on each repaint
m_balancer.AddAction(() => CustomClass.DisplayMarquee("example"));
}
GUILayout.EndHorizontal();
}
// Inside of the Balancer class we have
// We use System.Linq.Expressions to identify actions that were added already
private HashSet<string> m_alreadyAddedActions = new HashSet<string>();
public void AddAction(Expression<Action> callback)
{
if(!m_alreadyAddedActions.Add(callback.ToString()))
return;
m_actions.Add(callback.Compile());
}
I can't figure this out. I couldn't find any information on the internet. Can anyone help me?
Ok, so, OnGui (IMGui) is awful to work with. If you aren't using it for an editor script, use the new 4.6 UI (UGui) instead.
Now then. The problem.
OnGui is called at least twice every frame. One of those is to calculate layouts and the other is to actually draw stuff ("repaint").
If the number of things, size of things, or anything else changes between these two calls then Unity will error with "Getting control 0's position in a group with only 0 controls when doing repaint."
That is: you cannot change UI state in the IMGui system at any point after Layout and before Repaint.
Only, only, only change state (and thereby which objects are being drawn) during Event.current == EventType.Repaint and only, only, only change state for the next frame (alternatively, do the changes during Event.current == EventType.Layout, provided that this same, new state will result in the same code path during Repaint). Do not, under any circumstances, make changes during Repaint that were not present during the previous Layout.
I'm working on porting a game engine from Java to Windows Phone 7 XNA. One thing I'm struggling with is how to create modal dialogs. The dialog is rendered in XNA using SpriteBatch just like everything else, but what I basically want is something like this:
result = Dialog.Ask("Some Question?", DialogButtons.YesNo);
Where Dialog.Ask doesn't return until the user clicks one of the buttons. The only thing I've done to come close is a method to continuously call RunOneFrame() on the game:
private int runLoopCount;
public void BeginRunLoop() {
int runIndex = ++runLoopCount;
while (runLoopCount == runIndex) {
RunOneFrame();
Thread.Sleep(1);
}
}
public void EndRunLoop() {
--runLoopCount;
}
There are a few problems with this:
RunOneFrame is only supposedly for debugging purposes.
Input doesn't work! Calling TouchPanel.GetState() or GamePad.GetState(PlayerIndex.One) doesn't return new values.
Is there any way to initiate a run loop without throwing away the Game class and all it does for initialization? And I don't really know how to do without the Game class anyway, as there is no Main() method in Windows Phone 7 XNA applications. It just goes right into the Game constructor.
What you want to do is to add another 'state', you just have to show dialog until user do not select something... and _modalDialogIsUp bool..
protected override void Update(GameTime gameTime)
{
if (_modalDialogIsUp)
{
// handle only secesary mouse and button clicks
}
else
{
// normal mouse and button clicks
}
}
protected override void Draw(GameTime gameTime)
{
if (_modalDialogIsUp)
{
// draw only modal dialog
}
else
{
// draw game
}
}
I just can't undestang why you so desparatly want exactly modal behavior... is it some kind of new "sync" sect? :)