I'm trying to make a simple animated menu with images bouncing off around the screen but the images leave a trail where ever the move.
public void handle(long now) {
// TODO Auto-generated method stub
boolean intersectFlag = false;
for(Letter l : letters){
gameMenuGraphicsContext.drawImage(l.letterImage, l.letterRectangle.getX(), l.letterRectangle.getY());
l.moveSimple();
}
}};
Any idea on how to stop this happening?
Think of the Canvas as a piece of paper onto which you are writing. If you don't erase anything explicitly everything will be visible what you have ever drawn to it. Actually you should reconsider your decision to use a Canvas at all. It is not very well suited for such kind of animations.
As mipa stated, your problem is that the drawn image is never erased. To erase your canvas, use:
graphicsContext.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
To keep yourself from having to call the clearRectmethod each time you want to draw on the screen, one easy write-and-forget way is to combine the clearing and drawing into one method. You can even use a lambda expression to draw on the canvas, as in the following code:
private static void clearAndDraw(GraphicsContext gc, Consumer<GraphicsContext> draw) {
gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
draw.accept(gc);
}
public void handle(long now) {
//...
for(Letter l : letters) {
clearAndDraw(graphicsContext, gc -> gc.drawImage(l.letterImage, l.letterRectangle.getX(), l.letterRectangle.getY()));
//...
}
}
Related
I'm working on a vertical scrolling game, and I'm using a thread to generate new enemies every 2 seconds. Each enemy is an image in a JPanel. For some reason, The generated enemies are not showing up in the JFrame, but they are present. When the player collides with one of the enemies, all the enemies show up.
Here's the code:
private void checkCollision() {
for(AlienShip as : enemies) {
if(player.getBounds().intersects(as.getBounds()))
player.setVisible(false);
}
}
private void setAlien() {
alien = new AlienShip();
add(alien);
enemies.add(alien);
System.out.println("Enemies: " + enemies.size());
}
public Thread alienGenerator() {
for(int i = 0; i < 3; i++) { // these are being drawn
setAlien();
}
return new Thread(new Runnable() {
#Override
public void run() {
int sleepTime = 2000;
while(true) {
try {
Thread.sleep(sleepTime);
} catch(InterruptedException e) {
System.out.println(e);
}
setAlien(); //these aren't
}
}
});
}
private void gameLoop() {
alienGenerator().start();
mainTimer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
checkCollision();
}
});
mainTimer.start();
}
It always seems that you're Darned If You Do And Darned If You Don't. As far as I'm concerned the code you had placed in your earlier post was adequate, as a matter of fact, it was still lacking (no PlayerShip Class). The code example in this post does even less justice. Never the less......
Before I get started I just want you to know that I personally would have tackled this task somewhat differently and the meager assistance provided here will be solely based on the code you have already provided in this and previous posts.
The reason you are not seeing your Alien Ships displaying onto the Game Board upon creation is because you don't revalidate the board panel. As you currently have your code now this can be done from within the Board.setAlien() method where the Alien Ships are added. Directly under the code lines:
alien = new AlienShip();
add(alien);
enemies.add(alien);
add the code line: revalidate();, so the code would look like this:
alien = new AlienShip();
add(alien);
enemies.add(alien);
revalidate();
Your Alien Ships should now display.
On A Side Note:
What is to happen when any Alien Ship actually makes it to the bottom of the Game Board? As a suggestion, have them re-spawn to the top of the game board (serves ya right fer missin em). This can be done from within the AlienShip.scrollShip() method by checking to see if the Alien Ship has reached the bottom of the board, for example:
public void scrollShip() {
if (getY() + 1 > this.getParent().getHeight()) {
setY(0 - PANEL_HEIGHT);
}
else {
setY(getY() + 1);
}
}
In my opinion, PANEL_HEIGHT is the wrong field name to use. I think it would be more appropriate to use something like ALIEN_SHIP_WIDTH and ALIEN_SHIP_HEIGHT. Same for the variables panelX and panelY, could be alienShipX and alienShipY. Food for thought.
As you can see in the code above the current Game Board height is acquired by polling the Game Board's getHeight() method with: this.getParent().getHeight(). This allows you to change the Game Board size at any time and the Alien Ships will know where that current boundary is when scrolling down. All this then means that the setResizable(false); property setting done in the Main Class for the Game's JFrame window can now be resizable: setResizable(true);.
You will also notice that when the Alien Ship is re-spawned at top of the Game Board it is actually out of site and it flows into view as it moves downward. I think this is a much smoother transition into the gaming area rather than just popping into view. This is accomplished with the setY(0 - PANEL_HEIGHT); code line above. As a matter of fact even when the game initially starts, your Alien Ships should flow into the the gaming area this way and that can be done from within the AlienShip.initAlienShip() method by initializing the panelY variable to panelY = -PANEL_HEIGHT;.
This now takes me to the initialization of the PANEL_WIDTH and PANEL_HEIGHT fields. The values seem enormous (224 and 250 respectively). Of course you may have set to these sizes for collision testing purposes, etc but I think an image size of 64 x 35 would most likely suffice:
This image should be a PNG image with a transparent background which then eliminates the need for the setBackground(Color.BLUE); code line located within the AlienShip.initAlienShip() method.
The AlienShip.getX() and AlienShip.getY() methods should be overridden:
#Override
public int getX() { ... }
#Override
public int getY() { ... }
I think extending the AlienShip Class to JLabel would be better than to JPanel. To JPanel seems like overkill:
public class AlienShip extends JLabel { ... }
Adding a background image to the Game Board can add pizazz to the game. This can be achieved by adding the following code to the Board.paintComponent() method:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
ImageIcon imgIcon = new ImageIcon("images/StarBackground.png");
Image img = imgIcon.getImage();
g.drawImage(img, 0, 0, this.getSize().width, this.getSize().height, this);
}
Images can be acquired here.
This should keep you going for a while. Before to long it'll be Alien mayhem.
I've been struggling with how to use and set up Viewports in LibGDX for quite some time. I want to be able to render everything like its on a display that is 1920x1080 and have it scale to fit the display its on, and I need some help getting to work like that.
This is what I want it to look like (taken from a computer with a 1920x1080 monitor), but when I run the same code on my laptop which is 1440x800, it looks like this. I apologize for the poor photo of a screen, I couldn't get it to take a screenshot of the game running for whatever reason, but it shows that the top of the display remains unused, and that not everything is being fit to the display. This is the main code running the show:
public class Main extends Game {
...
public void create() {
...
Gdx.graphics.setFullscreenMode(Gdx.graphics.getDisplayMode());
//last
this.setScreen(new MainMenu(this));
}
public void render() {
super.render(); //important!
}
...
}
And then the MainMenu class
public class MainMenu implements Screen{
...
public MainMenu(final Main game) {
this.game = game;
cam = new OrthographicCamera();
cam.setToOrtho(false, 1920, 1080);
...
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0.025f, .025f, 0.025f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
cam.update();
game.batch.setProjectionMatrix(cam.combined);
...
if(Gdx.input.isKeyPressed(Keys.ESCAPE)) {
Gdx.app.exit();
}
}
...
}
How would I implement a Viewport or something of the like to get it to look the same on the smaller screen as it does on the larger? Any help is really appreciated! If you want to see the code that I left out for brevity, its all on my GitHub. Thanks again!
That didn't take me long, hopefully someone will learn from me though, ha ha.
Turns out when you use the camera with fixed height and width like that, it does fill up the whole monitor, but the cameras width DOES NOT equal the value returned by Gdx.graphics.getWidth(). Because of this all my code was rendering like it was being compressed because it was referencing the width returned by Gdx.graphics, and not the camera.viewportWidth.
Lesson learned: Gdx.graphics.getWidth() can and will change depending on device and cam.veiwportWidth wont. Oops!
it's my first time posting and I'm self taught so be please gentle!
I've been building a bomberman replica game in libGDX using Game and Screen classes:
public class Main extends Game {
...
#Override
public void create() {
levelScreen = new LevelScreen(playerCount, new int[playerCount]);
levelScreen.level.addAction(Actions.sequence(Actions.alpha(0), Actions.fadeIn(2f)));
this.setScreen(levelScreen);
}
However when the game launches there is no fade effect.
public class LevelScreen implements Screen {
...
#Override
public void render(float delta) {
Gdx.gl.glClearColor(1, 0.1f, 0.5f, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
level.act();
level.draw();
batch.end();
}
I want this levelScreen to fade in from black but it just doesn't!
When the round is over I want to fadeOut of this levelScreen to black, then fadeIn to a trophyScreen from black:
(From Main Class)
#Override
public void render() {
super.render();
if (endRoundTimer <= 0) {
trophyScreen = new TrophyScreen(playerCount, levelScreen.getScore());
levelScreen.level.addAction(Actions.sequence(Actions.fadeOut(1), Actions.run(new Runnable() {
#Override
public void run() {
setScreen(trophyScreen);
}
})));
}
}
And I've tried using the show() method in the TrophyScreen:
public class TrophyScreen implements Screen {
...
#Override
public void show() {
stage.addAction(Actions.sequence(Actions.alpha(0), Actions.fadeIn(1)));
}
I've done loads of searching and tried various things but no joy. I'm sure I'm missing something somewhere in a draw() or render() method that is preventing the fade Action from taking place.
UPDATE1
#Override public void draw() {
super.draw();
if (roundOver) {
this.getBatch().begin(); String s = String.format("%s", message);
font_text.draw(this.getBatch(), s, (90 + (2 * 30)), (this.getHeight() / 2));
this.getBatch().end();
}
For fading to work on actors, they must properly apply their own color's alpha in the draw method. And for an entire hierarchy of objects to fade at once, they must all also apply the parentAlpha parameter from the draw method signature.
So your draw method in any custom Actor subclass should look like this:
public void draw (Batch batch, float parentAlpha) {
Color color = getColor();
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
//..do drawing
}
If you are using a Sprite in your Actor instead of a TextureRegion (which I don't recommend due to redundancies) you must apply the color to the Sprite instead of Batch.
Note that this method of fading the whole game is not a "clean" fade. Any actors that are overlapping other actors will show through each other when the parent alpha is less than 1 during the fade. An alternative that would provide a clean-looking fade would be to draw a copy of your background (or black) over your entire scene and fade that instead.
I assume that level is an object of class that extends Stage and you are creating a control inside the stage, which is weird. You are not appling color to your font_text which I assume it is a BitmapFont
Solution, the weird way
If you want to do it in this way you will need something like that:
#Override public void draw() {
super.draw();
if (roundOver) {
getBatch().begin();
String s = String.format("%s", message);
font_text.setColor(getRoot().getColor())
font_text.draw(this.getBatch(), s, (90 + (2 * 30)), (this.getHeight() / 2));
getBatch().end();
}
}
getRoot() gets Group from Stage, we do it, because every action applied to Stage is actually applied to this Group root element. We get color (which has alpha channel) and we copy the color to the bitmapFont.
This solution is weird, because you are actually creating an Label inside Stage. It is pointless, actors plays on stage, not inside.
Solution, the good way
You want to draw text, right? So just use Label which is an actor, who shows a text. Actors do jobs for you:
stage = new Stage();
Label.LabelStyle labelStyle = new Label.LabelStyle(bitmapFont, Color.WHITE);
Label label = new Label("Hi, I am a label!", labelStyle);
stage.addActor(label);
Then you can apply actions and they will work fine (and every actor can have own actions applied).
stage.addAction(Actions.sequence(Actions.alpha(0), Actions.fadeIn(5)));
label.addAction(Actions.moveBy(0, 300, 15));
There is a lot of different actors like TextButton, Image, ScrollPane. They are customizable, easy to manage and they can be integrated in groups and tables.
Output:
A better way would be to just start by drawing a black image over everything, so you don't have to mess with every scene object's alpha. Use layering to do that. This post may be helpful.
Then you can control it's alpha channel, change it's rendering to 0 right before unpausing the game action to get it's drawing cycles back. Reactivate it on stage ending for your fade out effect.
Thank you cray, it's way better like this.
Inside my game I have this code. It renders a texture that serve as a button:
private void drawStart(){
startTexture = new Texture(Gdx.files.internal("start.png"));
startTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
stageStart = new Stage();
stageStart.clear();
buttonStart = new Image(startTexture);
buttonStart.setX(10);
buttonStart.setY(Gdx.graphics.getHeight()/2.75f);
buttonStart.setWidth(Gdx.graphics.getWidth()/4);
buttonStart.setHeight(Gdx.graphics.getHeight()/4);
Gdx.input.setInputProcessor(stageStart);
buttonStart.addListener(new ClickListener() {
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
{
currentState = GameState.RESET;
startTexture.dispose();
stageStart.dispose();
return true;
}
});
stageStart.addActor(buttonStart);
stageStart.draw();
startTexture.dispose();
}
However, whenever I put drawStart(); into my render method, the Java Heap and Native Heap slowly increases by 1 every 10 seconds. So, if the user leaves the game on the menu for about 5 minutes the game will crash on their phone. I've tested it and it only occurs when the texture is rendered.
I would appreciate any help on fixing this. I have tried an if statement that states if rendered = 0, render the texture then set rendered 1 but that didn't work.
This might help you. You only need draw in your render. So now you can put drawStart() in your render method which will only draw the stage, while leaving screen dont forget to call dispose.
private void drawStart(){
stageStart.draw();
}
public void initialize() {
startTexture = new Texture(Gdx.files.internal("start.png"));
startTexture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
stageStart = new Stage();
stageStart.clear();
buttonStart = new Image(startTexture);
buttonStart.setX(10);
buttonStart.setY(Gdx.graphics.getHeight()/2.75f);
buttonStart.setWidth(Gdx.graphics.getWidth()/4);
buttonStart.setHeight(Gdx.graphics.getHeight()/4);
Gdx.input.setInputProcessor(stageStart);
buttonStart.addListener(new ClickListener() {
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button)
{
currentState = GameState.RESET;
startTexture.dispose();
stageStart.dispose();
return true;
}
});
stageStart.addActor(buttonStart);
}
public void dispose() {
startTexture.dispose();
}
Your problem is, that in drawStart() you are creating new Textures and a new Stage.
If you call this every render loop, you create new Textures and a new Stage about 60 times/second.
This ofc causes a memory leak.
You should load/create Textures and the Stage only once, in the constructor or in the create() or show() method.
Also think about disposing them when needed. Here is a list of the things you need to dispose manually.
In the render loop you should then only update and draw the things.
But as you only have 3 month of experience i suggest you to learn the basics first. Don't rush into game programming, it will kill your motivation.
First learn the basics, then start with some ASCII-Games (commandline) and then you can start with libgdx.
If you are ready for libgdx, read the Wiki (at least the parts you need) as well as some tutorials (maybe they don't use the latest version of libgdx, but the concept should be more or less the same and it should help you understanding it.)
So I have 3 rectangles drawn on my JPanel that are acting as buttons, and what I would like to do is when the mouse is hovered over one of them, the Jpanel will repaint, and change the color of only that rectangle to red.
The only way I can think to do it is to have 3 separate variables that determines if the mouse is over each component. Then there would be code in the repaint method that, if rect1hover is true, then draw using g.setColor to red.
#Override
public void mouseMoved(MouseEvent e) {
if(rect1.contains(e.getX(), e.getY())){
rect1hover = true;
}
}
But this seems really inefficient. Is there a better way to do this?
Sure. Put your drawn rectangles in a List. Then you can write this code.
#Override
public void mouseMoved(MouseEvent e) {
for (DrawnRectangle r : rectangles) {
if (r.contains(e.getX(), e.getY())){
r.setHoverBackground();
}
}
}
You'll have to create a DrawnRectangle class with a setHoverBackground method.
You can use real components, then just add a MouseListener to each component and you don't need to do any lookup or do custom painting.
See Playing With Shapes for more information.