根据设计分辨率生成当前屏幕分辨率的正确坐标

如何解决根据设计分辨率生成当前屏幕分辨率的正确坐标

我有一个我无法完全理解的问题,因此我正在努力解决它。

基本上我正忙于为 Java Swing 编写一个小型游戏引擎,这个引擎的关键组件之一是能够将设计分辨率与屏幕分辨率分开。这意味着如果我以 400 (w) x 300 (h) 的分辨率设计游戏,并将一个对象放置在设计分辨率的中心,那么用户可以指定他们想要玩游戏的实际分辨率,例如800 (w) x 600 (h) 并且对象仍会以当前分辨率正确放置在屏幕中央。

这是我遇到问题的地方,当设计分辨率和当前分辨率相同时,即设计分辨率 400 x 300 和当前分辨率为 400 x 300,对象似乎正确放置在屏幕中央无论玩家移动时的位置如何,启动和子弹正确地位于玩家的中心:

enter image description here

但是,当设计分辨率和当前屏幕分辨率不同时,即设计分辨率为 400 x 300 且当前分辨率为 800 x 600 时,对象不再正确放置在屏幕中央,玩家的子弹也不再居中:

enter image description here

我有一种方法可以为所有可见对象(红色参考点、精灵/玩家和子弹)生成中心重生点 这个方法是一种帮助生成中心的简单方便的方法容器或另一个 Sprite 内的 Sprite 的基于坐标:

public static Point2D getCenterSpawnPoint(int parentWidth,int parentHeight,int childWidth,int childHeight,double childXOffset,double childYOffset) {
    double spawnX = ((parentWidth - childWidth) / 2) + childXOffset;
    double spawnY = ((parentHeight - childHeight) / 2) + childYOffset;
    return new Point2D.Double((int) spawnX,(int) spawnY);
}

使用屏幕坐标渲染 Sprite 和子弹:

    public int getScreenX() {
        //return (int) (imageScaler.getWidthScaleFactor() * this.getX());
        return (int) ((double) this.getX() / DESIGN_SCREEN_SIZE.width * CURRENT_SCREEN_SIZE.width);
    }

    public int getScreenY() {
        //return (int) (imageScaler.getHeightScaleFactor() * this.getY());
        return (int) ((double) this.getY() / DESIGN_SCREEN_SIZE.height * CURRENT_SCREEN_SIZE.height);
    }

我不确定我哪里出错了,但基本上我想在我的第一个 GIF 中看到相同的行为,无论游戏当前的屏幕大小如何,红色参考点似乎位置正确,而且是只需吸引到 JPanel 并绕过 getScreen... 调用:

// lets draw a centered dot based on the panels dimensions for a reference
int dotSize = 10;
g2d.setColor(Color.red);
Point2D centeredReferencePoint = getCenterSpawnPoint(getWidth(),getHeight(),dotSize,0);
g2d.fillOval((int) centeredReferencePoint.getX(),(int) centeredReferencePoint.getY(),dotSize);

这是 minaml 可重现的示例:

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.*;

public class ResolutionIndependentLocationIssue {

    /**
     * uncommenting this and commenting the line below will result in the bullet
     * spawning correctly at the center of the sprite/player
     */
    private static final Dimension CURRENT_SCREEN_SIZE = new Dimension(800,600);
    //private static final Dimension CURRENT_SCREEN_SIZE = new Dimension(400,300);
    private static final Dimension DESIGN_SCREEN_SIZE = new Dimension(400,300);
    
    private Scene scene;
    private Sprite player;

    public ResolutionIndependentLocationIssue() {
        try {
            createAndShowUI();
        } catch (IOException ex) {
            Logger.getLogger(ResolutionIndependentLocationIssue.class.getName()).log(Level.SEVERE,null,ex);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(ResolutionIndependentLocationIssue::new);
    }

    private void createAndShowUI() throws MalformedURLException,IOException {
        JFrame frame = new JFrame("Resolution Issue");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        BufferedImage bulletImage = resize(ImageIO.read(new URL("https://i.stack.imgur.com/JlSEL.png")),20,20);
        BufferedImage playerImage = resize(ImageIO.read(new URL("https://icons.iconarchive.com/icons/icons8/windows-8/512/Programming-Java-Duke-Logo-icon.png")),100,100);
        player = new Sprite(playerImage);
        player.setBulletImage(bulletImage);

        System.out.println();

        // center player according to our design resolution
        Point2D spawnPoint = getCenterSpawnPoint(DESIGN_SCREEN_SIZE.width,DESIGN_SCREEN_SIZE.height,playerImage.getWidth(),playerImage.getHeight(),0);
        player.setPosition((int) spawnPoint.getX(),(int) spawnPoint.getY());

        System.out.println("ResolutionScalingIssue#createAndShowUI() - Player spawn point (always expressed in design resolution co-ordinates): X: " + spawnPoint.getX() + " Y: " + spawnPoint.getY());
        System.out.println("ResolutionScalingIssue#createAndShowUI() - Player Design Resolution X: " + player.getX() + " Y: " + player.getY());
        System.out.println("ResolutionScalingIssue#createAndShowUI() - Player Screen X: " + player.getScreenX() + " Screen Y: " + player.getScreenY());
        System.out.println("ResolutionScalingIssue#createAndShowUI() - Player Width: " + playerImage.getWidth() + " Height: " + playerImage.getHeight());
        System.out.println();

        this.scene = new Scene();
        this.scene.add(player);

        this.addKeyBindings();

        frame.add(this.scene);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        Thread gameLoop = new Thread(() -> {
            while (true) {
                this.scene.update();
                this.scene.repaint();

                try {
                    Thread.sleep(15);
                } catch (InterruptedException ex) {
                }
            }
        });
        gameLoop.start();
    }

    private void addKeyBindings() {
        this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A,false),"A pressed");
        this.scene.getActionMap().put("A pressed",new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.LEFT = true;
            }
        });
        this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A,true),"A released");
        this.scene.getActionMap().put("A released",new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.LEFT = false;
            }
        });
        this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D,"D pressed");
        this.scene.getActionMap().put("D pressed",new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.RIGHT = true;
            }
        });
        this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D,"D released");
        this.scene.getActionMap().put("D released",new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.RIGHT = false;
            }
        });
        this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W,"W pressed");
        this.scene.getActionMap().put("W pressed",new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.UP = true;
            }
        });
        this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W,"W released");
        this.scene.getActionMap().put("W released",new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.UP = false;
            }
        });
        this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S,"S pressed");
        this.scene.getActionMap().put("S pressed",new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.DOWN = true;
            }
        });
        this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S,"S released");
        this.scene.getActionMap().put("S released",new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.DOWN = false;
            }
        });
        this.scene.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,"Space pressed");
        this.scene.getActionMap().put("Space pressed",new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                player.shoot();
            }
        });
    }

    public static BufferedImage resize(BufferedImage image,int width,int height) {
        BufferedImage bi = new BufferedImage(width,height,BufferedImage.TRANSLUCENT);
        Graphics2D g2d = (Graphics2D) bi.createGraphics();
        g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY));
        g2d.drawImage(image,width,null);
        g2d.dispose();
        return bi;
    }

    /**
     * Used to calculate the center based spawning point,to ensure calculations
     * are the same for the player spawning on the screen and bullet spawning
     * from the player
     *
     * @return
     */
    public static Point2D getCenterSpawnPoint(int parentWidth,double childYOffset) {
        double spawnX = ((parentWidth - childWidth) / 2) + childXOffset;
        double spawnY = ((parentHeight - childHeight) / 2) + childYOffset;
        return new Point2D.Double((int) spawnX,(int) spawnY);
    }

    public class Scene extends JPanel {

        private final ArrayList<Sprite> sprites;

        public Scene() {
            this.sprites = new ArrayList<>();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g;

            sprites.forEach((sprite) -> {
                sprite.render(g2d);
            });

            // lets draw a centered dot based on the panels dimensions for a reference
            int dotSize = 10;
            g2d.setColor(Color.red);
            Point2D centeredReferencePoint = getCenterSpawnPoint(getWidth(),0);
            g2d.fillOval((int) centeredReferencePoint.getX(),dotSize);
        }

        @Override
        public Dimension getPreferredSize() {
            return CURRENT_SCREEN_SIZE;
        }

        @Override
        public boolean getIgnoreRepaint() {
            return true;
        }

        public void add(Sprite sprite) {
            sprite.setScence(this);
            this.sprites.add(sprite);
        }

        private void update() {
            sprites.forEach((sprite) -> {
                sprite.update();
            });
        }
    }

    public class Sprite {

        protected int x;
        protected int y;
        protected int speed = 5;
        protected final BufferedImage image;

        public boolean UP,DOWN,LEFT,RIGHT;
        private boolean isFlippedX = false;
        private Scene scene;
        private BufferedImage bulletImage;

        public Sprite(BufferedImage image) {
            this.image = image;
        }

        public void render(Graphics2D g2d) {
            // sprite is drawn based on the position of the current screen relative to our design screen size
            g2d.setColor(Color.red);
            g2d.drawRect(this.getScreenX(),this.getScreenY(),this.getWidth(),this.getHeight());

            if (this.isFlippedX) {
                // flip horizontally
                g2d.drawImage(this.image,this.getScreenX() + this.image.getWidth(),-this.getWidth(),this.getHeight(),null);
            } else {
                g2d.drawImage(this.image,this.getScreenX(),null);
            }
        }

        public void update() {
            if (LEFT) {
                setFlippedX(true);
                this.x -= this.speed;
            }
            if (RIGHT) {
                setFlippedX(false);
                this.x += this.speed;
            }
            if (UP) {
                this.y -= this.speed;
            }
            if (DOWN) {
                this.y += this.speed;
            }
        }

        public void setFlippedX(boolean isFlippedX) {
            this.isFlippedX = isFlippedX;
        }

        /**
         *
         * @return The current screen x co-ordindate of the sprite relative to
         * the design resolution
         */
        public int getScreenX() {
            //return (int) (imageScaler.getWidthScaleFactor() * this.getX());
            return (int) ((double) this.getX() / DESIGN_SCREEN_SIZE.width * CURRENT_SCREEN_SIZE.width);
        }

        /**
         *
         * @return The current screen y co-ordindate of the sprite relative to
         * the design resolution
         */
        public int getScreenY() {
            //return (int) (imageScaler.getHeightScaleFactor() * this.getY());
            return (int) ((double) this.getY() / DESIGN_SCREEN_SIZE.height * CURRENT_SCREEN_SIZE.height);
        }

        /**
         *
         * @return The design resolution x co-ordindate
         */
        public int getX() {
            return this.x;
        }

        /**
         *
         * @return The design resolution y co-ordindate
         */
        public int getY() {
            return this.y;
        }

        public int getWidth() {
            return this.image.getWidth();
        }

        public int getHeight() {
            return this.image.getHeight();
        }

        public void setPosition(int x,int y) {
            this.x = x;
            this.y = y;
        }

        public void setBulletImage(BufferedImage bulletImage) {
            this.bulletImage = bulletImage;
        }

        public void shoot() {
            System.out.println("Sprite#shoot() - Player Design Resolution X: " + this.getX() + " Y: " + this.getY());
            System.out.println("Sprite#shoot() - Player Width: " + this.getWidth() + " Height: " + this.getHeight());

            /**
             * center the bullet according to the players design x and y
             * co-ordinates,this is necessary as x and y should the design
             * co-ordinates and render method will call getScreenX and
             * getScreenY to calculate the current screen resolution
             * co-ordinates
             *
             */
            Point2D spawnPoint = getCenterSpawnPoint(this.getWidth(),bulletImage.getWidth(),bulletImage.getHeight(),this.getX(),this.getY());
            Bullet bullet = new Bullet((int) spawnPoint.getX(),(int) spawnPoint.getY(),this.bulletImage);

            System.out.println("Sprite#shoot() - Bullet spawn point (always expressed in design resolution co-ordinates): X: " + spawnPoint.getX() + " Y: " + spawnPoint.getY());
            System.out.println("Sprite#shoot() - Bullet spawn: X: " + bullet.getX() + " Y: " + bullet.getY());
            System.out.println("Sprite#shoot() - Bullet spawn: Screen X: " + bullet.getScreenX() + " Screen Y: " + bullet.getScreenY());
            System.out.println();

            //bullet.LEFT = this.isFlippedX;
            //bullet.RIGHT = !this.isFlippedX;
            this.scene.add(bullet);
        }

        public void setScence(Scene scene) {
            this.scene = scene;
        }

    }

    public class Bullet extends Sprite {

        public Bullet(int x,int y,BufferedImage image) {
            super(image);
            this.x = x;
            this.y = y;
            this.speed = 10;
        }
    }

}

任何帮助将不胜感激!

更新:

当使用@akuzminykh 的解决方案时,一切似乎都很好,但是,现在当我将玩家位置设置为 player.setPosition(0,0) 之类的东西时,期望它出现在左上角,我得到了这个:

enter image description here

这是有道理的,因为我假设我们现在通过位于精灵中心的坐标进行定位,但是我将如何修复他的 setPosition 左上角和中心都可以工作,我想我可能需要修复 getCenterSpawnPoint

解决方法

在您的方法 getScreenXgetScreenY 中,您忽略了 getXgetY 包括精灵的宽度和高度。例如。 getX 不会为您提供精灵在 x 轴上的中心位置,而是减去精灵宽度一半的位置。当您像在 getScreenX 中那样缩放时,您还可以为精灵缩放 x 中的偏移量。要解决这个问题,只需先添加偏移量,然后进行缩放,最后减去偏移量。

/**
 *
 * @return The current screen x co-ordindate of the sprite relative to
 * the design resolution
 */
public int getScreenX() {
    //return (int) (imageScaler.getWidthScaleFactor() * this.getX());
    //return (int) ((double) this.getX() / DESIGN_SCREEN_SIZE.width * CURRENT_SCREEN_SIZE.width);
    double halfWidth = this.getWidth() / 2.0;
    double xCenterDesign = this.getX() + halfWidth;
    double xCenterCurrent = xCenterDesign / DESIGN_SCREEN_SIZE.width * CURRENT_SCREEN_SIZE.width;
    return (int) (xCenterCurrent - halfWidth);
}

/**
 *
 * @return The current screen y co-ordindate of the sprite relative to
 * the design resolution
 */
public int getScreenY() {
    //return (int) (imageScaler.getHeightScaleFactor() * this.getY());
    //return (int) ((double) this.getY() / DESIGN_SCREEN_SIZE.height * CURRENT_SCREEN_SIZE.height);
    double halfHeight = this.getHeight() / 2.0;
    double yCenterDesign = this.getY() + halfHeight;
    double yCenterCurrent = yCenterDesign / DESIGN_SCREEN_SIZE.height * CURRENT_SCREEN_SIZE.height;
    return (int) (yCenterCurrent - halfHeight);
}

或者更数学:

如果我们以“设计”分辨率为 400x300 为例,“当前”分辨率为 800x600,精灵为 100x100 大:精灵的位置是 (150,100),这是有道理的:(400 / 2 - 100 / 2、300 / 2 - 100 / 2)。现在,您用来将其置于“当前”分辨率的公式(仅适用于 x,因为我很懒):150 / 400 * 800 = 300。嗯,但是 800 的一半是 400,位置应该是 400 - 100 / 2?确切地说,精灵的偏移量 100 / 2 也被缩放了,从 50 到 100,结果是 .. 400 - 100 = 300。

因此,最初添加偏移量,以便缩放中心。然后是:(150 + 50) / 400 * 800 = 400。别忘了最后减去偏移量:400 - 50 = 350。现在你在 x 轴上有了正确的位置。

回复:更新:

当您想将精灵放置在左上角时,您可能希望 player.setPosition(0,0) 能够做到这一点。不是这种情况。按照你写的方式,getXgetY 给出的坐标包括精灵的宽度和高度,记得吗?像 getScreenXgetScreenY 这样的方法,在我的修复中,考虑到这一点,并用于在正确的位置渲染精灵。这意味着坐标 (0,0) 描述了中心在 (0 + 50,0 + 50) 的位置,其中 50 只是 100 / 2,即精灵的宽度和高度除以 2。

要将精灵放置在左上角,使用setPosition方法设置位置时需要考虑精灵的宽度和高度:在我们的示例中,精灵为100x100大,您需要通过(0 - 100 / 2,0 - 100 / 2),所以调用看起来像这样:player.setPosition(-50,-50)。您当然可以通过使用 playerImage.getWidth() 等等来使其动态化。


建议:

我建议您让xySprite 相对于相应精灵的中心。这将对代码进行一些必要的更改,但它也会简化其他事情并使它们更直观。例如。 player.setPosition(0,0) 的问题不存在,它实际上会将精灵放在左上角,这正是您直觉所期望的。这也将简化 getScreenXgetScreenY。仅在 render 方法中考虑由精灵的宽度和高度引起的偏移。这应该足够了。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res