Java-Gaming.org    
Featured games (81)
games approved by the League of Dukes
Games in Showcase (488)
Games in Android Showcase (112)
games submitted by our members
Games in WIP (553)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  How do I handle "screens"?  (Read 4151 times)
0 Members and 1 Guest are viewing this topic.
Offline Regenuluz
« Posted 2012-09-18 06:51:07 »

Alright, so I'm working "hard" on the gui for my game. But I'm wondering how best to handle different screens.

Currently I have an interface called Screen, which all screens implement, this interface defines an update() method and a render() method. So these has to be implemented whenever you create a new screen. My question is, how best to handle new screens, e.g. when you're in the main menu and you go to a submenu, etc. how should I go about switching them?

Should I just replace the content of the screen variable, in the Game class, with a new instance of a screen, and then just reinitialize the screen whenever I go back to a previous menu?

Cheers! Smiley
Offline princec

JGO Kernel


Medals: 367
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #1 - Posted 2012-09-18 08:15:28 »

I've got the notion of a stack of Screens (held statically in my Screen class, meh, you might want to do something like a ScreenManager but whatever...). Only the screen on top of the stack accepts input from the mouse and keyboard. Every time I open a screen it can do one of two things:

1. If it's a totally new screen, the current screen on top of the stack is popped and discarded and the new screen is plonked on top.
2. If it's a "dialog" screen it gets plonked on top of the stack.

All the screens in the stack are updated and rendered, one after the other, every frame. However only the topmost one actually processes input. In each screen's update method, for example, there's a little check:
1  
if (!isOnTop()) { return; }


at some point; what I do is allow certain things to still tick away like animations and so on in the background, but stop short of then processing input at that point.

Cas Smiley

Offline Regenuluz
« Reply #2 - Posted 2012-09-18 10:01:01 »

Hrm, I never thought of doing it like that, that even solves the issue of dialogs and such.

Thanks a whole bunch! Cheesy
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Oskuro

JGO Knight


Medals: 39
Exp: 6 years


Coding in Style


« Reply #3 - Posted 2012-09-18 11:30:32 »

I use the screen stack idea too, although I'm thinking about flagging screens I want to stop updating, maybe if they are covered by a new screen that takes up the whole display.

And maybe using the stack as a Z-depth check to allow switching from foreground to background screens (menus, sub-windows) via mouse input, although that would mess up the stack and need a linked list or something.



A question though. Regarding the Screen class in Java. I want to use the most barebones container for the graphics context, as I do all the drawing myself and have no intention of using Java2d items. Is Screen the best option? Window? Canvas? I get a bit confused with the inheritance between them.

Offline DrHalfway
« Reply #4 - Posted 2012-09-18 12:36:41 »

for my Screens I wrote something of a GUI Manager which is dedicated to managing and layering of GUI elements. For example, the user can create a GUIGroup which can store things like buttons, labels and graphic areas (fancy on-screen stuff). If the GUIGroup is "hidden" no rendering or input is performed on it or any of the elements that belong to it. This allows great control of say showing and hiding a number of groups at same time. It is considered a complete separate entity from the screen itself, it allows for greater control and abstraction from GUI elements to in-game elements such as GameObjects.

Offline Regenuluz
« Reply #5 - Posted 2012-09-18 13:13:45 »

This:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
public class ScreenManager {
   private static final LinkedList<Screen> screens = new LinkedList<Screen>();
   
   public static void add(Screen screen) {
      ScreenManager.screens.push(screen);
   }

   public static void render(Graphics2D g) {
      for(Screen screen : ScreenManager.screens) {
         screen.render(g);
      }
   }

   public static void update() {
      for(Iterator<Screen> it = ScreenManager.screens.iterator(); it.hasNext(); ) {
         Screen screen = it.next();
         screen.update();
         
         if(screen.shouldRemove()) {
            it.remove();
         }
      }
   }
}


is what I've cooked up so far, but I haven't tested it at all yet. (And frankly, then I don't think that will really work as well as I want)

But could something like that work? I know it's still missing some stuff, for it to be perfectly usable.

A question though. Regarding the Screen class in Java. I want to use the most barebones container for the graphics context, as I do all the drawing myself and have no intention of using Java2d items. Is Screen the best option? Window? Canvas? I get a bit confused with the inheritance between them.

I'm drawing on a Canvas. Smiley
Offline theagentd
« Reply #6 - Posted 2012-09-18 20:16:39 »

(I know it doesn't matter in this case, but use ArrayList instead of LinkedList. Pardon the interruption.)

Myomyomyo.
Offline Best Username Ever

Junior Member





« Reply #7 - Posted 2012-09-18 20:34:26 »

State machine and decks. (Or so I heard. I'm not sure and have never made anything with more than half a dozen screens. Someone fill in the rest.)
Offline Regenuluz
« Reply #8 - Posted 2012-09-19 06:27:33 »

(I know it doesn't matter in this case, but use ArrayList instead of LinkedList. Pardon the interruption.)

Interruption pardonen Tongue But isn't LinkedList faster than ArrayList, when you only push, pop and iterate?

--
Alright, I've now had a little time to actually try and make something that seems to be working as I'd like. The class now looks like this:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
package org.infloop.main;


import java.awt.Dimension;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.LinkedList;

import org.infloop.ui.Screen;

public class ScreenManager {
   private static final ArrayList<Screen> screens = new ArrayList<Screen>();  
   private static final ArrayList<Screen> screensToAdd = new ArrayList<Screen>();
   private static int screensToAddCount = 0;
   
   public static Dimension screenDimension = new Dimension();
   
   public static void add(Screen screen) {
      ScreenManager.screensToAdd.add(screen);
      ScreenManager.screensToAddCount++;
   }
   
   /**
    * Determine if the {@code Screen} is the top one in the stack.
    *
    * @param screen the screen to check
    * @return true if the {@code Screen} is on top
    */

   public static boolean isOnTop(Screen screen) {
      return ScreenManager.screens.get(ScreenManager.screens.size() - 1) == screen;
   }
   
   /**
    * Render all the {@code Screen}'s.
    *
    * @param g the graphics object to render the screens on
    */

   public static void render(Graphics2D g) {
      for(int i = ScreenManager.screens.size()-1; i >= 0; i--) {
         ScreenManager.screens.get(i).render(g);
      }
   }
   
   /**
    * Update all the {@code Screen}'s.
    */

   public static void update() {
      LinkedList<Screen> toBeRemoved = new LinkedList<Screen>();
     
      // Update all Screens
     for(Screen screen : ScreenManager.screens) {
         screen.update();
         
         if(screen.shouldRemove()) {
            toBeRemoved.push(screen);
         }
      }
     
      // Remove Screens
     for(Screen screen : toBeRemoved) {
         ScreenManager.screens.remove(screen);
      }
     
      // Add new Screens
     if(ScreenManager.screensToAddCount > 0) {
         for(Screen screen : ScreenManager.screensToAdd) {
            ScreenManager.screens.add(screen);
         }
         ScreenManager.screensToAdd.clear();
         ScreenManager.screensToAddCount = 0;
      }
   }
}


So now just to figure out how to allow animations in components to continue running, without the component answering to input, unless the screen is on top. I think I might just either split update() into updateAnimation() and updateInput() or change it to update(boolean allowInput).
Offline 65K
« Reply #9 - Posted 2012-09-19 07:56:35 »

I say
- isOnTop shouldn't break if there are no screens at all
- screensToAddCount can be replaced by screensToAdd.size()
- toBeRemoved could be a field as well
- no good reason to make this all static

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Regenuluz
« Reply #10 - Posted 2012-09-19 08:06:05 »

I say
- isOnTop shouldn't break if there are no screens at all
- screensToAddCount can be replaced by screensToAdd.size()
- toBeRemoved could be a field as well
- no good reason to make this all static

Ups, fixed the isOnTop, so it returns false if there's no screens, instead of throwing an index out of bounds exception. Thanks for noticing. Smiley
I also removed the count and just use size() instead. No idea why I didn't just do that to begin with. :/

The reason it's static, is so that I don't have to pass references around to the screens. Smiley Though I guess the only real overhead for not making them static would be that I'd have to type more to pass around the reference. Tongue
Offline 65K
« Reply #11 - Posted 2012-09-19 08:26:01 »

It would be better to have the dependency only point in one direction: from ScreenManager to Screen.
Screens should not know about the underlying overall screen handling.
Besides removing the static stuff, maybe split the ScreenManager, give screens a callback interface, register listeners, something like that.

Offline Regenuluz
« Reply #12 - Posted 2012-09-19 08:46:49 »

Hm, well a screen should be able to add new screens to the stack, so it needs to be able to talk to the ScreenManager ^^ (at least through the add() function)

How'd you go about making it non-static without having to pass references around? The screen would need to know something of the manager, or else it can't add new screens.
Offline Danny02
« Reply #13 - Posted 2012-09-19 10:12:00 »

only use static methods for utility functions.
Remove all the statics, then you can easily create a single instance of the object, which reference is somehow passed around.
This so called Singelton can be made a static final instance, like many do on this board.
Or what I prefer, hand the reference always around so you don't have any hidden dependecies.

1  
2  
3  
public class ScreenManager {
private static final ScreenManager instance = new ScreenManager();
public static ScreenManager getInstance(){return instance;}
Offline 65K
« Reply #14 - Posted 2012-09-19 10:18:06 »

Passing and accessing references is daily business in object orientation and it is inevitable to get accustomed to it. Yes, it is sometimes annoying. But it's good to first know how to get by with it before breaking this rule for appropriate exceptions.

For restricting dependencies, you could have an interface and give each screen a reference to it and let the manager implement it.
1  
2  
3  
4  
5  
public interface ScreenController {
   void addScreen(Screen screen);
   void removeScreen(Screen screen);
   ...
}

So whenever a screen feels like adding another one, it just addresses the controller, says "hey guy, please add this new screen for me. By the way, I don't care who you are or how you do it. Just do it."
Only add methods that screens need to call.

What if one day you want a new screen manager which draws some fancy additional debug graphics ? Just let it implement the interface, create the object and hand it over to the screens. No change in any screen required. Could even be switched during runtime.

Offline 65K
« Reply #15 - Posted 2012-09-19 10:39:36 »

One more thought:
What if you implement a whole load of debug screens which are useful to watch in parallel to the game ? Then you will most likely have two screen managers running in parallel, each in its own native window context. Only possible without a static setup.
The state of the ScreenManager (lists of screens) is an indicator that statics are not that good here and a possible dead end.
Utility functions on the other hand are typically context free, they get all parameters with one method call. No side effects when called in parallel.

Offline princec

JGO Kernel


Medals: 367
Projects: 3
Exp: 16 years


Eh? Who? What? ... Me?


« Reply #16 - Posted 2012-09-19 13:24:10 »

And thusly another 50 classes were written that did the same job Smiley Sigh.

Cas Smiley

Offline Oskuro

JGO Knight


Medals: 39
Exp: 6 years


Coding in Style


« Reply #17 - Posted 2012-10-09 12:39:56 »

Sometimes you need to reinvent the wheel so it fits your specific needs.  Grin

Offline Regenuluz
« Reply #18 - Posted 2012-10-09 19:16:32 »

I tend to reinvent the wheel all the time, because I refuse to use solutions other people have cooked up, until I know how to do it myself Tongue

However I'm fairly happy with how my Screen managing system has worked out. There's a few kinks left in it, that I'll have to solve eventually, but right now there's too much school stuff going on, for me to have proper time to look at it. Dat uni, so strong! xD
Offline Oskuro

JGO Knight


Medals: 39
Exp: 6 years


Coding in Style


« Reply #19 - Posted 2012-10-09 20:42:05 »

Well, if you're learning, it is actually advisable to reinvent as many wheels as necessary to perfect your skills  Wink (That's what I do, in any case)


Offline Roquen
« Reply #20 - Posted 2012-10-09 21:19:08 »

Rule of thumb: If the "right" way to do something takes longer to write, is harder to read & maintain the then "wrong" way.  Remember that the people that define the "right" way don't really write software.
Offline Oskuro

JGO Knight


Medals: 39
Exp: 6 years


Coding in Style


« Reply #21 - Posted 2012-11-18 23:46:39 »

Wooo! Threadomancy!

only use static methods for utility functions.

I've been doing this with the global managers in my application:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
public class GlobalManager 
{
    // Singleton Pattern ( Double Checked Lazy Loading )...

    private static GlobalManager getInstance() { return instance; }

    // Stuff...

    public static void staticMethod()  
    {
       GlobalManager.getInstance().internallMethod();  
    }

    private void internalMethod()
    {
       // Do stuff...
   }
}


Note that the getInstance method is private. The idea is for the access methods to be static, so externally there is no need to call the getInstance method, since the classes are meant to be unique and global all the time.

For example, for Logging, instead of this:

1  
2  
Logger.getInstance().Debug( "I'm logging!" );
Logger.getInstance().Error( "Like a BOSS!" );


I have this:

1  
2  
Logger.Debug( "I'm logging!" );
Logger.Error( "Like a BOSS!" );


Which is technically the same, but feels more concise, and cleaner.

My question... What horrible pitfalls await me?

Oh, and calling getInstance or the static methods from the internal methods I already figured out can lead to ugly times. Example:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
//...

private static GlobalManager getInstance()
{
     if(instance == null) instance = new GlobalManager();

     return instance;
}

private GlobalManager()
{
     init();
}

//....

public static void init() // static method
{
    GlobalManager.getInstance().doInit(); //<- EVIL HEAP-KILLING LOOP OF EVIL!
}

private void doInit() // internal method
{
   //...
}


Which is solved by making sure the static interface methods are never called from inside.


Sooooo, anything else I need to be wary of? Is this horribly inefficient? All I get from the net is comparisons of static vs singleton, but no comments on the mutant cross-breed I'm using.  Clueless

Offline Danny02
« Reply #22 - Posted 2012-11-19 00:22:40 »

aaaaaaaaaaaaaah
kill it, kill it with fire

so first you had a bunch of global functions,
as a next step you bundled them in a class as methods,
which you then exposed as static functions again, so back to square one.

so you made your stuff even more error prone and unreadable, congratulations^^


Ps:

instead of doing this, which isn't even thread safe
1  
2  
3  
4  
5  
6  
private static GlobalManager getInstance()
{
     if(instance == null) instance = new GlobalManager();

     return instance;
}


you should do this for lazy initialisation of your Globaltons
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
public class GlobalManager
{
  private static class Lazy
  {
     private static final GlobalManager instance = new GlobalManager();
  }
  private static GlobalManager getInstance()
  {
      return Lazy.instance;
  }
}


and this should only be done if you have other stuff in this class which can be used without the need of the instance.
For your normal Globalton you should not need any lazy initialization stuff. So when you really need a Globalton, just creat a private constructor and one
public static final instance field.
Offline Oskuro

JGO Knight


Medals: 39
Exp: 6 years


Coding in Style


« Reply #23 - Posted 2012-11-19 10:46:06 »

Errrrr, sorry if the example wasn't clear, but no, these classes are not just a conglomeration of static global functions, they actually need an instance.

For example, the Logger class keeps track of registered log appenders (console, files, window components, etc..) to log to, so it cannot be implemented as simple System.out.prinln calls.


As for the Singleton initialization, I actually use the more classic:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
// Singleton Pattern ( Double Checked Lazy Loading )----------------------------
public class DCLSingleton
{
   private static volatile DCLSingleton instance = null;
   
   /** @return Returns the Singleton Logger instance. */
   public static DCLSingleton getInstance()
   {
      if(instance == null)
      {
         synchronized(DCLSingleton.class)
         {
            if(instance == null)
               instance = new DCLSingleton();
         }
      }
     
      return instance;
   }
   
   private DCLSingleton()
   {
      if(instance != null)
         throw new IllegalStateException( "Singleton Already Instanced!" );
   }
}


I am aware of the "instance-holder class" pattern you have referenced, it's just that I'm not entirely sure why it works (In the method I use, I understand why having the instance be volatile and synchronizing the instantiation help solve related concurrency issues)... so an explanation would be awesome, since it is a more elegant way to solve it.

I also considered using enums as my Singleton containers:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
public enum EnumSingleton
{
   INSTANCE;

   private String my_hello = "Hey there World!";

   public String sayHi() { return my_hello; }
}

// ....

EnumSingleton.INSTANCE.sayHi();


But I didn't see clearly how to instantiate said Singleton with specific properties (say, system properties at runtime), and I really wanted to try my static access pattern, which isn't possible since there is no way to hide the INSTANCE element from the enum, unless the whole enum is encapsulated within a container class... Which may be way too convoluted.


To summarize, all I'm doing is encapsulating the Singleton instance calls inside static interface methods. Internally it is a regular Singleton.


Oh, and in case someone wonders (And to prevent a "why do this?!" discussion) I'm using Singleton classes to wrap Java specific elements. The notion is to decouple the program's inner logic from Java specifics as much as possible to facilitate porting to C++ later on.



Offline 65K
« Reply #24 - Posted 2012-11-19 11:23:21 »

Oh, and in case someone wonders (And to prevent a "why do this?!" discussion) I'm using Singleton classes to wrap Java specific elements. The notion is to decouple the program's inner logic from Java specifics as much as possible to facilitate porting to C++ later on.
What is the advantage of going this rather weird static singleton way instead of just separating implemention details by using interfaces and concrete implementation classes ?

Offline Oskuro

JGO Knight


Medals: 39
Exp: 6 years


Coding in Style


« Reply #25 - Posted 2012-11-19 12:06:06 »

Uh... No concrete advantage.

Apart form the goal of having all the specifics packed into a concrete namespace, I'm just having fun with the implementation, experimenting and learning.  Grin

For me, it is more comfortable to treat System classes as static elements, and so far I haven't found a solid reason not to (hence why I'm asking about pitfalls).



Oh, and the reason I'm posting here is because I'm coding my application's screen manager currently and came here looking for ideas (maybe it'd be better to split this into a "Singleton Madness" thread?)

Offline Roquen
« Reply #26 - Posted 2012-11-19 13:22:24 »

Quote
... it's just that I'm not entirely sure why it works ...
Class loading specification.
Offline Oskuro

JGO Knight


Medals: 39
Exp: 6 years


Coding in Style


« Reply #27 - Posted 2012-11-19 14:49:16 »

Class loading specification.

I know I know, I'm reading on it.

Edit: Ok, I think I get it. My problem was that I wasn't seeing how the pattern was doing lazy initialization when the instance attribute instantiation was being hard-coded... Hadn't realized that the loading of the internal static InstanceHolder class doesn't happen until the wrapping class itself is used, resulting in a lazy initialization that the class loader itself makes sure will be thread safe.

Or that's what I'm getting out of it, anyway.  Clueless

Offline sproingie

JGO Kernel


Medals: 202



« Reply #28 - Posted 2012-11-19 18:58:29 »

While you're reinventing a DI container with this GlobalManager nonsense, you may as well just use one.  No one is a better coder for hand-writing half-assed solutions to long-solved problems.  I'd go with Guice, since it's very well-tested and closest to a standard (JSR330) but if you want the simplicity of just asking for an instance explicitly, then give PicoContainer a shot. 
Offline Oskuro

JGO Knight


Medals: 39
Exp: 6 years


Coding in Style


« Reply #29 - Posted 2012-11-19 20:59:39 »

Thanks for the suggestion, but I purposefully want to implement everything by hand to get a better idea of the inner workings of the system.  Grin

Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

TehJavaDev (17 views)
2014-08-28 18:26:30

CopyableCougar4 (26 views)
2014-08-22 19:31:30

atombrot (39 views)
2014-08-19 09:29:53

Tekkerue (36 views)
2014-08-16 06:45:27

Tekkerue (33 views)
2014-08-16 06:22:17

Tekkerue (22 views)
2014-08-16 06:20:21

Tekkerue (33 views)
2014-08-16 06:12:11

Rayexar (69 views)
2014-08-11 02:49:23

BurntPizza (46 views)
2014-08-09 21:09:32

BurntPizza (37 views)
2014-08-08 02:01:56
List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06

List of Learning Resources
by SilverTiger
2014-07-31 11:54:12

HotSpot Options
by dleskov
2014-07-08 01:59:08
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!