We are about to switch to a new forum software. Until then we have removed the registration on this forum.
hello all,
I am working on a bigger program and I am a bit overwhelmed by its complexity at the moment.
So I want to reduce the complexity.
At the moment, the screen buttons (the ones you click with the mouse) are a mess.
I am using good old states
but in each state/ screen I have like 3 to 10 different screen buttons.
So I was tinking about making a class State
now finally and an array of this class State
.
Thus I could say states[currentState].displayButtons();
or in mousePressed I could say Button currButton = states[currentState].getPressedButton();
and so on.
Since all states have a different number of buttons (no, I am not using a lib for the buttons) I'd use an ArrayList<Button> buttons = new ArrayList();
in the class State. Different for each state.
Each state would hold another list of buttons (and a different number of buttons).
Also, I was thinking to give each button a commandID
- when the button gets pressed all states could use the same executeCommandID()
function outside the class that gets an ID and executes it (with switch(commandID)
).
This last point is a bit vague, would it work?
The class would also handle the keyPressed()
(not done yet)
idea:
class State {
int id;
String name;
ArrayList<Button> buttons = new ArrayList();
void displayButtons() {
for (Button currButton : buttons) {
currButton.display();
}// for
} // method
void getPressedButton() {
for (Button currButton : buttons) {
if (currButton.over()) {
return currButton;
}
}// for
} // method
} // class
My question:
has somebody done this?
What is the right path here?
Also normally I use
drawStuffForStateSplashScreen
drawStuffForStateHelpScreen
drawStuffForStateGameScreen
but how can I implement this into a class State?
because when I make a new state I can not pass a String (or function) "drawStuffForStateSplashScreen" or "drawStuffForStateHelpScreen" to the class State for using this function (which would be outside the class) because functions can not be used as a param....
or I would have a long if else if construct / switch clause in the class State:
void displayTheScreenForTheState() {
switch(id) {
case stateSplashScreen:
drawStuffForStateSplashScreen();
break;
case stateHelpScreen:
drawStuffForStateHelpScreen();
break;
case stateGameScreen:
drawStuffForStateGame();
break;
} // switch
} // method
with drawStuffForStateSplashScreen()
etc. being outside the class.
but this looks a bit longwinded............
And I would have the
final int stateSplashScreen = 0;
final int stateHelpScreen = 1;
final int stateGameScreen = 2;
etc. outside the class too?
oh dear............... what a mess...............
Thank you for your thoughts,
Chrisir.... ;-)
Answers
Are you me? I've been kicking this idea about myself.
What you really want to do is avoid using switch at all.
wow, that's absolutely a cool approach.
but basically, you have to rewrite the whole thing for each sketch and you can't have one framework class State that you use and that stays fixed in all sketches while you build the individual sketch around it every new sketch, right?
Are there other approaches too?
When I started building more complex JS applications I found some really useful resources on design patterns. A search for 'java design patterns' might turn up some useful ideas; though maybe others here who have much more experience with Java than me can recommend some directly ;)
Must admit that looking at that Wikipedia page is a bit overwhelming: there are a lot there and some arcane language to comprehend... So, the observer and mediator patterns both spring out as ones I found useful and more relevant to what you're trying to achieve. One other commonly used pattern for interfaces is Model View Controller (MVC) so that's also worth searching for...
Essentially, the problem boils down to a few questions:
No matter how you go about it, you will still need to answer these questions for any program state.
My above example answers these questions by (respectively) the constructor, draw() and onMousePress() methods of a new class that extends the base State class
But there are other ways that you can deal with this too. If you can work out how to call a function based on its name, you can have a centralized State Manager that you can register functions to call when the above questions need answers.
The MVC framework Blindfish mentions is another option. Have one Model of your data (which would manage all your data and states), and then Views of it (so you can, for instance, know what to draw at a given moment), and also Controllers that allow you to manipulate that data (as you might have to when dealing with inputs like mouse clicks).
It's also interesting that Processing itself has easy answers to these questions.
setup() lets us setup a sketch.
draw() lets us update it and display things.
mousePressed() (and functions like it) let use deal with input.
Maybe we should see if we can extend the PApplet somehow?
great discussion, thanks to both of you!
Chrisir
The 'state manager' you describe sounds a lot like the mediator pattern. In JS it's incredibly easy to register a callback function to launch on a given event; or call a method based on a string; but from the questions I've seen on the forum it's obviously less straightforward to do this in Java. Demos of both of these might prove really useful!
In java callbacks are done with reflection... An example is the
selectFoder()
from Processing's API.In the source at line 6359 one can see that selectFolder() calls
and in line 6365 goes the selectCallback() code:
there are some good answers in this question:
http://forum.processing.org/two/discussion/570/how-to-pass-a-function-method-as-a-parameter-to-another-function-method-callback-function
Don't forget that we can call a function by its String name via method("") or thread("")! =P~
What? Really?
Well then. That's just super.
Just asking why it's best to avoid using switch in draw(), other than a bit of time overhead it does work. Are there other reasons?
Good OOP? No reason?
Anyway, here's a StateSystem class & associated code, with a small demo sketch...
Yes, I like your method, neat. I have an int variable that I use in a switch to choose what gets displayed. It works. I'm self taught in OOP so I imagine my code could do with a lot of improving ...
amzing, TFGuy44
can you explain, briefly, what is it doing?
Sure. There's a StateSystem class, which I created an instance of, called ss. Now the state system will track all your sketch's states. But to do that, it needs to know which states you want. So you can tell it to add or remove states with add_state() and remove_state(), and pass in a String that is the name of the state that you want to add or remove.
... Actually don't use remove_state(). I didn't test it, and thinking about it now, it will probably break the sketch if you use it (imagine removing the active state, say).
After a state is added to the state system, you can also tell the state system which functions you want to run when that state is the active state. This is done with the register_*() functions. To register a draw function, you tell the state system which state you are registering it for (by the state's name) and the name of the function you want to call (or "" to remove it), by calling register_draw("StateName", "StatesDrawFunction" );
Now, Processing handles when to call draw(), mousePressed(), mouseReleased, keyPressed(), keyTyped(), and keyReleased(). When those functions are called, they simply call the state system and tell it which function was called. The state system then calls the correct registered function for the active state. I picked these ones because they are the most common (and only?) means of doing user input.
There's also a to_state() function that lets you change the active state. You can call this from your registered functions to go to a different state.
There are some drawbacks. For example, data in one state is not associated with that state, unlike in the inheriting version above. Tracking varibles is something that you will still have to handle. There's also a lot of function overhead. Plus you should only have one instance of the state system, as Processing's draw() is hard coded to call ss.draw().
A State, of course, is just an array of 7 Strings. One for the state's name and six more for the names of the registered functions to call (if any).
Does that help explain it?
Wow, that's good work indeed,
thank you so much!
@TFGuy44 :
So on an everyday basis: would you use your first solution?
It's good but complex.....
No, I wouldn't use either of them. Usually I write sketches that are not complex enough to warrant a state management system at all. Usually all the data the sketch needs is fine at the global scope, and switch statements inside draw() or mousePressed() are enough to route input.
If, however, I was going to write a more complex sketch, then, for the record, yes, I still like the inheriting approach better, for the simple reason that it also encapsulates the data associated with each state.
I see
There would also be other classes than state (player, enemy...)
And some global vars that are used by all states maybe
Thank you again, great advice!