0

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);

    }

}

3 Answers3

0

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();
0

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.

libik
  • 22,239
  • 9
  • 44
  • 87
0

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?

Community
  • 1
  • 1
PinkElephantsOnParade
  • 6,452
  • 12
  • 53
  • 91