亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何通過按鍵旋轉 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 關注
  • 441 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號