【问题标题】:Coding a game of Pool in Java - how can I define the balls and move them (via Graphics g)?用 Java 编写桌球游戏 - 我如何定义球并移动它们(通过 Graphics g)?
【发布时间】:2013-08-08 15:49:32
【问题描述】:

我的计划是用 Java 设计一个简单的桌球游戏。

OOP 在这里对球最有意义。所有的球都具有相同的功能,因此最好创建一个 Ball 类来处理球的相对位置和其他变量,例如当它进入一个洞时,它会自行移除并增加你的分数.所以当它碰到一个洞 Ball.dispose();会很合适。

我的问题是我不知道如何操作和处理球。另外为了移动它,我依赖 Thread.sleep 而不是 java.swing.timer 因为没有可用的 Action Performed 方法我可以依赖。

我怎样才能更轻松地移动球并在需要时摆脱它?

覆盖球的绿色东西是我通过在它上面画一个绿色椭圆来擦除球的最后位置的方法。

import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;


public class Main extends JFrame{

     Ball redball = new Ball (285, 600, 20, 20, Color.RED); 

    //variables to control redball
    private int rX = redball.getX();
    private int rY = redball.getY();
    private final int rWidth = redball.getWidth();
    private final int rHeight = redball.getHeight();

    int Force = 30; 
    int Bearing = 20; // True Bearing

 public Main (){

         setSize(600, 800);
         setVisible(true); 
         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         setTitle("Pool");

    }
     public void paint(Graphics g){
         super.paint(g);

         // draw the table
         g.setColor (Color.GREEN);
         g.fillRect(100, 100, 400, 600);
         g.setColor (Color.BLACK);
         g.drawRect(99, 99, 401, 601);


             //draw start ball
         g.setColor(redball.getColor());
         g.fillOval(rX, rY, rWidth, rHeight);



         if (Force == 30){
         for (int i = Force; i > 0;i--){
             try {
                    Thread.sleep(100);
                } catch(InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
             Force--;
             if (rY > 98 + rWidth) {
                 rY = rY - i;
                 rX = rX + (Bearing/5);
             }

             g.fillOval(rX, rY, rWidth, rHeight);
             g.setColor(Color.GREEN);
             repaint ();
             g.fillOval(rX - (Bearing/5), rY + i, rWidth, rHeight); // repaint last ball
             g.setColor(Color.RED);
             repaint ();
          }
         }



         // Ball.dispose (redball);

     }

    public static void main(String[] args) {

        new Main();
    }

这是球类

public class Ball {

    private int x;
    private int y;
    private int width;
    private int height;
    private Color color;

    public Ball (int x, int y, int width, int height, Color color)
    {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.color = color;
    }

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

    public int getX (){
        return this.x;
    }

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

    public int getY (){
        return this.y;
    }

    public void setWidth (int width){
        this.width = width;
    }

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

    public void setHeight (int height){
        this.height = height;
    }

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

    public void setColor (Color color){
        this.color = color;
    }

    public Color getColor (){
        return this.color;
    }

    public static void dispose(Ball ball) {

    ball = null; // if I call this method nothing happens

    }


}

【问题讨论】:

  • 另见Q&A; answers 之一引用了 Java 实现。

标签: java swing oop jframe graphics2d


【解决方案1】:

我要做的是维护所有活动球的List<Ball> 字段。然后每次迭代,遍历该列表并更新和绘制球。当一个球消失时,只需将其从列表中删除即可。

一个考虑因素是:谁来决定何时处理球?如果 Ball 之外的某些事情发生,您有以下几种选择:

  1. 如上所述,您可以简单地从列表中删除球。
  2. 您可以在其他地方的 Ball 中设置一个“dead”标志,然后在每次迭代中从列表中删除所有设置了“dead”标志的 Balls。

如果球本身决定何时离开,您有几个选择,除其他外,还有:

  1. 也许是一个方法boolean Ball.think(),它更新球的状态,如果球仍然是好的,则返回真,如果球死了,则返回假。然后,当您遍历您的球列表时,您对所有球调用 think() 进行更新,并从列表中删除返回 false 的球。
  2. 与上面的选项 2 相关,如果一个 Ball 有 think 方法或类似的东西,则该球可以设置自己的“死”标志(或球之外的东西可以设置它),然后主循环可以删除死列表中的球。

如果你想保留 Ball 实例而不是绘制它,你也有几个选择:

  1. 只需跳过标记为“死”的球的处理,而不是将它们移除。
  2. 将“死”球移至不同的死球列表。

就个人而言,我喜欢boolean think() 方法,因为它允许您轻松地为场景中的对象指定基本接口。在这种情况下,您也可以让对象自己绘制。例如:

interface Entity {
    public boolean think ();
    public void paint (Graphics g);
}

class Ball implements Entity {
    @Override public boolean think () {
        // return true if alive, false if dead
    }
    @Override public void paint (Graphics g) {
        // draw this ball
    }
}

// then in your main update loop:
List<Entity> entities = ...;
Iterator<Entity> eit = entities.iterator();
while (eit.hasNext())
    if (!eit.next().think()) 
        eit.remove();

// and in paint:
for (Entity e:entities)
    e.paint(graphics);

如果你想选择我上面提到的只是跳过“死”球而不是移除它们的选项,这样的事情会更合适(为了简洁而省略 Ball),如果球是,isActive() 返回 true如果它暂时“死亡”,则为 active 或 false:

interface Entity {
    public boolean isActive ();
    public void think (); // think returns nothing
    public void paint (Graphics g);
}

// then in your main update loop:
List<Entity> entities = ...;
for (Entity e:entities)
    if (e.isActive())
        e.think();
// it is the responsibility of something outside the ball to restore it to an
// active state, since think() isn't called if !isActive(). alternatively, you 
// could always call think(), and just don't paint inactive balls.

// and in paint:
for (Entity e:entities)
    if (e.isActive())
        e.paint(graphics);

不过,您没有必须这样做,上面列出的所有选项都有很多论据,等等。例如,在您的应用程序中,如果您知道您只处理球,则不需要Entity 接口;如果您的所有物理逻辑都发生在其他地方,think() 可能不是最方便的方法(当然可以编写代码将逻辑放在 Ball 中)。

正如您所见,给猫剥皮的方法有很多种,但我希望这里能有所帮助。

【讨论】:

  • 已编辑以添加其他选项的示例,包括保留未使用的 Ball 引用的选项。我现在完成了所有这些编辑。
  • 经过深思熟虑的描述性答案。
  • 正如我在对吉尔伯特的回答的评论中所暗示的那样,您只想从 JFrame 的 paintComponent 中进行绘制(正如您几乎已经在做的那样 - 您应该使用 paintComponent,而不是 @987654332 @)。绘制的一般方法是:擦除整个背景 -> 在当前位置重新绘制所有球。这将产生预期的效果。作为一种优化(但不要过早优化),您可以在脏区域和旧球位置下重绘背景(或将旧球位置添加到您维护的“脏”区域)。不过,首先,只需擦除整个背景并重新绘制即可。
  • 在您的原始代码中,您在循环中的每个球下方绘制 BG,您是在 移动球之后执行此操作,但您想要你移动球之前这样做,否则你会留下痕迹。
  • 同时回复您在public static void dispose (Ball ball) { ball = null; } 中的评论——不,这当然不会做任何事情,您所做的只是将本地参数变量ball 设置为null。它在该方法之外没有任何影响。
【解决方案2】:

这里有一些建议。

  • 创建一个包含游戏所有球的 Rack 类。

  • 将球的抽签代码移动到您的 Ball 类中。我意识到这个建议不是纯粹的 MVC,但是当对象自己绘制时,游戏更容易编写代码。

  • 不要尝试重新绘制屏幕的一部分。重绘整个屏幕要容易得多,而且速度非常快。

  • 不要释放任何 Ball 实例。将其移动到您的绘图代码可以识别但不绘图的某个负位置。

当您编写动作游戏时,您的主循环应该如下所示(伪代码)。

while (running) {
    calculateRackPosition();
    drawRack();
    Thread.sleep(100L);
}

您逐渐移动球,然后重新绘制屏幕。这就是您编写任何动画的方式。此代码将在球移动时运行。

你可以为这个人瞄准他的射击时编写其他代码。

【讨论】:

  • 一个重要的警告:如果你直接从另一个线程绘画,你将不得不使用SwingUtilities.invokeLater.invokeAndWait在UI线程上执行绘画——尽管你真的没有想要在paintComponent() 之外进行绘制。有无数个选项可以正确处理这个问题,评论太多了;例如将更新逻辑移动到 GUI 线程(简单但不理想),在每个更新帧之后发布重绘请求,并从 Balls 中获取 GUI 线程查询信息,仅存储重绘所需数据的线程安全“快照”,等等。
  • @Jason C:虽然绘图代码位于模型类中,但它是从绘图 JPanel 的 paintComponent 方法执行的。因此,它(希望)已经在 EDT 上。
  • 希望如此。您的伪代码while (running) 循环与drawRack() 不一定允许这样做,假设drawRack() 直接绘制,就好像循环在EDT 上,那么当然,EDT 被循环阻塞(这就是为什么我假设循环将在另一个线程上)。这不是批评,只是观察。 :) javax.swing.Timer 可用于在 EDT 上安排重复性任务,也可以使用常规 java.util.Timer(不在 EDT 上)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-09
  • 2015-08-23
  • 2013-06-27
  • 2019-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多