如何为我的 java 游戏添加流畅的动作?
How to add smooth movement to my java game?
我的目标:在像乒乓球这样的游戏中,关键控件应该能平稳地移动球棒。
期望:希望蝙蝠能顺畅的上下移动。
实际结果:蝙蝠移动起来像滞后或速度变化。
我试过使用带有 lerp 的速度变量,但它们滞后了。
我试过增加和减少 y 值,但它滞后得更厉害。
我搜索过,但找不到解决方案。
我叫长方形球棒和圆形乒乓球。
另外,这是我的 第一天,post 在这里。
Screenshot from the game
这里是 panal 代码:
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.JPanel;
import javax.swing.Timer;
public class gamePanel extends JPanel implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 6350159535293799269L;
static final int SCREEN_WIDTH = 840;
static final int SCREEN_HEIGHT = 610;
static final int PONG_SPAWN_FRAME = 50;
static final int padding = 30;
static final int pongSize = 30;
static final int batHeight = SCREEN_HEIGHT / 4;
static final int batWidth = SCREEN_WIDTH / 32;
static final int batSpeed = 4;
static final int pongSpeed = 3;
static final int scoreTextSize = 45;
boolean running = false;
int redX = padding;
int redY = SCREEN_HEIGHT / 2;
int redVelocity = 0;
int blueX = SCREEN_WIDTH - padding;
int blueY = SCREEN_HEIGHT / 2;
int blueVelocity = 0;
int redPoints = 0;
int bluePoints = 0;
int pongX = SCREEN_WIDTH / 4 - pongSize / 2;
int pongY = 0;
int pongVelocityX = pongSpeed;
int pongVelocityY = pongSpeed;
final int DELAY = 6;
Timer timer;
Random random;
gamePanel() {
random = new Random();
pongY = random.nextInt((int) (SCREEN_HEIGHT - PONG_SPAWN_FRAME) + PONG_SPAWN_FRAME);
if (pongVelocityY == 0) {
if (pongY > SCREEN_HEIGHT / 2 + pongSize) {
pongVelocityY = -pongSpeed;
} else {
pongVelocityY = pongSpeed;
}
}
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.DARK_GRAY);
this.setFocusable(true);
this.addKeyListener(new myKeyAdapter());
startGame();
}
void startGame() {
running = true;
timer = new Timer(DELAY, this);
timer.start();
}
void restart() {
redX = padding;
redY = SCREEN_HEIGHT / 2;
redVelocity = 0;
blueX = SCREEN_WIDTH - padding;
blueY = SCREEN_HEIGHT / 2;
blueVelocity = 0;
pongX = SCREEN_WIDTH / 4 - pongSize / 2;
pongY = 0;
pongVelocityX = pongSpeed;
pongVelocityY = pongSpeed;
spawnPong();
}
void spawnPong() {
pongY = random.nextInt((int) (SCREEN_HEIGHT - PONG_SPAWN_FRAME * 2)) + PONG_SPAWN_FRAME;
if (pongVelocityY == 0) {
if (pongY > SCREEN_HEIGHT / 2 + pongSize) {
pongVelocityY = -pongSpeed;
} else {
pongVelocityY = pongSpeed;
}
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
void draw(Graphics g) {
g.setColor(Color.LIGHT_GRAY);
g.fillOval(pongX, pongY, pongSize, pongSize);
g.setColor(Color.red);
g.fillRect(redX - batWidth / 2, redY - batHeight / 2, batWidth, batHeight);
g.setColor(Color.blue);
g.fillRect(blueX - batWidth / 2, blueY - batHeight / 2, batWidth, batHeight);
g.setColor(Color.white);
g.setFont(new Font("Roman New Times", Font.PLAIN, scoreTextSize));
g.drawString(String.valueOf(redPoints), SCREEN_WIDTH / 4, scoreTextSize);
g.drawString(String.valueOf(bluePoints), SCREEN_WIDTH / 4 * 3, scoreTextSize);
}
void pongMove() {
pongX += pongVelocityX;
pongY += pongVelocityY;
}
void batMove() {
if (redY > batHeight / 2 && redVelocity < 0) {
redY += redVelocity * batSpeed;
}
if (redY < SCREEN_HEIGHT - batHeight / 2 && redVelocity > 0) {
redY += redVelocity * batSpeed;
}
blueY = pongY;
/*
* if (blueY>batHeight/2 && blueVelocity < 0) { blueY += blueVelocity *
* pongSpeed; } if (blueY<SCREEN_HEIGHT-batHeight/2 && blueVelocity > 0) { blueY
* += blueVelocity * pongSpeed; }
*/
}
void batLerp() {
redVelocity *= 0.5f;
blueVelocity *= 0.5f;
}
void checkCollision() {
if ((pongX > blueX - pongSize - batWidth / 2 && pongX < blueX - pongSize + batWidth / 2)
&& (pongY > blueY - batHeight / 2 && pongY < blueY + batHeight / 2)) {
pongVelocityX *= -1;
}
if (pongX < redX + batWidth / 2 && (pongY > redY - batHeight / 2 && pongY < redY + batHeight / 2)) {
pongVelocityX *= -1;
}
if (pongY < 0) {
pongVelocityY *= -1;
}
if (pongY > SCREEN_HEIGHT - pongSize) {
pongVelocityY *= -1;
}
if (pongX < -pongSize) {
bluePoints += 1;
restart();
}
if (pongX > SCREEN_WIDTH) {
redPoints += 1;
restart();
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (running) {
checkCollision();
batMove();
batLerp();
pongMove();
}
repaint();
}
public class myKeyAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_W:
redVelocity = -1;
break;
case KeyEvent.VK_S:
redVelocity = 1;
break;
case KeyEvent.VK_UP:
blueVelocity = -1;
break;
case KeyEvent.VK_DOWN:
blueVelocity = 1;
break;
}
}
}
}
原答案
我修改了桨的移动,只有按住键时桨才会移动。为了测试,我不得不放慢游戏速度。
我在遵循您的代码时遇到了问题。您的 JPanel
class 工作量太大,太难理解了。我将从普通 Java getter / setter class 创建一个 Ball
class 和一个 Paddle
class es 来保持球和桨的区域。
这是完整的可运行代码。
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ClassicPongGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new ClassicPongGUI());
}
@Override
public void run() {
JFrame frame = new JFrame("Pong");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new GamePanel());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class GamePanel extends JPanel implements ActionListener {
private static final long serialVersionUID = 6350159535293799269L;
static final int SCREEN_WIDTH = 840;
static final int SCREEN_HEIGHT = 610;
static final int PONG_SPAWN_FRAME = 50;
static final int padding = 30;
static final int pongSize = 30;
static final int batHeight = SCREEN_HEIGHT / 4;
static final int batWidth = SCREEN_WIDTH / 32;
static final int batSpeed = 20;
static final int pongSpeed = 1;
static final int scoreTextSize = 45;
boolean running = false;
int redX = padding;
int redY = SCREEN_HEIGHT / 2;
int redVelocity = 0;
int blueX = SCREEN_WIDTH - padding;
int blueY = SCREEN_HEIGHT / 2;
int blueVelocity = 0;
int redPoints = 0;
int bluePoints = 0;
int pongX = SCREEN_WIDTH / 4 - pongSize / 2;
int pongY = 0;
int pongVelocityX = pongSpeed;
int pongVelocityY = pongSpeed;
final int DELAY = 6;
Timer timer;
Random random;
public GamePanel() {
random = new Random();
pongY = random.nextInt((int) (SCREEN_HEIGHT - PONG_SPAWN_FRAME) + PONG_SPAWN_FRAME);
if (pongVelocityY == 0) {
if (pongY > SCREEN_HEIGHT / 2 + pongSize) {
pongVelocityY = -pongSpeed;
} else {
pongVelocityY = pongSpeed;
}
}
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.DARK_GRAY);
this.setFocusable(true);
this.addKeyListener(new myKeyAdapter());
startGame();
}
void startGame() {
running = true;
timer = new Timer(DELAY, this);
timer.start();
redX = padding;
redY = SCREEN_HEIGHT / 2;
redVelocity = 0;
}
void restart() {
blueX = SCREEN_WIDTH - padding;
blueY = SCREEN_HEIGHT / 2;
blueVelocity = 0;
pongX = SCREEN_WIDTH / 4 - pongSize / 2;
pongY = 0;
pongVelocityX = pongSpeed;
pongVelocityY = pongSpeed;
spawnPong();
}
void spawnPong() {
pongY = random.nextInt((int) (SCREEN_HEIGHT - PONG_SPAWN_FRAME * 2)) + PONG_SPAWN_FRAME;
if (pongVelocityY == 0) {
if (pongY > SCREEN_HEIGHT / 2 + pongSize) {
pongVelocityY = -pongSpeed;
} else {
pongVelocityY = pongSpeed;
}
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
void draw(Graphics g) {
g.setColor(Color.LIGHT_GRAY);
g.fillOval(pongX, pongY, pongSize, pongSize);
g.setColor(Color.red);
g.fillRect(redX - batWidth / 2, redY - batHeight / 2, batWidth, batHeight);
g.setColor(Color.blue);
g.fillRect(blueX - batWidth / 2, blueY - batHeight / 2, batWidth, batHeight);
g.setColor(Color.white);
g.setFont(new Font("Times New Roman", Font.PLAIN, scoreTextSize));
g.drawString(String.valueOf(redPoints), SCREEN_WIDTH / 4, scoreTextSize);
g.drawString(String.valueOf(bluePoints), SCREEN_WIDTH / 4 * 3, scoreTextSize);
}
void pongMove() {
pongX += pongVelocityX;
pongY += pongVelocityY;
}
void batMove() {
if (redY > batHeight / 2 && redVelocity < 0) {
redY += redVelocity * batSpeed;
}
if (redY < SCREEN_HEIGHT - batHeight / 2 && redVelocity > 0) {
redY += redVelocity * batSpeed;
}
blueY = pongY;
/*
* if (blueY>batHeight/2 && blueVelocity < 0) { blueY += blueVelocity *
* pongSpeed; } if (blueY<SCREEN_HEIGHT-batHeight/2 && blueVelocity > 0) { blueY
* += blueVelocity * pongSpeed; }
*/
}
void batLerp() {
redVelocity = 0;
blueVelocity = 0;
}
void checkCollision() {
if ((pongX > blueX - pongSize - batWidth / 2
&& pongX < blueX - pongSize + batWidth / 2)
&& (pongY > blueY - batHeight / 2
&& pongY < blueY + batHeight / 2)) {
pongVelocityX *= -1;
}
if (pongX < redX + batWidth / 2 && (pongY > redY - batHeight / 2
&& pongY < redY + batHeight / 2)) {
pongVelocityX *= -1;
}
if (pongY < 0) {
pongVelocityY *= -1;
}
if (pongY > SCREEN_HEIGHT - pongSize) {
pongVelocityY *= -1;
}
if (pongX < -pongSize) {
bluePoints += 1;
restart();
}
if (pongX > SCREEN_WIDTH) {
redPoints += 1;
restart();
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (running) {
checkCollision();
batMove();
batLerp();
pongMove();
}
repaint();
}
public class myKeyAdapter extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_W:
redVelocity = -1;
break;
case KeyEvent.VK_S:
redVelocity = 1;
break;
case KeyEvent.VK_UP:
redVelocity = -1;
break;
case KeyEvent.VK_DOWN:
redVelocity = 1;
break;
}
}
}
}
}
修改后的答案
我认为这将是一个有趣的项目,所以我重新编写了代码。 GUI 的显示方式如下。
W 和向上箭头键向上移动蓝色桨。 S 和向下箭头键向下移动蓝色桨。必须按住这些键才能继续运动。释放键停止桨运动,
space 条重新开始游戏。
当我创建 Swing GUI 时,我使用 model / view / controller (MVC) 模式。这种模式使我能够将我的关注点分开,并一次专注于应用程序的一小部分。
对于 Swing GUI,MVC 模式定义为:
- 视图从模型中读取信息。
- 视图不更新模型。
- 控制器更新模型并重新绘制/重新验证视图。
一个 Swing GUI 可以有多个控制器 classes。我为这个 Pong 游戏创建了三个控制器 classes。
我创建了 8 个不同的 classes。三个 class 组成应用程序模型,两个 class 定义视图,三个控制器 class 管理应用程序。
型号
我创建了一个 Ball
class 来保存创建球和在屏幕上移动球所需的字段。我用 Point
来保持圆心,用 int
来保持球的半径。
Ball
class 不是典型的 getter / setter class。 moveBall
方法使用极坐标来计算球的新位置。极坐标转换为直角坐标在绘图JPanel上画球
Paddle
class 创建一个球拍。我将桨的形状存储在 Rectangle
中。这是比较典型的Javagetter/setterclass,虽然有代码限制桨在Y轴上能走多远
GameModel
class是应用模型。这个 class 包含一个 Ball
实例和两个 Paddle
实例,以及两个分数 ints
和游戏运行的帧速率。此 class 的实例通过视图和控制器 classes.
传递
查看
我创建了一个 JFrame
和一个绘图 JPanel
。绘图JPanel
画出了球的当前位置、两个球拍和比分。时期。没有别的。
控制器 classes 负责移动球和球拍并重新绘制 JPanel
。
我使用 KeyBindings
将键盘键设置为各种操作。有了KeyBindings
,我再也不用担心绘图JPanel
是否有焦点了。
控制器
TimerListener
class 动画 Pong 游戏。
MovePaddleAction
class 根据按下的键向上或向下移动桨。必须按住该键才能继续运动。
RestartAction
class 重新开始游戏。
代码
这是完整的可运行代码。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ClassicPongGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new ClassicPongGUI());
}
private final Dimension panelSize;
private GameModel gameModel;
private GamePanel gamePanel;
private Timer timer;
public ClassicPongGUI() {
this.panelSize = new Dimension(840, 610);
this.gameModel = new GameModel(panelSize);
}
@Override
public void run() {
JFrame frame = new JFrame("Pong");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.gamePanel = new GamePanel(this, gameModel);
frame.add(gamePanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
setKeyBindings();
startTimer();
}
private void setKeyBindings() {
String moveUp = "moveUp";
String moveDown = "moveDown";
String restart = "restart";
InputMap inputMap = gamePanel.getInputMap(
JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = gamePanel.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_W, 0), moveUp);
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_UP, 0), moveUp);
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_S, 0), moveDown);
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_DOWN, 0), moveDown);
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_SPACE, 0), restart);
int increment = 15;
actionMap.put(moveUp, new MovePaddleAction(
this, gameModel, -increment));
actionMap.put(moveDown, new MovePaddleAction(
this, gameModel, increment));
actionMap.put(restart, new RestartAction(
this, gameModel));
}
public void startTimer() {
int delay = 1000 / gameModel.getFrameRate();
timer = new Timer(delay, new TimerListener(this, gameModel));
timer.setInitialDelay(5000);
timer.start();
}
public void stopTimer() {
if (timer != null) {
timer.stop();
}
}
public Dimension getPanelSize() {
return panelSize;
}
public void repaint() {
gamePanel.repaint();
}
public class GamePanel extends JPanel {
private static final long serialVersionUID = 1L;
private final GameModel model;
public GamePanel(ClassicPongGUI frame, GameModel model) {
this.model = model;
this.setBackground(Color.DARK_GRAY);
this.setPreferredSize(frame.getPanelSize());
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawBall(g);
drawPaddles(g);
drawScores(g);
}
private void drawBall(Graphics g) {
Ball ball = model.getBall();
g.setColor(ball.getColor());
int radius = ball.getRadius();
int diameter = radius + radius;
Point point = ball.getCenterPoint();
g.fillOval(point.x - radius, point.y - radius,
diameter, diameter);
}
private void drawPaddles(Graphics g) {
Paddle[] paddles = model.getPaddles();
for (Paddle paddle : paddles) {
g.setColor(paddle.getColor());
Rectangle r = paddle.getPaddle();
g.fillRect(r.x, r.y, r.width, r.height);
}
}
private void drawScores(Graphics g) {
int[] scores = model.getScore();
int scoreTextSize = 45;
g.setColor(Color.WHITE);
g.setFont(new Font("Times New Roman",
Font.PLAIN, scoreTextSize));
g.drawString(String.valueOf(scores[0]),
panelSize.width / 4, scoreTextSize);
g.drawString(String.valueOf(scores[1]),
panelSize.width * 3 / 4, scoreTextSize);
}
}
public class TimerListener implements ActionListener {
private final ClassicPongGUI frame;
private final GameModel model;
public TimerListener(ClassicPongGUI frame, GameModel model) {
this.frame = frame;
this.model = model;
}
@Override
public void actionPerformed(ActionEvent event) {
int frameRate = model.getFrameRate();
model.getBall().moveBall(frameRate);
Point point = model.getBall().getCenterPoint();
Paddle[] paddles = model.getPaddles();
for (int i = 0; i < paddles.length; i++) {
Rectangle p = paddles[i].getPaddle();
int y = point.y - p.height / 2;
if (i == 0) {
paddles[i].setPaddle(y);
}
model.contactsPaddle();
}
if (point.x < 0) {
model.incrementScore(1, 1);
model.resetBall();
}
if (point.x > frame.getPanelSize().width) {
model.incrementScore(0, 1);
model.resetBall();
}
frame.repaint();
}
}
public class MovePaddleAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final int increment;
private final ClassicPongGUI frame;
private final GameModel model;
public MovePaddleAction(ClassicPongGUI frame, GameModel model,
int increment) {
this.frame = frame;
this.model = model;
this.increment = increment;
}
@Override
public void actionPerformed(ActionEvent event) {
model.getPaddles()[1].movePaddle(increment);
frame.repaint();
}
}
public class RestartAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final ClassicPongGUI frame;
private final GameModel model;
public RestartAction(ClassicPongGUI frame, GameModel model) {
this.frame = frame;
this.model = model;
}
@Override
public void actionPerformed(ActionEvent event) {
frame.stopTimer();
model.restartGame();
frame.startTimer();
frame.repaint();
}
}
public class GameModel {
private int[] score;
/** frames per second **/
private final int frameRate;
private Ball ball;
private final Dimension panelSize;
private Paddle[] paddles;
public GameModel(Dimension panelSize) {
this.panelSize = panelSize;
this.score = new int[2];
this.paddles = new Paddle[2];
this.frameRate = 50;
int ballRadius = 15;
Point centerPoint = new Point(panelSize.width / 2,
panelSize.height / 2);
this.ball = new Ball(centerPoint, ballRadius,
panelSize, Color.LIGHT_GRAY);
int batWidth = panelSize.width / 32;
int batHeight = panelSize.height / 4;
int padding = 30;
int x = padding;
int y = calculateCenterY(batHeight);
Rectangle r = new Rectangle(x, y, batWidth, batHeight);
this.paddles[0] = new Paddle(r, panelSize.height, Color.RED);
x = panelSize.width - padding - batWidth;
r = new Rectangle(x, y, batWidth, batHeight);
this.paddles[1] = new Paddle(r, panelSize.height, Color.BLUE);
resetScore();
}
public void restartGame() {
int batHeight = panelSize.height / 4;
resetBall();
paddles[0].setPaddle(calculateCenterY(batHeight));
paddles[1].setPaddle(calculateCenterY(batHeight));
resetScore();
}
public void resetBall() {
ball.setCenterPoint(new Point(panelSize.width / 2,
panelSize.height / 2));
}
private int calculateCenterY(int batHeight) {
return (panelSize.height - batHeight) / 2;
}
public void contactsPaddle() {
int radius = ball.getRadius();
int diameter = radius + radius;
Point point = ball.getCenterPoint();
Rectangle b = new Rectangle(point.x - radius,
point.y - radius, diameter, diameter);
for (Paddle paddle : paddles) {
if (paddle.getPaddle().intersects(b)) {
ball.reverseXDirection(frameRate);
}
}
}
public void resetScore() {
score[0] = 0;
score[1] = 0;
}
public void incrementScore(int index, int increment) {
score[index] += increment;
}
public int[] getScore() {
return score;
}
public Ball getBall() {
return ball;
}
public Paddle[] getPaddles() {
return paddles;
}
public int getFrameRate() {
return frameRate;
}
}
public class Paddle {
private final int upperBound;
private final Color color;
private Rectangle paddle;
public Paddle(Rectangle paddle, int upperBound, Color color) {
this.paddle = paddle;
this.upperBound = upperBound;
this.color = color;
}
public void movePaddle(int increment) {
paddle.y += increment;
limitMovement();
}
public void setPaddle(int y) {
paddle.y = y;
limitMovement();
}
private void limitMovement() {
paddle.y = Math.max(paddle.y, 0);
int height = paddle.y + paddle.height;
height = Math.min(height, upperBound);
paddle.y = height - paddle.height;
}
public Rectangle getPaddle() {
return paddle;
}
public Color getColor() {
return color;
}
}
public class Ball {
/** Pixels per second **/
private final double velocity;
private final int radius;
private int angle;
private final Color color;
private final Dimension panelSize;
private Point centerPoint;
private Point2D.Double doubleCenterPoint;
private Random random;
public Ball(Point centerPoint, int radius, Dimension panelSize,
Color color) {
this.centerPoint = centerPoint;
setDoubleCenterPoint(centerPoint);
this.radius = radius;
this.panelSize = panelSize;
this.color = color;
this.velocity = 300.0;
this.random = new Random();
setRandomAngle();
}
public void reverseXDirection(int frameRate) {
angle = bounceXAngle(angle);
angle += (angle < 0) ? 360 : 0;
moveBall(frameRate);
}
public void moveBall(int frameRate) {
double theta = Math.toRadians(angle);
double distance = velocity / frameRate;
doubleCenterPoint.x = Math.cos(theta) * distance +
doubleCenterPoint.x;
doubleCenterPoint.y = Math.sin(theta) * distance +
doubleCenterPoint.y;
if (doubleCenterPoint.y < radius) {
double adjustment = radius - doubleCenterPoint.y;
doubleCenterPoint.y += adjustment;
angle = bounceYAngle(angle);
}
if ((doubleCenterPoint.y + radius) > panelSize.height) {
double adjustment = panelSize.height -
doubleCenterPoint.y - radius;
doubleCenterPoint.y += adjustment;
angle = bounceYAngle(angle);
}
int x = (int) Math.round(doubleCenterPoint.x);
int y = (int) Math.round(doubleCenterPoint.y);
this.centerPoint = new Point(x, y);
}
private int bounceXAngle(int angle) {
return 180 - angle;
}
private int bounceYAngle(int angle) {
return 360 - angle;
}
public Point getCenterPoint() {
return centerPoint;
}
public void setCenterPoint(Point centerPoint) {
this.centerPoint = centerPoint;
setRandomAngle();
setDoubleCenterPoint(centerPoint);
}
private void setDoubleCenterPoint(Point centerPoint) {
this.doubleCenterPoint =
new Point2D.Double(centerPoint.x, centerPoint.y);
}
private void setRandomAngle() {
int[] angles = {30, 150, 210, 330 };
this.angle = angles[random.nextInt(angles.length)];
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
}
}
我的目标:在像乒乓球这样的游戏中,关键控件应该能平稳地移动球棒。
期望:希望蝙蝠能顺畅的上下移动。 实际结果:蝙蝠移动起来像滞后或速度变化。
我试过使用带有 lerp 的速度变量,但它们滞后了。 我试过增加和减少 y 值,但它滞后得更厉害。 我搜索过,但找不到解决方案。
我叫长方形球棒和圆形乒乓球。 另外,这是我的 第一天,post 在这里。
Screenshot from the game
这里是 panal 代码:
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.JPanel;
import javax.swing.Timer;
public class gamePanel extends JPanel implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 6350159535293799269L;
static final int SCREEN_WIDTH = 840;
static final int SCREEN_HEIGHT = 610;
static final int PONG_SPAWN_FRAME = 50;
static final int padding = 30;
static final int pongSize = 30;
static final int batHeight = SCREEN_HEIGHT / 4;
static final int batWidth = SCREEN_WIDTH / 32;
static final int batSpeed = 4;
static final int pongSpeed = 3;
static final int scoreTextSize = 45;
boolean running = false;
int redX = padding;
int redY = SCREEN_HEIGHT / 2;
int redVelocity = 0;
int blueX = SCREEN_WIDTH - padding;
int blueY = SCREEN_HEIGHT / 2;
int blueVelocity = 0;
int redPoints = 0;
int bluePoints = 0;
int pongX = SCREEN_WIDTH / 4 - pongSize / 2;
int pongY = 0;
int pongVelocityX = pongSpeed;
int pongVelocityY = pongSpeed;
final int DELAY = 6;
Timer timer;
Random random;
gamePanel() {
random = new Random();
pongY = random.nextInt((int) (SCREEN_HEIGHT - PONG_SPAWN_FRAME) + PONG_SPAWN_FRAME);
if (pongVelocityY == 0) {
if (pongY > SCREEN_HEIGHT / 2 + pongSize) {
pongVelocityY = -pongSpeed;
} else {
pongVelocityY = pongSpeed;
}
}
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.DARK_GRAY);
this.setFocusable(true);
this.addKeyListener(new myKeyAdapter());
startGame();
}
void startGame() {
running = true;
timer = new Timer(DELAY, this);
timer.start();
}
void restart() {
redX = padding;
redY = SCREEN_HEIGHT / 2;
redVelocity = 0;
blueX = SCREEN_WIDTH - padding;
blueY = SCREEN_HEIGHT / 2;
blueVelocity = 0;
pongX = SCREEN_WIDTH / 4 - pongSize / 2;
pongY = 0;
pongVelocityX = pongSpeed;
pongVelocityY = pongSpeed;
spawnPong();
}
void spawnPong() {
pongY = random.nextInt((int) (SCREEN_HEIGHT - PONG_SPAWN_FRAME * 2)) + PONG_SPAWN_FRAME;
if (pongVelocityY == 0) {
if (pongY > SCREEN_HEIGHT / 2 + pongSize) {
pongVelocityY = -pongSpeed;
} else {
pongVelocityY = pongSpeed;
}
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
void draw(Graphics g) {
g.setColor(Color.LIGHT_GRAY);
g.fillOval(pongX, pongY, pongSize, pongSize);
g.setColor(Color.red);
g.fillRect(redX - batWidth / 2, redY - batHeight / 2, batWidth, batHeight);
g.setColor(Color.blue);
g.fillRect(blueX - batWidth / 2, blueY - batHeight / 2, batWidth, batHeight);
g.setColor(Color.white);
g.setFont(new Font("Roman New Times", Font.PLAIN, scoreTextSize));
g.drawString(String.valueOf(redPoints), SCREEN_WIDTH / 4, scoreTextSize);
g.drawString(String.valueOf(bluePoints), SCREEN_WIDTH / 4 * 3, scoreTextSize);
}
void pongMove() {
pongX += pongVelocityX;
pongY += pongVelocityY;
}
void batMove() {
if (redY > batHeight / 2 && redVelocity < 0) {
redY += redVelocity * batSpeed;
}
if (redY < SCREEN_HEIGHT - batHeight / 2 && redVelocity > 0) {
redY += redVelocity * batSpeed;
}
blueY = pongY;
/*
* if (blueY>batHeight/2 && blueVelocity < 0) { blueY += blueVelocity *
* pongSpeed; } if (blueY<SCREEN_HEIGHT-batHeight/2 && blueVelocity > 0) { blueY
* += blueVelocity * pongSpeed; }
*/
}
void batLerp() {
redVelocity *= 0.5f;
blueVelocity *= 0.5f;
}
void checkCollision() {
if ((pongX > blueX - pongSize - batWidth / 2 && pongX < blueX - pongSize + batWidth / 2)
&& (pongY > blueY - batHeight / 2 && pongY < blueY + batHeight / 2)) {
pongVelocityX *= -1;
}
if (pongX < redX + batWidth / 2 && (pongY > redY - batHeight / 2 && pongY < redY + batHeight / 2)) {
pongVelocityX *= -1;
}
if (pongY < 0) {
pongVelocityY *= -1;
}
if (pongY > SCREEN_HEIGHT - pongSize) {
pongVelocityY *= -1;
}
if (pongX < -pongSize) {
bluePoints += 1;
restart();
}
if (pongX > SCREEN_WIDTH) {
redPoints += 1;
restart();
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (running) {
checkCollision();
batMove();
batLerp();
pongMove();
}
repaint();
}
public class myKeyAdapter extends KeyAdapter {
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_W:
redVelocity = -1;
break;
case KeyEvent.VK_S:
redVelocity = 1;
break;
case KeyEvent.VK_UP:
blueVelocity = -1;
break;
case KeyEvent.VK_DOWN:
blueVelocity = 1;
break;
}
}
}
}
原答案
我修改了桨的移动,只有按住键时桨才会移动。为了测试,我不得不放慢游戏速度。
我在遵循您的代码时遇到了问题。您的 JPanel
class 工作量太大,太难理解了。我将从普通 Java getter / setter class 创建一个 Ball
class 和一个 Paddle
class es 来保持球和桨的区域。
这是完整的可运行代码。
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ClassicPongGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new ClassicPongGUI());
}
@Override
public void run() {
JFrame frame = new JFrame("Pong");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new GamePanel());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class GamePanel extends JPanel implements ActionListener {
private static final long serialVersionUID = 6350159535293799269L;
static final int SCREEN_WIDTH = 840;
static final int SCREEN_HEIGHT = 610;
static final int PONG_SPAWN_FRAME = 50;
static final int padding = 30;
static final int pongSize = 30;
static final int batHeight = SCREEN_HEIGHT / 4;
static final int batWidth = SCREEN_WIDTH / 32;
static final int batSpeed = 20;
static final int pongSpeed = 1;
static final int scoreTextSize = 45;
boolean running = false;
int redX = padding;
int redY = SCREEN_HEIGHT / 2;
int redVelocity = 0;
int blueX = SCREEN_WIDTH - padding;
int blueY = SCREEN_HEIGHT / 2;
int blueVelocity = 0;
int redPoints = 0;
int bluePoints = 0;
int pongX = SCREEN_WIDTH / 4 - pongSize / 2;
int pongY = 0;
int pongVelocityX = pongSpeed;
int pongVelocityY = pongSpeed;
final int DELAY = 6;
Timer timer;
Random random;
public GamePanel() {
random = new Random();
pongY = random.nextInt((int) (SCREEN_HEIGHT - PONG_SPAWN_FRAME) + PONG_SPAWN_FRAME);
if (pongVelocityY == 0) {
if (pongY > SCREEN_HEIGHT / 2 + pongSize) {
pongVelocityY = -pongSpeed;
} else {
pongVelocityY = pongSpeed;
}
}
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.DARK_GRAY);
this.setFocusable(true);
this.addKeyListener(new myKeyAdapter());
startGame();
}
void startGame() {
running = true;
timer = new Timer(DELAY, this);
timer.start();
redX = padding;
redY = SCREEN_HEIGHT / 2;
redVelocity = 0;
}
void restart() {
blueX = SCREEN_WIDTH - padding;
blueY = SCREEN_HEIGHT / 2;
blueVelocity = 0;
pongX = SCREEN_WIDTH / 4 - pongSize / 2;
pongY = 0;
pongVelocityX = pongSpeed;
pongVelocityY = pongSpeed;
spawnPong();
}
void spawnPong() {
pongY = random.nextInt((int) (SCREEN_HEIGHT - PONG_SPAWN_FRAME * 2)) + PONG_SPAWN_FRAME;
if (pongVelocityY == 0) {
if (pongY > SCREEN_HEIGHT / 2 + pongSize) {
pongVelocityY = -pongSpeed;
} else {
pongVelocityY = pongSpeed;
}
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
void draw(Graphics g) {
g.setColor(Color.LIGHT_GRAY);
g.fillOval(pongX, pongY, pongSize, pongSize);
g.setColor(Color.red);
g.fillRect(redX - batWidth / 2, redY - batHeight / 2, batWidth, batHeight);
g.setColor(Color.blue);
g.fillRect(blueX - batWidth / 2, blueY - batHeight / 2, batWidth, batHeight);
g.setColor(Color.white);
g.setFont(new Font("Times New Roman", Font.PLAIN, scoreTextSize));
g.drawString(String.valueOf(redPoints), SCREEN_WIDTH / 4, scoreTextSize);
g.drawString(String.valueOf(bluePoints), SCREEN_WIDTH / 4 * 3, scoreTextSize);
}
void pongMove() {
pongX += pongVelocityX;
pongY += pongVelocityY;
}
void batMove() {
if (redY > batHeight / 2 && redVelocity < 0) {
redY += redVelocity * batSpeed;
}
if (redY < SCREEN_HEIGHT - batHeight / 2 && redVelocity > 0) {
redY += redVelocity * batSpeed;
}
blueY = pongY;
/*
* if (blueY>batHeight/2 && blueVelocity < 0) { blueY += blueVelocity *
* pongSpeed; } if (blueY<SCREEN_HEIGHT-batHeight/2 && blueVelocity > 0) { blueY
* += blueVelocity * pongSpeed; }
*/
}
void batLerp() {
redVelocity = 0;
blueVelocity = 0;
}
void checkCollision() {
if ((pongX > blueX - pongSize - batWidth / 2
&& pongX < blueX - pongSize + batWidth / 2)
&& (pongY > blueY - batHeight / 2
&& pongY < blueY + batHeight / 2)) {
pongVelocityX *= -1;
}
if (pongX < redX + batWidth / 2 && (pongY > redY - batHeight / 2
&& pongY < redY + batHeight / 2)) {
pongVelocityX *= -1;
}
if (pongY < 0) {
pongVelocityY *= -1;
}
if (pongY > SCREEN_HEIGHT - pongSize) {
pongVelocityY *= -1;
}
if (pongX < -pongSize) {
bluePoints += 1;
restart();
}
if (pongX > SCREEN_WIDTH) {
redPoints += 1;
restart();
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (running) {
checkCollision();
batMove();
batLerp();
pongMove();
}
repaint();
}
public class myKeyAdapter extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_W:
redVelocity = -1;
break;
case KeyEvent.VK_S:
redVelocity = 1;
break;
case KeyEvent.VK_UP:
redVelocity = -1;
break;
case KeyEvent.VK_DOWN:
redVelocity = 1;
break;
}
}
}
}
}
修改后的答案
我认为这将是一个有趣的项目,所以我重新编写了代码。 GUI 的显示方式如下。
W 和向上箭头键向上移动蓝色桨。 S 和向下箭头键向下移动蓝色桨。必须按住这些键才能继续运动。释放键停止桨运动,
space 条重新开始游戏。
当我创建 Swing GUI 时,我使用 model / view / controller (MVC) 模式。这种模式使我能够将我的关注点分开,并一次专注于应用程序的一小部分。
对于 Swing GUI,MVC 模式定义为:
- 视图从模型中读取信息。
- 视图不更新模型。
- 控制器更新模型并重新绘制/重新验证视图。
一个 Swing GUI 可以有多个控制器 classes。我为这个 Pong 游戏创建了三个控制器 classes。
我创建了 8 个不同的 classes。三个 class 组成应用程序模型,两个 class 定义视图,三个控制器 class 管理应用程序。
型号
我创建了一个 Ball
class 来保存创建球和在屏幕上移动球所需的字段。我用 Point
来保持圆心,用 int
来保持球的半径。
Ball
class 不是典型的 getter / setter class。 moveBall
方法使用极坐标来计算球的新位置。极坐标转换为直角坐标在绘图JPanel上画球
Paddle
class 创建一个球拍。我将桨的形状存储在 Rectangle
中。这是比较典型的Javagetter/setterclass,虽然有代码限制桨在Y轴上能走多远
GameModel
class是应用模型。这个 class 包含一个 Ball
实例和两个 Paddle
实例,以及两个分数 ints
和游戏运行的帧速率。此 class 的实例通过视图和控制器 classes.
查看
我创建了一个 JFrame
和一个绘图 JPanel
。绘图JPanel
画出了球的当前位置、两个球拍和比分。时期。没有别的。
控制器 classes 负责移动球和球拍并重新绘制 JPanel
。
我使用 KeyBindings
将键盘键设置为各种操作。有了KeyBindings
,我再也不用担心绘图JPanel
是否有焦点了。
控制器
TimerListener
class 动画 Pong 游戏。
MovePaddleAction
class 根据按下的键向上或向下移动桨。必须按住该键才能继续运动。
RestartAction
class 重新开始游戏。
代码
这是完整的可运行代码。
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ClassicPongGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new ClassicPongGUI());
}
private final Dimension panelSize;
private GameModel gameModel;
private GamePanel gamePanel;
private Timer timer;
public ClassicPongGUI() {
this.panelSize = new Dimension(840, 610);
this.gameModel = new GameModel(panelSize);
}
@Override
public void run() {
JFrame frame = new JFrame("Pong");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.gamePanel = new GamePanel(this, gameModel);
frame.add(gamePanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
setKeyBindings();
startTimer();
}
private void setKeyBindings() {
String moveUp = "moveUp";
String moveDown = "moveDown";
String restart = "restart";
InputMap inputMap = gamePanel.getInputMap(
JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = gamePanel.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_W, 0), moveUp);
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_UP, 0), moveUp);
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_S, 0), moveDown);
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_DOWN, 0), moveDown);
inputMap.put(KeyStroke.getKeyStroke(
KeyEvent.VK_SPACE, 0), restart);
int increment = 15;
actionMap.put(moveUp, new MovePaddleAction(
this, gameModel, -increment));
actionMap.put(moveDown, new MovePaddleAction(
this, gameModel, increment));
actionMap.put(restart, new RestartAction(
this, gameModel));
}
public void startTimer() {
int delay = 1000 / gameModel.getFrameRate();
timer = new Timer(delay, new TimerListener(this, gameModel));
timer.setInitialDelay(5000);
timer.start();
}
public void stopTimer() {
if (timer != null) {
timer.stop();
}
}
public Dimension getPanelSize() {
return panelSize;
}
public void repaint() {
gamePanel.repaint();
}
public class GamePanel extends JPanel {
private static final long serialVersionUID = 1L;
private final GameModel model;
public GamePanel(ClassicPongGUI frame, GameModel model) {
this.model = model;
this.setBackground(Color.DARK_GRAY);
this.setPreferredSize(frame.getPanelSize());
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawBall(g);
drawPaddles(g);
drawScores(g);
}
private void drawBall(Graphics g) {
Ball ball = model.getBall();
g.setColor(ball.getColor());
int radius = ball.getRadius();
int diameter = radius + radius;
Point point = ball.getCenterPoint();
g.fillOval(point.x - radius, point.y - radius,
diameter, diameter);
}
private void drawPaddles(Graphics g) {
Paddle[] paddles = model.getPaddles();
for (Paddle paddle : paddles) {
g.setColor(paddle.getColor());
Rectangle r = paddle.getPaddle();
g.fillRect(r.x, r.y, r.width, r.height);
}
}
private void drawScores(Graphics g) {
int[] scores = model.getScore();
int scoreTextSize = 45;
g.setColor(Color.WHITE);
g.setFont(new Font("Times New Roman",
Font.PLAIN, scoreTextSize));
g.drawString(String.valueOf(scores[0]),
panelSize.width / 4, scoreTextSize);
g.drawString(String.valueOf(scores[1]),
panelSize.width * 3 / 4, scoreTextSize);
}
}
public class TimerListener implements ActionListener {
private final ClassicPongGUI frame;
private final GameModel model;
public TimerListener(ClassicPongGUI frame, GameModel model) {
this.frame = frame;
this.model = model;
}
@Override
public void actionPerformed(ActionEvent event) {
int frameRate = model.getFrameRate();
model.getBall().moveBall(frameRate);
Point point = model.getBall().getCenterPoint();
Paddle[] paddles = model.getPaddles();
for (int i = 0; i < paddles.length; i++) {
Rectangle p = paddles[i].getPaddle();
int y = point.y - p.height / 2;
if (i == 0) {
paddles[i].setPaddle(y);
}
model.contactsPaddle();
}
if (point.x < 0) {
model.incrementScore(1, 1);
model.resetBall();
}
if (point.x > frame.getPanelSize().width) {
model.incrementScore(0, 1);
model.resetBall();
}
frame.repaint();
}
}
public class MovePaddleAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final int increment;
private final ClassicPongGUI frame;
private final GameModel model;
public MovePaddleAction(ClassicPongGUI frame, GameModel model,
int increment) {
this.frame = frame;
this.model = model;
this.increment = increment;
}
@Override
public void actionPerformed(ActionEvent event) {
model.getPaddles()[1].movePaddle(increment);
frame.repaint();
}
}
public class RestartAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final ClassicPongGUI frame;
private final GameModel model;
public RestartAction(ClassicPongGUI frame, GameModel model) {
this.frame = frame;
this.model = model;
}
@Override
public void actionPerformed(ActionEvent event) {
frame.stopTimer();
model.restartGame();
frame.startTimer();
frame.repaint();
}
}
public class GameModel {
private int[] score;
/** frames per second **/
private final int frameRate;
private Ball ball;
private final Dimension panelSize;
private Paddle[] paddles;
public GameModel(Dimension panelSize) {
this.panelSize = panelSize;
this.score = new int[2];
this.paddles = new Paddle[2];
this.frameRate = 50;
int ballRadius = 15;
Point centerPoint = new Point(panelSize.width / 2,
panelSize.height / 2);
this.ball = new Ball(centerPoint, ballRadius,
panelSize, Color.LIGHT_GRAY);
int batWidth = panelSize.width / 32;
int batHeight = panelSize.height / 4;
int padding = 30;
int x = padding;
int y = calculateCenterY(batHeight);
Rectangle r = new Rectangle(x, y, batWidth, batHeight);
this.paddles[0] = new Paddle(r, panelSize.height, Color.RED);
x = panelSize.width - padding - batWidth;
r = new Rectangle(x, y, batWidth, batHeight);
this.paddles[1] = new Paddle(r, panelSize.height, Color.BLUE);
resetScore();
}
public void restartGame() {
int batHeight = panelSize.height / 4;
resetBall();
paddles[0].setPaddle(calculateCenterY(batHeight));
paddles[1].setPaddle(calculateCenterY(batHeight));
resetScore();
}
public void resetBall() {
ball.setCenterPoint(new Point(panelSize.width / 2,
panelSize.height / 2));
}
private int calculateCenterY(int batHeight) {
return (panelSize.height - batHeight) / 2;
}
public void contactsPaddle() {
int radius = ball.getRadius();
int diameter = radius + radius;
Point point = ball.getCenterPoint();
Rectangle b = new Rectangle(point.x - radius,
point.y - radius, diameter, diameter);
for (Paddle paddle : paddles) {
if (paddle.getPaddle().intersects(b)) {
ball.reverseXDirection(frameRate);
}
}
}
public void resetScore() {
score[0] = 0;
score[1] = 0;
}
public void incrementScore(int index, int increment) {
score[index] += increment;
}
public int[] getScore() {
return score;
}
public Ball getBall() {
return ball;
}
public Paddle[] getPaddles() {
return paddles;
}
public int getFrameRate() {
return frameRate;
}
}
public class Paddle {
private final int upperBound;
private final Color color;
private Rectangle paddle;
public Paddle(Rectangle paddle, int upperBound, Color color) {
this.paddle = paddle;
this.upperBound = upperBound;
this.color = color;
}
public void movePaddle(int increment) {
paddle.y += increment;
limitMovement();
}
public void setPaddle(int y) {
paddle.y = y;
limitMovement();
}
private void limitMovement() {
paddle.y = Math.max(paddle.y, 0);
int height = paddle.y + paddle.height;
height = Math.min(height, upperBound);
paddle.y = height - paddle.height;
}
public Rectangle getPaddle() {
return paddle;
}
public Color getColor() {
return color;
}
}
public class Ball {
/** Pixels per second **/
private final double velocity;
private final int radius;
private int angle;
private final Color color;
private final Dimension panelSize;
private Point centerPoint;
private Point2D.Double doubleCenterPoint;
private Random random;
public Ball(Point centerPoint, int radius, Dimension panelSize,
Color color) {
this.centerPoint = centerPoint;
setDoubleCenterPoint(centerPoint);
this.radius = radius;
this.panelSize = panelSize;
this.color = color;
this.velocity = 300.0;
this.random = new Random();
setRandomAngle();
}
public void reverseXDirection(int frameRate) {
angle = bounceXAngle(angle);
angle += (angle < 0) ? 360 : 0;
moveBall(frameRate);
}
public void moveBall(int frameRate) {
double theta = Math.toRadians(angle);
double distance = velocity / frameRate;
doubleCenterPoint.x = Math.cos(theta) * distance +
doubleCenterPoint.x;
doubleCenterPoint.y = Math.sin(theta) * distance +
doubleCenterPoint.y;
if (doubleCenterPoint.y < radius) {
double adjustment = radius - doubleCenterPoint.y;
doubleCenterPoint.y += adjustment;
angle = bounceYAngle(angle);
}
if ((doubleCenterPoint.y + radius) > panelSize.height) {
double adjustment = panelSize.height -
doubleCenterPoint.y - radius;
doubleCenterPoint.y += adjustment;
angle = bounceYAngle(angle);
}
int x = (int) Math.round(doubleCenterPoint.x);
int y = (int) Math.round(doubleCenterPoint.y);
this.centerPoint = new Point(x, y);
}
private int bounceXAngle(int angle) {
return 180 - angle;
}
private int bounceYAngle(int angle) {
return 360 - angle;
}
public Point getCenterPoint() {
return centerPoint;
}
public void setCenterPoint(Point centerPoint) {
this.centerPoint = centerPoint;
setRandomAngle();
setDoubleCenterPoint(centerPoint);
}
private void setDoubleCenterPoint(Point centerPoint) {
this.doubleCenterPoint =
new Point2D.Double(centerPoint.x, centerPoint.y);
}
private void setRandomAngle() {
int[] angles = {30, 150, 210, 330 };
this.angle = angles[random.nextInt(angles.length)];
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
}
}