I have a project I am working on in that I have to create two objects, I would like the big rectangle(BLUE) to move around the frame every time I press the arrow keys on my keyboard while the small rectangle(RED) is moving away, once the big square touches/tags the small rectangle, screen refreshes and I can move the big rectangle again to chase down the small rectangle. Below is my main class, and IT class where I have implemented my two shapes.
The goal is to have the two rectangles, small rectangle runs away in the frame every time the big rectangle comes close until it's tagged. Would also have to add some kind of score panel on the frame to show updated scores and a timer to count down when the player starts playing.
I need help having the two rectangles move differently and not on top of each other. I would like the second rectangle to move away every time the first rectangle comes close to it moving around the Frame
`
My class IT
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
public class IT extends JPanel implements ActionListener, KeyListener {
Timer shapeTimer = new Timer(5, this);
public double xPos = 0, yPos = 0, movementX = 0, movementY = 0;
public int rectSize = 50;
public int rectSize2 = 35;
public int windowWidth;
int windowHeight;
public int xBound;
public int yBound;
public IT(int w, int h){
shapeTimer.start();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
windowWidth = w;
windowHeight = h;
xBound = (windowWidth - rectSize);
yBound = (windowHeight - rectSize);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Rectangle2D movableRect = new Rectangle2D.Double(xPos, yPos, rectSize, rectSize);
g2.setColor(Color.BLUE);
g2.draw(movableRect);
g2.fill(movableRect);
Rectangle2D movableRect2 = new Rectangle2D.Double(xPos, yPos, rectSize2, rectSize2);
g2.setColor(Color.RED);
g2.draw(movableRect2);
g2.fill(movableRect2);
}
public void actionPerformed(ActionEvent e){
repaint();
xPos += movementX;
yPos += movementY;
}
public void moveUp(){
if (yPos == 0){
movementY = 0;
movementX = 0;
}
movementY = -0.5;
movementX = 0;
}
public void moveDown(){
if (yPos == yBound){
movementY = 0;
movementX = 0;
}
movementY = 0.5;
movementX = 0;
}
public void moveLeft()
{
if (xPos == 0){
movementY = 0;
movementX = 0;
}
movementX = -0.5;
movementY = 0;
}
public void moveRight(){
if (xPos == xBound)
{
movementY = 0;
movementX = 0;
}
movementX = 0.5;
movementY = 0;
}
public void enlargeSquare(){
rectSize++;
rectSize2++;
}
public void shrinkSquare(){
rectSize--;
rectSize2--;
}
public void keyPressed(KeyEvent e){
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_UP){
moveUp();
}
if (keyCode == KeyEvent.VK_DOWN){
moveDown();
}
if (keyCode == KeyEvent.VK_RIGHT){
moveRight();
}
if (keyCode == KeyEvent.VK_LEFT){
moveLeft();
}
if (keyCode == KeyEvent.VK_OPEN_BRACKET)
{
shrinkSquare();
}
if (keyCode == KeyEvent.VK_CLOSE_BRACKET)
{
enlargeSquare();
}
}
public void keyTyped(KeyEvent e){
}
public void keyReleased(KeyEvent e){
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_UP){
movementX = 0;
movementY = 0;
}
if (keyCode == KeyEvent.VK_DOWN){
movementX = 0;
movementY = 0;
}
if (keyCode == KeyEvent.VK_RIGHT){
movementX = 0;
movementY = 0;
}
if (keyCode == KeyEvent.VK_UP){
movementX = 0;
movementY = 0;
}
}
}
`
MainTester class
`
import javax.swing.*;
import javax.swing.JFrame;
public class MainTester {
public static void main(String[] args) {
// TODO Auto-generated method stub
int frameWidth = 850;
int frameHeight = 650;
JFrame frmMain = new JFrame();
frmMain.setSize(frameWidth, frameHeight);
IT it = new IT(frameWidth, frameHeight);
frmMain.add(it);
frmMain.setVisible(true);
frmMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frmMain.setTitle("Tag Game");
}
}
`
Use key bindings, seriously, this is going to solve a swagger of issues related to KeyListener.
Decouple and seperate your logic. Each "entity" should be a self contained unit of work. In your case, they should contain information about their color, location and size at a minimum.
For simplicity, I started out with something which could just be painted...
public interface Entity {
public void paint(Graphics2D g2d);
}
Now, you could have a lot of different interfaces which reflect this which can be painted, moved, controlled, represents effects or what ever you need - then your classes should implement the interfaces they need. Then, your engine just deals with the "concepts" it wants to - need all "paintable" entities, need all "movable" entities, etc, when it needs to.
Next I created a concept of a "player". A player is a "paintable" entity, but which can be controlled by the player in some way, so it takes the current state of the "input"s and updates itself based on those states (more about that to come)
public static class PlayerEntity implements Entity {
protected static final int DELTA = 2;
private Rectangle bounds = new Rectangle(0, 0, 35, 35);
private Color fillColor;
public PlayerEntity(Color fillColor, Point location) {
this.fillColor = fillColor;
this.bounds.setLocation(location);
}
public Color getFillColor() {
return fillColor;
}
public Rectangle getBounds() {
return bounds;
}
public Point getCenter() {
return new Point((int)getBounds().getCenterX(), (int)getBounds().getCenterY());
}
public void update(Set<PlayerAction> actions, Dimension size) {
Rectangle currentBounds = getBounds();
int x = currentBounds.x;
int y = currentBounds.y;
if (actions.contains(PlayerAction.UP)) {
y -= DELTA;
}
if (actions.contains(PlayerAction.DOWN)) {
y += DELTA;
}
if (actions.contains(PlayerAction.LEFT)) {
x -= DELTA;
}
if (actions.contains(PlayerAction.RIGHT)) {
x += DELTA;
}
if (y < 0) {
y = 0;
}
if (y + currentBounds.height > size.height) {
y = size.height - currentBounds.height;
}
if (x < 0) {
x = 0;
}
if (x + currentBounds.width > size.width) {
x = size.width - currentBounds.width;
}
getBounds().setLocation(x, y);
}
#Override
public void paint(Graphics2D g2d) {
g2d.setColor(getFillColor());
g2d.fill(getBounds());
}
}
For reference, PlayerAction represents all the valid actions which can be performed by the player, for simplicity sake, I've just stuck to movement:
public enum PlayerAction {
UP, DOWN, LEFT, RIGHT;
}
Next I created a "monster" entity, in this case, the "monster" will always try and follow the player, this implementation is loosely based on Java: Move image towards mouse position
public static class MonsterEntity implements Entity {
protected static final int DELTA = 1;
private Rectangle bounds = new Rectangle(0, 0, 15, 15);
private Color fillColor;
public MonsterEntity(Color fillColor, Point location) {
this.fillColor = fillColor;
this.bounds.setLocation(location);
}
public Color getFillColor() {
return fillColor;
}
public Rectangle getBounds() {
return bounds;
}
public Point getCenter() {
return new Point((int)getBounds().getCenterX(), (int)getBounds().getCenterY());
}
public void moveTowards(Point target) {
Rectangle bounds = getBounds();
Point center = getCenter();
int xDelta = target.x < center.x ? -DELTA : DELTA;
int yDelta = target.y < center.y ? -DELTA : DELTA;
getBounds().setLocation(bounds.x + xDelta, bounds.y + yDelta);
}
#Override
public void paint(Graphics2D g2d) {
g2d.setColor(getFillColor());
g2d.fill(getBounds());
}
}
The monster is always trying to move it's center to the target location (which will eventually be the center of the player)
Now, this is where things become complicated.
We need:
A renderable surface onto which we can paint the player and monster(s)
Input bindings
A "game loop" to update the state of the entities and schedule repaints
For simplicity, I started with a JPanel, made use of Swing Timer and the key bindings API.
public class MainPane extends JPanel {
// This represents the "input bindings", these represent
// abstract actions which can be applied to the player
// or game state.
private enum InputKey {
PRESSED_UP, PRESSED_DOWN, PRESSED_LEFT, PRESSED_RIGHT,
RELEASED_UP, RELEASED_DOWN, RELEASED_LEFT, RELEASED_RIGHT;
public KeyStroke getKeyStroke() {
switch (this) {
case PRESSED_UP: return KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false);
case PRESSED_DOWN: return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false);
case PRESSED_LEFT: return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false);
case PRESSED_RIGHT: return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false);
case RELEASED_UP: return KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true);
case RELEASED_DOWN: return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true);
case RELEASED_LEFT: return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true);
case RELEASED_RIGHT: return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true);
}
return null;
}
}
private PlayerEntity playerEntity;
private MonsterEntity monsterEntity;
private Timer timer;
private Set<PlayerAction> actions = new HashSet<PlayerAction>();
public MainPane() {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(InputKey.PRESSED_UP.getKeyStroke(), InputKey.PRESSED_UP);
inputMap.put(InputKey.PRESSED_DOWN.getKeyStroke(), InputKey.PRESSED_DOWN);
inputMap.put(InputKey.PRESSED_LEFT.getKeyStroke(), InputKey.PRESSED_LEFT);
inputMap.put(InputKey.PRESSED_RIGHT.getKeyStroke(), InputKey.PRESSED_RIGHT);
inputMap.put(InputKey.RELEASED_UP.getKeyStroke(), InputKey.RELEASED_UP);
inputMap.put(InputKey.RELEASED_DOWN.getKeyStroke(), InputKey.RELEASED_DOWN);
inputMap.put(InputKey.RELEASED_LEFT.getKeyStroke(), InputKey.RELEASED_LEFT);
inputMap.put(InputKey.RELEASED_RIGHT.getKeyStroke(), InputKey.RELEASED_RIGHT);
actionMap.put(InputKey.PRESSED_UP, new MoveAction(actions, PlayerAction.UP, true));
actionMap.put(InputKey.PRESSED_DOWN, new MoveAction(actions, PlayerAction.DOWN, true));
actionMap.put(InputKey.PRESSED_LEFT, new MoveAction(actions, PlayerAction.LEFT, true));
actionMap.put(InputKey.PRESSED_RIGHT, new MoveAction(actions, PlayerAction.RIGHT, true));
actionMap.put(InputKey.RELEASED_UP, new MoveAction(actions, PlayerAction.UP, false));
actionMap.put(InputKey.RELEASED_DOWN, new MoveAction(actions, PlayerAction.DOWN, false));
actionMap.put(InputKey.RELEASED_LEFT, new MoveAction(actions, PlayerAction.LEFT, false));
actionMap.put(InputKey.RELEASED_RIGHT, new MoveAction(actions, PlayerAction.RIGHT, false));
Dimension size = getPreferredSize();
Point center = new Point((size.width - 35) / 2, (size.height - 35) / 2);
playerEntity = new PlayerEntity(Color.BLUE, center);
monsterEntity = new MonsterEntity(Color.RED, new Point(size.width - 15, size.height - 15));
}
#Override
public void addNotify() {
super.addNotify();
if (timer != null) {
timer.stop();
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
performTick();
}
});
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
if (timer != null) {
timer.stop();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void performTick() {
playerEntity.update(actions, getSize());
monsterEntity.moveTowards(playerEntity.getCenter());
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
playerEntity.paint(g2d);
monsterEntity.paint(g2d);
g2d.dispose();
}
}
Movement (or input) is controlled through the key bindings API, this triggers a MoveAction which updates a centralised state repository (which is then passed to the PlayerEntity so it apply the state accordingly).
For simplicity, I've only used a single Action, but you could make a couple, one representing "press/activate" or "release/deactivate"
public class MoveAction extends AbstractAction {
private Set<PlayerAction> actions;
private PlayerAction action;
private boolean activate;
public MoveAction(Set<PlayerAction> directions, PlayerAction direction, boolean activate) {
this.actions = directions;
this.action = direction;
this.activate = activate;
}
#Override
public void actionPerformed(ActionEvent e) {
if (activate) {
actions.add(action);
} else {
actions.remove(action);
}
}
}
See How to Use Actions for more details about actions.
But why follow this workflow?!
It decouples and decentralises a lot of the workflows. In fact, if you really wanted to, you could also seperate out the Timer and "paint" workflows to seperate classes, further decoupling the classes.
Key bindings solve all the issues related to KeyListener weirdness. It also decouples the input - want to add touch controls/buttons, no worries, it's done via Actions. Want to add joystick/controllers, no worries, it's done via Actions.
Want more monsters?
Change:
private MonsterEntity monsterEntity;
to:
private List<MonsterEntity> monsterEntitys = new ArrayList<>(32);
Change:
monsterEntity = new MonsterEntity(Color.RED, new Point(size.width - 15, size.height - 15));
to:
monsterEntitys.add(new MonsterEntity(Color.RED, new Point(size.width - 15, size.height - 15)));
monsterEntitys.add(new MonsterEntity(Color.RED, new Point(0, 0)));
monsterEntitys.add(new MonsterEntity(Color.RED, new Point(size.width - 15, 0)));
monsterEntitys.add(new MonsterEntity(Color.RED, new Point(0, size.height - 15)));
Change:
monsterEntity.paint(g2d);
to:
for (MonsterEntity entity : monsterEntitys) {
entity.paint(g2d);
}
And now you have more monsters! Have fun with that!
Runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
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.util.HashSet;
import java.util.Set;
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.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new MainPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public enum PlayerAction {
UP, DOWN, LEFT, RIGHT;
}
public class MainPane extends JPanel {
private enum InputKey {
PRESSED_UP, PRESSED_DOWN, PRESSED_LEFT, PRESSED_RIGHT,
RELEASED_UP, RELEASED_DOWN, RELEASED_LEFT, RELEASED_RIGHT;
public KeyStroke getKeyStroke() {
switch (this) {
case PRESSED_UP: return KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false);
case PRESSED_DOWN: return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false);
case PRESSED_LEFT: return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false);
case PRESSED_RIGHT: return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false);
case RELEASED_UP: return KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true);
case RELEASED_DOWN: return KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true);
case RELEASED_LEFT: return KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true);
case RELEASED_RIGHT: return KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true);
}
return null;
}
}
private PlayerEntity playerEntity;
private MonsterEntity monsterEntity;
private Timer timer;
private Set<PlayerAction> actions = new HashSet<PlayerAction>();
public MainPane() {
InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getActionMap();
inputMap.put(InputKey.PRESSED_UP.getKeyStroke(), InputKey.PRESSED_UP);
inputMap.put(InputKey.PRESSED_DOWN.getKeyStroke(), InputKey.PRESSED_DOWN);
inputMap.put(InputKey.PRESSED_LEFT.getKeyStroke(), InputKey.PRESSED_LEFT);
inputMap.put(InputKey.PRESSED_RIGHT.getKeyStroke(), InputKey.PRESSED_RIGHT);
inputMap.put(InputKey.RELEASED_UP.getKeyStroke(), InputKey.RELEASED_UP);
inputMap.put(InputKey.RELEASED_DOWN.getKeyStroke(), InputKey.RELEASED_DOWN);
inputMap.put(InputKey.RELEASED_LEFT.getKeyStroke(), InputKey.RELEASED_LEFT);
inputMap.put(InputKey.RELEASED_RIGHT.getKeyStroke(), InputKey.RELEASED_RIGHT);
actionMap.put(InputKey.PRESSED_UP, new MoveAction(actions, PlayerAction.UP, true));
actionMap.put(InputKey.PRESSED_DOWN, new MoveAction(actions, PlayerAction.DOWN, true));
actionMap.put(InputKey.PRESSED_LEFT, new MoveAction(actions, PlayerAction.LEFT, true));
actionMap.put(InputKey.PRESSED_RIGHT, new MoveAction(actions, PlayerAction.RIGHT, true));
actionMap.put(InputKey.RELEASED_UP, new MoveAction(actions, PlayerAction.UP, false));
actionMap.put(InputKey.RELEASED_DOWN, new MoveAction(actions, PlayerAction.DOWN, false));
actionMap.put(InputKey.RELEASED_LEFT, new MoveAction(actions, PlayerAction.LEFT, false));
actionMap.put(InputKey.RELEASED_RIGHT, new MoveAction(actions, PlayerAction.RIGHT, false));
Dimension size = getPreferredSize();
Point center = new Point((size.width - 35) / 2, (size.height - 35) / 2);
playerEntity = new PlayerEntity(Color.BLUE, center);
monsterEntity = new MonsterEntity(Color.RED, new Point(size.width - 15, size.height - 15));
}
#Override
public void addNotify() {
super.addNotify();
if (timer != null) {
timer.stop();
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
performTick();
}
});
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
if (timer != null) {
timer.stop();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void performTick() {
playerEntity.update(actions, getSize());
monsterEntity.moveTowards(playerEntity.getCenter());
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
playerEntity.paint(g2d);
monsterEntity.paint(g2d);
g2d.dispose();
}
}
public class MoveAction extends AbstractAction {
private Set<PlayerAction> actions;
private PlayerAction action;
private boolean activate;
public MoveAction(Set<PlayerAction> directions, PlayerAction direction, boolean activate) {
this.actions = directions;
this.action = direction;
this.activate = activate;
}
#Override
public void actionPerformed(ActionEvent e) {
if (activate) {
actions.add(action);
} else {
actions.remove(action);
}
}
}
public interface Entity {
public void paint(Graphics2D g2d);
}
public static class MonsterEntity implements Entity {
protected static final int DELTA = 1;
private Rectangle bounds = new Rectangle(0, 0, 15, 15);
private Color fillColor;
public MonsterEntity(Color fillColor, Point location) {
this.fillColor = fillColor;
this.bounds.setLocation(location);
}
public Color getFillColor() {
return fillColor;
}
public Rectangle getBounds() {
return bounds;
}
public Point getCenter() {
return new Point((int)getBounds().getCenterX(), (int)getBounds().getCenterY());
}
public void moveTowards(Point target) {
Rectangle bounds = getBounds();
Point center = getCenter();
int xDelta = target.x < center.x ? -DELTA : DELTA;
int yDelta = target.y < center.y ? -DELTA : DELTA;
getBounds().setLocation(bounds.x + xDelta, bounds.y + yDelta);
}
#Override
public void paint(Graphics2D g2d) {
g2d.setColor(getFillColor());
g2d.fill(getBounds());
}
}
public static class PlayerEntity implements Entity {
protected static final int DELTA = 2;
private Rectangle bounds = new Rectangle(0, 0, 35, 35);
private Color fillColor;
public PlayerEntity(Color fillColor, Point location) {
this.fillColor = fillColor;
this.bounds.setLocation(location);
}
public Color getFillColor() {
return fillColor;
}
public Rectangle getBounds() {
return bounds;
}
public Point getCenter() {
return new Point((int)getBounds().getCenterX(), (int)getBounds().getCenterY());
}
public void update(Set<PlayerAction> actions, Dimension size) {
Rectangle currentBounds = getBounds();
int x = currentBounds.x;
int y = currentBounds.y;
if (actions.contains(PlayerAction.UP)) {
y -= DELTA;
}
if (actions.contains(PlayerAction.DOWN)) {
y += DELTA;
}
if (actions.contains(PlayerAction.LEFT)) {
x -= DELTA;
}
if (actions.contains(PlayerAction.RIGHT)) {
x += DELTA;
}
if (y < 0) {
y = 0;
}
if (y + currentBounds.height > size.height) {
y = size.height - currentBounds.height;
}
if (x < 0) {
x = 0;
}
if (x + currentBounds.width > size.width) {
x = size.width - currentBounds.width;
}
getBounds().setLocation(x, y);
}
#Override
public void paint(Graphics2D g2d) {
g2d.setColor(getFillColor());
g2d.fill(getBounds());
}
}
}
Other considerations...
Right now the speed/delta is actually really high. I would consider making use of the Shape API, using things like Point2D and Rectangle2D which provide double based properties instead of int, which would give away to reduce the delta values and slow down the entities.
See Working with Geometry for some more details
Related
I am coding a Java pong game I am stuck on how to get the player paddle to move. I for some reason cannot get the program to notice the key inputs.
import java.awt.Component;
import javax.swing.JButton;
import javax.swing.JFrame;
public class Pong {
Board board = new Board();
public void frame() {
JFrame b = new JFrame("Pong");
b.setSize(905,705);
b.setLocation(300,60);
b.setResizable(false);
b.setVisible(true);
b.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
b.add(board);
}
public static void main(String[] args) {
Pong start = new Pong();
start.frame();
}
}
The board class
public class Board extends JPanel{
public int playerScore = 0;
public int opponentScore = 0;
Player player = new Player();
int test = 1;
private Timer timer;
private int time = 100;{
timer = new Timer(time, player);
timer.start();
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
board(g);
g.setColor(Color.WHITE);
// player.playerGenerate();
g2d.fill(player.player);
g.fillRect(30, test, 20, 20);
player.paint(g);
control();
//repaint();
}
public void board(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g.setColor(Color.black);
g.fillRect(0, 0, 900, 900);
Stroke stroke1 = new BasicStroke(4f);
g2d.setColor(Color.white);
g2d.setStroke(stroke1);
g2d.drawRect(20, 50, 850, 600);
g2d.setColor(Color.white);
float[] dashingPattern2 = {10f, 4f};
Stroke stroke2 = new BasicStroke(4f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 1.0f, dashingPattern2, 0.0f);
g2d.setStroke(stroke2);
g2d.drawLine(435, 50, 435, 650);
g.setFont(new Font("arial",Font.PLAIN,30));
g.drawString(""+playerScore, 20, 35);
g.drawString(""+opponentScore, 855, 35);
}
public void control() {
if (player.down == true) {
player.playerYpos = player.playerYpos = player.playerYpos -10;
repaint();
}
}
}
And finally the player class.
public class Player extends JPanel implements KeyListener,ActionListener{
//Board theBoard = new Board();
public boolean down = false;
public boolean up = false;
public int playerXpos = 45;
public int playerYpos = 300;
public int playerWidth = 15;
public int playerHeight = 80;
Rectangle player = new Rectangle(playerXpos,playerYpos,playerWidth,playerHeight);
// Board theBoard = new Board();
public void playerGenerate() {
playerXpos = 45;
playerYpos = 300;
playerWidth = 15;
playerHeight = 80;
//Rectangle r = new Rectangle(playerXpos,playerYpos,playerWidth,playerHeight);
}
#Override
public void actionPerformed(ActionEvent e) {
if (down) {
down = true;
if(up != true) {
down = true;
}
else
{
up = true;
down = false;
}
}
if (up) {
up = true;
if(down != true) {
up = true;
}
else
{
up = false;
down = true;
}
}
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_UP) {
for(int r = playerYpos;r >=0;r--) {
if(r==0) {
playerYpos = playerYpos -20;
}
else {
playerYpos = playerYpos -1 ;
}
if(playerYpos < 50){
playerYpos = 50;
}
}
//repaint();
}
if(e.getKeyCode() == KeyEvent.VK_DOWN) {
for(int r = playerYpos;r >=0;r--) {
if(r==0) {
playerYpos = playerYpos +20;
}
else {
playerYpos = playerYpos -1 ;
System.out.print("down");
}
if(playerYpos > 800){
playerYpos = 800;
}
}
//repaint();
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
}
I tried googling around and looked at code that went with a similar approach of implementing action/key listener in the player/paddle class. I assume there is something minor and small I am just not seeing.
You have just wrote the method names. You need to register action/key listener to a particular swing component which you want to listen.
Suppose if your JComponent is JButton named btnPlayer you need to register the action performed event like :
private void btnPlayerActionPerformed(java.awt.event.ActionEvent e) {....
...}
You can add action listener from swing design. Right click on the JComponent -> Events -> ActionPerformed.
It will register click event for the component. Adding events through the properties is a better option.
I have this code which is basically a home menu with two clickable rectangles.
Start Game
Info
Start Game works fine.
Info is what is not really working. When pressed, the info screen will appear, but the home menu buttons will still be there though not visible (can be clicked).. it seems that when the info menu is appearing, the home menu buttons are not getting cleared.
Also, any point on the info menu is clickable and will show the home menu again. (not what intended, only the back buttons should do that).
How can I fix those problems ?
package test;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
public class HomeMenu extends JComponent implements MouseListener, MouseMotionListener {
private static final String GAME_TITLE = "BRICK DESTROY";
private static final String START_TEXT = "START";
private static final String INFO_TEXT = "INFO";
private static final String howtoPlay = """
1- Click Start\n
2- Choose the mode\n
3- Each mode has 3 levels\n
4- To play/pause press space, use 'A' and 'D' to move\n
5- To open pause menu press 'ESC'\n
6- To open DebugPanel press 'ALT-SHIFT-F1'""";
private static final String backText = "BACK";
private static final Color BORDER_COLOR = new Color(200,8,21); //Venetian Red
private static final Color DASH_BORDER_COLOR = new Color(255, 216, 0);//school bus yellow
private static final Color TEXT_COLOR = new Color(255, 255, 255);//white
private static final Color CLICKED_BUTTON_COLOR = Color.ORANGE.darker();;
private static final Color CLICKED_TEXT = Color.ORANGE.darker();
private static final int BORDER_SIZE = 5;
private static final float[] DASHES = {12,6};
private Rectangle menuFace;
private Rectangle infoFace;
private Rectangle startButton;
private Rectangle infoButton;
private Rectangle backButton;
private BasicStroke borderStoke;
private BasicStroke borderStoke_noDashes;
private Image img = Toolkit.getDefaultToolkit().createImage("1.jpeg");
private Font gameTitleFont;
private Font infoFont;
private Font buttonFont;
private Font howtoPlayFont;
private GameFrame owner;
private boolean startClicked;
private boolean infoClicked = false;
private boolean backClicked = false;
public HomeMenu(GameFrame owner,Dimension area){
this.setFocusable(true);
this.requestFocusInWindow();
this.addMouseListener(this);
this.addMouseMotionListener(this);
this.owner = owner;
menuFace = new Rectangle(new Point(0,0),area);
infoFace = new Rectangle(new Point(0,0),area);
this.setPreferredSize(area);
Dimension btnDim = new Dimension(area.width / 3, area.height / 12);
startButton = new Rectangle(btnDim);
infoButton = new Rectangle(btnDim);
backButton = new Rectangle(btnDim);
borderStoke = new BasicStroke(BORDER_SIZE,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND,0,DASHES,0);
borderStoke_noDashes = new BasicStroke(BORDER_SIZE,BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND);
gameTitleFont = new Font("Calibri",Font.BOLD,28);
infoFont = new Font("Calibri",Font.BOLD,24);
buttonFont = new Font("Calibri",Font.BOLD,startButton.height-2);
howtoPlayFont = new Font("Calibri",Font.PLAIN,14);
}
public void paint(Graphics g){
drawMenu((Graphics2D)g);
}
public void drawMenu(Graphics2D g2d){
if(infoClicked) {
drawInfoMenu(g2d);
return;
}else{
drawContainer(g2d);
Color prevColor = g2d.getColor();
Font prevFont = g2d.getFont();
double x = menuFace.getX();
double y = menuFace.getY();
g2d.translate(x,y);
//methods calls
drawText(g2d);
drawButton(g2d);
//end of methods calls
g2d.translate(-x,-y);
g2d.setFont(prevFont);
g2d.setColor(prevColor);
}
Toolkit.getDefaultToolkit().sync();
}
private void drawContainer(Graphics2D g2d){
Color prev = g2d.getColor();
//g2d.setColor(BG_COLOR);
g2d.drawImage(img,0,0,menuFace.width,menuFace.height,this);
//g2d.fill(menuFace);
Stroke tmp = g2d.getStroke();
g2d.setStroke(borderStoke_noDashes);
g2d.setColor(DASH_BORDER_COLOR);
g2d.draw(menuFace);
g2d.setStroke(borderStoke);
g2d.setColor(BORDER_COLOR);
g2d.draw(menuFace);
g2d.setStroke(tmp);
g2d.setColor(prev);
}
private void drawText(Graphics2D g2d){
g2d.setColor(TEXT_COLOR);
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D gameTitleRect = gameTitleFont.getStringBounds(GAME_TITLE,frc);
int sX,sY;
sY = (int)(menuFace.getHeight() / 4);
sX = (int)(menuFace.getWidth() - gameTitleRect.getWidth()) / 2;
sY += (int) gameTitleRect.getHeight() * 1.1;//add 10% of String height between the two strings
g2d.setFont(gameTitleFont);
g2d.drawString(GAME_TITLE,sX,sY);
}
private void drawButton(Graphics2D g2d){
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D txtRect = buttonFont.getStringBounds(START_TEXT,frc);
Rectangle2D mTxtRect = buttonFont.getStringBounds(INFO_TEXT,frc);
g2d.setFont(buttonFont);
int x = (menuFace.width - startButton.width) / 2;
int y =(int) ((menuFace.height - startButton.height) * 0.5);
startButton.setLocation(x,y);
x = (int)(startButton.getWidth() - txtRect.getWidth()) / 2;
y = (int)(startButton.getHeight() - txtRect.getHeight()) / 2;
x += startButton.x;
y += startButton.y + (startButton.height * 0.9);
if(startClicked){
Color tmp = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(startButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(START_TEXT,x,y);
g2d.setColor(tmp);
}
else{
g2d.draw(startButton);
g2d.drawString(START_TEXT,x,y);
}
x = startButton.x;
y = startButton.y;
y *= 1.3;
infoButton.setLocation(x,y);
x = (int)(infoButton.getWidth() - mTxtRect.getWidth()) / 2;
y = (int)(infoButton.getHeight() - mTxtRect.getHeight()) / 2;
x += infoButton.getX();
y += infoButton.getY() + (startButton.height * 0.9);
if(infoClicked){
Color tmp = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(infoButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(INFO_TEXT,x,y);
g2d.setColor(tmp);
}
else{
g2d.draw(infoButton);
g2d.drawString(INFO_TEXT,x,y);
}
}
private void drawInfoMenu(Graphics2D g2d){
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D infoRec = infoFont.getStringBounds(INFO_TEXT,frc);
Color prev = g2d.getColor();
Stroke tmp = g2d.getStroke();
g2d.setStroke(borderStoke_noDashes);
g2d.setColor(DASH_BORDER_COLOR);
g2d.draw(infoFace);
g2d.setStroke(borderStoke);
g2d.setColor(BORDER_COLOR);
g2d.draw(infoFace);
g2d.fillRect(0,0,infoFace.width,infoFace.height);
g2d.setStroke(tmp);
g2d.setColor(prev);
g2d.setColor(TEXT_COLOR);
int sX,sY;
sY = (int)(infoFace.getHeight() / 15);
sX = (int)(infoFace.getWidth() - infoRec.getWidth()) / 2;
sY += (int) infoRec.getHeight() * 1.1;//add 10% of String height between the two strings
g2d.setFont(infoFont);
g2d.drawString(INFO_TEXT,sX,sY);
TextLayout layout = new TextLayout(howtoPlay, howtoPlayFont, frc);
String[] outputs = howtoPlay.split("\n");
for(int i=0; i<outputs.length; i++) {
g2d.setFont(howtoPlayFont);
g2d.drawString(outputs[i], 40, (int) (80 + i * layout.getBounds().getHeight() + 0.5));
}
backButton.setLocation(getWidth()/3,getHeight()-50);
int x = (int)(backButton.getWidth() - infoRec.getWidth()) / 2;
int y = (int)(backButton.getHeight() - infoRec.getHeight()) / 2;
x += backButton.x+11;
y += backButton.y + (layout.getBounds().getHeight() * 1.35);
backButton.setLocation(getWidth()/3,getHeight()-50);
if(backClicked){
Color tmp1 = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(backButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(backText,x,y);
g2d.setColor(tmp1);
infoClicked = false;
repaint();
}
else{
g2d.draw(backButton);
g2d.drawString(backText,x,y);
}
}
#Override
public void mouseClicked(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if(startButton.contains(p)){
owner.enableGameBoard();
}
else if(infoButton.contains(p)){
infoClicked = true;
}
else if(backButton.contains(p)){
infoClicked = false;
}
repaint();
}
#Override
public void mousePressed(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if(startButton.contains(p)){
startClicked = true;
repaint(startButton.x,startButton.y,startButton.width+1,startButton.height+1);
}
else if(infoButton.contains(p)){
infoClicked = true;
}
else if(backButton.contains(p)){
infoClicked = false;
}
repaint();
}
#Override
public void mouseReleased(MouseEvent mouseEvent) {
if(startClicked){
startClicked = false;
repaint(startButton.x,startButton.y,startButton.width+1,startButton.height+1);
}
else if(infoClicked){
infoClicked = false;
}
else if(backClicked){
infoClicked = true;
}
repaint();
}
#Override
public void mouseEntered(MouseEvent mouseEvent) {
}
#Override
public void mouseExited(MouseEvent mouseEvent) {
}
#Override
public void mouseDragged(MouseEvent mouseEvent) {
}
#Override
public void mouseMoved(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if(startButton.contains(p) || infoButton.contains(p) || backButton.contains(p)) {
this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
else {
this.setCursor(Cursor.getDefaultCursor());
}
}
}
Here are the images of both windows
main menu
info menu, pressing anywhere = back to home menu, pressing roughly in the middle = start game or back to main menu too
First read, Performing Custom Painting and Painting in AWT and Swing to get a better understanding how painting in Swing works and how you're suppose to work with it.
But I already have ...
public void paint(Graphics g){
drawMenu((Graphics2D)g);
}
would suggest otherwise. Seriously, go read those links so you understand all the issues that the above decision is going to create for you.
You're operating in a OO language, you need to take advantage of that and decouple your code and focus on the "single responsibility" principle.
I'm kind of tired of talking about it, so you can do some reading:
https://softwareengineering.stackexchange.com/questions/244476/what-is-decoupling-and-what-development-areas-can-it-apply-to
Cohesion and Decoupling, what do they represent?
Single Responsibility Principle
Single Responsibility Principle in Java with Examples
SOLID Design Principles Explained: The Single Responsibility Principle
These are basic concepts you really need to understand as they will make your live SOOO much easier and can be applied to just about any language.
As an example, from your code...
public HomeMenu(GameFrame owner,Dimension area){
//...
this.setPreferredSize(area);
There is no good reason (other than laziness (IMHO)) that any caller should be telling a component what size it should be, that's not their responsibility. It's the responsibility of the component to tell the parent container how big it would like to be and for the parent component to figure out how it's going to achieve that (or ignore it as the case may be).
The "basic" problem you're having is a simple one. Your "God" class is simply trying to do too much (ie it's taken on too much responsibility). Now we "could" add a dozen or more flags into the code to compensate for this, which is just going to increase the coupling and complexity, making it harder to understand and maintain, or we can take a step back, break it down into individual areas of responsibility and build the solution around those, for example...
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new HomePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class HomePane extends JPanel {
public HomePane() {
setLayout(new BorderLayout());
navigateToMenu();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void navigateToMenu() {
removeAll();
HomeMenuPane pane = new HomeMenuPane(new HomeMenuPane.NavigationListener() {
#Override
public void navigateToInfo(HomeMenuPane source) {
HomePane.this.navigateToInfo();
}
#Override
public void navigateToStartGame(HomeMenuPane source) {
startGame();
}
});
add(pane);
revalidate();
repaint();
}
protected void navigateToInfo() {
removeAll();
HowToPlayPane pane = new HowToPlayPane(new HowToPlayPane.NavigationListener() {
#Override
public void navigateBack(HowToPlayPane source) {
navigateToMenu();
}
});
add(pane);
revalidate();
repaint();
}
protected void startGame() {
removeAll();
add(new JLabel("This is pretty awesome, isn't it!", JLabel.CENTER));
revalidate();
repaint();
}
}
public abstract class AbstractBaseMenuPane extends JPanel {
protected static final Color BORDER_COLOR = new Color(200, 8, 21); //Venetian Red
protected static final Color DASH_BORDER_COLOR = new Color(255, 216, 0);//school bus yellow
protected static final Color TEXT_COLOR = new Color(255, 255, 255);//white
protected static final Color CLICKED_BUTTON_COLOR = Color.ORANGE.darker();
protected static final Color CLICKED_TEXT = Color.ORANGE.darker();
protected static final int BORDER_SIZE = 5;
protected static final float[] DASHES = {12, 6};
private Rectangle border;
private BasicStroke borderStoke;
private BasicStroke borderStoke_noDashes;
private BufferedImage backgroundImage;
public AbstractBaseMenuPane() {
borderStoke = new BasicStroke(BORDER_SIZE, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, DASHES, 0);
borderStoke_noDashes = new BasicStroke(BORDER_SIZE, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
border = new Rectangle(new Point(0, 0), getPreferredSize());
// You are now responsible for filling the background
setOpaque(false);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
BufferedImage backgroundImage = getBackgroundImage();
if (backgroundImage != null) {
g2d.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), this);
}
Color prev = g2d.getColor();
Stroke tmp = g2d.getStroke();
g2d.setStroke(borderStoke_noDashes);
g2d.setColor(DASH_BORDER_COLOR);
g2d.draw(border);
g2d.setStroke(borderStoke);
g2d.setColor(BORDER_COLOR);
g2d.draw(border);
g2d.dispose();
}
public void setBackgroundImage(BufferedImage backgroundImage) {
this.backgroundImage = backgroundImage;
repaint();
}
public BufferedImage getBackgroundImage() {
return backgroundImage;
}
}
public class HomeMenuPane extends AbstractBaseMenuPane {
public static interface NavigationListener {
public void navigateToInfo(HomeMenuPane source);
public void navigateToStartGame(HomeMenuPane source);
}
private static final String GAME_TITLE = "BRICK DESTROY";
private static final String START_TEXT = "START";
private static final String INFO_TEXT = "INFO";
private Rectangle startButton;
private Rectangle infoButton;
private Font gameTitleFont;
private Font buttonFont;
// Don't do this, this just sucks (for so many reasons)
// Use ImageIO.read instead and save yourself a load of frustration
//private Image img = Toolkit.getDefaultToolkit().createImage("1.jpeg");
private Point lastClickPoint;
private NavigationListener navigationListener;
public HomeMenuPane(NavigationListener navigationListener) {
this.navigationListener = navigationListener;
try {
setBackgroundImage(ImageIO.read(getClass().getResource("/images/BrickWall.jpg")));
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
lastClickPoint = p;
if (startButton.contains(p)) {
peformStartGameAction();
} else if (infoButton.contains(p)) {
performInfoAction();
}
repaint();
}
#Override
public void mouseReleased(MouseEvent mouseEvent) {
lastClickPoint = null;
repaint();
}
});
this.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if (startButton.contains(p) || infoButton.contains(p)) {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
setCursor(Cursor.getDefaultCursor());
}
}
});
Dimension area = getPreferredSize();
Dimension btnDim = new Dimension(area.width / 3, area.height / 12);
startButton = new Rectangle(btnDim);
infoButton = new Rectangle(btnDim);
gameTitleFont = new Font("Calibri", Font.BOLD, 28);
buttonFont = new Font("Calibri", Font.BOLD, startButton.height - 2);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Color prevColor = g2d.getColor();
Font prevFont = g2d.getFont();
//methods calls
drawText(g2d);
drawButton(g2d);
//end of methods calls
g2d.setFont(prevFont);
g2d.setColor(prevColor);
g2d.dispose();
}
private void drawText(Graphics2D g2d) {
g2d.setColor(TEXT_COLOR);
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D gameTitleRect = gameTitleFont.getStringBounds(GAME_TITLE, frc);
int sX, sY;
sY = (int) (getHeight() / 4);
sX = (int) (getWidth() - gameTitleRect.getWidth()) / 2;
sY += (int) gameTitleRect.getHeight() * 1.1;//add 10% of String height between the two strings
g2d.setFont(gameTitleFont);
g2d.drawString(GAME_TITLE, sX, sY);
}
private void drawButton(Graphics2D g2d) {
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D txtRect = buttonFont.getStringBounds(START_TEXT, frc);
Rectangle2D mTxtRect = buttonFont.getStringBounds(INFO_TEXT, frc);
g2d.setFont(buttonFont);
int x = (getWidth() - startButton.width) / 2;
int y = (int) ((getHeight() - startButton.height) * 0.5);
startButton.setLocation(x, y);
x = (int) (startButton.getWidth() - txtRect.getWidth()) / 2;
y = (int) (startButton.getHeight() - txtRect.getHeight()) / 2;
x += startButton.x;
y += startButton.y + (startButton.height * 0.9);
if (lastClickPoint != null && startButton.contains(lastClickPoint)) {
Color tmp = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(startButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(START_TEXT, x, y);
g2d.setColor(tmp);
} else {
g2d.draw(startButton);
g2d.drawString(START_TEXT, x, y);
}
x = startButton.x;
y = startButton.y;
y *= 1.3;
infoButton.setLocation(x, y);
x = (int) (infoButton.getWidth() - mTxtRect.getWidth()) / 2;
y = (int) (infoButton.getHeight() - mTxtRect.getHeight()) / 2;
x += infoButton.getX();
y += infoButton.getY() + (startButton.height * 0.9);
if (lastClickPoint != null && infoButton.contains(lastClickPoint)) {
Color tmp = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(infoButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(INFO_TEXT, x, y);
g2d.setColor(tmp);
} else {
g2d.draw(infoButton);
g2d.drawString(INFO_TEXT, x, y);
}
}
protected void peformStartGameAction() {
navigationListener.navigateToStartGame(this);
}
protected void performInfoAction() {
navigationListener.navigateToInfo(this);
}
}
public class HowToPlayPane extends AbstractBaseMenuPane {
public static interface NavigationListener {
public void navigateBack(HowToPlayPane source);
}
private static final String HOW_TO_PLAY_TEXT = """
1- Click Start\n
2- Choose the mode\n
3- Each mode has 3 levels\n
4- To play/pause press space, use 'A' and 'D' to move\n
5- To open pause menu press 'ESC'\n
6- To open DebugPanel press 'ALT-SHIFT-F1'""";
private static final String BACK_TEXT = "BACK";
private static final String INFO_TEXT = "INFO";
private Rectangle backButton;
private boolean backClicked = false;
private Font infoFont;
private Font howtoPlayFont;
private NavigationListener navigationListener;
public HowToPlayPane(NavigationListener navigationListener) {
this.navigationListener = navigationListener;
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if (backButton.contains(p)) {
backClicked = true;
repaint();
performBackAction();
}
}
#Override
public void mouseReleased(MouseEvent e) {
backClicked = false;
}
});
this.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent mouseEvent) {
Point p = mouseEvent.getPoint();
if (backButton.contains(p)) {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
setCursor(Cursor.getDefaultCursor());
}
}
});
Dimension btnDim = new Dimension(getPreferredSize().width / 3, getPreferredSize().height / 12);
backButton = new Rectangle(btnDim);
infoFont = new Font("Calibri", Font.BOLD, 24);
howtoPlayFont = new Font("Calibri", Font.PLAIN, 14);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(BORDER_COLOR);
g2d.fillRect(0, 0, getWidth(), getHeight());
FontRenderContext frc = g2d.getFontRenderContext();
Rectangle2D infoRec = infoFont.getStringBounds(INFO_TEXT, frc);
//
// Color prev = g2d.getColor();
//
// Stroke tmp = g2d.getStroke();
//
// g2d.setStroke(borderStoke_noDashes);
// g2d.setColor(DASH_BORDER_COLOR);
// g2d.draw(infoFace);
//
// g2d.setStroke(borderStoke);
// g2d.setColor(BORDER_COLOR);
// g2d.draw(infoFace);
//
// g2d.fillRect(0, 0, infoFace.width, infoFace.height);
//
// g2d.setStroke(tmp);
//
// g2d.setColor(prev);
//
g2d.setColor(TEXT_COLOR);
int sX, sY;
sY = (int) (getHeight() / 15);
sX = (int) (getWidth() - infoRec.getWidth()) / 2;
sY += (int) infoRec.getHeight() * 1.1;//add 10% of String height between the two strings
g2d.setFont(infoFont);
g2d.drawString(INFO_TEXT, sX, sY);
TextLayout layout = new TextLayout(HOW_TO_PLAY_TEXT, howtoPlayFont, frc);
String[] outputs = HOW_TO_PLAY_TEXT.split("\n");
for (int i = 0; i < outputs.length; i++) {
g2d.setFont(howtoPlayFont);
g2d.drawString(outputs[i], 40, (int) (80 + i * layout.getBounds().getHeight() + 0.5));
}
backButton.setLocation(getWidth() / 3, getHeight() - 50);
int x = (int) (backButton.getWidth() - infoRec.getWidth()) / 2;
int y = (int) (backButton.getHeight() - infoRec.getHeight()) / 2;
x += backButton.x + 11;
y += backButton.y + (layout.getBounds().getHeight() * 1.35);
backButton.setLocation(getWidth() / 3, getHeight() - 50);
if (backClicked) {
Color tmp1 = g2d.getColor();
g2d.setColor(CLICKED_BUTTON_COLOR);
g2d.draw(backButton);
g2d.setColor(CLICKED_TEXT);
g2d.drawString(BACK_TEXT, x, y);
g2d.setColor(tmp1);
repaint();
} else {
g2d.draw(backButton);
g2d.drawString(BACK_TEXT, x, y);
}
g2d.dispose();
}
protected void performBackAction() {
navigationListener.navigateBack(this);
}
}
}
Now, this example makes use of components to present different views (it even has a nice abstract implementation to allow for code re-use 😱), but it occurs to me, that, if you "really" wanted to, you could have a series of "painter" classes, which could be used to delegate the painting of the current state to, and mouse clicks/movements could be delegated to, meaning you could have a single component, which would simple delegate the painting (via the paintComponent method) to which ever painter is active.
And, wouldn't you know it, they have a design principle for that to, the Delegation Pattern
The above example also makes use of the observer pattern, so you might want to have a look into that as well
I'm attempting to recreate Pong, and can't quite get the animations to run as smooth as I would like them too. Sometimes the ball does motion where it slows and then jerks really fast. Where in my code do I go wrong? Also, if you could review my code as well it's be appreciated. I'm just trying to get better.
Code for the ball
import java.awt.geom.Ellipse2D;
public class Ball extends Ellipse2D.Double{
//Ball movement variables
boolean movementX = true;
boolean movementY = true;
// Player one info
private int p1Number = 0;
private String p1Score = "0";
// Player two info
private int p2Number = 0;
private String p2Score = "0";
// Constructor
public Ball(double x, double y){
this.x = x;
this.y = y;
height = 20;
width = 20;
}
//function created to move the puck based on the position of the Puck, and the Players
public void MovePuck(double PlayerX, double PlayerY, double PlayerTwoX, double PlayerTwoY) {
// automatically move the X values
if (movementX) {
x+=5;
if (x >= 580) {
movementX = false;
}
} else {
x-=5;
if (x <= 0) {
movementX = true;
}
}
// automatically move the Y values
if (movementY) {
y+=2;
if (y >= 380) {
movementY = false;
}
} else {
y-=2;
if (y <= 0) {
movementY = true;
}
}
// Make Puck Bounce Off Player One
if(x == PlayerX + 10 && y <= PlayerY + 50 && y >= PlayerY){
movementX = true;
}
// Make Puck Bounce off Player Two
if(x == PlayerTwoX - 10 && y <= PlayerTwoY + 50 && y >= PlayerTwoY){
movementX = false;
}
}
// function created to keep track of Player One's Score
public String p1Score(){
if(x == 580){
p1Number++;
}
p1Score = String.valueOf(p1Number);
return p1Score;
}
// function created to keep track of Player Two's Score
public String p2Score(){
if (x == 0) {
p2Number++;
}
p2Score = String.valueOf(p2Number);
return p2Score;
}
}
Code for the ball
import java.awt.geom.Rectangle2D;
public class Rectangle extends Rectangle2D.Double{
private boolean isMovingUp, isMovingDown;
public Rectangle(double x, double y){
this.x = x;
this.y = y;
width = 10;
height = 50;
}
public void movePlayerUP(){
y-=3;
}
public void movePlayerDown(){
y+=3;
}
public void setMovingDown(boolean movingDown) {
isMovingDown = movingDown;
}
public boolean isMovingDown() {
return isMovingDown;
}
public void setMovingUp(boolean movingUp) {
isMovingUp = movingUp;
}
public boolean isMovingUp() {
return isMovingUp;
}
#Override
public double getY() {return super.getY();}
public double getX(){return super.getX();}
}
And finally, the Board that holds the logic. The main just calls this and runs it.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
public class Board extends JPanel {
private BufferedImage background;
//player one information
private int p1X = 20;
private int p1Y = 150;
private Rectangle playerOne;
//player two information
private int p2X = 550;
private int p2Y = 150;
private Rectangle playerTwo;
// puck information
private int PuckX = 290;
private int PuckY = 190;
private Ball Puck;
public Board() {
// creating each player and puck
playerOne = new Rectangle(p1X, p1Y);
playerTwo = new Rectangle(p2X, p2Y);
Puck = new Ball(PuckX, PuckY);
setDoubleBuffered(true);
addKeyBinding(this, KeyEvent.VK_UP, "moveUpPressed", false, (evt) -> {
playerOne.setMovingUp(true);
});
addKeyBinding(this, KeyEvent.VK_UP, "moveUpReleased", true, (evt) -> {
playerOne.setMovingUp(false);
});
addKeyBinding(this, KeyEvent.VK_DOWN, "moveDownPressed", false, (evt) -> {
playerOne.setMovingDown(true);
});
addKeyBinding(this, KeyEvent.VK_DOWN, "moveDownReleased", true, (evt) -> {
playerOne.setMovingDown(false);
});
addKeyBinding(this, KeyEvent.VK_W, " moveP2UpPressed", false, (evt) -> {
playerTwo.setMovingUp(true);
});
addKeyBinding(this, KeyEvent.VK_W, " moveP2UpReleased", true, (evt) -> {
playerTwo.setMovingUp(false);
});
addKeyBinding(this, KeyEvent.VK_S, " moveP2DownPressed", false, (evt) -> {
playerTwo.setMovingDown(true);
});
addKeyBinding(this, KeyEvent.VK_S, " moveP2DownReleased", true, (evt) -> {
playerTwo.setMovingDown(false);
});
// Timer to represent frames per second and constantly check the status of each button pressed
Timer timer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Move puck
Puck.MovePuck(playerOne.getX(), playerOne.getY(), playerTwo.getX(), playerTwo.getY());
repaint();
//Only move if the player is pressing each button
//Player One Moving UP
if(playerOne.isMovingUp()){
playerOne.movePlayerUP();
}
//Player One Moving Down
if(playerOne.isMovingDown()){
playerOne.movePlayerDown();
}
//Player Two Moving Up
if(playerTwo.isMovingUp()){
playerTwo.movePlayerUP();
}
//Player Two Moving Down
if(playerTwo.isMovingDown()){
playerTwo.movePlayerDown();
}
}
});
//Start Timer
timer.start();
}
// Created a function that represents adding keybinding. Easier than re-writing a function each time
public void addKeyBinding(JComponent comp, int keyCode, String id, boolean movement, ActionListener ActionListener){
InputMap im = comp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap ap = comp.getActionMap();
im.put(KeyStroke.getKeyStroke(keyCode, 0 , movement), id);
ap.put(id, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
ActionListener.actionPerformed(e);
repaint();
}
});
}
public void paintComponent(Graphics g) {
super.paintComponents(g);
setOpaque(false);
// setting background with image
background = new BufferedImage(600, 400, BufferedImage.TYPE_INT_RGB);
g.drawImage(background, 0, 0, this);
// drawing player one
g.setColor(Color.blue);
((Graphics2D) g).fill(playerOne);
// drawing player two
g.setColor(Color.red);
((Graphics2D) g).fill(playerTwo);
// drawing puck
g.setColor(Color.orange);
((Graphics2D) g).fill(Puck);
//Player One Score
g.setColor(Color.white);
g.setFont(new Font("Arial", Font.PLAIN, 20));
g.drawString(Puck.p1Score(), 100, 30);
//Player Two Score
g.setColor(Color.white);
g.setFont(new Font("Arial", Font.PLAIN, 20));
g.drawString(Puck.p2Score(), 450, 30);
g.dispose();
}
}
I am trying to implement langton's ant , and i did it well :
langton's ant java simulation screen
for painting in my jPanel, i override the paintComponent at each step but it take so much time for painting every black or white rectangle , i just want that at each step i only paint the two rectangle who have changed!?
So my question is, how to only paint a rectangle without changing what was painted in previous frame?
here is my code for painting
public void paintComponent(Graphics g){
g.setColor(Color.white);
g.fillRect(0,0,getWidth(),getHeight());
g.setColor(Color.black);
int careLargeur = getWidth() / m;
int careHauteur = getHeight() / n;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++){
if(map[i][j])
g.fillRect(j*careLargeur,i*careHauteur,careLargeur,careHauteur);
}
//draw fourmi
g.setColor(Color.red);
g.fillOval(jF*careLargeur, iF*careHauteur, careLargeur, careHauteur);
}
any help? or should i give more details ?
here is the jar :
Paint your rectangles to a BufferedImage, and then draw that BufferedImage within your paintComponent method. You could also limit how much is re-drawn by using one of the repaint(...) overrides that specifies the exact rectangular region to repaint.
So your paintComponent method could be as simple as this:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
g.drawImage(img, 0, 0, this);
}
g.setColor(Color.red);
g.fillOval(jF*careLargeur, iF*careHauteur, careLargeur, careHauteur);
}
With drawing changes being made to the img BufferedImage.
Assuming that you're using a Swing Timer to drive the state changes to your model, you could
change the model, and then
update the BufferedImage based on the model changes, and
call repaint(...) on only the updated region.
Incomplete code attempt.... not yet done!
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
/**
* https://en.wikipedia.org/wiki/Langton%27s_ant
* https://stackoverflow.com/a/44930371/522444
* #author Pete
*
*/
public class LangtonsAnt {
private static final int TIMER_DELAY = 30;
private static void createAndShowGui() {
Model model = new Model(800);
View view = new View(800);
Controller controller = new Controller(model, view);
JFrame frame = new JFrame("Langtons Ant");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
controller.startTimer(TIMER_DELAY);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static class Model {
public static final String POINT = "point";
private SwingPropertyChangeSupport support = new SwingPropertyChangeSupport(this);
private int gridSize;
private boolean[][] grid; // false is white. Better to use enums
private Point oldValue;
private Point point; // ant location
public Model(int gridSize) {
this.gridSize = gridSize;
grid = new boolean[gridSize][gridSize];
int x = gridSize / 2;
int y = gridSize / 2;
setPoint(new Point(x, y));
}
public void setPoint(Point point) {
this.oldValue = this.point;
Point newValue = point;
this.point = point;
support.firePropertyChange(POINT, oldValue, newValue);
}
public Point getPoint() {
return point;
}
public boolean[][] getGrid() {
return grid;
}
public int getGridSize() {
return gridSize;
}
public void step() {
// first will hold relative new positions
int newX = 0;
int newY = 0;
boolean gridPoint = getGridPoint(point);
if (oldValue == null) {
newX = point.x;
newY = point.y - 1;
} else {
int dX = point.x - oldValue.x;
int dY = point.y - oldValue.y;
if (dX != 0) {
// from left or right
newY = dX > 0 ? 1 : -1; // assume "white" or false
newY = gridPoint ? -newY : newY; // if "black" then reverse
} else {
// from up or down
newX = dY > 0 ? -1 : 1; // assume "white" or false
newX = gridPoint ? -newX : newX; // if "black" then reverse
}
// convert from relative to absolute new positions
newX = point.x + newX;
newY = point.y + newY;
}
setGridPoint(point, !gridPoint);
setPoint(new Point(newX, newY));
}
public boolean getGridPoint(int x, int y) {
return grid[x][y];
}
public boolean getGridPoint(Point p) {
return getGridPoint(p.x, p.y);
}
public void setGridPoint(int x, int y, boolean b) {
grid[x][y] = b;
}
public void setGridPoint(Point p, boolean b) {
setGridPoint(p.x, p.y, b);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
support.addPropertyChangeListener(propertyName, l);
}
}
private static class Controller {
private Model model;
private View view;
private Timer timer;
public Controller(Model model, View view) {
this.model = model;
this.view = view;
view.setAntImg(createAntImg());
model.addPropertyChangeListener(Model.POINT, new ModelListener());
}
private BufferedImage createAntImg() {
// trivial image for now
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics g = img.getGraphics();
g.setColor(Color.RED);
g.fillRect(0, 0, 1, 1);
g.dispose();
return img;
}
public void startTimer(int delay) {
timer = new Timer(delay, new TimerListener());
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
model.step();
}
}
private class ModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent evt) {
// TODO Finish this.
// get the new point and old point
// translate model coord to view coord
// Change the state of the view's buffered image
// repaint the limited region that was changed
}
}
}
private static class View extends JPanel {
private static final Color BACKGROUND = Color.WHITE;
private BufferedImage gridImg;
private BufferedImage antImg;
private Point guiAntLocation;
private int pixelWidth;
public View(int pixelWidth) {
this.pixelWidth = pixelWidth;
gridImg = new BufferedImage(pixelWidth, pixelWidth, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = gridImg.createGraphics();
g2.setColor(BACKGROUND);
g2.fillRect(0, 0, pixelWidth, pixelWidth);
g2.dispose();
}
public int getPixelWidth() {
return pixelWidth;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (getGridImg() != null) {
g.drawImage(getGridImg(), 0, 0, this);
}
if (guiAntLocation != null && antImg != null) {
int x = guiAntLocation.x;
int y = guiAntLocation.y;
g.drawImage(antImg, x, y, this);
}
}
public void setGuiAntLocation(Point guiAntLocation) {
this.guiAntLocation = guiAntLocation;
}
public Point getGuiAntLocation() {
return guiAntLocation;
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet() || getGridImg() == null) {
return super.getPreferredSize();
}
return new Dimension(getGridImg().getWidth(), getGridImg().getHeight());
}
public BufferedImage getGridImg() {
return gridImg;
}
public void setGridImg(BufferedImage gridImg) {
this.gridImg = gridImg;
}
public void setAntImg(BufferedImage antImg) {
this.antImg = antImg;
}
}
}
I am creating a Space Invaders Game with just one Invader. The images are stuttering and are only visible ~10% of the time. What is wrong with my code?
package spaceinvader;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class spaceInvaders extends JApplet implements KeyListener, ActionListener
{
//Declare components and variables
JPanel mainPanel = new JPanel();
ImageIcon carImage = new ImageIcon("ship.png");
ImageIcon invaderImage = new ImageIcon("invader.png");
int intPosX = 240;
int intPosY = 330;
int intXAmount = 15;
boolean shipMoveLeft = false;
boolean shipMoveRight = false;
Timer shipTimer = new Timer(100,this);
int intBulletX = -50;
int intBulletY = -50;
boolean bulletMove = false;
boolean bulletActive = false;
Timer bulletTimer = new Timer(50,this);
int intInvaderX = 0;
int intInvaderY = 0;
int invaderXAmount = 10;
boolean invaderMove = true;
Timer invaderTimer= new Timer(1000,this);
public void init()
{
addKeyListener(this);
setFocusable(true);
resize(600,400);
setContentPane(mainPanel);
shipTimer.start();
bulletTimer.start();
invaderTimer.start();
}
public void actionPerformed(ActionEvent e)
{
requestFocus();
if(shipMoveLeft)
intPosX += intXAmount;
else if(shipMoveRight)
intPosX -= intXAmount;
if(bulletMove && bulletActive){
intBulletY -= 15;
if(intBulletY <= -50){
bulletMove = false;
bulletActive = false;
}
}
if(invaderMove){
intInvaderX += invaderXAmount;
if(intInvaderX > getWidth() - 60 || intInvaderX < 0){
intInvaderY += 40;
invaderXAmount *= -1;
}
}
repaint();
}
public void keyPressed(KeyEvent e)
{
int key = e.getKeyCode();
if (key == 37){
shipMoveRight = true;
}
else if (key == 39){
shipMoveLeft = true;
}
else if (key == 32){
if(bulletActive == false){
intBulletX = intPosX;
intBulletY = intPosY;
}
bulletMove = true;
bulletActive = true;
}
}
public void keyReleased(KeyEvent e)
{
shipMoveLeft = false;
shipMoveRight = false;
}
public void keyTyped(KeyEvent e)
{
}
public void paint(Graphics gr)
{
super.paint(gr);
gr.setColor(Color.red);
gr.fillOval(intBulletX, intBulletY, 10, 25);
carImage.paintIcon(this,gr, intPosX, intPosY); //Draw image in new spot
invaderImage.paintIcon(this,gr, intInvaderX, intInvaderY);
}
}
So there are a number of issues which jump out immediately...
Applets are a dead end, most browsers will actively block them and/or have dropped support for the plugin
You're adding a JPanel to the applet, but overriding the applet's paint method, because of the way painting can work, the panel can be painted independently of the applet, causing it to paint over whatever you might have painted
You don't need multiple timers, you just need to have better delta values (smaller been faster)
KeyListener is a poor choice for detecting keyboard input, it's rather low level and has focus related issues which are easily overcome by using the Key Bindings API
I'd start by having a look at Performing Custom Painting, Painting in AWT and Swing and How to Use Key Bindings for more details.
So how would you start fixing it? Start by using a JPanel as you basic container, override it's paintComponent and place all your paint logic here.
Use a single Timer to manage the updates
There are any number of approaches you can take, but I'd start with defining some contracts that define what can go on in the game
public interface GameSpace extends ImageObserver {
public Dimension getGameSpace();
public boolean hasInput(Input input);
}
public interface Entity {
public void paint(GameSpace gameSpace, Graphics2D g2d);
public boolean update(GameSpace gameSpace);
public Rectangle getBounds();
}
public abstract class AbstractEntity implements Entity {
private int x;
private int y;
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
protected abstract int getWidth();
protected abstract int getHeight();
public Rectangle getBounds() {
return new Rectangle(getX(), getY(), getWidth(), getHeight());
}
}
public abstract class AbstractImageEntity extends AbstractEntity {
protected abstract BufferedImage getImage();
#Override
protected int getWidth() {
return getImage().getWidth();
}
#Override
protected int getHeight() {
return getImage().getHeight();
}
}
This separates some of management of key elements, allowing for a more flexible design. You might have a lot more entities, one's which could move, ones which could not, some which are painted, some which are not, but all which provide support for the core engine to get work done.
Once you have that you can start defining some core entities you need
public class ShipEntity extends AbstractImageEntity {
private BufferedImage ship;
public ShipEntity(GameSpace gameSpace) throws IOException {
ship = ImageIO.read(getClass().getResource("/resources/ship.png"));
setY(gameSpace.getGameSpace().height - getBounds().height);
setX((gameSpace.getGameSpace().width - getBounds().width) / 2);
}
#Override
public BufferedImage getImage() {
return ship;
}
#Override
public void paint(GameSpace gameSpace, Graphics2D g2d) {
g2d.drawImage(ship, getX(), getY(), gameSpace);
}
#Override
public boolean update(GameSpace gameSpace) {
int x = getX();
if (gameSpace.hasInput(Input.LEFT)) {
x -= 2;
}
if (gameSpace.hasInput(Input.RIGHT)) {
x += 2;
}
if (x < 0) {
x = 0;
} else if (x + getWidth() > gameSpace.getGameSpace().width) {
x = gameSpace.getGameSpace().width - getWidth();
}
setX(x);
return true;
}
}
public class InvaderEntity extends AbstractImageEntity {
private BufferedImage invader;
public InvaderEntity() throws IOException {
invader = ImageIO.read(getClass().getResource("/resources/Invader.png"));
}
#Override
protected BufferedImage getImage() {
return invader;
}
#Override
public void paint(GameSpace gameSpace, Graphics2D g2d) {
g2d.drawImage(invader, getX(), getY(), gameSpace);
}
#Override
public boolean update(GameSpace gameSpace) {
return true;
}
}
public class ProjectileEntity extends AbstractEntity {
private int delta;
public ProjectileEntity(int delta) {
this.delta = delta;
}
#Override
protected int getWidth() {
return 10;
}
#Override
protected int getHeight() {
return 10;
}
#Override
public void paint(GameSpace gameSpace, Graphics2D g2d) {
g2d.setColor(Color.RED);
int width = getWidth();
int height = getHeight();
g2d.fillOval(getX() - width / 2, getY() - height / 2, width, height);
}
#Override
public boolean update(GameSpace gameSpace) {
int y = getY() + delta;
setY(getY() + delta);
return y + getHeight() >= 0 && y + getHeight() <= gameSpace.getGameSpace().height;
}
}
Basically, they contain the logic required for getting their jobs done.
Finally, you need to setup the actual game UI
public class GamePane extends JPanel implements GameSpace {
private Set<Input> inputs;
private Entity playerEntity;
private List<Entity> projectileEntities;
private List<Entity> invaderEntities;
private long timeOfLastProjectile = -1;
public GamePane() throws IOException {
setBackground(Color.BLACK);
inputs = new HashSet<>(2);
playerEntity = new ShipEntity(this);
projectileEntities = new ArrayList<>(25);
invaderEntities = new ArrayList<>(25);
InvaderEntity invader = new InvaderEntity();
invader.setX((getGameSpace().width - invader.getBounds().width) / 2);
invader.setY((getGameSpace().height - invader.getBounds().height) / 2);
invaderEntities.add(invader);
addKeyBinding(Input.LEFT, "left", KeyEvent.VK_LEFT);
addKeyBinding(Input.RIGHT, "right", KeyEvent.VK_RIGHT);
addKeyBinding(Input.SPACE, "space", KeyEvent.VK_SPACE);
Timer timer = new Timer(15, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateState();
processCollisions();
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
protected void updateState() {
playerEntity.update(this);
if (hasInput(Input.SPACE)) {
long time = System.currentTimeMillis() - timeOfLastProjectile;
if (time < 0 || time > 1000) {
timeOfLastProjectile = System.currentTimeMillis();
Rectangle bounds = playerEntity.getBounds();
ProjectileEntity projectile = new ProjectileEntity(-1);
int x = bounds.x + ((bounds.width - projectile.getWidth()) / 2);
int y = bounds.y - projectile.getHeight();
projectile.setX(x);
projectile.setY(y);
projectileEntities.add(projectile);
}
}
for (Entity entity : invaderEntities) {
entity.update(this);
}
List<Entity> outOfBounds = new ArrayList<>(25);
for (Entity entity : projectileEntities) {
if (!entity.update(this)) {
outOfBounds.add(entity);
}
}
projectileEntities.removeAll(outOfBounds);
}
protected void processCollisions() {
Set<Entity> hitInvaders = new HashSet<>(25);
Set<Entity> hitProjectiles = new HashSet<>(25);
for (Entity invader : invaderEntities) {
for (Entity projectile : projectileEntities) {
if (projectile.getBounds().intersects(invader.getBounds())) {
// Maybe lots of cool explosiions
hitInvaders.add(invader);
hitProjectiles.add(projectile);
}
}
}
invaderEntities.removeAll(hitInvaders);
projectileEntities.removeAll(hitProjectiles);
}
protected void addKeyBinding(Input input, String name, int virtualKey) {
ActionMap am = getActionMap();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(virtualKey, 0, false), name + ".pressed");
im.put(KeyStroke.getKeyStroke(virtualKey, 0, true), name + ".released");
am.put(name + ".pressed", new KeyAction(inputs, input, true));
am.put(name + ".released", new KeyAction(inputs, input, false));
}
#Override
public Dimension getGameSpace() {
return getPreferredSize();
}
#Override
public boolean hasInput(Input input) {
return inputs.contains(input);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
playerEntity.paint(this, g2d);
g2d.dispose();
for (Entity entity : invaderEntities) {
g2d = (Graphics2D) g.create();
entity.paint(this, g2d);
g2d.dispose();
}
for (Entity entity : projectileEntities) {
g2d = (Graphics2D) g.create();
entity.paint(this, g2d);
g2d.dispose();
}
}
}
public class KeyAction extends AbstractAction {
private Input input;
private Set<Input> inputs;
private boolean pressed;
public KeyAction(Set<Input> inputs, Input input, boolean pressed) {
this.input = input;
this.inputs = inputs;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent e) {
if (pressed) {
inputs.add(input);
} else {
inputs.remove(input);
}
}
}
Now you could go a little further and generate an "engine" class which controls the entities and performs all the required updating, but I'm lazy ;)
That's a really rough idea of some the basic concepts you need to develop to be able to move forward, hope it helps