I'm trying to write my first game in Java. I followed some tutorials and learned how to load and update a background using a Canvas and how to load and move a player sprite. I did these two separately and they worked fine, but when I put the two together and try to move the player, the game slows down to the point that it is unplayable. This only happens when I hold down an arrow key to move the player; the game actually runs "smoothly" if I rapidly tap the arrow key. After quite a bit of testing, I'm convinced that the problem occurs when the background is redrawn each frame. Any other improvements would also be appreciated.
Code (All of it):
Game.Java:
package Game;
import Level.Level;
import Player.Player;
import Sprites.SpriteSheetLoader;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import javax.swing.JFrame;
public class Game extends Canvas implements Runnable {
// Set dimensions of the game.
public static final int HEIGHT = 320;
public static final int WIDTH = 480;
public static final int SCALE = 2;
public static Dimension GAME_DIM = new Dimension(WIDTH * SCALE, HEIGHT * SCALE);
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
public SpriteSheetLoader loader;
public Screen screen;
public Level level;
public InputHandler input = new InputHandler(this);
public Player player = new Player();
private boolean running = false;
private boolean moving = true;
private int FPS = 60;
private long targetTime = 1000 / FPS;
// Set character's starting position at the center. I have no idea why I had to add the "- 50" to each value.
public int x = GAME_DIM.width / 2 - 50;
public int y = GAME_DIM.height / 2 - 50;
public int xScroll = 0;
public int yScroll = 0;
public int col = 0;
public int row = 0;
public int ticks = 0;
public int frame = 0;
public static void main(String[] args) {
Game game = new Game();
game.setPreferredSize(new Dimension(GAME_DIM));
game.setMaximumSize(new Dimension(GAME_DIM));
game.setMinimumSize(new Dimension(GAME_DIM));
JFrame frame = new JFrame("Valkyrie Game");
frame.add(game);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
game.start();
}
public void start() {
running = true;
new Thread(this).start();
}
public Game() {
}
public void init() {
loader = new SpriteSheetLoader();
screen = new Screen(WIDTH, HEIGHT);
level = new Level(16, 16);
}
public void run() {
init();
long start, elapsed, wait;
while (running) {
start = System.nanoTime();
render();
tick();
elapsed = System.nanoTime() - start;
//System.out.println("Elapsed: " + elapsed);
wait = targetTime - elapsed / 1000000;
if(wait < 0) {
wait = 5;
}
try {
Thread.sleep(wait);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stop() {
running = false;
}
public void tick() {
// Movement
if (input.right) {
xScroll++;
player.setAnimation(player.walkRight);
//x++;
row = 2;
ticks++;
if(ticks < 10) {
frame = 1;
} else if(ticks == 10) {
frame = 2;
} else if(ticks == 20) {
frame = 3;
} else if(ticks == 30) {
frame = 2;
} else if(ticks == 40) {
frame = 1;
} else if(ticks == 50) {
ticks = 0;
frame = 0;
}
moving = true;
} else if (input.left) {
xScroll--;
player.setAnimation(player.walkLeft);
//x--;
row = 1;
ticks++;
if(ticks < 10) {
frame = 1;
} else if(ticks == 10) {
frame = 2;
} else if(ticks == 20) {
frame = 3;
} else if(ticks == 30) {
frame = 2;
} else if(ticks == 40) {
frame = 1;
} else if(ticks == 50) {
ticks = 0;
frame = 0;
}
moving = true;
} else if (input.up) {
yScroll--;
player.setAnimation(player.walkUp);
//y--;
row = 3;
ticks++;
if(ticks < 10) {
frame = 1;
} else if(ticks == 10) {
frame = 2;
} else if(ticks == 20) {
frame = 3;
} else if(ticks == 30) {
frame = 2;
} else if(ticks == 40) {
frame = 1;
} else if(ticks == 50) {
ticks = 0;
frame = 0;
}
moving = true;
} else if (input.down) {
yScroll++;
player.setAnimation(player.walkDown);
//y++;
row = 0;
ticks++;
if(ticks < 10) {
frame = 1;
} else if(ticks == 10) {
frame = 2;
} else if(ticks == 20) {
frame = 3;
} else if(ticks == 30) {
frame = 2;
} else if(ticks == 40) {
frame = 1;
} else if(ticks == 50) {
ticks = 0;
frame = 0;
}
moving = true;
}
if (!input.down && !input.left && !input.right && !input.up) {
player.setAnimation(player.stand);
frame = 0;
ticks = 1;
moving = false;
}
//System.out.println("Tick: " + ticks);
}
public void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
requestFocus();
return;
}
do {
Graphics g = bs.getDrawGraphics();
try {
for (int i = 0; i < ticks; i++) {
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g.drawImage(player.Player(frame, row), x, y, null);
level.renderBackground(xScroll, yScroll, screen);
for (int y = 0; y < this.screen.h; y++) {
for (int x = 0; x < screen.w; x++) {
pixels[x + (y * WIDTH)] = screen.pixels[x + (y * screen.w)];
}
}
}
} finally {
g.dispose();
}
bs.show();
this.update(bs.getDrawGraphics());
} while (bs.contentsLost());
// Graphics g = bs.getDrawGraphics();
//
// g.dispose();
// bs.show();
}
}
InputHandler.Java:
package Game;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class InputHandler implements KeyListener {
public boolean up = false;
public boolean down = false;
public boolean left = false;
public boolean right = false;
public InputHandler(Game game) {
game.addKeyListener(this);
}
public void toggle(KeyEvent ke, boolean pressed) {
int keyCode = ke.getKeyCode();
if(keyCode == KeyEvent.VK_UP) up = pressed;
if(keyCode == KeyEvent.VK_DOWN) down = pressed;
if(keyCode == KeyEvent.VK_LEFT) left = pressed;
if(keyCode == KeyEvent.VK_RIGHT) right = pressed;
}
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
toggle(e, true);
}
public void keyReleased(KeyEvent e) {
toggle(e, false);
}
}
Screen.Java:
package Game;
import Sprites.Sprite;
public class Screen {
public int w, h;
int xOffset = 0;
int yOffset = 0;
public int[] pixels;
public Screen(int w, int h) {
this.w = w; // 480
this.h = h; // 320
pixels = new int[w * h]; // 153600
}
public void renderSprite(int xPos, int yPos, Sprite sprite) {
int height = sprite.h;
int width = sprite.w;
xPos -= xOffset;
yPos -= yOffset;
for(int y = 0; y < height; y++) {
if(yPos + y < 0 || yPos + y >= h) continue;
for(int x = 0; x < width; x++) {
if(xPos + x < 0 || xPos + x >= w) continue;
int col = sprite.pixels[x + (y * height)];
if(col != -65281 && col < 0) pixels[(x + xPos) + (y + yPos) *w]= col;
}
}
}
public void setOffs(int xOffs, int yOffs) {
xOffset = xOffs;
yOffset = yOffs;
}
}
Level.Java:
package Level;
import Game.Screen;
import Sprites.Sprite;
import Sprites.Sprites;
import Tiles.Tile;
public class Level {
int w, h;
public int[] tiles;
public Level(int w, int h) {
this.w = w;
this.h = h;
tiles = new int[w * h];
loadMap(0, 0, 0, 0);
}
public void renderBackground(int xScroll, int yScroll, Screen screen) {
int xo = xScroll >> 4;
int yo = yScroll >> 4;
int w = (screen.w + 15) >> 4;
int h = (screen.h + 15) >> 4;
screen.setOffs(xScroll, yScroll);
for(int y = yo; y <= h + yo; y++) {
for(int x = xo; x <= w + xo; x++) {
getTile(x, y).render(x, y, screen);
}
}
screen.setOffs(0, 0);
}
public Tile getTile(int x, int y) {
if(x < 0 || y < 0 || x >= w || y >= h) return Tile.rockTile;
return Tile.tiles[tiles[x + y * w]];
}
public void loadMap(int x0, int y0, int x1, int y1) {
Sprite sprite = Sprites.level[x0][y0];
for(int y = 0; y < sprite.h; y++) {
for(int x = 0; x < sprite.w; x++) {
if(sprite.pixels[x + y * sprite.h] == -9276814) {
tiles[x + x1 + (y + y1) * h] = Tile.rockTile.id;
} else {
tiles[x + x1 + (y + y1) * h] = Tile.grassTile.id;
}
}
}
}
}
Player.Java:
package Player;
import Animation.Animation;
import Sprites.Sprite;
import java.awt.image.BufferedImage;
public class Player {
// Images for each animation
private BufferedImage[] walkingLeft = {Sprite.getSprite(0, 1), Sprite.getSprite(1, 1), Sprite.getSprite(2, 1)}; // Gets the upper left images of my sprite sheet
private BufferedImage[] walkingRight = {Sprite.getSprite(0, 2), Sprite.getSprite(1, 2), Sprite.getSprite(2, 2)};
private BufferedImage[] walkingUp = {Sprite.getSprite(0, 3), Sprite.getSprite(1, 3), Sprite.getSprite(2, 3)};
private BufferedImage[] walkingDown = {Sprite.getSprite(0, 0), Sprite.getSprite(1, 0), Sprite.getSprite(2, 0)};
private BufferedImage[] standing = {Sprite.getSprite(1, 0)};
// These are animation states.
public Animation walkLeft = new Animation(walkingLeft, 10);
public Animation walkRight = new Animation(walkingRight, 10);
public Animation walkUp = new Animation(walkingUp, 10);
public Animation walkDown = new Animation(walkingDown, 10);
public Animation stand = new Animation(standing, 10);
// This is the actual animation
public Animation animation = stand;
public BufferedImage Player(int x, int y) {
BufferedImage player = Sprite.getSprite(x, y);
return player;
}
public void update() {
animation.update();
}
public void render() {
}
public void setAnimation(Animation animation) {
this.animation = animation;
}
}
Sprite.Java:
package Sprites;
import Game.Game;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Sprite {
public int w, h;
public int[] pixels;
public static BufferedImage sprite = null;
public Sprite(int w, int h) {
this.w = w;
this.h = h;
pixels = new int[w * h];
}
public void clear(int color) {
for(int i = 0; i < pixels.length; i++) {
pixels[i] = color;
}
}
private static BufferedImage spriteSheet;
private static final int TILE_SIZE = 80;
public static BufferedImage loadSprite() {
try {
sprite = ImageIO.read(Game.class.getResource("/valkyrie.png"));
} catch (IOException e) {
e.printStackTrace();
}
return sprite;
}
public static BufferedImage getSprite(int xGrid, int yGrid) {
if(spriteSheet == null) {
spriteSheet = loadSprite();
}
// xGrid and yGrid refer to each individual sprite
return spriteSheet.getSubimage(xGrid * TILE_SIZE, yGrid * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
}
This is going to require double buffering. Any game with a lot going on needs double buffering.
How do you double buffer in java for a game?
Although I couldn't go through the code completely, it seems you do not do double buffering which affect performance drastically.
Try this in the relevant part of your program:
JFrame frame = new JFrame("Valkyrie Game");
frame.add(game);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDoubleBuffered(true); //added line, rest is the same
game.start();
You really should use Timer. It will solve all your problems.
Every tick, you redraw all what you need.
And every tick, you should just check, which keys are pressed and which are not, instead of adding listeners. To keep tracking this, you always have to remember the keys pressed "before".
You can even create two Timers, one for graphic redraw and one for game logic.
Even timers can be delayed or something, the usual approach is to find out, how much time elapsed (System.nanoTime for example) and count how much of game logic you should forward to keep game always unlaggy and fluent.
Related
I am trying to have each brick in my game have a random color, however when I try to do this the whole set of bricks become the same color. How do I make each individual brick a random color? Any help is appreciated.
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Game extends JoeApplet implements KeyListener
{
String status;
int ballx = 294; // ball spawn x coordinate
int bally = 640; // ball spawn y coordinate
int batx = 294;
int baty = 654;
int brickx = 32;
int bricky = 50;
double movex = -16; // x speed of ball
double movey = -16; //y speed of ball
int count = 0;
int currentLevel=0;
int score=0; //starts score at 0
int lives=3; //lives start at 3
static boolean right = false;
static boolean left = false;
boolean ballFallDown = false;
boolean bricksOver = false;
Rectangle Ball = new Rectangle(ballx, bally, 14, 14); //creates ball
Rectangle Bat = new Rectangle(batx, baty, 100, 12); //creates bat(paddle)
Rectangle[] Brick = new Rectangle[49]; //creates desired number of bricks
public void paint(Graphics art)
{
switch(currentLevel)
{
case 0:
menuScreen(art);
break;
case 1:
game(art);
break;
}
}
public void menuScreen(Graphics art)
{
setSize(700, 700);
art.setColor(Color.BLACK);
art.fillRect(0, 0, 698, 698);
Color ballcolor=new Color(0,0,66);
art.setColor(ballcolor);
art.fillOval(Ball.x, Ball.y, Ball.width, Ball.height);
Color batcolor=new Color(0,0,66);
art.setColor(batcolor);
art.fill3DRect(Bat.x, Bat.y, Bat.width, Bat.height, true);
art.setColor(Color.green);
art.drawRect(0, 0, 698, 698);
art.setColor(Color.yellow);
Font menu = new Font("Arial", Font.BOLD, 20);
art.setFont(menu);
art.drawString("Brick Breaker", 100,400);
art.drawString("Press P to Play", 100,425);
art.drawString("Press Q to Quit game", 100,450);
for (int i = 0; i < Brick.length; i++)
{
if (Brick[i] != null)
{
Color mycolor=new Color(100,0,0);
art.setColor(mycolor);
art.fill3DRect(Brick[i].x, Brick[i].y, Brick[i].width,
Brick[i].height, true);
}
}
art.setColor(Color.YELLOW);
if (ballFallDown || bricksOver)
{
Font f = new Font("Arial", Font.BOLD, 20);
art.setFont(f);
art.drawString(status, 294, 349);
ballFallDown = false;
bricksOver = false;
}
}
public void game(Graphics art)
{
setSize(700, 700);
art.setColor(Color.BLACK);
art.fillRect(0, 0, 698, 698);
Color ballcolor=new Color(0,0,225);
art.setColor(ballcolor);
art.fillOval(Ball.x, Ball.y, Ball.width, Ball.height);
Color batcolor=new Color(0,0,139);
art.setColor(batcolor);
art.fill3DRect(Bat.x, Bat.y, Bat.width, Bat.height, true);
art.setColor(Color.green);
art.drawRect(0, 0, 698, 698);
for (int i = 0; i < Brick.length; i++)
{
if (Brick[i] != null)
{
Color mycolor=new Color(200,0,0);
art.setColor(mycolor);
art.fill3DRect(Brick[i].x, Brick[i].y, Brick[i].width,
Brick[i].height, true);
}
}
if (ballFallDown || bricksOver)
{
Font f = new Font("Arial", Font.BOLD, 20);
art.setFont(f);
art.drawString(status, 100,425);
ballFallDown = false;
bricksOver = false;
}
for (int i = 0; i < Brick.length; i++)
{
if (Brick[i] != null)
{
if (Brick[i].intersects(Ball))
{
score=score+10;
Brick[i] = null;
movey = -movey;
count++;
}
}
}
if (count == Brick.length)
{
bricksOver = true;
movex=0;
movey=0;
art.setColor(Color.green);
status = "YOU BEAT THE LEVEL!!";
art.drawString("Press E to Exit", 100,450);
art.drawString("Press N for Next Level", 100,475);
repaint();
}
repaint();
Font f = new Font("Arial", Font.BOLD, 20);
art.setFont(f);
art.setColor(Color.white);
art.drawString("Score:"+score, 600, 684);
Ball.x += movex;
Ball.y += movey;
if (left == true)
{
Bat.x -= 18;
right = false;
}
if (right == true)
{
Bat.x += 18;
left = false;
}
if (Bat.x <= 4)
{
Bat.x = 4;
}
else if (Bat.x >= 586)
{
Bat.x = 596;
}
if (Ball.intersects(Bat))
{
movey = -movey-.1;
}
if (Ball.x <= 0 || Ball.x + Ball.height >= 698)
{
movex = -movex;
}
if (Ball.y <= 0)
{
movey = -movey;
}
Font f1 = new Font("Arial", Font.BOLD, 20);
art.setFont(f1);
art.setColor(Color.white);
art.drawString("Lives:"+ lives, 5, 684);
if (Ball.y >= 698 && (bricksOver==false) && lives>0)
{
ballFallDown = true;
art.setColor(Color.red);
status = "";
art.drawString("", 100,450);
lives=lives-1;
ballx = 294;
bally = 640;
Ball = new Rectangle(ballx, bally, 14, 14);
movex = -16;
movey = -16;
repaint();
}
if(lives==0 && Ball.y >= 698)
{
art.setColor(Color.red);
art.drawString("You lost!!", 100,425);
art.drawString("Press E to Exit", 100,450);
}
}
public void init()
{
addKeyListener(this);
for (int i = 0; i < Brick.length; i++) //creates bricks
{
Brick[i] = new Rectangle(brickx, bricky, 40, 20);
if (i == 12) //1st row of bricks
{
brickx = 32;
bricky = 84;
}
if (i == 23) //2nd row of bricks
{
brickx = 82;
bricky = 118;
}
if (i == 32) //3rd row of bricks
{
brickx = 132;
bricky = 152;
}
if (i == 39) //4th row of bricks
{
brickx = 182;
bricky = 186;
}
if (i == 44) //5th row of bricks
{
brickx = 232;
bricky = 220;
}
if (i == 47) //6th row of bricks
{
brickx = 282;
bricky = 254;
}
if (i == 48) //7th row of bricks
{
brickx = 144;
bricky = 132;
}
brickx += 50; //spacing between each brick
}
}
public void restart()
{
ballx = 294;
bally = 640;
batx = 294;
baty = 654;
brickx = 32;
bricky = 50;
Ball = new Rectangle(ballx, bally, 14, 14);
Bat = new Rectangle(batx, baty, 100, 12);
movex = -16;
movey = -16;
ballFallDown = false;
bricksOver = false;
count = 0;
status = null;
for (int i = 0; i < Brick.length; i++) //recreates bricks
{
Brick[i] = new Rectangle(brickx, bricky, 40, 20);
if (i == 12)
{
brickx = 32;
bricky = 84;
}
if (i == 23)
{
brickx = 82;
bricky = 118;
}
if (i == 32)
{
brickx = 132;
bricky = 152;
}
if (i == 39)
{
brickx = 182;
bricky = 186;
}
if (i == 44)
{
brickx = 232;
bricky = 220;
}
if (i == 47)
{
brickx = 282;
bricky = 254;
}
if (i == 48)
{
brickx = 144;
bricky = 132;
}
brickx += 50;
}
repaint();
}
#Override
public void keyPressed(KeyEvent e) //allows each key to do desired action
{
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT)
{
left = true;
}
if (keyCode == KeyEvent.VK_RIGHT)
{
right = true;
}
if (keyCode == e.VK_P && currentLevel == 0)
{
currentLevel = 1;
}
else if (keyCode == e.VK_E && currentLevel == 1)
{
currentLevel = 0;
score=0;
lives=3;
restart();
}
else if(keyCode == e.VK_Q)
{
System.exit(0);
}
}
#Override
public void keyReleased(KeyEvent e)
{
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT)
{
left = false;
}
if (keyCode == KeyEvent.VK_RIGHT)
{
right = false;
}
}
#Override
public void keyTyped(KeyEvent e)
{
}
public static void main(String[] args)
{
Game prog = new Game();
prog.init();
}
}
I'd throw that code out and start over as you've got both program logic and repaints within your painting methods, neither of which will help you, and your code appears to be one big huge "God" class, all of which will leave you with code that's a horrific nightmare to debug. Recommendations:
Create at least two separate JPanels to display your program with, a GamePanel and a MenuPanel.
Swap these JPanels using a CardLayout.
Do all graphics within a JPanel's paintComponent method and not within a JFrame's or JApplet's paint method.
Don't forget to call the super's painting method, the same method as your override within your override. This is to clean up any dirty pixels.
Separate your program logic from your GUI
This means that you should have a logical non-GUI representation of a single Brick class as well as a collection of these non-GUI bricks.
You can always give your Brick class a Color field, one that the view or gui uses to paint it with.
Create a game-loop, one that you can control, one that doesn't involve calling repaint() within a painting method, since this leads to a completely uncontrollable loop.
Favor use of Key Bindings over KeyListeners.
Try to avoid use of "magic" numbers, such as hard-coding your brick width and spacing in the class itself. Better to use constants as this too makes debugging and enhancing much easier.
For example, some code that's just to demonstrate showing random colors:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.swing.*;
public class BrickBreak {
private static void createAndShowGui() {
GamePanel gamePanel = new GamePanel();
JFrame frame = new JFrame("Brick Break");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(gamePanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// JPanel that draws the game
class GamePanel extends JPanel {
private static final long serialVersionUID = 1L;
private static final Color BACK_GRND = Color.BLACK;
private int prefW;
private int prefH;
private Bricks bricks = new Bricks();
public GamePanel() {
// wide enough to hold the complete top-row of Bricks
// using constants, so GUI automatically resizes if any sizes change
prefW = (Brick.WIDTH + Bricks.X_SPACING) * Bricks.ROW_COUNTS[0] + Bricks.X_SPACING;
prefH = prefW;
setBackground(BACK_GRND);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(prefW, prefH);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for (Brick brick : bricks) {
brick.draw(g2);
}
}
}
// non-GUI class that represents a logical Brick
class Brick {
public static final int WIDTH = 40;
public static final int HEIGHT = 20;
private int x;
private int y;
private Color color;
private Rectangle boundingRectangle;
public Brick(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
boundingRectangle = new Rectangle(x, y, WIDTH, HEIGHT);
}
// yeah, I'm mixing some view with model here.
public void draw(Graphics2D g2) {
g2.setColor(color);
g2.fill3DRect(x, y, WIDTH, HEIGHT, true);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return color;
}
// use this to test for collisions
public boolean contains(Point p) {
return boundingRectangle.contains(p);
}
#Override
public String toString() {
return "Brick [x=" + x + ", y=" + y + ", color=" + color + "]";
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Brick other = (Brick) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
// logical class that holds all Bricks
// Have class implement Iterable<Brick> so we can easily iterate through its containing
// Brick objects in a for-each loop
class Bricks implements Iterable<Brick> {
public static final int X_SPACING = 10;
public static final int Y_SPACING = X_SPACING;
public static final int[] ROW_COUNTS = {13, 11, 9, 7, 5, 3, 1};
private static final float MIN_SAT = 0.8f;
private List<Brick> brickList;
private Random random = new Random();
public Bricks() {
init(); // safe to call since it's final
}
public final void init() {
// recreate the brickList ArrayList
brickList = new ArrayList<>();
int y = Y_SPACING;
// for each row of bricks
for (int row = 0; row < ROW_COUNTS.length; row++) {
int x = X_SPACING + ((ROW_COUNTS[0] - ROW_COUNTS[row]) / 2) * (X_SPACING + Brick.WIDTH);
// for each column
for (int j = 0; j < ROW_COUNTS[row]; j++) {
// create a random color
float hue = random.nextFloat();
float saturation = MIN_SAT + random.nextFloat() * (1f - MIN_SAT);
float brightness = MIN_SAT + random.nextFloat() * (1f - MIN_SAT);
Color color = Color.getHSBColor(hue, saturation, brightness);
Brick brick = new Brick(x, y, color); // create a new Brick with this Color
brickList.add(brick);
x += X_SPACING + Brick.WIDTH;
}
y += Y_SPACING + Brick.HEIGHT;
}
}
// returns null if no collision
public Brick collision(Point p) {
for (Brick brick : brickList) {
if (brick.contains(p)) {
return brick;
}
}
return null;
}
#Override
public Iterator<Brick> iterator() {
return brickList.iterator();
}
public boolean remove(Brick brick) {
// because Brick implements equals and hashCode, we can do this
return brickList.remove(brick);
}
}
Note that I like using Color's static getHSBColor(float h, float s, float b) method for creating random Colors as this helps me to avoid creating dull Colors, since I can guarantee that the saturation and brightness are above minimum values. All three parameters must be float values between 0f and 1.0f
float hue = random.nextFloat();
float saturation = MIN_SAT + random.nextFloat() * (1f - MIN_SAT);
float brightness = MIN_SAT + random.nextFloat() * (1f - MIN_SAT);
Color color = Color.getHSBColor(hue, saturation, brightness);
Your code has quite a lot of issues, which #HovercaftFullOfEels answer already points out.
As for why your code doesn't work:
for (int i = 0; i < Brick.length; i++)
{
if (Brick[i] != null)
{
Color mycolor=new Color(100,0,0);
art.setColor(mycolor);
art.fill3DRect(Brick[i].x, Brick[i].y, Brick[i].width,
Brick[i].height, true);
}
}
This is the part that renders the bricks. You never create a random-number, but use the same Color for each brick (new Color(100, 0, 0);). Instead introduce a new variable into Brick that specifies the color of each brick and is initialized once with a random color.
The Brick-class would afterwards look like this:
public class Brick{
public int x;
public int y;
...
public Color color;
...
}
The ... are just placeholders for other code that may be content of the class. Regarding public access of Class-variables: Encapsulation is a fundamental concept of OOP and should be used (on encapsulation). E.g. instead of giving direct access to Brick.x consider introducing a method Brick#getX().
I am trying to make a scrolling game - where the player (in space) is constantly at the center of the screen. As he moves left right up and down, a background spritesheet will randomly generate coloured stars - so the moving stars will be an indication of which direction the player is moving in.
The problem I am now having is that the stars are not displaying when I run the game. Each tile is supposed to be 32x32, each containing at least one star, with the 'nostars' tile being empty. When I run the game, I just get a black screen.
RandomLevel.java:
protected void generateLevel() {
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
bgtiles[x + y * width] = random.nextInt(4);
}
}
}
Level.java
public void render(int xScroll, int yScroll, Screen screen) {
screen.setOffset(xScroll, yScroll);
int x0 = xScroll >> 5;
int x1 = (xScroll + screen.width + 32) >> 5;
int y0 = yScroll >> 5;
int y1 = (yScroll + screen.height + 32) >> 5;
for(int y = y0; y < y1; y++) {
for(int x = x0; x < x1; x++) {
getTile(x, y).render(x, y, screen);
}
}
}
public Tile getTile(int x, int y) {
if(x < 0 || y < 0 || x >= width || y >= height) return Tile.nostars;
if(bgtiles[x + y * width] == 0) return Tile.stars1;
if(bgtiles[x + y * width] == 1) return Tile.stars2;
if(bgtiles[x + y * width] == 2) return Tile.stars3;
if(bgtiles[x + y * width] == 3) return Tile.stars4;
else return Tile.nostars;
}
SpaceTile.java
public class SpaceTile extends Tile {
public SpaceTile(Sprite sprite) {
super(sprite);
}
public void render(int x, int y, Screen screen) {
screen.renderTile(x << 5, y << 5, this);
}
}
SpriteSheet.java
public static SpriteSheet bgtiles = new SpriteSheet("/textures/bgsheet.png", 256);
Sprite.java
public static Sprite spaceSprite = new Sprite(32, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars1 = new Sprite(64, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars2 = new Sprite(96, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars3 = new Sprite(128, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars4 = new Sprite(160, 0, 0, SpriteSheet.bgtiles);
Tile.java
public class Tile {
public int x, y;
public Sprite sprite;
public static Tile nostars = new SpaceTile(Sprite.spaceSprite);
public static Tile stars1 = new SpaceTile(Sprite.stars1);
public static Tile stars2 = new SpaceTile(Sprite.stars2);
public static Tile stars3 = new SpaceTile(Sprite.stars3);
public static Tile stars4 = new SpaceTile(Sprite.stars4);
public Tile(Sprite sprite) {
this.sprite = sprite;
}
public void render(int x, int y, Screen screen) {
}
public boolean solid() {
return false;
}
}
Game.java
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
public static int width = 300;
public static int height = width / 16 * 9;
public static int scale = 3;
public static String title = "Game";
private Thread thread;
private JFrame frame;
private Keyboard key;
private Level level;
private boolean running = false;
private Screen screen;
private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
public Game() {
Dimension size = new Dimension(width * scale, height * scale);
setPreferredSize(size);
screen = new Screen(width, height);
frame = new JFrame();
key = new Keyboard();
level = new RandomLevel(64, 64);
addKeyListener(key);
}
public synchronized void start() {
running = true;
thread = new Thread(this, "Display");
thread.start();
}
public synchronized void stop() {
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
double ns = 1000000000.0 / 60.0;
double delta = 0;
int frames = 0;
int updates = 0;
long lastTime = System.nanoTime();
long timer = System.currentTimeMillis();
requestFocus();
while (running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >= 1) {
update();
updates++;
delta--;
}
render();
frames++;
if(System.currentTimeMillis() - timer >= 1000) {
timer += 1000;
frame.setTitle(title + " | " + updates + " ups, " + frames + " fps");
frames = 0;
updates = 0;
}
}
stop();
}
int x, y = 0;
public void update() {
key.update();
if(key.up == true) y--;
if(key.down == true) y++;
if(key.left == true) x--;
if(key.right == true) x++;
}
public void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
screen.clear();
level.render(x, y, screen);
for(int i = 0; i < pixels.length; i++) {
pixels[i] = screen.pixels[i];
}
Graphics g = bs.getDrawGraphics();
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g.dispose();
bs.show();
}
public static void main(String[] args) {
Game game = new Game();
game.frame.setResizable(false);
game.frame.setTitle(Game.title);
game.frame.add(game);
game.frame.pack();
game.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.frame.setLocationRelativeTo(null);
game.frame.setVisible(true);
game.start();
}
}
bgsheet.png
https://i.imgur.com/0yUKql2.png?1
In the generatelevel() I am only trying it out with the first 4 tiles, not all of the 64 tiles.
When I run the game, I expect to see 4 different stars scattered everywhere but instead I just get a black screen.
Thanks in advance for any help !
From the code posted, it appears that you forgot to load the background into image. I placed this code into a new public method called loadAssets(). Call this before you call game.start().
public void loadAssets() {
try {
image = ImageIO.read(new URL("https://i.imgur.com/0yUKql2.png?1"));
} catch (MalformedURLException ex) {
Logger.getLogger(GameTwo.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(GameTwo.class.getName()).log(Level.SEVERE, null, ex);
}
}
I also commented out the following code in render().
screen.clear();
level.render(x, y, screen);
for(int i = 0; i < pixels.length; i++) {
pixels[i] = screen.pixels[i];
}
So from what I understand, you make a call to draw using BufferedImage image, however you have not actually loaded your image data into the variable image. I will provide a code snippet that you may need to tailor a bit
File imageFile = new File("/path/to/image.file");
BufferedImage image = ImageIO.read(imageFile);
There may be a faster way as you have already called
private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
But as you can probably imagine, that doesn't actually connect you image variable to an image file. My best guess is that the code I provided will work, but honestly you'll have to try it.
Happy coding and leave a comment if you have any questions!
The problem turned out to be simply that I had the wrong co-ordinates for each sprite. Sorry for wasting your time and thanks for the help anyway!
public static Sprite spaceSprite = new Sprite(32, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars1 = new Sprite(32, 1, 0, SpriteSheet.bgtiles);
public static Sprite stars2 = new Sprite(32, 2, 0, SpriteSheet.bgtiles);
public static Sprite stars3 = new Sprite(32, 3, 0, SpriteSheet.bgtiles);
public static Sprite stars4 = new Sprite(32, 4, 0, SpriteSheet.bgtiles);
I am trying to use Jpanel and Jform to load a pic and perform some actions on it,so far the picture loads, but when I click to perform the action, it doesnt do anything. in fact it goes through the click event, but what I guess about the problem is, that the repaint() maybe doesnt work properly there.
here is the code:
public class SeamCarving
{
static SeamCarving frame=new SeamCarving();
public SeamCarving() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage input;
BufferedImage[] toPaint;
public TestPane() {
try {
input = ImageIO.read(new File("C:\\Users\\SONY\\Desktop\\my-pic\\Fatima.jpg"));
toPaint = new BufferedImage[]{input};
toPaint = new BufferedImage[1];
} catch (IOException ex) {
ex.printStackTrace();
}
setLayout(new GridBagLayout());
JButton loadButton = new JButton("Load");
loadButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
BufferedImage out = input;
out = input;
toPaint[0] = out;
repaint();
System.out.println("Do Something Clicked");
}
});
add(loadButton);
JButton startButton = new JButton("Start");
add(startButton);
startButton.addActionListener(new ActionListener() {
BufferedImage out = input;
#Override
public void actionPerformed(ActionEvent ae) {
out = deleteVerticalSeam(out);
System.out.println("Do Something Clicked");
toPaint[0]=out;
repaint();
}
});
}
what i want is this line: out = deleteVerticalSeam(out);
but it doesnt effect the photo at all.
EDIT
If doing like below, it will work. but I want it with button and Panel
public class SeamCarving
{
public static void main(String[] args) throws IOException {
final BufferedImage input = ImageIO.read(new File(args[0]));
final BufferedImage[] toPaint = new BufferedImage[]{input};
final Frame frame = new Frame("Seams") {
#Override
public void update(Graphics g) {
final BufferedImage im = toPaint[0];
if (im != null) {
g.clearRect(0,0,getWidth(), getHeight());
g.drawImage(im,0,0,this);
}
}
};
frame.setSize(input.getWidth(), input.getHeight());
frame.setVisible(true);
BufferedImage out = input;
for(int i = 0; i < 200; i++) {
out = deleteVerticalSeam(out);
toPaint[0]=out;
frame.repaint();
}
}
and the deleteVerticalSeam
private static BufferedImage deleteVerticalSeam(BufferedImage input) {
return deleteVerticalSeam(input, findVerticalSeam(input));
}
private static BufferedImage deleteVerticalSeam(final BufferedImage input, final int[] seam) {
int w = input.getWidth(), h = input.getHeight();
final BufferedImage out = new BufferedImage(w-1,h, BufferedImage.TYPE_INT_ARGB);
for(int y = 0; y < h; y++) {
for(int x = 0; x < seam[y]; x++) {
out.setRGB(x,y,input.getRGB(x, y));
}
for(int x = seam[y]+1; x < w; x++) {
out.setRGB(x-1,y,input.getRGB(x, y));
}
}
return out;
}
private static int[] findVerticalSeam(BufferedImage input) {
final int w = input.getWidth(), h = input.getHeight();
final FloatImage intensities = FloatImage.fromBufferedImage(input);
final FloatImage energy = computeEnergy(intensities);
final FloatImage minima = FloatImage.createSameSize(energy);
//First row is equal to the energy
for(int x = 0; x < w; x++) {
minima.set(x,0, energy.get(x,0));
}
//I assume that the rightmost pixel column in the energy image is garbage
for(int y = 1; y < h; y++) {
minima.set(0,y, energy.get(0,y) + min(minima.get(0, y - 1),
minima.get(1, y - 1)));
for(int x = 1; x < w-2; x++) {
final float sum = energy.get(x,y) + min(min(minima.get(x - 1, y - 1),
minima.get(x, y - 1)),minima.get(x + 1, y - 1));
minima.set(x,y, sum);
}
minima.set(w-2,y, energy.get(w-2,y) + min(minima.get(w-2, y - 1),minima.get(w-3, y - 1)));
}
//We find the minimum seam
float minSum = Float.MAX_VALUE;
int seamTip = -1;
for(int x = 1; x < w-1; x++) {
final float v = minima.get(x, h-1);
if(v < minSum) {
minSum=v;
seamTip=x;
}
}
//Backtrace the seam
final int[] seam = new int[h];
seam[h-1]=seamTip;
for(int x = seamTip, y = h-1; y > 0; y--) {
float left = x>0?minima.get(x-1, y-1):Float.MAX_VALUE;
float up = minima.get(x, y-1);
float right = x+1<w?minima.get(x+1, y-1):Float.MAX_VALUE;
if(left < up && left < right) x=x-1;
else if(right < up && right < left) x= x+1;
seam[y-1]=x;
}
return seam;
}
floatimage
public final class FloatImage extends JFrame{
private final int width;
private final int height;
private final float[] data;
public FloatImage(int width, int height) {
this.width = width;
this.height = height;
this.data = new float[width*height];
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public float get(final int x, final int y) {
if(x < 0 || x >= width) throw new IllegalArgumentException("x: " + x);
if(y < 0 || y >= height) throw new IllegalArgumentException("y: " + y);
return data[x+y*width];
}
public void set(final int x, final int y, float value) {
if(x < 0 || x >= width) throw new IllegalArgumentException("x: " + x);
if(y < 0 || y >= height) throw new IllegalArgumentException("y: " + y);
data[x+y*width] = value;
}
public static FloatImage createSameSize(final BufferedImage sample) {
return new FloatImage(sample.getWidth(), sample.getHeight());
}
public static FloatImage createSameSize(final FloatImage sample) {
return new FloatImage(sample.getWidth(), sample.getHeight());
}
public static FloatImage fromBufferedImage(final BufferedImage src) {
final int width = src.getWidth();
final int height = src.getHeight();
final FloatImage result = new FloatImage(width, height);
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
final int argb = src.getRGB(x, y);
int r = (argb >>> 16) & 0xFF;
int g = (argb >>> 8) & 0xFF;
int b = argb & 0xFF;
result.set(x,y, (r*0.3f+g*0.59f+b*0.11f)/255);
}
}
return result;
}
public BufferedImage toBufferedImage(float scale) {
final BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
for(int x = 0; x < width; x++) {
for(int y = 0; y < height; y++) {
final int intensity = ((int) (get(x, y) * scale)) & 0xFF;
result.setRGB(x,y,0xFF000000 | intensity | intensity << 8 | intensity << 16);
}
}
return result;
}
}
I "think" your basic problem starts here...
startButton.addActionListener(new ActionListener() {
BufferedImage out = input;
#Override
public void actionPerformed(ActionEvent ae) {
out = deleteVerticalSeam(out);
System.out.println("Do Something Clicked");
toPaint[0]=out;
repaint();
}
});
Try removing the instance variable out...
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
BufferedImage out = deleteVerticalSeam(input);
System.out.println("Do Something Clicked");
toPaint[0]=out;
repaint();
}
});
Updated
I've updated the ActionListener to work more like...
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
BufferedImage out = deleteVerticalSeam(toPaint[0]);
System.out.println("Do Something Clicked");
toPaint[0] = out;
repaint();
}
});
Which feeds the last result of the operation back into itself, which gradually decreases the number of vertical pixels in the image...
Updated with working example
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import static java.lang.Math.abs;
import static java.lang.Math.min;
public class SeamCarving {
public static void main(String[] args) {
new SeamCarving();
}
public SeamCarving() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage input;
BufferedImage[] toPaint;
public TestPane() {
try {
input = ImageIO.read(new File("..."));
toPaint = new BufferedImage[]{input};
toPaint = new BufferedImage[1];
} catch (IOException ex) {
ex.printStackTrace();
}
setLayout(new GridBagLayout());
JButton loadButton = new JButton("Load");
loadButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
BufferedImage out = input;
toPaint[0] = input;
repaint();
System.out.println("Do Something Clicked");
}
});
add(loadButton);
JButton startButton = new JButton("Start");
add(startButton);
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
BufferedImage out = deleteVerticalSeam(toPaint[0]);
System.out.println("Do Something Clicked");
toPaint[0] = out;
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return input == null ? super.getPreferredSize() : new Dimension(input.getWidth(), input.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(toPaint[0], 0, 0, this);
g2d.dispose();
}
}
private static BufferedImage deleteVerticalSeam(BufferedImage input) {
return deleteVerticalSeam(input, findVerticalSeam(input));
}
private static BufferedImage deleteVerticalSeam(final BufferedImage input, final int[] seam) {
int w = input.getWidth(), h = input.getHeight();
final BufferedImage out = new BufferedImage(w - 1, h, BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < h; y++) {
for (int x = 0; x < seam[y]; x++) {
out.setRGB(x, y, input.getRGB(x, y));
}
for (int x = seam[y] + 1; x < w; x++) {
out.setRGB(x - 1, y, input.getRGB(x, y));
}
}
return out;
}
private static int[] findVerticalSeam(BufferedImage input) {
final int w = input.getWidth(), h = input.getHeight();
final FloatImage intensities = FloatImage.fromBufferedImage(input);
final FloatImage energy = computeEnergy(intensities);
final FloatImage minima = FloatImage.createSameSize(energy);
//First row is equal to the energy
for (int x = 0; x < w; x++) {
minima.set(x, 0, energy.get(x, 0));
}
//I assume that the rightmost pixel column in the energy image is garbage
for (int y = 1; y < h; y++) {
minima.set(0, y, energy.get(0, y) + min(minima.get(0, y - 1),
minima.get(1, y - 1)));
for (int x = 1; x < w - 2; x++) {
final float sum = energy.get(x, y) + min(min(minima.get(x - 1, y - 1),
minima.get(x, y - 1)), minima.get(x + 1, y - 1));
minima.set(x, y, sum);
}
minima.set(w - 2, y, energy.get(w - 2, y) + min(minima.get(w - 2, y - 1), minima.get(w - 3, y - 1)));
}
//We find the minimum seam
float minSum = Float.MAX_VALUE;
int seamTip = -1;
for (int x = 1; x < w - 1; x++) {
final float v = minima.get(x, h - 1);
if (v < minSum) {
minSum = v;
seamTip = x;
}
}
//Backtrace the seam
final int[] seam = new int[h];
seam[h - 1] = seamTip;
for (int x = seamTip, y = h - 1; y > 0; y--) {
float left = x > 0 ? minima.get(x - 1, y - 1) : Float.MAX_VALUE;
float up = minima.get(x, y - 1);
float right = x + 1 < w ? minima.get(x + 1, y - 1) : Float.MAX_VALUE;
if (left < up && left < right) {
x = x - 1;
} else if (right < up && right < left) {
x = x + 1;
}
seam[y - 1] = x;
}
return seam;
}
private static FloatImage computeEnergy(FloatImage intensities) {
int w = intensities.getWidth(), h = intensities.getHeight();
final FloatImage energy = FloatImage.createSameSize(intensities);
for (int x = 0; x < w - 1; x++) {
for (int y = 0; y < h - 1; y++) {
//I'm aproximating the derivatives by subtraction
float e = abs(intensities.get(x, y) - intensities.get(x + 1, y))
+ abs(intensities.get(x, y) - intensities.get(x, y + 1));
energy.set(x, y, e);
}
}
return energy;
}
public static final class FloatImage {
private final int width;
private final int height;
private final float[] data;
public FloatImage(int width, int height) {
this.width = width;
this.height = height;
this.data = new float[width * height];
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public float get(final int x, final int y) {
if (x < 0 || x >= width) {
throw new IllegalArgumentException("x: " + x);
}
if (y < 0 || y >= height) {
throw new IllegalArgumentException("y: " + y);
}
return data[x + y * width];
}
public void set(final int x, final int y, float value) {
if (x < 0 || x >= width) {
throw new IllegalArgumentException("x: " + x);
}
if (y < 0 || y >= height) {
throw new IllegalArgumentException("y: " + y);
}
data[x + y * width] = value;
}
public static FloatImage createSameSize(final BufferedImage sample) {
return new FloatImage(sample.getWidth(), sample.getHeight());
}
public static FloatImage createSameSize(final FloatImage sample) {
return new FloatImage(sample.getWidth(), sample.getHeight());
}
public static FloatImage fromBufferedImage(final BufferedImage src) {
final int width = src.getWidth();
final int height = src.getHeight();
final FloatImage result = new FloatImage(width, height);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
final int argb = src.getRGB(x, y);
int r = (argb >>> 16) & 0xFF;
int g = (argb >>> 8) & 0xFF;
int b = argb & 0xFF;
result.set(x, y, (r * 0.3f + g * 0.59f + b * 0.11f) / 255);
}
}
return result;
}
public BufferedImage toBufferedImage(float scale) {
final BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
final int intensity = ((int) (get(x, y) * scale)) & 0xFF;
result.setRGB(x, y, 0xFF000000 | intensity | intensity << 8 | intensity << 16);
}
}
return result;
}
}
}
I am making a tile based platformer game in java. I render a map which is stored in a 2 dimensional array but when this array is very big my game starts to become slow. I realised that I had to only render the part of the map that is viewable, I tried to do that but i wrote very hacky code that only worked partly so I removed it. How can I do this properly? Here is my code (without the hacky stuff). Also how could I use System.nanoTime() rather than System.currentTimeMillis()?
package sexy_robot_from_another_dimension;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.TexturePaint;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class Game extends JPanel
{
int playerX = 50;
int playerY = 50;
static boolean up = false;
static boolean down = false;
static boolean right = false;
static boolean left = false;
int playerSpeed = 1;
String[][] map;
int blockSize = 20;
int jumpLoop = 0;
int maxJumpLoop = 280;
static BufferedImage block, player;
int playerWidth = 20;
int playerHeight = 35;
int cameraX = 0;
int cameraY = 0;
long nextSecond = System.currentTimeMillis() + 1000;
int frameInLastSecond = 0;
int framesInCurrentSecond = 0;
public Game()
{
super();
try
{
map = load("/maps/map1.txt");
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
Timer timer = new Timer();
TimerTask task = new TimerTask()
{
#Override
public void run()
{
if(up)
{
if((!playerIsOnBlock(playerX, playerY).equals("0")) || (!playerIsOnBlock(playerX + (playerWidth - 1), playerY).equals("0")))
{
timeToJump();
}
}
if(down)
{
}
if(right)
{
if((playerIsLeftBlock(playerX, playerY).equals("0")) && (playerIsLeftBlock(playerX, playerY + (playerHeight/2 - 1)).equals("0")) && (playerIsLeftBlock(playerX, playerY + (playerHeight - 1)).equals("0")))
{
playerX += playerSpeed;
}
}
if(left)
{
if((playerIsRightBlock(playerX, playerY).equals("0")) && (playerIsRightBlock(playerX, playerY + (playerHeight/2 - 1)).equals("0")) && (playerIsRightBlock(playerX, playerY + (playerHeight - 1)).equals("0")))
{
playerX -= playerSpeed;
}
}
repaint();
}
};
timer.scheduleAtFixedRate(task, 0, 10);
Timer timerGrav = new Timer();
TimerTask taskGrav = new TimerTask()
{
#Override
public void run()
{
if((playerIsOnBlock(playerX, playerY).equals("0")) && (playerIsOnBlock(playerX + (playerWidth - 1), playerY).equals("0")))
{
playerY += playerSpeed;
repaint();
}
}
};
timerGrav.scheduleAtFixedRate(taskGrav, 0, 6);
}
void timeToJump()
{
if(jumpLoop == 0)
{
jumpLoop = 1;
Timer timer = new Timer();
TimerTask task = new TimerTask()
{
#Override
public void run()
{
if((playerIsBelowBlock(playerX, playerY).equals("0")) && (playerIsBelowBlock(playerX + (playerWidth - 1), playerY).equals("0")))
{
playerY -= playerSpeed;
jumpLoop++;
repaint();
}
else
{
jumpLoop = maxJumpLoop;
}
if(jumpLoop == maxJumpLoop)
{
jumpLoop = 0;
cancel();
}
}
};
timer.scheduleAtFixedRate(task, 0, 3);
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
long currentTime = System.currentTimeMillis();
if (currentTime > nextSecond)
{
nextSecond += 1000;
frameInLastSecond = framesInCurrentSecond;
framesInCurrentSecond = 0;
}
framesInCurrentSecond++;
g.drawString(frameInLastSecond + " fps", 10, 20);
cameraX = -playerX + getWidth()/2;
cameraY = -playerY + getHeight()/2;
g.translate(cameraX, cameraY);
for (int x = 0; x < map.length; x++)
{
for (int y = 0; y < map[0].length; y++)
{
switch(map[x][y])
{
case "0":
break;
case "1":
if(block != null)
{
TexturePaint tp0 = new TexturePaint(block, new Rectangle(0, 0, blockSize, blockSize));
g2.setPaint(tp0);
}
g.fillRect(y*blockSize, x*blockSize, 20, 20);
break;
}
}
}
g.setColor(Color.BLACK);
if(player != null)
{
TexturePaint tp0 = new TexturePaint(player, new Rectangle(playerX, playerY, playerWidth, playerHeight));
g2.setPaint(tp0);
}
g.fillRect(playerX, playerY, playerWidth, playerHeight);
g.setColor(Color.black);
g.setFont(new Font("Droid Sans Mono", Font.PLAIN, 12));
g.drawString("Sexy!", playerX - 5, playerY - 10);
}
boolean outOfMap(int x, int y)
{
y -= blockSize - 1;
x -= blockSize - 1;
if((y/blockSize <= map.length - 2) && (y/blockSize >= 0) && (x/blockSize <= map[0].length-2) && (x/blockSize >= 0))
{
return false;
}
return true;
}
String playerIsOnBlock(int x, int y)
{
y += playerHeight;
if(!outOfMap(x, y))
{
if(map[y/blockSize][x/blockSize] != "0")
{
return map[y/blockSize][x/blockSize];
}
}
return "0";
}
String playerIsBelowBlock(int x, int y)
{
y -= playerSpeed;
if(!outOfMap(x, y))
{
if(map[y/blockSize][x/blockSize] != "0")
{
return map[y/blockSize][x/blockSize];
}
}
return "0";
}
String playerIsLeftBlock(int x, int y)
{
x += playerWidth;
if(!outOfMap(x, y))
{
if(map[y/blockSize][x/blockSize] != "0")
{
return map[y/blockSize][x/blockSize];
}
}
return "0";
}
String playerIsRightBlock(int x, int y)
{
x -= playerSpeed;
if(!outOfMap(x, y))
{
if(map[y/blockSize][x/blockSize] != "0")
{
return map[y/blockSize][x/blockSize];
}
}
return "0";
}
String[][] load(String file) throws IOException
{
BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(file)));
int lines = 1;
int length = br.readLine().split(" ").length;
while (br.readLine() != null) lines++;
br.close();
br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(file)));
String[][] map = new String[lines][length];
for (int i = 0; i < lines; i++)
{
String line = br.readLine();
String[] parts = line.split(" ");
for (int y = 0; y < length; y++)
{
map[i][y] = parts[y];
}
}
br.close();
return map;
}
}
Thank you!
It seems your camera is centered on the player, then there are two ways of doing this, I like the first way, it is a bit cleaner:
1th: Create a rectangle that bounds your cameras view, and check if the map x,y is within this view, render only if true.
Rectangle cameraView = new Rectangle(playerX - getWidth() / 2, playerY - getHeight() / 2, getWidth(), getHeight());
for (int x = 0; x < map.length; x++) {
for (int y = 0; y < map[0].length; y++) {
if (!cameraView.contains(x*blockSize, y*blockSize))
continue;
switch (map[x][y]) {
case "0":
break;
case "1":
if (block != null) {
TexturePaint tp0 = new TexturePaint(block, new Rectangle(0, 0, blockSize, blockSize));
g2.setPaint(tp0);
}
g.fillRect(y * blockSize, x * blockSize, 20, 20);
break;
}
}
}
The second option is to simply calculate the distance to the center of the screen (playerX,playerY) from each map[x][y] and skip all map[x][y] that falls outside your viewing bounds, this is a bit uglier to code and I really don't recommend this, the rectangle option above should be fast enough.
Edit:
#JasonC That is true, I didn't consider for instance when an x value is definitely outside the view, it will still go into the y loop through all the y values. One can simply create a dummy variable in the x-loop and do the following check
for (int x = 0; x < map.length; x++) {
int dummyY = playerY
if(!cameraView.contains(x,dummyY))
continue;
....
//rest of code ommitted
Another optimization you can do is considering not setting a TexturePaint (expensive operation) but instead simply drawing the image of the block:
g.fillRect(y * blockSize, x * blockSize, 20, 20);
Replaced with
g.drawImage(block, y*blockSize, x*blockSize, null);
The same with the playerimage.
Set the clipping region to the visible area with Graphics.setClip(), that will prevent most rendering operations from taking effect outside that region.
For drawing operations where this isn't sufficient (perhaps you also want to avoid doing calculations or something for objects outside the clipping area), test your objects bounds against the clipping rectangle and skip the object if it doesn't intersect.
See Graphics.setClip().
A further optimization can be done by, for example, calculating the range of blocks on your map that is definitely outside of the visible area, and excluding that from your for loop (no sense testing blocks against the clipping region if you know they are outside already). Take the clipping region, transform it to map index coordinates, then you will know where in your map the visible area is and you can just iterate over that subsection of the map.
So I'm trying to implement collision detection in my game and for some reason the collision isnt working properly.
public class ArenaKeys extends KeyAdapter {
arenaScreenBuild arena;
int xPos = 0, playerFace = 4;
int xPPos = 200, yPPos = 150;
int pX = 40, pY = 30;
AttackAshe aAtk = new AttackAshe();
int[][] mask = new int[400][92];
#SuppressWarnings("static-access")
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();// Get key pressed
if (keyCode == e.VK_RIGHT) {
playerFace = 4;
xPos += 5;
pX = (xPos + xPPos) / 5;
if (checkBoundary(pX, pY) == (false))
xPos -= 5;
} else if (keyCode == e.VK_LEFT) {
playerFace = 3;
xPos -= 5;
pX = (xPos + xPPos) / 5;
if (checkBoundary(pX, pY) == (false))
xPos += 5;
} else if (keyCode == e.VK_UP) {
playerFace = 2;
yPPos -= 5;
pY = yPPos / 5;
if (checkBoundary(pX, pY) == (false))
yPPos += 5;
} else if (keyCode == e.VK_DOWN) {
playerFace = 1;
yPPos += 5;
pY = yPPos / 5;
if (checkBoundary(pX, pY) == (false))
yPPos -= 5;
}
if (keyCode == e.VK_SPACE) {
aAtk.regArrow(arena.xPosition(), arena.yPosition());
arena.shoot(playerFace);
arena.xArrow = xPPos;
arena.yArrow = yPPos;
} else if (keyCode == e.VK_ESCAPE)
System.exit(0);
arena.moveArena(xPos);
arena.turnPlayer(playerFace);
arena.updateScreen(xPPos, yPPos);
}
public boolean checkBoundary(int x, int y) {
Rectangle t1 = new Rectangle(Turret.x, Turret.y, Turret.WIDTH,
Turret.HEIGHT);
Rectangle p = new Rectangle(pX, pY, Player.WIDTH, Player.HEIGHT);
if (t1.intersects(p))
// if (mask[x][y] == 0)
return false;
else
return true;
}
public static class Turret {
static int x = 168;
static int y = 40;
static final int WIDTH = 50;
static final int HEIGHT = 50;
}
public static class Player {
static final int WIDTH = 25;
static final int HEIGHT = 25;
}
public ArenaKeys(arenaScreenBuild arena) throws Exception {
this.arena = arena;
}
}
Approximately 20 units before the actual turret, the sprite stops being able to move any further. The sprite cannot go above or below the turret even if you go really high or really low.
What seems to be going wrong is that the sprite is colliding into the turret rectangle too early but I don't understand how that it possible. I draw the turret exactly 50 wide, 50 high at 168,40. The player is moving so it's x, y is different everytime but it's dimensions are 25 wide and 25 high.
The original turret is 126x111 approximately but I draw it as 50x50
25x25
public class arenaScreenBuild extends JPanel implements ActionListener {
String picPath = "pictures/";
String[] fileName = { "stageBridge.png", "turret.png", "Ashe.png",
"regArrow.png", "arenaScreen.png" };
ClassLoader cl = arenaScreenBuild.class.getClassLoader();
URL imgURL[] = new URL[5];
Toolkit tk = Toolkit.getDefaultToolkit();
Image imgBG, imgTurret, imgPlayer, imgRegArrow, imgBorder;
Boolean[] ptFunc = new Boolean[3];
int PLAYER_INITIAL_X = 200, PLAYER_INITIAL_Y = 150;
int xPos = 0, xPFace = 150, yPFace = 0;
int xPPos = 200, yPPos = 150;
int xVal, yVal, xArrow, yArrow, xTemp, yTemp;
Timer space;
int counter, facePosition = 1;
public arenaScreenBuild() throws Exception {
for (int x = 0; x < 5; x++) {
imgURL[x] = cl.getResource(picPath + fileName[x]);
}
imgBG = tk.createImage(imgURL[0]);
imgTurret = tk.createImage(imgURL[1]);
imgPlayer = tk.createImage(imgURL[2]);
imgRegArrow = tk.createImage(imgURL[3]);
imgBorder = tk.createImage(imgURL[4]);
for (int x = 0; x < 3; x++)
ptFunc[x] = true;
space = new Timer(100, this);
}
public void updateScreen() {
repaint();
}
public void moveArena(int x) {
xPos = x;
}
public void updateScreen(int x, int y) {
xPPos = x;
yPPos = y;
repaint();
}
public boolean arrow() {
return (true);
}
public void turnPlayer(int face) {
if (face == 4) {
xPFace = 150;
} else if (face == 3) {
xPFace = 100;
} else if (face == 2) {
xPFace = 50;
} else if (face == 1) {
xPFace = 0;
}
if (yPFace == 50)
yPFace = 0;
else if (yPFace == 0)
yPFace = 50;
}
public void paintComponent(Graphics g) {
g.drawImage(imgBG, 10, 30, 610, 490, xPos, 0, xPos + 600, 460, this);
g.drawImage(imgTurret, 850 - xPos, 200, 950 - xPos, 300, 0, 0, 126,
110, this);
g.drawImage(imgTurret, 1350 - xPos, 200, 1450 - xPos, 300, 0, 0, 126,
110, this);
g.drawImage(imgPlayer, xPPos, yPPos, 50 + (xPPos),
50 + (yPPos), xPFace, yPFace, xPFace + 50, yPFace + 50, this);
if (counter <= 5000 && counter > 0)
g.drawImage(imgRegArrow, xArrow, yArrow, this);
g.drawImage(imgBorder, 0, 0, 620, 600, 0, 0, 620, 600, this);
g.setColor(Color.WHITE);
g.drawString("x:" + (xPPos + xPos), 535, 525);
g.drawString("y:" + yPPos, 535, 545);
}
public int xPosition() {
xVal = xPPos + xPos;
return (xVal);
}
public int yPosition() {
yVal = yPPos;
return (yVal);
}
public void shoot(int i) {
facePosition = i;
xTemp = xPosition();
yTemp = yPosition();
space.start();
}
public void actionPerformed(ActionEvent e) {
counter++;
if (facePosition == 4) {
if (counter <= 5000) {
xArrow += 50;
}
}
else if (facePosition == 3) {
if (counter <= 5000) {
xArrow -= 50;
}
}
else if (facePosition == 2) {
if (counter <= 5000) {
yArrow -= 50;
}
}
else if (facePosition == 1) {
if (counter <= 5000) {
yArrow += 50;
}
}
if (xArrow == (xTemp + 100)) {
counter = 0;
space.stop();
}
updateScreen();
}
}
Turns out that my values for the x position were wrong. Also due to my previous boundary code, there were still remenants of it which messed me up therefore not allowing me to see the problem sooner.
For any one looking for how to make the collision detection boundary, just make 2 rectangles and use the
Rectangle rect1 = new Rectangle(topLeftX, topLeftY, rectangleWidth,rectangleHeight);
Rectangle rect2 = new Rectangle(topLeftX, topLeftY, rectangleWidth, rectangleHeight);
if (rect1.intersects(rect2))
//enter code to do if it intersects here