如何通过按键旋转Java中的2D矩形?

时间:2018-08-11 00:41:03

标签: java rotation awt 2d keylistener

我目前正在尝试制作我的第一个简单的Java游戏。到目前为止,我一直在关注某个YouTube教程,但是我想添加自己的功能,其中之一就是可以通过按某个键来旋转播放器。我一直在寻找方法,但经过多次失败尝试之后,如果有人能提出建议,我将不胜感激。

这是我的播放器类,我在其中尝试通过实现KeyListener来旋转播放器:

package topDownGame;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;

import javax.swing.Timer;

public class Player extends GameObject implements KeyListener{

    private Handler handler;
    private HUD hud;
    public float rotation = 0;

    public Player(int x, int y, ID id, Handler handler, HUD hud) {
        super(x, y, id);
        this.handler = handler;
        this.hud = hud;

    }

    public void tick() {

        x += velX;
        y += velY;

        x = Game.clamp(x, 0, Game.WIDTH - 38);
        y = Game.clamp(y, 0, Game.HEIGHT - 67);

        collision();

    }

    public void render(Graphics g) {
        //g.setColor(Color.WHITE);      
        //g.fillRect(x, y, 32, 32);
        Graphics2D g2d = (Graphics2D)g;
        Rectangle r = new Rectangle(x, y, 32, 32);
        Path2D.Double path = new Path2D.Double();
        path.append(r, false);

        AffineTransform t = new AffineTransform();
        t.rotate(rotation);
        path.transform(t);
        g2d.setColor(Color.WHITE);
        g2d.draw(path);

    }


    public void collision() {

        for (int i = 0; i < handler.object.size(); i++) {
            GameObject tempObject = handler.object.get(i);

            if (tempObject.getId() == ID.BasicEnemy) {
                if (getBounds().intersects(tempObject.getBounds())) {
                    hud.HEALTH -= 2;
                }
            }
        }

    }


    public Rectangle getBounds() {

        return new Rectangle(x, y, 32, 32);

    }

    @Override
    public void keyPressed(KeyEvent e) {
    int key = e.getKeyCode();

        for (int i = 0; i < handler.object.size(); i++) {
            GameObject tempObject = handler.object.get(i);

            if (tempObject.getId() == ID.Player) {
                if (key == KeyEvent.VK_E) {
                    rotation = (float) (rotation + 0.1);
                }
            }
        }

    }

    @Override
    public void keyReleased(KeyEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void keyTyped(KeyEvent arg0) {
        // TODO Auto-generated method stub

    }   

}

下面是我剩下的一些可能很重要的代码

游戏类:

package topDownGame;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;

public class Game extends Canvas implements Runnable{

    /**
     * 
     */
    private static final long serialVersionUID = 1744439430685015162L;
    public static final int WIDTH = 640, HEIGHT = WIDTH / 12*9;

    private boolean running = false;
    private Thread thread;
    private Handler handler;

    public Game() {
        handler = new Handler();
        this.addKeyListener(new KeyInput(handler));
        new Window(WIDTH, HEIGHT, "Game", this);
        handler.addObject(new Player(200, 200, ID.Player, handler, hud));
    }

    public synchronized void start() {
        running = true;
        thread = new Thread(this);
        thread.start();
    }

    public synchronized void stop() {
        try{
            running = false;
            thread.join();
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

    public void run() {
        this.requestFocus();
        long lastTime = System.nanoTime();
        double delta = 0.0;
        double amountOfTicks = 60.0;
        double ns = 1000000000/amountOfTicks;
        long timer = System.currentTimeMillis();
        int frames = 0;
        while(running) {
            long now = System.nanoTime();
            delta += (now-lastTime)/ns;
            lastTime = now;
            while(delta >= 1) {
                delta--;
                tick();
            }
            if (running) {
                frames++;
                render();
            }
            if (System.currentTimeMillis() - timer > 1000) {
                timer += 1000;
                System.out.println("FPS: " + frames);
                frames = 0;
            }
        }
        stop();
    }

    public void tick() {
        handler.tick();
    }

    public void render() {
        BufferStrategy bs = this.getBufferStrategy();
        if (bs == null) {
            this.createBufferStrategy(3);
            return;
        }
        Graphics g = bs.getDrawGraphics();



        g.setColor(Color.BLACK);
        g.fillRect(0, 0, WIDTH, HEIGHT);
        handler.render(g);
        g.dispose();
        bs.show();
    }

    public static int clamp(int var, int min, int max) {

        if (var <= min) {
            var = min;
        }

        if (var >= max) {
            var = max;
        }

        return var;

    }


    public static void main(String args[]) {
        new Game();
    }



}

窗口类:

package topDownGame;

import java.awt.Canvas;
import java.awt.Dimension;

import javax.swing.JFrame;

public class Window extends Canvas{

    /**
     * 
     */
    private static final long serialVersionUID = -8646632868321067448L;

    public Window(int width, int height, String title, Game game) {

        JFrame jframe = new JFrame(title);

        jframe.setMaximumSize(new Dimension(width, height));
        jframe.setMinimumSize(new Dimension(width, height));
        jframe.setPreferredSize(new Dimension(width, height));

        jframe.setVisible(true);
        jframe.setDefaultCloseOperation(jframe.EXIT_ON_CLOSE);
        jframe.setResizable(false);
        jframe.setLocationRelativeTo(null);
        jframe.add(game);
        game.start();

    }

}

GameObject类:

package topDownGame;

import java.awt.Graphics;
import java.awt.Rectangle;

public abstract class GameObject {

    protected int x, y;
    protected ID id;
    protected int velX, velY;

    public GameObject(int x, int y, ID id) {
        this.x = x;
        this.y = y;
        this.id = id;
    }

    public abstract void tick();
    public abstract void render(Graphics g);
    public abstract Rectangle getBounds();

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public ID getId() {
        return id;
    }

    public void setId(ID id) {
        this.id = id;
    }

    public int getVelX() {
        return velX;
    }

    public void setVelX(int velX) {
        this.velX = velX;
    }

    public int getVelY() {
        return velY;
    }

    public void setVelY(int velY) {
        this.velY = velY;
    }



}

处理程序类:

package topDownGame;

import java.awt.Graphics;
import java.util.LinkedList;

public class Handler {

    LinkedList <GameObject> object = new LinkedList <GameObject>();

    public void tick() {
        for (int i = 0; i < object.size(); i++) {
            GameObject tempObject = object.get(i);

            tempObject.tick();
        }
    }

    public void render(Graphics g) {
        for (int i = 0; i < object.size(); i++) {
            GameObject tempObject = object.get(i);

            tempObject.render(g);
        }
    }

    public void addObject(GameObject object) {
        this.object.add(object);
    }

    public void removeObject(GameObject object) {
        this.object.remove(object);
    }

    public void addObject(int x, int y, ID basicenemy) {
        // TODO Auto-generated method stub

    }

}

KeyInput类:

package topDownGame;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

public class KeyInput extends KeyAdapter{


    private Handler handler;
    public KeyInput(Handler handler) {
        this.handler = handler;

    }


    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();

        for (int i = 0; i < handler.object.size(); i++) {
            GameObject tempObject = handler.object.get(i);

            if (tempObject.getId() == ID.Player) {
                if (key == KeyEvent.VK_W) {
                    tempObject.setVelY(-5);
                }
                if (key == KeyEvent.VK_S) {
                    tempObject.setVelY(5);
                }
                if (key == KeyEvent.VK_A) {
                    tempObject.setVelX(-5);
                }
                if (key == KeyEvent.VK_D) {
                tempObject.setVelX(5);
                }

            }
        }
        if (key == KeyEvent.VK_SPACE) {
            System.exit(1);
        }
    }

    public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();
        for (int i = 0; i < handler.object.size(); i++) {
            GameObject tempObject = handler.object.get(i);

            if (tempObject.getId() == ID.Player) {
                if (key == KeyEvent.VK_W) {
                tempObject.setVelY(0);
                }
                if (key == KeyEvent.VK_S) {
                    tempObject.setVelY(0);
                }
                if (key == KeyEvent.VK_A) {
                    tempObject.setVelX(0);
                }
                if (key == KeyEvent.VK_D) {
                    tempObject.setVelX(0);
                }
            }
        }
    }

}

ID枚举:

package topDownGame;

public enum ID {

    Player();

}

1 个答案:

答案 0 :(得分:0)

好吧,所以我遍历了该示例,您遇到的“基本”问题一无所获,正在调用Player的{​​{1}}方法-事实是,什么都不应该。 / p>

目的是,应将“输入”与实体分离,并且实体应根据游戏引擎的当前状态更新其状态。

所以,我要做的第一件事是“概括”可能发生的(游戏模型可以响应的)“输入操作”

keyPressed/Released

就是这样。这些是游戏支持和实体可以使用的输入。它们与可能发生的“方式”脱钩,只是提供了一种手段。

现在,为了支持该想法,我们实际上需要某种方式告诉实体它们应该“更新”,这应该在呈现它们之前完成,但是由于我们试图去耦这些操作(所以对象可以进行更新,然后再进行渲染),我们需要提供一种执行此操作的新方法...

public enum InputAction {
    UP, DOWN, LEFT, RIGHT, ROTATE;
}

(nb:从技术上讲,该方法可以是public abstract class GameObject { //... public void update() { } //... } ,因为几乎所有实体都需要进行某种方式的更改,但为简单起见,我将其设为空实现)

接下来,我们需要使实体对这些输入动作做出响应的某种方式以及对它们进行管理的某种方式,在您的情况下,abstract可能是最佳选择,因为它提供了实体与实体之间的链接。系统的其他方面(例如渲染和输入控制)

Handler

好的,现在,“输入机制”可以根据状态的实现告诉public class Handler { //... private Set<InputAction> inputActions = new HashSet<InputAction>(); public void render(Graphics g) { for (int i = 0; i < object.size(); i++) { GameObject tempObject = object.get(i); tempObject.update(); tempObject.render(g); } } public boolean is(InputAction action) { return inputActions.contains(action); } public void set(InputAction action) { inputActions.add(action); } public void remove(InputAction action) { inputActions.remove(action); } //... } 状态改变了。

Handler

(是的,它们可能是public class KeyInput extends KeyAdapter { private Handler handler; public KeyInput(Handler handler) { this.handler = handler; } public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_W) { handler.set(InputAction.UP); } if (key == KeyEvent.VK_S) { handler.set(InputAction.DOWN); } if (key == KeyEvent.VK_A) { handler.set(InputAction.LEFT); } if (key == KeyEvent.VK_D) { handler.set(InputAction.RIGHT); } if (key == KeyEvent.VK_E) { handler.set(InputAction.ROTATE); } } public void keyReleased(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_W) { handler.remove(InputAction.UP); } if (key == KeyEvent.VK_S) { handler.remove(InputAction.DOWN); } if (key == KeyEvent.VK_A) { handler.remove(InputAction.LEFT); } if (key == KeyEvent.VK_D) { handler.remove(InputAction.RIGHT); } if (key == KeyEvent.VK_E) { handler.remove(InputAction.ROTATE); } } } 语句,但是为了简洁起见,我只是修改了现有代码)

最后,我们需要更新if-else if对象,以便它可以根据游戏引擎的当前“状态”来“更新”其状态...

Player

我想仔细研究public class Player extends GameObject { private Handler handler; public float rotation = 0; public Player(int x, int y, ID id, Handler handler) {//, HUD hud) { super(x, y, id); this.handler = handler; } @Override public void update() { if (handler.is(InputAction.UP)) { setVelY(-5); } else if (handler.is(InputAction.DOWN)) { setVelY(5); } else { setVelY(0); } if (handler.is(InputAction.LEFT)) { setVelX(-5); } else if (handler.is(InputAction.RIGHT)) { setVelX(5); } else { setVelX(0); } if (handler.is(InputAction.ROTATE)) { rotation += 0.1; } } public void tick() { x += velX; y += velY; x = Game.clamp(x, 0, Game.WIDTH - 38); y = Game.clamp(y, 0, Game.HEIGHT - 67); collision(); } public void render(Graphics g) { //g.setColor(Color.WHITE); //g.fillRect(x, y, 32, 32); Graphics2D g2d = (Graphics2D) g.create(); Rectangle r = new Rectangle(0, 0, 32, 32); Path2D.Double path = new Path2D.Double(); path.append(r, false); AffineTransform t = new AffineTransform(); t.translate(x, y); t.rotate(rotation, 16, 16); path.transform(t); g2d.setColor(Color.WHITE); g2d.draw(path); g2d.dispose(); } public void collision() { for (int i = 0; i < handler.object.size(); i++) { GameObject tempObject = handler.object.get(i); // if (tempObject.getId() == ID.BasicEnemy) { // if (getBounds().intersects(tempObject.getBounds())) { // hud.HEALTH -= 2; // } // } } } public Rectangle getBounds() { return new Rectangle(x, y, 32, 32); } } 方法,因为它有点复杂...

render

好的:

  1. public void render(Graphics g) { // 1... Graphics2D g2d = (Graphics2D) g.create(); // 2... Rectangle r = new Rectangle(0, 0, 32, 32); Path2D.Double path = new Path2D.Double(); path.append(r, false); AffineTransform t = new AffineTransform(); // 3... t.translate(x, y); // 4... t.rotate(rotation, 16, 16); path.transform(t); g2d.setColor(Color.WHITE); g2d.draw(path); // 5... g2d.dispose(); } 是一个分片概念,这意味着需要绘制的每个实体都将具有相同的Graphics上下文,包括以前的实体对其进行的所有更改。这种“可能”是理想的,但总的来说,您希望减少可能出现的“副作用”。因此,我们首先为其创建一个新副本。
  2. 我们创建了Graphics。奇怪的是,这(现在)是在此处进行操作的一个不好的地方,因为它的状态实际上从未改变。 Rectangle始终在位置Rectangle处创建,并且大小为0x0 ...但是,请等待,我希望它移动并执行操作!我知道,您会在...中看到“如何”。
  3. 我们将32x32上下文的原点转换为玩家的位置...现在使Graphics的位置与玩家的位置相同。这是一种巧妙的作弊手段,并且如上所述,这意味着您不再需要每次调用0x0时都创建一个Rectangle,这将进一步提高性能
  4. 我们围绕对象的中心点旋转render上下文(对象Graphics使中心点32x32-记住,原点是16x16。 ..您知道为什么这么小的变化如此重要和有用)
  5. 我们0x0的副本。这只会释放此副本所拥有的所有资源,我们执行的操作仍会应用到原始副本,但不会影响此后可能发生的任何操作(因此,原点和旋转与{ {1}}首先被调用。)

观察...

在遍历代码的过程中,很明显,代码的组织方式不够好。令我非常恼火的一件事是,dispose会创建render的一个实例以显示自身-这实际上是一个副作用,Game不应这样做(无关紧要)。

因此,我采用了您的Window方法并将其争用为...

Game

所以,有一些小的变化...

  • main的实例几乎立即添加到框架中(这对我所做的另一项更改很重要)
  • 框架被“包装”在组件周围
  • 框架的位置已设置好(因此它出现在屏幕的中间),这是在打包窗口之后完成的,因为在我们打包之前不会设置框架的大小。
  • 使框架可见-这将使窗口从“跳转”到屏幕中心停止

我还添加了...

public static void main(String args) {
    EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
            JFrame jframe = new JFrame("Game");

            Game game = new Game();
            jframe.setDefaultCloseOperation(jframe.EXIT_ON_CLOSE);
            jframe.add(game);
            jframe.pack();
            jframe.setLocationRelativeTo(null);
            jframe.setVisible(true);
            game.start();
        }
    });
}

game,这为添加@Override public Dimension getPreferredSize() { return new Dimension(WIDTH, HEIGHT); } 的容器提供了尺寸提示。这也意味着当GameGame设置时,窗口将比内容区域稍大一些,因为框架的边界围绕在其周围。

我还建议您看看JavaDocs for BufferStrategy,因为它有一个“应如何使用”的示例。

为此,我相应地修改了JFrame方法...

pack

我所做的一项重大更改是render-现在它将填充组件的“实际”尺寸,而不仅仅是“所需”尺寸...我是最讨厌(热情)的人之一)不可调整大小的窗口;)

如果您希望看到稍微复杂一些的解决方案,可以使用look at this example,它提出了一个更“通用”和“分离”的概念