As the title suggests, my button (closeImage) goes null when I try to access it.
I have a canvas that pops up an image based on a tapped marker (button component) on a map. The said canvas will show the image together with the button in question. Clicking on the button closes the pop-up;
The button in question
However, once I press on the marker on the map to show the pop-up image, it gives me an error that the Close image is null. The close button does show on the first time I press on the marker on the map and able to close the pop-up, even with the error message. But once I click on the same marker on the map again, the image shows but the close button is not showing anymore.
Here's my block of code:
public class TappedMarkerManager : MonoBehaviour {
private static TappedMarkerManager instance;
private List<GameObject> markerImageList = new List<GameObject>();
string tappedMarkerName;
Button closeImage;
GameObject closeImageGO;
public static TappedMarkerManager Instance {
get {
if (instance == null) {
instance = FindObjectOfType<TappedMarkerManager>();
}
return instance;
}
}
void Start() {
closeImageGO = GameObject.FindGameObjectWithTag("close_img");
if(closeImageGO != null) {
closeImage = closeImageGO.GetComponent<Button>();
closeImage.onClick.AddListener(TappedMarkerInformation);
} else {
Debug.Log("closeImageGO is null");
}
//Loop through preview images.
//This will dynamically add the images on the list if more markers will be added on the map
//Use tag "marker_img"; must be same name with the marker and image
var markerImages = GameObject.FindGameObjectsWithTag("marker_img");
foreach(var markerImage in markerImages) {
TappedMarkerManager.Instance.markerImageList.Add(markerImage.gameObject);
//Set false to hide it at first
markerImage.SetActive(false);
}
}
public void TappedMarkerInformation() {
var name = tappedMarkerName;
foreach(var img in TappedMarkerManager.Instance.markerImageList) {
if(name == img.GetComponent<Image>().name) {
img.SetActive(true);
if(closeImage != null) {
if(!closeImage.gameObject.activeInHierarchy) {
closeImage.gameObject.SetActive(true);
}
} else {
Debug.LogError("Close image is null");
}
}
if(GetButtonName() == "ExitBtn") {
img.SetActive(false);
closeImage.gameObject.SetActive(false);
}
}
}
public void SetTappedName(string name) {
tappedMarkerName = name;
TappedMarkerInformation();
}
//Get the name of the button pressed
public string GetButtonName() {
if(EventSystem.current.currentSelectedGameObject != null) {
string ClickedButtonName = EventSystem.current.currentSelectedGameObject.name;
return ClickedButtonName;
} else {
return "";
}
}
}
The markers on the map are controlled from another script with:
...
TappedMarkerManager tappedMarkerManager;
tappedMarkerManager = new TappedMarkerManager();
...
private void HandleFingerTap(LeanFinger finger) {
Ray ray = Camera.main.ScreenPointToRay(finger.ScreenPosition);
if (Physics.Raycast(ray, out RaycastHit hit)) {
string markerName = hit.collider.gameObject.name;
tappedMarkerManager.SetTappedName(markerName);
}
}
Any help is highly appreciated. Thank you. I can provide the github repository if needed.
What I tried so far:
I already tried setting up the closeImage in the Inspector with the button
Made sure that the GameObject has the button component
Checked and verified on the hierarchy that the closeImage button is not being destroyed
Related
I created a custom control in MAUI that must work if user select with a click or tap, a Popup must show with some content, let's say for example a Calculator instead a Keyboard. I'm using CommunityToolkit.Maui. But the sentence
var popup = new PickerControl();
var result = await PopupExtensions.ShowPopupAsync<PickerControl>(this, popup);
throw me an error because this in inside the control and expects a Page, so need to know how handle the page or parent page in the same control. Picker control is the Popup with the content.
The code:
public partial class EntryCalculator : Frame
{
TapGestureRecognizer _tapGestureRecognizer;
public EntryCalculator()
{
InitializeComponent();
}
///Properties here
private void Initialize()
{
_tapGestureRecognizer = new TapGestureRecognizer();
}
private async static void IsDisplayPickerPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var controls = (EntryCalculator)bindable;
if (newValue != null)
{
if ((bool)newValue)
{
var popup = new PickerControl();
var response = PopupExtensions.ShowPopupAsync<PickerControl>(this, popup);
if (response != null && response is decimal)
{
controls.Value = (decimal)response;
}
}
}
}
///... other methods
At first, you can get the current page from the navigation stack:
If you use the shell:
Page currentpage = Shell.Current.Navigation.NavigationStack.LastOrDefault();
If you use the NavigationPage:
Page currentpage = Navigation.NavigationStack.LastOrDefault();
Or just only use:Page currentpage = App.Current.MainPage.Navigation.NavigationStack.LastOrDefault();. The App.Current.MainPage will be the Shell or the NavigationPage, it depends on what you used in your project.
In addition, you can get the current page from the custom control. Such as:
public static class ViewExtensions
{
/// <summary>
/// Gets the page to which an element belongs
/// </summary>
/// <returns>The page.</returns>
/// <param name="element">Element.</param>
public static Page GetParentPage (this VisualElement element)
{
if (element != null) {
var parent = element.Parent;
while (parent != null) {
if (parent is Page) {
return parent as Page;
}
parent = parent.Parent;
}
}
return null;
}
}
I put several button and i linked onClick() each buttons.
It's okay to print each button name.
But when i click another button, The text didn't combine previous text...
========================================================
public Text showText; //print text
public int count = 0; // count how many times click button
public void ButtonClick_1()
{
Debug.Log("Button Clicked!");
print(gameObject.name);
if (count == 0) { showText.text = gameObject.name; }
else { showText.text += gameObject.name; }
count++;
}
==================================================
First
click button1 => it's okay to print " button1 "
Second
click button1 again => It's okay to print " button1button1"
Third
click button2 => It's not okay to print. print like "button2". I hope it print"button1button1button2"
==========================================================
I want to figure out this.....
I have to put several buttons so use tag is more useful?
I'm not used to use tag. Hope someone help me out.
So as I understood you have this script on multiple buttons.
You have a check on count which initially is 0 for each individual instance.
So instead you could make it static so it is "shared" between all instances or better said makes it a static class member which is not related to any instance.
public Text showText;
public static int count = 0;
public void ButtonClick_1()
{
Debug.Log("Button Clicked!");
print(gameObject.name);
if (count == 0)
{
showText.text = gameObject.name;
}
else
{
showText.text += gameObject.name;
}
count++;
}
So that the text is only overwritten by the very first button click.
Your comment
Actually i print like "000" ,"001",,"012","123".
sounds like you actually want to print always the last three buttons.
I would rather use e.g. a static List<string> like
private static List<string> lastThreeButtons = new List<string>();
public void ButtonClick()
{
lastThreeButtons.Add(name);
if(lastThreeButtons.Count == 3)
{
showText.text = lastThreeButtons[0] + lastThreeButtons[1] + lastThreeButtons[3];
lastThreeButtons.Clear();
}
}
This is how I would do it :
Have a singleton class :
public class TextManager : MonoBehaviour
{
public static TextManager Instance;
public UnityEngine.UI.Text myUIText;
private void Awake()
{
Instance = this;
}
public void AddLine(string _line)
{
myUIText.text += $"{_line} \n";
}
}
Put this Component on a GameObject and link the UIText in the inspector
Then on my ButtonScript I would have this :
public void ButtonClick()
{
Debug.Log($"{gameObject.name} Clicked!");
TextManager.Instance.AddLine(gameObject.name);
}
Generally, most objects are placed in the scene view by dragging or something. I want to right click the mouse (without dragging an object) to create an object in the scene view. I know this would require some editor coding but I’m not sure how to go about it.
UPDATE
After giving it some thought I realised that using a MenuItem would be quite appropriate for me. Here is my code below:
SLMenuItems:
public class SLMenuItems : MonoBehaviour {
public bool canClickSceneViewToCreatePath = false;
void Start()
{
}
[MenuItem("Component/Create Custom Object")]
static void CreateObject() {
Debug.Log("menu item selected");
canClickSceneViewToCreatePath = true;
}
}
SLMenuItemsEditor:
[CustomEditor(typeof(SLMenuItems))]
public class SLMenuItemsEditor : Editor {
SLMenuItems slMenuItems;
void OnEnable()
{
slMenuItems = (SLMenuItems)target;
}
void OnSceneGUI()
{
if (slMenuItems.canClickSceneViewToCreatePath) {
Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
// create object here at pointsPos
slMenuItems.canClickSceneViewToCreatePath = false;
}
}
}
}
I keep getting the following error:
Assets/SLMenuItems.cs(23,9): error CS0120: An object reference is required to access non-static member `SLMenuItems.canClickSceneViewToCreatePath'
pointing to the line:
canClickSceneViewToCreatePath = true;
in SLMenuItems.
Your CreateObject method is static but your canClickSceneViewToCreatePath value is not.
It has nothing to do with the editor script but with your class SlMenuItems itself.
A static method is not instanced or with other words it is kind of shared between all instances of that component type while the non-static value might be different for each component.
So how should a static method - which is the same for all instances - "know", which of the instances values it should access?
So either make the method non-static or the variable static. Depending on what your further need is.
Since the MenuItem needs a static method make the variable static as well.
I would suggest you make that class not inherit from MonoBehaviour at all since it doesn't have any behaviour for a GameObject. It only brings some editor features so rather make it a static class that can "live" in the Assets without needing to be instanced.
Than you can use SceneView.onSceneGUIDelegate to register a callback for OnSceneGUI without implementing an editor script for that:
private static GameObject lastCreated;
private static bool isCreating;
public static class SLMenuItems
{
[MenuItem("Component/Create Custom Object")]
private static void CreateObject()
{
Debug.Log("menu item selected");
isCreating = true;
lastCreated = null;
// Add a callback for SceneView update
SceneView.onSceneGUIDelegate -= UpdateSceneView;
SceneView.onSceneGUIDelegate += UpdateSceneView;
}
private static void UpdateSceneView(SceneView sceneView)
{
if(lastCreated)
{
// Keep lastCreated focused
Selection.activeGameObject = lastCreated;
}
if(isCreating)
{
if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
{
Vector3 pointsPos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
//Todo create object here at pointsPos
lastCreated = Instantiate(somePrefab);
// Avoid the current event being propagated
// I'm not sure which of both works better here
Event.current.Use();
Event.current = null;
// Keep the created object in focus
Selection.activeGameObject = lastCreated;
// exit creation mode
isCreating = false;
}
}
else
{
// Skip if event is Layout or Repaint
if(e.type == EventType.Layout || e.type == EventType.Repaint)
{
Selection.activeGameObject = lastCreated;
return;
}
// Prevent Propagation
Event.current.Use();
Event.current = null;
Selection.activeGameObject = lastCreated;
lastCreated = null;
// Remove the callback
SceneView.onSceneGUIDelegate -= UpdateSceneView;
}
}
}
But I suggest you change your questions title since this is actually not the solution to the "task" you describe before.
I am working on a multiplayer game in Unity which is using Playfab and the Authentication and Photon which is hosting the multiplayer. I can successfully get players into the same room and I can load the scene after players 'join' the room, however, when 2 players are in the same room, they can not see each other.
This is my authentication service:
public class LoginWithCustomID : MonoBehaviour
{
private string _playFabPlayerIdCache;
private bool _isNewAccount;
private string _playerName;
// Use this to auth normally for PlayFab
void Awake()
{
PhotonNetwork.autoJoinLobby = false;
PhotonNetwork.automaticallySyncScene = true;
DontDestroyOnLoad(gameObject);
authenticateWithPlayfab();
}
private void authenticateWithPlayfab()
{
var request = new LoginWithCustomIDRequest
{
CustomId = "CustomId123",
CreateAccount = true,
InfoRequestParameters = new GetPlayerCombinedInfoRequestParams()
{
GetUserAccountInfo = true,
ProfileConstraints = new PlayerProfileViewConstraints()
{ ShowDisplayName = true }
}
};
PlayFabClientAPI.LoginWithCustomID(request, requestPhotonToken, OnLoginFailure);
}
private void requestPhotonToken(LoginResult result)
{
PlayerAccountService.loginResult = result;
_playFabPlayerIdCache = result.PlayFabId;
_playerName = result.InfoResultPayload.AccountInfo.TitleInfo.DisplayName;
if (result.NewlyCreated)
{
_isNewAccount = true;
setupNewPlayer(result);
}
PlayFabClientAPI.GetPhotonAuthenticationToken(new GetPhotonAuthenticationTokenRequest()
{
PhotonApplicationId = "photonId123"
}, AuthenticateWithPhoton, OnLoginFailure);
}
private void setupNewPlayer(LoginResult result)
{
PlayFabClientAPI.UpdateUserData(
new UpdateUserDataRequest()
{
Data = new Dictionary<string, string>()
{
{ "Level", "1" },
{ "xp", "0" }
}
}, success =>
{
Debug.Log("Set User Data");
}, failure =>
{
Debug.Log("Failed to set User Data..");
}
);
}
private void AuthenticateWithPhoton(GetPhotonAuthenticationTokenResult result)
{
Debug.Log("Photon token acquired: " + result.PhotonCustomAuthenticationToken);
var customAuth = new AuthenticationValues { AuthType = CustomAuthenticationType.Custom };
customAuth.AddAuthParameter("username", _playFabPlayerIdCache);
customAuth.AddAuthParameter("token", result.PhotonCustomAuthenticationToken);
PhotonNetwork.AuthValues = customAuth;
setNextScene();
}
private void setNextScene()
{
if(_isNewAccount || _playerName == null)
{
SceneManager.LoadSceneAsync("CreatePlayerName", LoadSceneMode.Single);
}
else
{
SceneManager.LoadSceneAsync("LandingScene", LoadSceneMode.Single);
}
}
private void OnLoginFailure(PlayFabError error)
{
Debug.LogWarning("something went wrong in auth login");
Debug.LogError("Here's some debug info:");
Debug.LogError(error.GenerateErrorReport());
}
}
}
This all works and a player is logged into PlayFab, as well as Photon I would assume if I got the Photon auth token. This brings me to my landing scene, which is essentially a place for an authenticated user to click a button to join a random room via Photon:
public static GameManager instance;
public static GameObject localPlayer;
private void Awake()
{
if (instance != null)
{
DestroyImmediate(instance);
return;
}
DontDestroyOnLoad(gameObject);
instance = this;
PhotonNetwork.automaticallySyncScene = true;
}
// Use this for initialization
void Start()
{
PhotonNetwork.ConnectUsingSettings("A_0.0.1");
}
public void JoinGame()
{
RoomOptions ro = new RoomOptions();
ro.MaxPlayers = 4;
PhotonNetwork.JoinOrCreateRoom("Test Room 2", ro, null);
}
public override void OnJoinedRoom()
{
Debug.Log("Joined Room!");
if (PhotonNetwork.isMasterClient)
{
PhotonNetwork.LoadLevel("Test_Map1");
}
}
private void OnLevelWasLoaded(int level)
{
if (!PhotonNetwork.inRoom)
return;
localPlayer = PhotonNetwork.Instantiate(
"Player",
new Vector3(0, 1f, 0),
Quaternion.identity,
0);
}
public void LeaveRoom()
{
PhotonNetwork.LeaveRoom();
SceneManager.LoadScene("LandingScene", LoadSceneMode.Single);
}
This loads the scene that I named "Test_scene1" successfully and I show within my scene, the room name and number of active players in the room. When I do a run and build, I get a user's playerPrefab to load into the room. When I run the game through unity, I can get a second player to log into the room. The problem is, the players do not see eachother and I can not figure out why that is. I am following the PLayerfab/Photon tutorials on their respective sites, but I can't find anything that I did wrong in either one.
From what I read, it looks like my instantiate method might be wrong but I'm not sure why. Below is my player Prefab showing the components attached to it:
I apologize for this huge question, I just wanted to provide as much information as I could.
This question was answered by the OP on PlayFab forums here.
I'm trying to animate ListCell when they appear.
Specially I try to animate a new cell when it was just added to the list.
For now it's working pretty OK except when I scroll the ListView, then indexes get messed up and the wrong cell is animated.
I use a boolean flag (entering) in my item model to detect when a cell is used for a brand new item.
public class TimeListCell extends ListCell<MarkItem> {
private static final String BUTTON_GOTO_MARK_CLASS = "but-markgoto";
private static final String LABEL_TIME_MARK_CLASS = "track-time";
private static final String BUTTON_DELETE_MARK_CLASS = "but-markdel";
private static final String MARK_HIGHLIGHT_CURRENT_CLASS = "highlighted";
private Instant time;
private MarkItem markItem;
protected ListCellAnimation anim;
private HBox root = new HBox();
private Button go = new Button();
private Label track = new Label();;
private Button del = new Button();
private ChangeListener<? super Boolean> highlightChange = (e, o, n) -> { setHighlighted(n); };
public TimeListCell (Consumer<MarkItem> onGoto, Consumer<MarkItem> onDelete) {
root.setAlignment(Pos.CENTER);
go.getStyleClass().add(BUTTON_GOTO_MARK_CLASS);
go.setOnAction( e -> {
if (onGoto != null) {
// Trigger GOTO consumer function
onGoto.accept(markItem);
}
});
track.getStyleClass().add(LABEL_TIME_MARK_CLASS);
del.getStyleClass().add(BUTTON_DELETE_MARK_CLASS);
del.setOnAction( e -> {
// First trigger exit animation then delete item
this.animateExit(onDelete);
});
root.getChildren().add(go);
root.getChildren().add(track);
root.getChildren().add(del);
}
#Override
protected void updateItem (final MarkItem item, boolean empty) {
super.updateItem(item, empty);
if (markItem != null) {
markItem.highlightedProperty().removeListener(highlightChange);
}
if (!empty && item != null) {
markItem = item;
time = item.getTime();
track.setText(DateUtil.format(time, DateUtil.Pattern.TIME));
setGraphic(root);
item.highlightedProperty().addListener(highlightChange);
setHighlighted(item.isHighlighted());
if (anim == null) {
//Adding Animation to the ListCell
anim = new ListCellAnimation(this);
//KeyFrame[] f = getKeyFrames(types);
KeyFrame[] frames = null;
if (anim.getKeyFrames().size() == 0) {
KeyFrame[] f = anim.getPopIn(frames);
if (f != null) {
anim.getKeyFrames().addAll(f);
}
}
}
if (item.isEntering()) {
//Checking when to play Animation
animateEnter();
item.setEntering(false);
}
} else {
setGraphic(null);
}
}
/**
* Set/unset cell highlighted style for display.
*
* #param highlighted
* Whether or not to highlight the cell
*/
public void setHighlighted (boolean highlighted) {
track.getStyleClass().remove(MARK_HIGHLIGHT_CURRENT_CLASS);
if (highlighted)
track.getStyleClass().add(MARK_HIGHLIGHT_CURRENT_CLASS);
}
/**
* Animate entering cell.
*/
private void animateEnter() {
if (anim != null && anim.getKeyFrames().size() >= 0
&& (anim.getTimeline().getStatus() == Timeline.Status.STOPPED
|| anim.getTimeline().getStatus() == Timeline.Status.PAUSED)) {
anim.getTimeline().playFromStart();
}
}
/**
* Animate exiting cell.
* Trigger DELETE consumer function when animation is complete.
*/
private void animateExit (Consumer<MarkItem> onDelete) {
anim.getReversedTimeline().setOnFinished( t -> {
// Remove item from list
if (onDelete != null) {
onDelete.accept(markItem);
}
// Prepare cell for next item to use it
scaleXProperty().set(1);
scaleYProperty().set(1);
});
anim.getReversedTimeline().playFromStart();
}
public Instant getTime () {
return time;
}
}
Has anyone any idea of what could mess up the cell indexing ?
Thanks.
If a cell which is animating is reused to display an item that is not "entering", then you need to stop the current animation:
if (item.isEntering()) {
//Checking when to play Animation
animateEnter();
item.setEntering(false);
} else {
anim.getTimeline().stop();
}
In general, you seem to be assuming that any given cell is only ever used for a single item, which is certainly not the case. There may be other bugs in your code that are consequences of this assumption, but this is the main one I can see.