I am following a tutorial on creating a game with LibGdx from some ebook. the tutorial has steps for creating a game called "Canyon Bunny". Its a simple 2D game. but i keep getting this annoying error! (i also used to get the error on a different tutorial of the same genre)
i am in the early stages of the development for this game. and i am doing some test (of which i follow to the letter from the tutorial). I use a MAC and a i have tried many solutions with no luck at all.
Exception in thread "LWJGL Application" java.lang.NullPointerException
at com.Adel.CanyonBunny.game.WorldUpdater.updateTestObjects(WorldUpdater.java:83)
at com.Adel.CanyonBunny.game.WorldUpdater.update(WorldUpdater.java:76)
at com.Adel.CanyonBunny.CanyonBunnyMain.render(CanyonBunnyMain.java:39)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:207)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:114)
It is truly one of the most frustrating things a striving programmer can face.
ill get the code of all the classes in case that's related somehow...
This is CanyonBunnyMain in the general program:
package com.Adel.CanyonBunny;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.Adel.CanyonBunny.game.*;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.Application;
public class CanyonBunnyMain implements ApplicationListener {
private static final String TAG = CanyonBunnyMain.class.getName();
private WorldUpdater worldUpdater;
private WorldRenderer worldRenderer ;
private boolean paused ;
public void create() {
//I'll set the log to debug for the developing process
Gdx.app.setLogLevel(Application.LOG_DEBUG) ;
worldUpdater = new WorldUpdater();
worldRenderer = new WorldRenderer() ;
// since, upon creation, the game is not paused, then:
paused = false ;
}
public void render() {
if (paused = true) {
//update the game by the time passed since the last update
worldUpdater.update(Gdx.graphics.getDeltaTime()) ;
}
//sets the screen color to: CornFlower Blue
Gdx.gl.glClearColor(0x64 / 255.0f, 0x95 / 255.0f, 0xed / 255.0f, 0xff / 255.0f);
//clears the screen to prevent flickering
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT) ;
//Render the game to the screen
worldRenderer.render();
}
public void resize (int w, int h) {
worldRenderer.resize(w, h) ;
}
public void pause () {
paused = true ;
}
public void resume() {
paused = false ;
}
public void dispose() {
worldRenderer.dispose() ;
} }
this is the WorldRenderer (general program too) :
package com.Adel.CanyonBunny.game;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
public class WorldRenderer {
private OrthographicCamera cam;
private SpriteBatch batch ;
private WorldUpdater updater;
public void WorldRenderer(WorldUpdater worldUpdater) { }
public void init() { }
public void render() { }
public void resize(int w, int h) { }
public void dispose() { }
}
this is the main class (from the desktop project: the one that i run on my MAC) :
package com.Adel.CanyonBunny;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
cfg.title = "CanyonBunny";
cfg.useGL20 = false;
cfg.width = 800;
cfg.height = 480;
new LwjglApplication(new CanyonBunnyMain(), cfg);
}
}
Any help will be wonderful.
tell me should you need extra data
this is the WorldUpdater class for those who asked:
package com.Adel.CanyonBunny.game;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.math.MathUtils;
public class WorldUpdater {
private final String TAG = WorldUpdater.class.getName();
public Sprite[] testSprites;
public int selectedSprite;
public WorldRenderer worldRenderer;
public void worldUpdater() {
init() ;
}
public void init() {
initTestObjects() ;
}
private void initTestObjects() {
// create new array of 5 sprites
testSprites = new Sprite[5] ;
// Create empty POT-sized Pixmap with 8 bit RGBA pixel data
int w = 32;
int h = 32;
Pixmap pixmap = createProceduralPixmap(w, h) ;
//create a new texture from Pixmap data
Texture texture = new Texture(pixmap) ;
//create sprites using the just created texture
for (int i = 0; i < testSprites.length; i++) {
Sprite spr = new Sprite(texture) ;
spr.setSize(1,1) ;
//set origin to sprite's center
spr.setOrigin(spr.getWidth() / 2.0f, spr.getHeight() / 2.0f) ;
float randomX = MathUtils.random(-2.0f, 2.0f) ;
float randomY = MathUtils.random(-2.0f, 2.0f) ;
spr.setPosition(randomX, randomY) ;
//put new sprite into array
testSprites[i] = spr ;
}
//set first sprite as the selected one
selectedSprite = 0 ;
}
private Pixmap createProceduralPixmap(int width, int height) {
Pixmap pixmap = new Pixmap(width, height , Format.RGBA8888) ;
//fill the square with red color at 50% opacity
pixmap.setColor(1, 0, 0, 0.5f) ;
pixmap.fill() ;
//draw a yellow X in the pixmap
pixmap.setColor(1, 1, 0 , 1) ;
pixmap.drawLine(0, 0, width, height) ;
pixmap.drawLine(width, 0, 0, height);
//draw a cyan-colored border around the square
pixmap.setColor(0, 1, 1, 1) ;
pixmap.drawRectangle(0, 0, width, height) ;
return pixmap;
}
public void update(float deltaTime) {
updateTestObjects(deltaTime);
}
private void updateTestObjects(float deltaTime) {
//get current rotation from the selected sprite
float rotation = testSprites[selectedSprite].getRotation();
//rotate sprite by 90 degrees per second
rotation += 90 * deltaTime;
//wrap around at 360 degrees
rotation %= 360 ;
testSprites[selectedSprite].setRotation(rotation);
}
}
Also, when i check this line out in Debugging mode:
testSprites = new Sprite[5] ;
"testSprites" keeps showing null.
i hope this clears up some details!
thanks again.
The problem is with your "constructors", mainly in the updater (as the renderer does nothing):
public void worldUpdater() { ... }
Constructors should not specify return types - that's part of how the compiler recognizes them as constructors. As it is in your code, it's just a method you could call on an existing object instance. Change it like so:
public WorldUpdater() { ... }
Note the lack of a return type and the uppercase W.
You can change the renderer the same way. (But then you will have to pass the updater to its constructor in the main class.)
Also, Nine Magics is right that the way you store renderer and updater references in each other doesn't make much sense, even if it's not related to this problem. I see no reason why an updater class would need to know about its renderer, I'd remove that field.
In your WorldRenderer you specify this:
public void WorldRenderer(WorldUpdater worldUpdater) { }
And WorldRendere also carries an instance of an worldUpdater?
private WorldUpdater updater;
But on your main file you create an instance of both renderer and updater?
worldUpdater = new WorldUpdater();
worldRenderer = new WorldRenderer() ;
I don't know, I might have tired eyes or something but this seems too complex. Can it be that you are refering to a wrong instance of WorldUpdater? Might edit this if I can wrap my head around it better.
Related
My question is I'm creating a game where I have to get an image moving from one side of the screen to the other, and then coming back from the same side of the screen. For example I have an image of a jellyfish, it starts moving from the right side of the screen, to the left side, and then I want it to come back from the right side. I'm stuck and don't know what to do. I'm using LIBGDX and JAVA.
My code so far is :
package gdx.game;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import java.awt.Graphics;
public class Main extends ApplicationAdapter {
SpriteBatch batch;
Texture img;
Sprite sprite;
float fGrav, fVelo, fX, fY;
#Override
public void create() {
batch = new SpriteBatch();
img = new Texture("JellyFish.png");
sprite = new Sprite(img);
sprite.setScale(0.3f);
fX=0;
fY=0;
}
#Override
public void render() {
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
sprite.draw(batch);
batch.end();
}
#Override
public void dispose() {
batch.dispose();
img.dispose();
}
}
If you want to continuously bounce an image from left to right, create an infinite loop that increases x until it is >= your display width, and then decrease it until it is <= 0, and then repeat.
I've never worked with LIBGDX, but if you wanted to use the Graphics class it would be as follows:
Example:
boolean f = true;
int x = 0;
while (f) {
while (x <= SCREEN_WIDTH) {
x++;
}
while (x >= 0) {
x--;
}
}
This should loop indefinitely.
I have just started with LibGdx and I have figured out how to center text with it. Now I am having trouble with center justifying text. I was wondering if someone can help. I have attach my code for centering. Thank you in advance.
package com.tutorials.game;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class TextDemo extends ApplicationAdapter {
SpriteBatch batch;
BitmapFont font;
String myText;
GlyphLayout layout = new GlyphLayout();
#Override
public void create () {
batch = new SpriteBatch();
font = new BitmapFont(Gdx.files.internal("myFont.fnt"));
myText = "I took one, one cause you left me\n"
+ "Two, two for my family\n"
+ "Three, three for my heartache";
layout.setText(font,myText);
}
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
float x = Gdx.graphics.getWidth()/2 - layout.width/2;
float y = Gdx.graphics.getHeight()/2 + layout.height/2;
batch.begin();
font.draw(batch,layout,x,y);//Center Text
batch.end();
}
You can use following setText() method instead and set targetWidth to screen width, Aligh.center, and set wrap to true. Also, set x = 0 so the text is centered across the whole screen.
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
public void setText(BitmapFont font,
java.lang.CharSequence str,
Color color,
float targetWidth,
int halign,
boolean wrap)
Updated example:
#Override
public void create () {
batch = new SpriteBatch();
font = new BitmapFont(Gdx.files.internal("myFont.fnt"));
myText = "I took one, one cause you left me\n"
+ "Two, two for my family\n"
+ "Three, three for my heartache";
layout.setText(font,myText,Color.BLACK,Gdx.graphics.getWidth(),Align.center,true);
}
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
float x = 0;
float y = Gdx.graphics.getHeight()/2 + layout.height/2;
batch.begin();
font.draw(batch,layout,x,y);//Center Text
batch.end();
}
Instead of using the font.draw method, use the following one instead...
public TextBounds drawMultiLine (Batch batch, CharSequence str, float x, float y, float alignmentWidth, HAlignment alignment)
alignmentWidth is the max width you want your text to take up. Any more than that and it will wrap. Set it to something stupidly high if you don't want wrapping.
'alignment' is the key thing and takes an HAlignment enum and can be either LEFT, RIGHT or CENTER
The batch, str, x and y parameters are the same as you're already doing.
Hello StackOverflow community!
I have been working on a game project and have recently run into an error in my Spritesheet class.
Problem: The game is developed in LibGDX which has a built-in feature that lets you split apart an image into a 2D array with the first dimension being the row and the second being the column.
E.g: spriteSheet[0][1] would give you the second column of the first row.
So I came up with a little method to generate animations using the row but when I run the game, the animation doesn't seem to work as the player remains static the whole time!
There are 4 classes involved in this process:
The Player class:
package com.darkbyte.games.tfa.game.entity.entities;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.darkbyte.games.tfa.game.entity.Entity;
import com.darkbyte.games.tfa.game.entity.SpriteSheet;
import com.darkbyte.games.tfa.render.RenderManager;
public class Player extends Entity {
//The constructor for the player class
public Player(String name, SpriteSheet spriteSheet) {
super(name, spriteSheet);
direction = Direction.DOWN;
}
//A flag to see if the player is moving
private boolean isMoving;
//The player's walking animations
private Animation[] walkAnimations = {
spriteSheet.getAnimation(4, 1/16f),
spriteSheet.getAnimation(5, 1/16f),
spriteSheet.getAnimation(6, 1/16f),
spriteSheet.getAnimation(7, 1/16f)
};
//The player's static frames
private TextureRegion[] staticFrames = {
spriteSheet.getTexture(4, 0),
spriteSheet.getTexture(5, 0),
spriteSheet.getTexture(6, 0),
spriteSheet.getTexture(7, 0)
};
//The render code for the player
#Override
public void render() {
//Gets the player's direction, if the player's moving, it sets the current frame to the frame that would be played at the current moment based on the state time
//If the player isn't moving, it sets the current frame to the static frame associated to the direction
switch(direction) {
case UP:
if(isMoving)
currentFrame = walkAnimations[0].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[0];
break;
case LEFT:
if(isMoving)
currentFrame = walkAnimations[1].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[1];
break;
case DOWN:
if(isMoving)
currentFrame = walkAnimations[2].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[2];
break;
case RIGHT:
if(isMoving)
currentFrame = walkAnimations[3].getKeyFrame(RenderManager.getStateTime(), true);
else
currentFrame = staticFrames[3];
break;
}
}
//The tick code for the player
#Override
public void tick() {
if(Gdx.input.isKeyPressed(Keys.W) || Gdx.input.isKeyPressed(Keys.UP)) {
direction = Direction.UP;
y += 2;
} else if(Gdx.input.isKeyPressed(Keys.A) || Gdx.input.isKeyPressed(Keys.LEFT)) {
direction = Direction.LEFT;
x -= 2;
} else if(Gdx.input.isKeyPressed(Keys.S) || Gdx.input.isKeyPressed(Keys.DOWN)) {
direction = Direction.DOWN;
y -= 2;
} else if(Gdx.input.isKeyPressed(Keys.D) || Gdx.input.isKeyPressed(Keys.RIGHT)) {
direction = Direction.RIGHT;
x += 2;
} else {
}
}
//Returns if the player is moving
public boolean isMoving() {
return isMoving;
}
}
The RenderManager class:
package com.darkbyte.games.tfa.render;
import javax.swing.JOptionPane;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.darkbyte.ammarlib.files.DataWriter;
import com.darkbyte.games.tfa.Core;
import com.darkbyte.games.tfa.music.MusicManager;
import com.darkbyte.games.tfa.screens.ScreenManager;
import com.darkbyte.games.tfa.screens.UnidentifiedScreenStateException;
public class RenderManager {
//timeSinceLastFramesPerSecondCheck is used to store the clock time since the last frames per second check was done
private static long timeSinceLastFramesPerSecondCheck;
//framesPassed is used to store the number of frames passed in a second
private static int framesPassed, framesPerSecond;
//The variable that stores the current state time of the render, used for animations
private static float stateTime;
//Initialisation for everything render related
public static void init() {
Camera.init();
Batch.init();
Button.init();
}
//Disposal for everything render related
public static void dispose() {
Batch.dispose();
Button.dispose();
}
//The main render method
public static void render() {
//Gets the current clock time in milliseconds
long now = System.currentTimeMillis();
//Increments the frames passed
framesPassed++;
//Updates the state time variable
stateTime += Gdx.graphics.getDeltaTime();
//Checks if 1 second has passed since the last frames per second check
if(now >= timeSinceLastFramesPerSecondCheck + 1000) {
//The frames per second is set to the frames passed that second
framesPerSecond = framesPassed;
//The frames passed is reset
framesPassed = 0;
//Sets the time since the last frames per second check to now
timeSinceLastFramesPerSecondCheck = now;
}
//Clears the screen from the previous render call
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//Renders the correct screen from the screen manager and plays the music for that screen
try {
ScreenManager.renderScreen();
MusicManager.playMusic();
} catch (UnidentifiedScreenStateException e) { //If the screen state runs into an error
//Prints the error log to a time-stamped file
DataWriter writer = new DataWriter(); //Creates a new data writer to write to the file
writer.openFile(Core.getTime(true) + ".txt"); //Opens a the file
writer.write("Tetros: First Age - An error has occurred!\nType: UnidentifiedScreenStateException in RenderManager!"); //Writes some basic data about the error
writer.write(e.getMessage()); //Prints a detailed error log from the exception
writer.closeFile(); //Closes the file, housekeeping reasons
e.printStackTrace(); //Prints the error log to the console
//Shows the client an error message in a dialog box
JOptionPane.showMessageDialog(null, "An unexpected error occured: UnidentifiedScreenStateException in RenderManager!", "Error", JOptionPane.ERROR_MESSAGE);
//Closes the game
System.exit(0);
}
}
//Returns the ticks per second rate
public static int getFPS() {
return framesPerSecond;
}
//Returns the frames passed
public static int getFramesPassed() {
return framesPassed;
}
//Returns the current state time
public static float getStateTime() {
return stateTime;
}
}
The SpriteSheet class:
package com.darkbyte.games.tfa.game.entity;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.*;
public class SpriteSheet {
//All the frames from the entity
private TextureRegion[][] textureSheet;
//Gets the sprite sheet for the entity and splits it into frames
public SpriteSheet(Texture spriteSheet, int tileWidth, int tileHeight) {
//Splits the textures into a two dimensional array
/*
* Dimension 1: Row
* Dimension 2: Column
* Example:
* ********
* ****0***
* ********
* ********
* 0 = textureSheet[1][4] to access the second row and the fifth column
* Some of the columns may be blank, check the sprite sheet to reference
*/
textureSheet = TextureRegion.split(spriteSheet, tileWidth, tileHeight);
}
//Disposes of the textures by looping through the rows then the individual columns and disposing of all the textures
public void dispose() {
for(TextureRegion[] textureRow : textureSheet) for(TextureRegion texture : textureRow) texture.getTexture().dispose();
}
//Returns the texture at a certain row or column (starting at 1,1 being textureSheet[0][0])
public TextureRegion getTexture(int row, int column) {
return textureSheet[row][column];
}
//Returns an animation using pieces of the sprite sheet
public Animation getAnimation(int row, float animationFPS) {
//Returns the animation generated using the frames from the row specified and the FPS from the parameters
return new Animation(animationFPS, textureSheet[row]);
}
}
The TestRoom class:
package com.darkbyte.games.tfa.game.world.room.rooms;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.darkbyte.games.tfa.game.entity.Entity;
import com.darkbyte.games.tfa.game.entity.EntityManager;
import com.darkbyte.games.tfa.game.world.room.Room;
import com.darkbyte.games.tfa.render.Batch;
import com.darkbyte.games.tfa.render.Camera;
public class TestRoom extends Room {
//Initialises the room's tiled map and map renderer
#Override
public void init() {
this.tileMap = Room.generateMap("assets/world/rooms/test.tmx");
this.mapRenderer = new OrthogonalTiledMapRenderer(this.tileMap);
entitiesInRoom.add(EntityManager.getPlayer(0, 0, 0));
}
//Disposes of the room
#Override
public void dispose() {
this.tileMap.dispose();
}
//Runs the render code for the room
#Override
public void render() {
//Sets the map renderer's viewpoint to the viewpoint of the camera
mapRenderer.setView(Camera.getCamera());
//Renders the map
mapRenderer.render();
//Begins the drawing batch
Batch.getGameBatch().begin();
//Loops through the entities in the room and renders them
for(Entity entityToRender : entitiesInRoom) {
entityToRender.render();
Batch.getGameBatch().draw(entityToRender.getCurrentFrame(), entityToRender.getX(), entityToRender.getY());
}
//Ends the drawing batch
Batch.getGameBatch().end();
}
//Runs the tick code for the room
#Override
public void tick() {
//Loops through the entities in the room and runs their tick code
for(Entity entityToRender : entitiesInRoom) entityToRender.tick();
}
}
Instead of building all the animation mechanism yourself, you should use the Libgdx's Animation, which is much easier to use. Read the wiki.
I've been taking AP Computer Science this year as a sophomore in high school and we mainly cover material like loops, classes, methods, general CS logic, and some math stuff. I am missing what I really loved about coding in the first place, making games. Now every game I have made had some sort of way to manage it whether it was using timers in visual basic or a XNA plugin for c# that setup a update method for me. The problem is I have not learned how to do this for java in my course. I've read up a little on threads and implements runnable but i'm not really sure where I'm going with it.
Class 1
import java.awt.FlowLayout;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class GFXScreen extends JFrame
{
/**
* #param screenHeigth
* #param screenHeigth
* #param The file name of the image. Make sure to include the extension type also
* #param The title at the top of the running screen
* #param The height of the screen
* #param The width of the screen
*/
public GFXScreen(String fileName, String screenTitle, int screenHeight, int screenWidth)
{
setLayout(new FlowLayout());
image1 = new ImageIcon(getClass().getResource(fileName));
label1 = new JLabel(image1);
this.add(label1);
//Set up JFrame
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
this.setTitle(screenTitle);
this.setSize(screenWidth, screenHeight);
}
/**
* #param desired amount to move picture
*/
public void updatePic(int increment)
{
//update pos
label1.setBounds(label1.bounds().x, label1.bounds().y - increment,
label1.bounds().width, label1.bounds().height);
}
private ImageIcon image1;
private JLabel label1;
}
Class 2
public class MainClass implements Runnable {
public static void main(String[] args)
{
(new Thread(new MainClass())).start();
GFXScreen gfx = new GFXScreen("pixel_man.png", "pixel_Man", 1000, 1000);
}
public void run()
{
gfx.updatePic(1);
}
}
In this instance what I want to happen is, I want a picture that starts in the top to slowly move down smoothly to the bottom. How would i do this?
Suggestions:
Again, a Swing Timer works well for simple Swing animations or simple game loops. It may not be the greatest choice for complex or rigorous tame loops as its timing is not precise.
Most game loops will not be absolutely precise with time slices
And so your game model should take this into consideration and should note absolute time slices and use that information in its physics engine or animation.
If you must use background threading, do take care that most all Swing calls are made on the Swing event thread. To do otherwise will invite pernicious infrequent and difficult to debug program-ending exceptions. For more details on this, please read Concurrency in Swing.
I avoid using null layouts, except when animating components, as this will allow my animation engine to place the component absolutely.
When posting code here for us to test, it's best to avoid code that uses local images. Either have the code use an image easily available to all as a URL or create your own image in your code (see below for a simple example).
Your compiler should be complaining to you about your using deprecated methods, such as bounds(...), and more importantly, you should heed those complaints as they're there for a reason and suggest increased risk and danger if you use them. So don't use those methods, but instead check the Java API for better substitutes.
Just my own personal pet peeve -- please indicate that you've at least read our comments. No one likes putting effort and consideration into trying to help, only to be ignored. I almost didn't post this answer because of this.
For example:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class GfxPanel extends JPanel {
private static final int BI_WIDTH = 26;
private static final int BI_HEIGHT = BI_WIDTH;
private static final int GAP = 6;
private static final Point INITIAL_LOCATION = new Point(0, 0);
private static final int TIMER_DELAY = 40;
public static final int STEP = 1;
private ImageIcon image1;
private JLabel label1;
private Point labelLocation = INITIAL_LOCATION;
private int prefW;
private int prefH;
private Timer timer;
public GfxPanel(int width, int height) {
// the only time I use null layouts is for component animation.
setLayout(null);
this.prefW = width;
this.prefH = height;
// My program creates its image so you can run it without an image file
image1 = new ImageIcon(createMyImage());
label1 = new JLabel(image1);
label1.setSize(label1.getPreferredSize());
label1.setLocation(labelLocation);
this.add(label1);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(prefW, prefH);
}
public void startAnimation() {
if (timer != null && timer.isRunning()) {
timer.stop();
}
labelLocation = INITIAL_LOCATION;
timer = new Timer(TIMER_DELAY, new TimerListener());
timer.start();
}
// My program creates its image so you can run it without an image file
private Image createMyImage() {
BufferedImage bi = new BufferedImage(BI_WIDTH, BI_HEIGHT,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = bi.createGraphics();
g2.setColor(Color.red);
g2.fillRect(0, 0, BI_WIDTH, BI_HEIGHT);
g2.setColor(Color.blue);
int x = GAP;
int y = x;
int width = BI_WIDTH - 2 * GAP;
int height = BI_HEIGHT - 2 * GAP;
g2.fillRect(x, y, width, height);
g2.dispose();
return bi;
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
int x = labelLocation.x + STEP;
int y = labelLocation.y + STEP;
labelLocation = new Point(x, y);
label1.setLocation(labelLocation);
repaint();
if (x + BI_WIDTH > getWidth() || y + BI_HEIGHT > getHeight()) {
System.out.println("Stopping Timer");
((Timer) e.getSource()).stop();
}
}
}
private static void createAndShowGui() {
final GfxPanel gfxPanel = new GfxPanel(900, 750);
JButton button = new JButton(new AbstractAction("Animate") {
#Override
public void actionPerformed(ActionEvent arg0) {
gfxPanel.startAnimation();
}
});
JPanel buttonPanel = new JPanel();
buttonPanel.add(button);
JFrame frame = new JFrame("GFXScreen");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(gfxPanel);
frame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
What I always use is an infinite loop that calls an update method each iteration, in that method, you would do whatever was required to update the state of the game or render a GUI.
Example
public static void main(String[] args){
// Initialise game
while(true){
updateGame();
}
}
public static void updateGame(){
// Update things here.
}
What I also do ,which is a little more complex, is create and interface called IUpdateListener and have certain classes that are specialised for a certain element of the game. I would example have an InputListener, an AIListener, each handling a certain element of game updating.
public interface IUpdateListener{
public void update();
}
public class Main{
public static ArrayList<IUpdateListener> listeners = new ArrayList<IUpdateListener>();
public static void main(String[] args){
listeners.add(new InputListener());
while(true){
for(IUpdateListener listener : listeners){
listener.update();
}
}
}
}
public class InputListener implements IUpdateListener{
public void update(){
// Handle user input here etc
}
}
Ok, I need to crop an ImageView in a particular shape, and I can't do this by adding over a png, because the background can be variable (ex. a pattern). So, I need that the area outside the shape is transparent.
The shape must be this:
I thought to use Path() to draw this shape and use it to mask the ImageView, but I have absolutely no idea how to draw a complex shape like this with Path().
Many thanks.
So I was bored and this looked like fun, so I've thrown together a simple Drawable you can use to do this. You could get fancier and add strokes and whatnot to it, but this works for the basic case you've suggested, and allows you to set the arrow to be pointing to any of the corners, and will also scale your image to fit the bounds of the Drawable. Here's the result:
You can use it by:
BubbleDrawable bubbleDrawable = new BubbleDrawable(
this, R.drawable.your_image, BubbleDrawable.Corner.TOP_RIGHT);
myImageView.setImageDrawable(bubbleDrawable);
And here's the code for BubbleDrawable:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import static android.graphics.Matrix.ScaleToFit.FILL;
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
import static android.graphics.Path.Direction.CW;
import static android.graphics.PixelFormat.TRANSPARENT;
import static android.graphics.Shader.TileMode.CLAMP;
import static test.com.testrotationanimation.BubbleDrawable.Corner.TOP_LEFT;
public final class BubbleDrawable extends Drawable {
private final Matrix mMatrix = new Matrix();
private final Paint mPaint = new Paint(ANTI_ALIAS_FLAG);
private final Path mPath = new Path();
private final RectF mSrcRect = new RectF();
private final RectF mDstRect = new RectF();
private final Shader mShader;
private Corner mArrowCorner = TOP_LEFT;
public BubbleDrawable(Bitmap bitmap, Corner arrowCorner) {
// Initialize a BitmapShader with the image you wish to draw
// (you can use other TileModes like REPEAT or MIRROR if you prefer)
mShader = new BitmapShader(bitmap, CLAMP, CLAMP);
mPaint.setShader(mShader);
// Save the bounds of the bitmap as the src rectangle -- will
// be used later to update the matrix when the bounds change
// so that the image fits within the bounds of this drawable
mSrcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
// Set the corner in which the arrow will be drawn
mArrowCorner = arrowCorner;
}
public BubbleDrawable(Context ctx, int drawableResource, Corner arrowCorner) {
this(BitmapFactory.decodeResource(ctx.getResources(), drawableResource), arrowCorner);
}
public Corner getArrowCorner() {
return mArrowCorner;
}
public void setArrowCorner(Corner corner) {
mArrowCorner = corner;
updatePath();
invalidateSelf();
}
#Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
updateMatrix(bounds);
updatePath();
}
private void updateMatrix(Rect bounds) {
// Set the destination rectangle for the bitmap to be the
// new drawable bounds
mDstRect.set(bounds);
// Scale the bitmap's rectangle to the bounds of this drawable
mMatrix.setRectToRect(mSrcRect, mDstRect, FILL);
// Update the shader's matrix (to draw the bitmap at the right size)
mShader.setLocalMatrix(mMatrix);
}
private void updatePath() {
final Rect bounds = getBounds();
final float x = bounds.exactCenterX();
final float y = bounds.exactCenterY();
// Draw the initial circle (same for all corners)
mPath.reset();
mPath.addCircle(x, y, Math.min(x, y), CW);
// Add the rectangle which intersects with the center,
// based on the corner in which the arrow should draw
switch (mArrowCorner) {
case TOP_LEFT:
mPath.addRect(bounds.left, bounds.top, x, y, CW);
break;
case TOP_RIGHT:
mPath.addRect(x, bounds.top, bounds.right, y, CW);
break;
case BOTTOM_LEFT:
mPath.addRect(bounds.left, y, x, bounds.bottom, CW);
break;
case BOTTOM_RIGHT:
mPath.addRect(x, y, bounds.right, bounds.bottom, CW);
break;
}
}
#Override
public void draw(Canvas canvas) {
// Easy enough, just draw the path using the paint.
// It already has the BitmapShader applied which
// will do the work for you.
canvas.drawPath(mPath, mPaint);
}
#Override
public int getOpacity() {
// Indicate that this Drawable has fully-transparent pixel values
return TRANSPARENT;
}
#Override
public void setColorFilter(ColorFilter colorFilter) {
// Yay, you can even support color filters for your drawable
mPaint.setColorFilter(colorFilter);
}
#Override
public void setAlpha(int i) {
// You could do this by doing some canvas magic but I'm
// lazy and don't feel like it. Exercise for the reader. :)
throw new UnsupportedOperationException("Not implemented.");
}
public enum Corner {
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
}
}