为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

喵喔喔 2021-07-04 12:47:41
我目前正在尝试制作我的第一个简单的 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);    }
查看完整描述

1 回答

?
天涯尽头无女友

TA贡献1831条经验 获得超9个赞

好的,所以我已经深入研究了这个例子,你遇到的“基本”问题是没有调用Player'skeyPressed/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位置与玩家的位置相同 ??。这是一个巧妙的作弊手段,正如我上面所说的,您不再需要在Rectangle每次render调用时都创建一个,这将进一步提高性能

我们围绕Graphics对象的中心点旋转上下文(对象32x32成为中心点16x16- 记住,原点是0x0......你明白为什么这个小变化如此重要和有用吗?)

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

}

to Game,这为Game添加到的容器提供了大小调整提示。这也意味着,当JFrame被pack编,窗口会稍大则内容区域,作为帧的边框缠着。


我还建议您查看JavaDocs,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());- 现在它将填充组件的“实际”大小,而不仅仅是“所需”大小......我是那些讨厌(热情地)不可调整大小的窗口的人之一;)


如果你有兴趣看到一个稍微复杂的解决方案,你可以看看这个例子,它提供了一个更“通用”和“解耦”的概念


查看完整回答
反对 回复 2021-07-14
  • 1 回答
  • 0 关注
  • 424 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号