Having an issue with tables and updating a label! Here is the dilemma, I have a sell button in my game that is updating the player's coins whenever they sell an item, that part is working perfectly. The issue I am having is trying to get the coin value to update on the screen while there in this separate menu (see pic attached, coins in the top left). The problem is that the coin value is in another stage in another class. This is because I have different tables that pop up in the middle when I click the different buttons at the bottom. I have tried helper methods for going in and clearing that table and updating it and then sending me back to this item page but it is not working, I can post any code needed but this is more of a general question on how to update a label within a table in a stage.
Update: So to kinda sum up my question, I have a Screen and I have have three tables in it the bottom table the top left and the top right. Then I add the table to the stage in the middle when they press the inventory or shop button etc. What I am looking to do is to keep the item page open and simply just update the value of the Coin label, I know I can change the text using .setText(); Im just not sure how I can update that portion of the screen etc..
Update 2: If I just set the screen back to a new screen of this screen it updates the coin value but then I am not on the item page anymore which is not ideal.
Update 3: Thanks for the help so far guys, #John your answer is super helpful aswell. Im still not getting this working though here is a little bit of the code where the label is being handled.
playerCoinLabel = new Label(playerSave.getCoinsString(),skin,"defaultMiddle");
This is where it is getting added to the table.
tableLeft = new Table(skin);
stage.addActor(tableLeft);
tableLeft.setBounds(0,0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
tableLeft.setFillParent(true);
tableLeft.top().left();
tableLeft.add(healthNonButton).size(84,80).left().padLeft(10).padTop(5);
tableLeft.add(playerHealthLabel).left().padLeft(15);
tableLeft.row();
tableLeft.add(levelNonButton).size(74,70).center().padLeft(10);
tableLeft.add(playerLevelLabel).left().padLeft(19);
tableLeft.row();
tableLeft.add(coinNonButton).size(74,70).center().padLeft(10);
tableLeft.add(this.playerCoinLabel).left().padLeft(15); //This line
tableLeft.row();
Then I have this method for updating my label using the setText like you guys were telling me about.
public void updatePlayerCoins() {
playerCoinLabel.setText(playerSave.getCoinsString());
}
and if I call this method anywhere, render() or where im setting the new coin value it is not updating/changing the label in the top left of my screen. I can post all the code to a github if I need to just posted the things involving the label. This is just a project im working on to increase my skill set so sorry if I sound amateur, it is because I am!
Thanks everyone!
It seems like you're asking two things- how do I update a label? and How do I structure my code? It's hard to tell what's going wrong with the former since we can't see your code, but #Tenfour04 is right- you want to retain a reference to the label somewhere and call setText() when you want to change the amount.
As far as structuring your code, I would suggest a simple OOP design and then evolve it like so:
First, we need an object to represent the player:
class Player {
private int coins; // Pretend there are getters / setters.
private int health;
private int level;
}
Now you probably have more than one way that you want to represent this player information, so we'll split the rendering code into a separate class or set of classes:
class StatWidget {
private Stage stage;
private Player player;
private Label lblCoins;
public StatWidget(Player player) { // Pseudo-code
this.player = player;
this.stage = new Stage();
Table tbl = new Table();
this.lblCoins = new Label(); // Notice we keep a reference to the label
tbl.add( this.coins );
}
public void update() {
lblCoins.setText(player.getCoins());
}
}
Now you can sync the UI with your player object's state simply by calling Player#update(). But when do you call it?
You could call update() in your render method. This is a little inefficient because you're updating the object whether it needs to be updated or not, but it's dead simple, and if you're only updating a few UI elements this way it probably doesn't matter. Personally, I'd stop here.
If you want to be more precise, you would only call update() when you actually make a change to the Player's coins. You can do this by finding the places in your code where you set the player's coins and add the update call like so:
player.setCoins( A_LOT_OF_MONEY );
statWidget.update();
Problem is this gets more cumbersome as you add more widgets- all your game logic now has to know about StatWidget and make calls to it. We could cut this dependency a little bit by using an event-driven architecture. Essentially, whenever player's state changes, it would send an event to interested parties notifying them of the change. You could use the pseudo-code below:
interface Publisher {
void subscribe(Subscriber subby);
void unsubscribe(Subscriber subby);
}
class Player implements Publisher {
private List<Subscriber> subscribers;
private int coins;
// ...
public void setCoins(int amount) {
this.coins = amount;
for(Subscriber subscriber : subscribers) subscriber.notify("COINS", amount);
}
public void subscribe(Subscriber subby) {
this.subscribers.add(subby);
}
public void unsubscribe(Subscriber subby) {
this.subscribers.remove(subby);
}
}
interface Subscriber {
void notify(String event, int qty);
void dispose();
}
class StatWidget implements Subscriber {
private Publisher player;
private Label label;
// ...
public StatWidget(Player player) {
this.player = player;
this.player.addSubscriber(this);
void notify(String event, int qty) {
if(event.equals("COINS")) label.setText(qty);
}
void dispose() {
this.player.unsubscribe(this);
}
}
The event system above could certainly be polished, and you could likely do clever things with generics (or use a library that has thought all this out for your), but hopefully it illustrates the concepts.
Related
I was wondering if you could help me. I have a puzzle that needs to implement an undo method. There are two moves you can complete an assign move (assigns a number to the grid) and a relation move (which adds a relation move).
I have an abstract class (UserEntry) which instantiates one common method with Assign and RelEntry. I have called this public void addToPuzzle() {
it is as follows in the Assign Class:
#Override
public void addToPuzzle() {
Assign a = new Assign(row, col, num);
}
and this in the RelEntry Class:
#Override
public void addToPuzzle() {
RelEntry re = new RelEntry(greaterRow, greaterCol, lesserRow, lesserCol);
}
I am trying to create an undo method in my puzzle UI using a stack. I have tried this:
private void undo() {
if(stack.empty())
return;
stack.pop().addToPuzzle();
}
Moves are instantiated in the puzzle UI and I have pushed each move in the text UI into the stack using a stack.push line of code following each move being assigned. I have a feeling I'm not being clear, if this is the case, go easy on me as I'm new to Java.
I'm creating a media player in JavaFX. In one of my methods, I've created a way to search for metadata in a Media-file and then display it in ImageView. Works fine first time, but as soon as I want to call it again using another Media object, the image doesn't show up. I'm a bit confused and inexperienced, but I think that perhaps I need to reset/stop the listener before going to next object in line?
So my question is! How do you remove the listener when "image" has been found, what do you type to make it happen?
If you think that there's another reason why my image wont display the second time, please let me know as well.
Thanks on purpose.
private void displayAlbumCover (){
// Will start to show a blank CD
File file = new File("src/sample/images/blank_cd.jpeg");
Image image = new Image(file.toURI().toString());
albumCoverView.setImage(image);
// However if an album cover is found in the meta-data it will be displayed
ObservableMap<String,Object> meta_data=me.getMetadata();
meta_data.addListener((MapChangeListener<String, Object>) ch -> {
if(ch.wasAdded()){
String key=ch.getKey();
Object value=ch.getValueAdded();
switch(key){
case "image":
albumCoverView.setImage((Image)value);
break;
}
}
});
}
ObservableMap has removeListner method. You can keep the listener instance to variable and then remove it later.
private MapChangeListener<String, Object> listener;
private void displayAlbumCover (){
// ...
this.listener = //...
meta_data.addListener(listener);
}
private void removeListener() {
me.getMetadata().removeListener(this.listener);
}
https://docs.oracle.com/javase/8/javafx/api/javafx/collections/ObservableMap.html#removeListener-javafx.collections.MapChangeListener-
I dont think my problem is that hard to solve but I have been searching for a while and cant figure it out.
I have two scene2d SelectBox widgets one above the other, in a table, on a stage. Let's call them A and B. Whatever is selected in A determines which list is shown in B. I implement this using a ChangeListener on A and all works fine (this isn't the problem).
However, my list A was getting extremely long (500+ items) so I wanted to add a TextField above it which would search and match the strings, replacing the old list of A with a shorter one, making it much easier to find what you are looking for. This works fine, I use a ChangeListener on the textfield to get the string, compare it to a main list of strings using a for loop and use aList.setItems(); to add the adjusted string to the SelectBox. The list displays (without a click, so I use aList.showList(); in the ChangeListener of the TextField) and I think this is where the problem occurs - instead of a click, showList() is called from elsewhere. Lets say I change my mind and want to select a different item from A, it will no longer drop down the menu on click. Yet if I change the text which is in the search bar, it displays the list. When the list is displayed, I can click an item and it hides as normal.
This might seems a bit confusing, so here is the code (edited for clarity, so if something is missing let me know)
SelectBox aSelect, bSelect;
TextField searchBar;
Stage stage;
Table table;
Skin skin;
ArrayList<String> completeAList;
ArrayList<String> abrevAList;
public chooseItemScreen()
{
stage = new Stage(new ScreenViewport());
skin = new Skin(Gdx.files.internal("uiskin.json"));
table = new Table();
table.setFillparent(true);
completeAList = new ArrayList<String>;
abrevAList = new ArrayList<String>;
aSelect = new SelectBox(skin);
//ItemList is a class with the list of strings as a static method
completeAList = ItemList.getAList();
aSelect.setItems(completeAList.toArray());
//bSelect omitted as is same as A
//aSelect changeListener also omitted as it is working fine
searchBar = new TextField("", skin);
searchBar.setMessageText("SEARCH LIST");
searchPokemon.addListener(new ChangeListener() {
#Override
public void changed(ChangeEvent event, Actor actor) {
updateASelect();
}
});
table.add(searchBar);
table.row();
table.add(aList);
stage.addActor(table);
Gdx.input.setInputProcessor(stage);
}
private void updateAList()
{
abrevAList.clear();
aSelect.clearItems();
aSelect.hideList()
for (String string: completeAList)
{
if (string.toLowerCase().startsWith(searchBar.getText().toLowerCase()))
{
abrevAList.add(string);
}
}
if (abrevAList.isEmpty())
{
abrevAList.add("NOT FOUND");
}
aSelect.setItems(abrevAList.toArray());
//It's at this point where I am no longer to click on aSelect
//I can still select an item from the drop down list, closing the list
//it's just I can't show list by clicking on the widget after that
aSelect.showList();
}
#Override
public void render(float delta) {
Gdx.gl20.glClearColor(0,0,0,0);
Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act();
stage.draw();
}
I added the following listener to tell if the selectBox was being clicked (which it was). I gave all actors names
stage.getRoot().addCaptureListener(new InputListener() {
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
System.out.println(event.getTarget().getName());
return false;
}
});
The click is recognised, just the list doesn't show. In my opinion, it is a problem with calling showList() and changing the list at the same time.
Any help is appreciated, and if you need more code or any other information, let me know.
Thanks
Set a fixed size to the selectbox when adding it to the table, something like
table.add(selectBox).width(someValue);
or
table.add(selectBox).growX();
Also, after reviewing your code, I suggest you to remove
aSelect.clearItems();
aSelect.hideList();
And make ArrayList be just libgdx Array< String>, it will make things easier, wont cause allocation when iterating with ':' and you wont need .toArray() when setting the items of your selectboxes. You also can set SelectBox type with SelectBox< String>, and, you can add a row in the same line with table.add(something).row().
After changing the size of the selectbox cell your code worked just fine in my side.
I'm making a simple point&click game using libGdx and their Scene2d. Now, when I enter a location my Stage is cleared and new Actors are beeing attached. It doesnt feel right and its not efficient.
Can I make all Actors at the begining (except backgrounds, I will load them when entering a location), add them to Stage and associate them with locations, so the Stage would know witch to draw?
My only idea was to check that in draw and act methods of every actor, but that would mean houndreds of checks in a loop. Maybe Scene2d got something to help me out? Or maybe there is another way to do it?
My only idea was to check that in draw and act methods of every actor, but that would mean houndreds of checks in a loop.
Yes, that will be inneficient, and above all, a hell to maintain.
Now, when I enter a location my Stage is cleared and new Actors are beeing attached.
This is where your problem is, you're not using scene2D as it should be IMHO. I hope you're up for an intense architecture & code refactoring session.
When entering a new location, you should be entering a new stage.
So first, you should have several stages :
class MainMenu extends Stage {
public MainMenu(){
// Add buttons to play or quit the game
}
}
class PointNClickStage extends Stage {
// Add stuff common to all point'n click stages such as an inventory display
}
class Island extends PointNClickStage {
public Island (){
// Add some palm trees and an hidden chest
}
}
class PirateShip extends PointNClickStage {
public PirateShip(){
// Add some pirates and their ship
}
}
... etc
Then in your application listener, you should implement a way to change the current stage being rendered. Conceptually, this is often called a "scene/stage director". Some scene-based frameworks, such as Cocos2D provides their own scene director, but libgdx doesn't currently. So, you have to implement this mechanism by yourself and here is a very basic example to help you get the gist of it :
public MyApp extends ApplicationAdapter {
private Stage currentStage;
private static MyApp instance;
// ...
#Override
public void create () {
instance = this;
MyApp.setStage(new MainMenu()); // The game begins in the main menu
}
#Override
public void render () {
Gdx.gl.glClearColor(0.15f, 0.1f, 0.15f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
currentStage.act();
currentStage.draw();
}
public static void setStage(Stage stage){
instance.currentStage = stage;
Gdx.input.setInputProcessor(stage); // Important ;)
}
// ...
}
So that to change the location (current stage) you will only have to do :
MyApp.setStage(new PirateShip())
Then, if you don't want to recreate a new stage every time you change your location, you may initialize and keep a reference on them somewhere so that you will be able to change the location like that for example.
MyApp.setStage(some_list_containing_initialized_stage.get(id))
Alternatively, you may also look into this libgdx extension that provides scene2d utils classes such as a scene director, and transitions that may be useful for you if you don't want to reinvent the wheel later.
I'm working on a project for a simple game where you can go to different rooms by using buttons (north, east, west, south). Within the makeFrame() method of my gui I'm creating the panel, buttons etc. I then set the default room to "hall" for example and the actionlistener calls the method goRoom and passing the direction and currentRoom to that method. The goRoom method change the currentRoom to another room depending on the currentRoom. I included print statements to see if it works and so far it works fine.
Everytime the game starts the default room is the hall.
So when you click a button to go for example "North", the northButton is called in which then we call the goRoom method passing the direction (north) and the default room "hall" (as the game just starts and uses the default room).
Then the room changes from hall to state room (within the method goRoom). When I try to press another button the currentRoom reset to the default value (hall).
I think the action listener get the value from the makeFrame() method instead of the updated value from the goRoom method. The code is below:
public class StoreGUI extends JFrame
{
public String currentRoom;
public StoreGUI()
{
makeFrame();
}
private void makeFrame()
{
currentRoom = "hall";
....
northButton = new JButton("Go North");
northButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
direction = "north";
goRoom(direction, currentRoom); }
});
toolbar.add(northButton);
westButton ....
southButton ....
eastButton ....
picture.setIcon(new ImageIcon("image/hall.png"));
frame.getContentPane().add(picture);
frame.pack();
frame.setVisible(true);
}
private void goRoom(String direction, String currentRoom)
{
// get current room and check which direction button the user has pressed
if (direction == "north"){
if(currentRoom == "hall"){
// Inserts the image icon and change currentRoom
imgageTitle = "image/stateRoom.png";
currentRoom = "stateRoom";
}
....
}
What the problem could be? How can I fix that? I'm pretty sure it's something really simple but I'm stack.
String comparison in Java is done with String#equals not ==. This will compare the actual text of the String and not its memory reference...
For example, instead of
if (direction == "north") {....
Use
if ("north".equals(direction)) {...
If you don't care about the case, you could use...
if ("north".equalsIgnoreCase(direction)) {...
Having said all that, you could actually use a enum to represent the directions, which restricts what values you can actually pass to the goRoom.
You could also use a Action to define each button's actions, which also means you could use them Key Bindings or menus without having to duplicate any code...but that's just me...
Updated
You're also shadowing your values...
private void goRoom(String direction, String currentRoom)
{
//...
currentRoom = "stateRoom";
Changing the value of currentRoom will have no effect beyond the scope of the method. This is because you're not actually changing the content of the String object, but changing it's memory reference.
Instead, either change the name of the parameter or, simply don't bother passing, as you already have access to the instance field of the same name...
private void goRoom(String direction)
{
//...
currentRoom = "stateRoom";