Say I have a splash screen. This screen is loaded only at the beginning of the game, and afterwards I don't use it.
Is it possible to dispose this screen on demand?
I tried to dispose it right after setting the screen after the splash, and also tried to call dispose() on the hide() method.
Both of the tries rendered in an exception.
How can I dispose this screen on demand? I have there pretty heavy textures, and wanted to free the memory as soon as possible.
Example:
// SplashScreen class
class SplashScreen implements Screen {
private boolean renderingEnabled = true;
private Object lock = new Object();
#Override
public void render(float delta) {
synchronized(lock) {
if (!renderingEnabled) {
return;
}
spriteBatch.begin();
// here render the animations
spriteBatch.end();
}
}
#Override
public void dispose() {
synchronized(lock) {
renderingEnabled = false;
// here come the disposing of SpriteBatch and TextureAtlas
atlas.dispose();
atlas = null;
spriteBatch.dispose();
spriteBatch = null;
}
}
}
// The usage
game.setScreen(splashScreen);
...
// now when the splash screen animation is finished, I am calling the following from the controller:
splashScreen.dispose();
game.setScreen(mainMenuScreen);
I get the following exception:
FATAL EXCEPTION: GLThread 398
java.lang.NullPointerException
at mypackage.SplashScreen.render(SplashScreen.java:85)
at com.badlogic.gdx.Game.render(Game.java:46)
at mypackage.Game.render(MyGame.java:33)
at com.badlogic.gdx.backends.android.AndroidGraphics.onDrawFrame(AndroidGraphics.java:414)
at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1522)
at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1240)
And when I have the following dispose pattern (disposing after showing the mainMenuScreen)
// The usage
game.setScreen(splashScreen);
...
// now when the splash screen animation is finished, I am calling the following from the controller:
game.setScreen(mainMenuScreen);
splashScreen.dispose();
I get the same exception.
The exception is on spriteBatch.end() row inside the render method. The spriteBatch member turns to be null. Really confusing, since I have mutual exclusive lock on the render and the dispose methods.
What I figured out, is that LibGDX works in one thread.
What happens is the following. When the game.setScreen(mainMenuScreen) is called, and the program is in the middle of the render method of the splash screen, the thread is interrupted, and the code for setting the mainMenuScreen is executed (in the Game class), and then going back to continue the render method of the splash screen. Weird, right? After setting the new screen still to render the old screen?
Anyways, what I did is the following:
As part of the Game.setScreen(Screen screen) method, the Screen.hide() method is called on the old screen.
So what I did, I introduced a flag, hideCalled and set it to true in the hide() event inside the splash screen.
Inside the render method of the splash screen I check this flag at the beginning and at the end of the method. If the flag is true - I dispose the screen.
I was just thinking about AssetManager, maybe it would help you with this problem in a cleaner way.
Take a look here.
I see it loads assets asynchronously so maybe this problem that you described would not be happening again.
And just for some knowledge sharing, I changed screens with another approach:
I used actions attached to an image, and these actions are executed in the order they are added. First i created an action which loads the images, then, when this first action was finished, i added another action which switches the screen.
Here is my code:
#Override
public void show() {
stage.addActor(splashImage);
splashImage.addAction(Actions.sequence(Actions.alpha(0), Actions.run(new Runnable() {
#Override
public void run() {
Assets.load();
}
}), Actions.fadeIn(0.5f), Actions.delay(1), Actions.fadeOut(0.5f), Actions.run(new Runnable() {
#Override
public void run() {
Utils.changeGameScreen(new GameScreen());
// ((Game) Gdx.app.getApplicationListener()).setScreen(new
// GameScreen());
}
})));
}
Related
I'm currently developing a game with my team on LibGDX and we're using a .tmx tile map. We're trying to get it to render, and it does render, but oddly in the corner of the screen instead of in the middle of the screen. As in, the bottom left corner of the map appears in the middle of the screen. I believe that at some point, we convince the program that the middle of the screen is x=0, y=0, because we set the initial position of our character (entity) to x=0, y=0, and it appears at the bottom left corner of the map (middle of the screen). I've worked on this problem for hours and I'm at a loss for what to do, especially since this is my first time using LibGDX.
Here's some of our code:
public class MyGame extends Game {
public static MyGame INSTANCE;
private OrthographicCamera cam;
public Viewport viewport;
public MyGame(){
INSTANCE = this;
}
#Override
public void create () {
cam = new OrthographicCamera();
cam.setToOrtho(false, 1130, 1130);
cam.update();
viewport = new Viewport(cam);
setScreen(viewport);
}
That's the MyGame class.
public class Viewport extends ScreenAdapter{
private OrthographicCamera camera;
private OrthogonalTiledMapRenderer orthogonalTiledMapRenderer;
private TileMapHelper tileMapHelper;
public Viewport(OrthographicCamera cameraIn){
camera = cameraIn;
tileMapHelper = new TileMapHelper(this);
orthogonalTiledMapRenderer = tileMapHelper.setupMap();
}
private void update(){
world.step(1 / 60f, 6, 2);
camera.update();
batch.setProjectionMatrix(camera.combined);
}
#Override
public void render (float delta) {
this.update();
orthogonalTiledMapRenderer.setView(camera);
orthogonalTiledMapRenderer.render();
batch.begin();
// render objects
batch.draw(img, player.getXLocation(), player.getYLocation());
batch.end();
camera.position.set(new Vector3(0, 0, 0));
}
That's the Viewport class. There's obviously more in those classes than that but I just included the things that have (or might have) something to do with the tile map.
Lastly, here's part of the TileMapHelper class.
public OrthogonalTiledMapRenderer setupMap(){
tiledMap = new TmxMapLoader().load("dungeon.tmx");
parseMapObjects(tiledMap.getLayers().get("Wall").getObjects());
return new OrthogonalTiledMapRenderer(tiledMap);
}
Before you ask about the parseMapObjects function, commenting it out changes nothing about how the code executes.
So basically, what's supposed to happen is the TileMapHelper loads the .tmx in its function setupMap(), the Viewport class calls setupMap (which returns an OrthogonalTiledMapEditor object) to initialize the OrthogonalTiledMapEditor, Viewport then sets the view and renders it, and MyGame is the one that initializes the camera and gives it to Viewport. Somewhere in that process something is going wrong and both the character and map are appearing in the middle/top corner of the screen like I explained earlier. Also, the reason I put the values 1130 in the setToOrtho function is because that was the only way to get the whole map to appear on the screen, but it's still in the corner.
Any help would be greatly appreciated.
Testing my libGDX app in RoboVM, I have encountered a major problem. When I pause my app (by actually going to the Home screen or sending app invites via Facebook) and then return to my app, classes of my games disappear. As if it does not store data properly on the resume() method. First i though it there was a problem of my AssetLoader, but after some debugging I found that the situation is worse. Actual instances of classes and shapes disappear. As if they never existed.
After googling the issue, I found that it might be related to IOSGraphics, but I have not managed to fix the problem.
My IOSLauncher looks something like this, I have erased the Facebook & Google AdMob specific code.
protected IOSApplication createApplication() {
IOSApplicationConfiguration config = new IOSApplicationConfiguration();
config.useAccelerometer = true;
config.useCompass = true;
config.orientationPortrait = true;
config.orientationLandscape = false;
return new IOSApplication(new Game(this), config);
}
#Override
public boolean didFinishLaunching(UIApplication application,
UIApplicationLaunchOptions launchOptions) {
FBSDKApplicationDelegate.getSharedInstance().didFinishLaunching(application, launchOptions);
initialize();
return true;
}
public void initialize() {
//...
}
public static void main(String[] argv) {
NSAutoreleasePool pool = new NSAutoreleasePool();
UIApplication.main(argv, null, IOSLauncher.class);
pool.close();
}
#Override
public void showAds(boolean show) {
//...
}
#Override
public void shareOnFacebook() {
//...
}
#Override
public void inviteFriends() {
//....
}
#Override
public boolean openURL(UIApplication application, NSURL url,
String sourceApplication, NSPropertyList annotation) {
super.openURL(application, url, sourceApplication, annotation);
return FBSDKApplicationDelegate.getSharedInstance().openURL(
application, url, sourceApplication, annotation);
}
#Override
public void didBecomeActive(UIApplication application) {
super.didBecomeActive(application);
FBSDKAppEvents.activateApp();
}
#Override
public void willResignActive(UIApplication application) {
super.willResignActive(application);
}
#Override
public void willTerminate(UIApplication application) {
super.willTerminate(application);
}
}
This sounds similar to a threading bug I once encountered. I fixed it by deferring resize and resume but I'm not sure if it will help in your case. Something like this:
private boolean needResize, needResume;
private void resize (int width, int height){
needResize = true;
}
private void deferredResize ();
if (!needResize) return;
needResize = false;
int width = Gdx.graphics.getWidth();
int height = Gdx.graphics.getHeight();
//move your resize code here
}
private void resume (){
needResume = true;
}
private void deferredResume (){
if (!needResume) return;
needResume = false;
//move your resume code here
}
public void render (){
deferredResize();
deferredResume();
//...
}
I suggest that you start looking for an alternative to RoboVM to avoid more issues in the future, as Robovm was acquired by Microsoft Xamarin (sad but true) and the framework is no longer maintained. License keys (with Libgdx) will continue to work until the 17th of April 2017, there will be no further updates to RoboVM, be it new features or bug fixes.
As far as I know, Libgdx will switch to Multi-OS engine as the default iOS backend for newly created libGDX projects in the next couple of weeks.
After a couple of days filled with headache I found the solution!
LifeCycle methods like pause & resume, hide & show are not always called When they are supposed to be called, therefore data is not stored properly. This issue can completely break your game.
This thing only occurred when testing my game on the iOS platform, mainly when I opened a 3rd party app, Facebook in this case. No such thing found on Android, though.
The only thing I changed on the iOS version was calling the mentioned methods manually to make sure it always pauses and resumes when it has to.
I want a timer that switches screens after a certain amount of time. Here is the code for my splash screen:
private boolean timerIsOn = false;
And here is the render() method code:
if(!timerIsOn) {
timerIsOn = true;
Timer.*schedule*(new Task() {
#Override
public void run() {
*changeScreen*();
}
}, 15);
} else if(Gdx.input.isTouched()) {
// Remove the task so we don't call changeScreen twice:
Timer.instance().clear();
*changeScreen*();
}
}
I'm getting red swiggly lines under the parts of the code that have * and * surrounding them but the suggested action to fix it doesn't help.
Also, if you know of an easier way to make this work or possibly a tutorial that would be great as well.
I am working with some strange legacy code. They have a custom object which implements a JPanel. This JPanel object is a secondary popup screen within the main application. The issue I'm having is to detect when the secondary popup screen is closed.
I tried to implement a WindowListener for the class, but when I try to add it, there is no JFrame associated with this object. I am assuming this is because they are using a custom object and it is an embedded popup screen.
I tried to retrieve a JFrame using:
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
which fails on a NullPointerException. I have no idea why it's so difficult to detect the right hand corner "x" close button on this page! I should mention that they were able to add Mouse and Key Listeners to the table which is embedded within the JPanel. But the outside listener for the entire window is causing me troubles.
(Please bear with me, this is my first stackoverflow post and I am new to Swing.)
Thanks so very much!!
Try to call getParent() for that strange panel. It should return the parent GUI component. If this is still not your frame but some intermediate panel instead, call getParent() on it as well. The top level component returns null.
Component p = strangePanel;
while ( p != null && ! (p instanceof Window))
p = p.getParent();
((Window) p ).addWindowListener(..);
Cannot understand why you are getting "NullPointerException" at:
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
In two cases this can happen:
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(null);
In your case, this is not possible as you have used this as a parameter.
Second, are you doing some other operations in above code line, like:
JFrame parentFrame = ((JFrame) SwingUtilities.getWindowAncestor(this)).someOperation();
In this case, if your this object represent the top window then you are supposed to get "NullPointerException" because ancestor of top parent is returned as "null". In other cases, I suspect you will get this exception.
Can you post a block of code where you are getting exception.
For this answer I'm making a minor assumption that the Nullpointer is not being thrown at the line that you mentioned, but rather when you attempt to add the WindowListener to the parentFrame. This is most likely because you're calling
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
before the JPanel has been added to the JFrame hierarchy.
Here's a rought code sample on how you could work around this. The thought it to wait for the panel to be notified that it has been attached to the JFrame somewhere in its hierarchy.
package test;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class HierarchyTest extends JPanel {
protected static void loadApp() {
HierarchyTest test = new HierarchyTest();
JFrame frame = new JFrame();
frame.add(test);
frame.setSize(200, 200);
frame.setVisible(true);
}
/**
* #param args
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
loadApp();
}
});
}
public HierarchyTest() {
this.addHierarchyListener(new HierarchyListener() {
#Override
public void hierarchyChanged(HierarchyEvent e) {
// This can be optimized by checking the right flags, but I leave that up to you to look into
boolean connected = setupListenersWhenConnected();
if (connected) {
HierarchyTest.this.removeHierarchyListener(this);
}
}
});
}
protected boolean setupListenersWhenConnected() {
JFrame parentFrame = (JFrame) SwingUtilities.getWindowAncestor(this);
if (parentFrame == null) {
return false;
}
parentFrame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
// Implementation here
System.out.println("This window is closing!");
}
});
return true;
}
}
I'm using repaint to trigger a SwingWorker. This worker automatically recreates an image to fit the width of the enclosing JPanel according to an autofit option in an application.
The issue is that is causing an Out of Memory Exception. This is because the images have a big resolution and the worker is being called continuously when the window is being drag-resized.
What I've tried to avoid this is only execute() when isDone() is true. However I feel there is a better way of doing this. Perhaps by using some sort of timer? What do you suggest?
It's a little left field, but basically what I do is use a javax.swing.Timer. The basic idea is to only perform an action when the timer fires.
private Timer timer;
.
.
.
timer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Perform required action
// Start the swing worker ??
}
});
timer.setCoalesce(true);
timer.setRepeats(false);
.
.
.
public void setBounds(int x, int y, int width, int height) {
// Force the time to start/reset
timer.restart();
super.setBounds(x, y, width, height);
}