Reputation: 49
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");
}
}
`
Upvotes: 2
Views: 88
Reputation: 347214
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 interface
s which reflect this which can be painted, moved, controlled, represents effects or what ever you need - then your classes should implement the interface
s 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:
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.
Timer
and "paint" workflows to seperate classes, further decoupling the classes.KeyListener
weirdness. It also decouples the input - want to add touch controls/buttons, no worries, it's done via Action
s. Want to add joystick/controllers, no worries, it's done via Action
s.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!
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());
}
}
}
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
Upvotes: 1