I'm making a game using Java.
At the moment, I have several classes. The important ones are:
the LevelBuilder class which, upon it's default constructor being called will create a jframe with the required components and then run a gameloop thread which will update even 1/20th of a second using a backbuffer.
The other class is the MainMenu class which i want to have the main method in and to display my logo in a JFrame.
In the end I want to have the MainMenu draw a splash screen to the JFrame, and then after 5 seconds the LevelBuilder to draw inside the original JFrame, without creating a new one.
Sorry if it's a basic question, I just started learning Java.
Well a splash-screen can simply be added to your jar via the manifest.
The problem is by default it will only show for as long as it takes Swing app to load. Thus the 2nd (3rd 4th etc) execution(s) shows the splash-screen fast as JVM and classes etc used by GUI have already been loaded.
In my game to create a splash that stays for longer I had these 2 methods:
/**
* This will render the splash for longer than just loading components
*
* #return true if there is a splash screen file supplied (set via java or
* manifest) or false if not
* #throws IllegalStateException
*/
private boolean showSplash() throws IllegalStateException {
final SplashScreen splash = SplashScreen.getSplashScreen();
if (splash == null) {
return false;
}
Graphics2D g = splash.createGraphics();
if (g == null) {
return false;
}
for (int i = 0; i < 100; i++) {//loop 100 times and sleep 50 thus 100x50=5000milis=5seconds
renderSplashFrame(g);
splash.update();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
splash.close();
return true;
}
private void renderSplashFrame(Graphics2D g2d) {
//draw anyhting else here
}
which will be called something like:
JFrame frame=...;
...
//show splash
if (!showSplash()) {
JOptionPane.showMessageDialog(null, "SplashScreen could not be shown!", "Splash Error: 0x003", JOptionPane.ERROR_MESSAGE);
}
// set JFrame visible
frame.setVisible(true);
frame.toFront();
Please note as showSplash() says it will return false if there is no splash-screen supplied i.e you have not added one to the manifest.
I also would recommend a read on How to Create a Splash Screen if you already haven't.
Also see this other similar answer/question: Make splash screen with progress bar like Eclipse
Related
We are tasked with making a simple game. Now, we have had the game done for a long while now, but I have only just gotten around to implementing the join/host menu (using a JPanel in our JFrame, which only contains a canvas which is used to render our sprites/shapes/etc.). We have 4 JTextFields and two JButtons. Our canvas is set to disabled when this display is shown so as to not interfere with input (i.e., with its mouse listener and such). On Windows machines, we can click on all of the boxes, we get the nice I-beam cursor, etc., and we can type in there normally and then click the buttons. However, when the same is attempted on MacOS, you cannot click on the boxes or the buttons. You don't get the I-beam on the boxes. It's like they don't exist. However, we can use the tab key to switch focus through all elements, and can use that to type in the boxes, press the buttons, etc., just as you should be able to with the mouse. I've tried requesting focus like 20 different ways, but that didn't seem to work. I've made many other apps the same way (JFrame > JPanel > JButton/TextField), and they all have worked just fine on MacOS. I have never seen anything like this before.
Rather than post a whole bunch of entire files, I'll trim them down. The first one is our main entry point, the Game class. It looks something like this:
public class Game extends Canvas implements Runnable
private JFrame frame;
private ConnectScreen connectScreen;
public getFrame() { /* get reference to frame */ }
public setFrame() { /* set the frame */ }
public void run() { /* game loop, calls render() */ }
public Game() { /* create window, add canvas to it, get reference to frame, instantiate stuff, etc. */ }
public void tick() { /* every frame this happens, just tells spawners to spawn stuff, etc. */ }
public void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
///////// Draw things below this/////////////
g.setColor(Color.black);
g.fillRect(0, 0, WIDTH, HEIGHT);
// SCREEN
if (!isPaused()) {
// THIS is where we tell the canvas to enable if we're not on the connect screen
if (gameState != STATE.Join && gameState != STATE.Host) {
this.setEnabled(true);
}
/* i omitted rendering from all of these, since it's not useful here. most say somethingScreen.render(g) or something like that. */
if (gameState == STATE.Wave || gameState == STATE.Multiplayer || gameState == STATE.Bosses || gameState == STATE.Survival) { // render gameplay items
} else if (gameState == STATE.Menu || gameState == STATE.Help || gameState == STATE.Credits) { // render menu
} else if (gameState == STATE.Upgrade) { // render upgrade screen
} else if (gameState == STATE.GameOver) { // render game over screen
} else if (gameState == STATE.Leaderboard) { // render leaderboard
} else if (gameState == STATE.Color) { // render color picker screen
} else if (gameState == STATE.Join || gameState == STATE.Host) {
// if we are on the connect screen, disable this canvas
this.setEnabled(false);
connectScreen.render(g);
} else if (gameState == STATE.LeaderboardDisplay) { // render the leaderboard
}
} else {
pauseMenu.render(g);
}
if(!isPaused()) {} // renders the handler's things
///////// Draw things above this//////////////
g.dispose();
bs.show();
}
}
Other than that, there's not much of interest in that class. Here's the (trimmed down) ConnectScreen class:
public class ConnectScreen extends JPanel {
JFrame frame; // this is the frame from the Game class
JTextField _____; // there are 4 of these
JButton _____; // there are 2 of these
public ConnectScreen(Game game) { // the game is passed in to get the frame from it
super();
this.setBackground(Color.black);
this.setPreferredSize(new java.awt.Dimension(Game.WIDTH, Game.HEIGHT));
this.setSize(new java.awt.Dimension(Game.WIDTH, Game.HEIGHT));
this.game = game;
this.mpSpawn = mp;
game.getFrame().add(this);
this.setFocusable(true);
/* set up each component */
/* add each component (click handlers, etc.) */
/* add panel to frame */
}
public void render(Graphics g) {
super.paintComponent(g);
this.paintComponents(g);
}
}
We know the setup of elements and such all worked, since we can see and use them all on Windows, but for some reason when we try it on MacOS, it doesn't work. We can tab-select them and type/press buttons, but for some reason you cannot click on them to focus. Any help would be appreciated. Thanks!
Fixed it, thanks to MadProgrammer. Apparently, a BufferStrategy cannot be used to render Swing components. I just added an if statement in Game::render() where it only does any rendering if the ConnectScreen isn't shown. If it is, it just disables and turns the canvas invisible, while the ConnectScreen::render() (renamed to override JPanel::paintComponent()) just uses super.paintComponent() to passively draw Swing components.
Thank you in advance for any attempts at trying to help me figure this one out. I've searched all over stack and google and haven't been able to find anything similar to the problem I am having.
I am designing a small program using NetBeans that switches between multiple frames
login screen > settings screen > main screen
My program can take a while in-between frames to load the next one due to a large amount of frame initialization code. To prevent it from looking like the program is unresponsive. I wrote code which displays an intermediary splash screen JWindow which performs all the initialization for the next frame. I realize that there is a splash screen class, but I could not get it to work as desired, this custom implementation of mine does almost everything I need except when I attempt to display the splash screen a second time, during a single program run, the image does not show up on the JWindow.
So run program > splash screen (works) > login > splash screen (image does not load) > settings.
Here is a sample clip of code which displays the splash screen JWindow:
public class Splash extends JPanel {
private static Calendar date;
private static long startTime;
private static final long MAX_TIME = 3000l;
public static final int LOAD_LOGIN = 0;
public static final int LOAD_SETTINGS = 1;
public static JWindow win;
private final ImageIcon img;
public Splash() {
date = Calendar.getInstance(TimeZone.getTimeZone("EST"), Locale.ENGLISH);
img = new ImageIcon("loader.gif");
startTime = date.getTimeInMillis();
this.setSize(300, 300);
win = new JWindow();
win.setSize(300, 300);
win.getContentPane().add(this);
Login.centerWindow(win); // method from login class that centers window
win.setVisible(true);
}
public void runSplash(int flag) {
if (flag == LOAD_LOGIN)
firstRunCheck();
else {
initSettings();
}
long currTime = date.getTimeInMillis();
// holds splash display for at least 3 secs
while ((currTime - startTime) < MAX_TIME) {
currTime = System.currentTimeMillis();
}
win.dispose(); // has been replaced with win.setVisible(false) before,
// same problem
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponents(g);
Graphics2D g2 = (Graphics2D) g;
//I have read several other questions and sample codes on stack and google
but using this method is the only way for me to get the animated gif image to
load correctly
g2.drawImage(img.getImage(), null, this);
}
}
In order to display this splash screen I instantiate it using new Splash().runSplash(value); where value runs the method that loads the following frames required values.
When I first begin run the program everything works perfectly. When I attempt to move from the login screen to the settings screen the JWindow loads but it just displays as a blank frame.
The program works fine still, the settings frame eventually loads, but the image never shows up and the window just closes. I am unsure as to what the problem is the class is instantiated the exact same way as the first time.
I suspect perhaps I may be incorrectly disposing of resources, but win.dispose() appears to be the correct method according to some of the other problems I have read.
Any help is appreciated thank you.
UPDATE: I am still attempting to resolve this problem. I have used a variety of different implementations to display the splash screen. I have used implementations which use a JLabel instead of JPanel, and use setIcon instead of overriding paintComponent. But the same result is observed whenever the implemented splash screen class is called a second time the image does not appear. Since different implementations are also not working I can only assume that that perhaps I have too many events in the event queue or something. To clarify.
My main method is in the Splash class detailed above. I call the class in the main method using new Splash.runSplash(value). This call works perfectly. I then call the login JFrame using:
if (value == LOAD_LOGIN) //RUN_LOGIN is a final variable int
{
java.awt.EventQueue.invokeLater(new Runnable()
{
#Override
public void run()
{
/* I have also tried this without the invokeLater
command and simply using new Login().setVisible(true).
The program still runs fine, but the image still
does not display */
new Login().setVisible(true);
}
});
}
This call is made at the end of the runSplash(value) method. After the user inputs the correct login information they are taken to the splash screen again using new Splash().runSplash(value) on this call however I cannot get the image to show up on the JWindow.
dispose() method releases the resource. to be able to use it again you have to use setVisible(false) and when you be sure that your code will not use this window again then only use dispose().In your case after first splash just use setVisible(False) and dispose after the second time when setting frame loads if you really want it so.
Im making a quiz application. And when a team pushed their button, they aren't allowed to still see the question or see the video/audio piece. This kind of information is displayed in the quizview ( this is a jpanel ) added to a parent JFrame. What i'm trying to do is minimize the JFrame when a button is pushed from the teams. This works perfectly. If a button is pushed the administator get a pop up in his view. If the answer is incorrect the JFrame should be maximized again. So when he pushes a button if the answer is incorrect, a boolean will be set on true in the quiz model ( $maximize ). We update the views then. In the update of the view im checking if the boolean is set on true. If it is true, i call the maximize method. And maximize it again when the answer is wrong. Minimizing works perfectly, but maximizing not.
Anyone knows what's wrong?
This is my code in the view, this view is a JPanel in the bigger JFrame, where the maximizing happens :
public void update(Observable arg0, Object arg1) {
$question = (Question) arg1;
if(((QuizModel) getModel()).getMaximize()) /* Only maximize the JFrame when needed */
maximizeFrame(); /* Maximize the frame first */
repaint();
}
/**
* Maximize the parent JFrame so the teams can see the question
*/
protected void maximizeFrame() {
System.out.println("MAXIMIZE");
JFrame topFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
topFrame.setState(JFrame.MAXIMIZED_BOTH);
}
The minimizing occurs when a team pushed their button, here is the code :
/**
* If a button gets pressed we call this function
* #param team the team that pressed their button
*/
protected void buttonPressed(int team) {
/* Check if a button is pressed */
if(((QuizModel) getModel()).getTeamPressed() > 0)
$isPressed = true;
else
$isPressed = false;
/* If there hasn't been pressed yet and the question is not null */
if(!$isPressed && $question != null){
minimizeFrame(); /* Minimize the frame */
/* If this question has a media path we need to pause the audio/video, we also check if the user has installed vlcplayer and selected the right path */
if($question.getMediaPath() != null && QuizSoftwareModel.$vlcPath != null)
((QuizController)getController()).pause(); /* Pause the video */
/* Give a pop up message to the admin that a team has pushed their button */
((QuizController)getController()).showScoreView(team);
}
}
/**
* Minimize the parent JFrame so the teams can't see the question anymore
*/
protected void minimizeFrame() {
JFrame topFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
topFrame.setState(JFrame.ICONIFIED);
}
[EDIT] Reduced the code.
Thanks!
The solution is using topFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); instead of setState.
Setstate only sets the state of the current frame, because i needed the parent frame i needed to use setExtendedState.
Also missed some booleans to maximize/minimize when needed.
I'm writing a java swing application which uses external resource files.
Many of the resource files are only necessary to load with certain selections.
Upon making a selection, the first window closes, the appropriate external resources are loaded, and another window opens with the resources.
The first window has a splash screen to cover the loading time.
How can I make the second window have something similar?
What I've seen involves a task occurring in the same window, which isn't viable for this project, and the Java's built-in splash screen won't start a second time (SplashScreen.getSplashScreen returns null).
In OtrosLogViewer I display first splash screen defined in MANIFEST.MF. When application is loading I render new splashscreen according to loading progress. OtrosSplah.java is calling method render to repaint splash:
private static void render() {
SplashScreen splashScreen = SplashScreen.getSplashScreen();
if (splashScreen == null) {
return;
}
Graphics2D g = splashScreen.createGraphics();
if (g == null) {
return;
}
if (version == null) {
try {
version = VersionUtil.getRunningVersion();
} catch (IOException e) {
version = "?";
}
version = "Version: " + version;
}
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setComposite(AlphaComposite.Clear);
Rectangle bounds = splashScreen.getBounds();
g.fillRect(0, 0, bounds.width, bounds.height);
g.setPaintMode();
g.setColor(Color.BLACK);
g.setFont(g.getFont().deriveFont(14f));
g.drawString(message, 20, 110);
g.drawString(version, 20, 130);
splashScreen.update();
}
You can do the same. Display first splash screen from MANIFEST.MF and later paint new one.
Instead of using the SplashScreen API, you could simply create yourself a JWindow.
Into this you can either add a bunch of components to provide the functionality you need (ie a JLabel for the background, a JLabel for the message) and make it visible while you're loading your resources.
When you done, you can simply dispose of the window.
Also, make sure, you're performing all you loading in a background thread. SwingWorker would be good for this purpose, IMHO
This answer demonstrates the concept, seek the second example...
I am using a JColorchooser at various places in an application. There can be multiple instances of the panel that can invoke a JColorChooser.
The "Swatches" panel in the chooser has an area of "recent" colors, which only persists within each instance of JColorChooser. I would like to (a) have the same "recent" colors in all my choosers in my application, and (b) to save the colors to disk so that these colors survive close and restart of the application.
(At least (a) could be solved by using the same single chooser instance all over the whole app, but that apears cumbersome because I would need to be very careful with attached changelisteners, and adding/removing the chooser panel to/from various dialogs.)
I did not find any method that lets me set (restore) these "recent" colors in the chooser panel. So to me, it appears that the only ways of achieving this would be:
serialize and save / restore the whole chooser (chooser panel?)
or
create my own chooser panel from scratch
Is this correct, or am I missing something?
BTW: I would also like to detect a double click in the chooser, but it seems hard to find the right place to attach my mouse listener to. Do I really need to dig into the internal structure of the chooser panel to do this? (No, it does not work to detect a second click on the same color, because the change listener only fires if a different color is clicked.)
As you noticed, there is no public api to access the recent colors in the DefaultSwatchChooserPanel, even the panel itself isn't accessible.
As you'll need some logic/bean which holds and resets the recent colors anyway (plus the extended mouse interaction), rolling your own is the way to go. For some guidance, have a look at the implementation of the swatch panel (cough ... c&p what you need and modify what you don't). Basically, something like
// a bean that keeps track of the colors
public static class ColorTracker extends AbstractBean {
private List<Color> colors = new ArrayList<>();
public void addColor(Color color) {
List<Color> old = getColors();
colors.add(0, color);
firePropertyChange("colors", old, getColors());
}
public void setColors(List<Color> colors) {
List<Color> old = getColors();
this.colors = new ArrayList<>(colors);
firePropertyChange("colors", old, getColors());
}
public List<Color> getColors() {
return new ArrayList<>(colors);
}
}
// a custom SwatchChooserPanel which takes and listens to the tracker changes
public class MySwatchChooserPanel ... {
ColorTracker tracker;
public void setColorTracker(....) {
// uninstall old tracker
....
// install new tracker
this.tracker = tracker;
if (tracker != null)
tracker.addPropertyChangeListener(.... );
updateRecentSwatchPanel()
}
/**
* A method updating the recent colors in the swatchPanel
* This is called whenever necessary, specifically after building the panel,
* on changes of the tracker, from the mouseListener
*/
protected void updateRecentSwatchPanel() {
if (recentSwatchPanel == null) return;
recentSwatchPanel.setMostRecentColors(tracker != null ? tracker.getColors() : null);
}
// the mouseListener which updates the tracker and triggers the doubleClickAction
// if available
class MainSwatchListener extends MouseAdapter implements Serializable {
#Override
public void mousePressed(MouseEvent e) {
if (!isEnabled())
return;
if (e.getClickCount() == 2) {
handleDoubleClick(e);
return;
}
Color color = swatchPanel.getColorForLocation(e.getX(), e.getY());
setSelectedColor(color);
if (tracker != null) {
tracker.addColor(color);
} else {
recentSwatchPanel.setMostRecentColor(color);
}
}
/**
* #param e
*/
private void handleDoubleClick(MouseEvent e) {
if (action != null) {
action.actionPerformed(null);
}
}
}
}
// client code can install the custom panel on a JFileChooser, passing in a tracker
private JColorChooser createChooser(ColorTracker tracker) {
JColorChooser chooser = new JColorChooser();
List<AbstractColorChooserPanel> choosers =
new ArrayList<>(Arrays.asList(chooser.getChooserPanels()));
choosers.remove(0);
MySwatchChooserPanel swatch = new MySwatchChooserPanel();
swatch.setColorTracker(tracker);
swatch.setAction(doubleClickAction);
choosers.add(0, swatch);
chooser.setChooserPanels(choosers.toArray(new AbstractColorChooserPanel[0]));
return chooser;
}
As to doubleClick handling: enhance the swatchChooser to take an action and invoke that action from the mouseListener as appropriate.
You can use the JColorChooser.createDialog method - one of the parameters is a JColorChooser. Use a static instance of the JColorChooser and make it the Dialog modal - that way, only one color chooser is displayed at a time.
The createDialog method also takes ActionListeners as parameters for the OK and Cancel button. Thus, don't really have to manage listeners. Of course, this doesn't persist the recent colors across invocations of the app, just persists recent colors in the current app.
Here's a workaround using reflection - it will work provided the underlying implementation doesn't change. Assuming you have a JColorChooser, add your recent colors to it like this:
final JColorChooser chooser = new JColorChooser(Color.white);
for (AbstractColorChooserPanel p : chooser.getChooserPanels()) {
if (p.getClass().getSimpleName().equals("DefaultSwatchChooserPanel")) {
Field recentPanelField = p.getClass().getDeclaredField("recentSwatchPanel");
recentPanelField.setAccessible(true);
Object recentPanel = recentPanelField.get(p);
Method recentColorMethod = recentPanel.getClass().getMethod("setMostRecentColor", Color.class);
recentColorMethod.setAccessible(true);
recentColorMethod.invoke(recentPanel, Color.BLACK);
recentColorMethod.invoke(recentPanel, Color.RED);
//add more colors as desired
break;
}
}