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

Posted

技术标签:

【中文标题】如何通过按键旋转Java中的2D矩形?【英文标题】:How do I rotate a 2D rectangle in Java by pressing keys? 【发布时间】:2019-01-18 14:22:51 【问题描述】:

我目前正在尝试制作我的第一个简单的 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();

    


游戏对象类:

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


【问题讨论】:

That's one example, that's another - 是的,它们是旋转图像,但“如何”完成的基本方法是您需要的 Rectangle based example、something a little more complex, but using the same concepts 和 an example which focuses on rotating around the centre of the specified object 好的,经过大量挖掘和返工 - 没有什么可以调用您的 Player 对象的 keyPressed 方法,并且由于 GameObject 本身没有旋转的概念,所以它正在运行不可能发生 如需尽快获得更好的帮助,请发帖minimal reproducible example 或Short, Self Contained, Correct Example。 【参考方案1】:

好的,所以我已经研究了这个例子,你遇到的“基本”问题是调用 PlayerkeyPressed/Released 方法 - 事实上,什么都不应该。

目的是,“输入”应该与实体分离,实体应该根据游戏引擎的当前状态更新它们的状态。

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

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

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

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

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 方法,因为它有点复杂...

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 是一个分片概念,这意味着需要绘制的每个实体都将获得相同的 Graphics 上下文,包括先前实体对其所做的任何和所有更改。这种“可能”是可取的,但总的来说,您希望减少可能发生的“副作用”的数量。因此,我们首先创建它的新副本。 我们创建Rectangle。奇怪的是,这(现在)是一个不好的地方,因为它的状态实际上永远不会改变。 Rectangle 总是创建在位置0x0 并且大小为32x32 ...但是等等,我希望它移动并做一些事情!我知道,你会在...中看到“如何”。 我们将Graphics 上下文的原点转换为玩家的位置...现在0x0 的位置与玩家的位置??相同。这是一个巧妙的作弊手段,正如我上面所说,您不再需要在每次调用 render 时创建一个 Rectangle,这将进一步提高性能 我们围绕对象的中心点旋转Graphics 上下文(对象为32x32 使中心点16x16 - 记住,原点是0x0 ...你明白为什么那么小吗?改变是如此重要和有用?) 我们dispose的副本。这只是释放了这个副本持有的所有资源,我们采取的行动仍然应用回原来的,但不影响之后可能发生的任何操作(所以原点和旋转与@时相同) 987654352@ 第一次被调用)。

观察...

在编写代码时,很明显代码组织得不好。真正让我恼火的一件事是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);

Game,这为添加Game 的容器提供大小提示。这也意味着当JFramepacked 时,窗口将比内容区域略大,因为框架的边框被环绕在它周围。

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

为此,我相应地修改了你的render方法...

public void render() 
    BufferStrategy bs = this.getBufferStrategy();
    if (bs == null) 
        this.createBufferStrategy(3);
        return;
    

    do 
        do 
            Graphics g = bs.getDrawGraphics();
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, getWidth(), getHeight());
            handler.render(g);
            g.dispose();
         while (bs.contentsRestored());
        bs.show();
     while (bs.contentsLost());

我所做的一个重大改变是g.fillRect(0, 0, getWidth(), getHeight()); - 现在它将填充组件的“实际”大小,而不仅仅是“所需”大小......我是那些讨厌(热情地)非- 可调整大小的窗口;)

如果您有兴趣看到稍微复杂一点的解决方案,您可以使用look at this example,它提出了一个更“通用”和“解耦”的概念

【讨论】:

哦,哇,这非常有用。您不仅为我面临的主要问题提供了详细的解决方案,而且还添加了改进代码的新方法。为此,我永远感激不尽。我现在可以旋转矩形,将来我一定会听从您的建议。非常感谢:)

以上是关于如何通过按键旋转Java中的2D矩形?的主要内容,如果未能解决你的问题,请参考以下文章

如何在Win2D中旋转矩形

VB.Net 如何通过鼠标单击和拖动来旋转矩形

如何通过拖动来旋转 qml 矩形?

OpenGL 2D 圆 - 旋转 AABB 碰撞

如何通过一次LibGDX Box2d一次按键来赋予玩家速度

Qt 画的矩形怎么旋转