简体   繁体   English

玩家移动时Java游戏速度变慢

[英]Java game slows down when player moves

I'm trying to write my first game in Java. 我正在尝试用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. 我按照一些教程学习了如何使用Canvas加载和更新背景,以及如何加载和移动播放器精灵。 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; Game.Java:封装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: 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: 屏幕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: 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: 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: 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);

    }

}

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. 您确实应该使用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. 即使计时器可能会延迟或发生某种情况,通常的方法是找出已耗费了多少时间(例如System.nanoTime),并计算应转发多少游戏逻辑以使游戏始终保持流畅和流畅。

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? 如何在Java中为游戏加倍缓冲?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM