Overlapping of Buttons with canvas for mouseinput - java

I am currently making a RPG hack and slash in java with just the standard libraries.
For these games , I had made a CustomButton class in an independent package that I just copy and paste in almost all of my games and then make some game-specific modifications.
This is the class::
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import objects.Collectable;
public final class CustomButton implements MouseListener {
// private BufferedImage image;
private String text = "";
private boolean pressed = false;
private int x;
private int y;
private int width;
private int height;
private Color currentColor, defaultColor, hoverColor, pressColor;
private Point mousePoint = new Point(0, 0);
private ButtonListener btnListener = null;
private Canvas canvasObject;
private BufferedImage image;
private BufferedImage darkImage;
private String actionCommand = "default";
private Collectable object;
private boolean enabled;
/*
* private CustomButton(Canvas canvasObject,JFrame frame) { this.x=100;
* this.y=100; this.width=100; this.height=100;
*
* canvasObject.addMouseListener(this); currentColor=new Color(255,255,255);
* defaultColor=new Color(255,255,255); hoverColor=new Color(255,255,255);
* pressColor=new Color(255,255,255); }
*/
public CustomButton(int x, int y, int width, int height, Canvas canvasObject) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.canvasObject = canvasObject;
canvasObject.addMouseListener(this);
currentColor = Color.GREEN;
currentColor = new Color(255, 255, 255);
defaultColor = new Color(255, 255, 255);
hoverColor = new Color(255, 255, 255);
pressColor = new Color(255, 255, 255);
enabled = true;
}
public CustomButton(int x, int y, int width, int height, Canvas canvasObject, BufferedImage image,
Collectable object) {
this.image = image;
this.darkImage = darkenImage(image);
this.x = x;
this.y = y;
this.width = width;
this.height = height;
canvasObject.addMouseListener(this);
currentColor = Color.GREEN;
currentColor = new Color(255, 255, 255);
defaultColor = new Color(255, 255, 255);
hoverColor = new Color(255, 255, 255);
pressColor = new Color(255, 255, 255);
this.canvasObject = canvasObject;
this.object = object;
enabled = true;
}
public void render(Graphics g) {
if (image == null) {
g.setColor(currentColor);
if (!pressed)
g.fillRect(this.x, this.y, width, height);
else
g.fill3DRect(this.x, this.y, width, height, true);
g.setColor(Color.BLACK);
g.drawString(text, this.x + 10, this.y + 15);
} else {
if (enabled) {
g.drawImage(image, x, y, width, height, null);
} else {
g.drawImage(darkImage, x, y, width, height, null);
}
}
}
public Rectangle getBounds() {
return new Rectangle(x, y, width, height);
}
public void tick() {
mousePoint = getMouseLocation();
changeColor();
}
private Point getMouseLocation() {
int x = 0;
int y = 0;
try {
x = (int) (canvasObject.getMousePosition().getX());
y = (int) (canvasObject.getMousePosition().getY());
} catch (NullPointerException nl) {
}
return new Point(x, y);
}
public void changeColor() {
if (!pressed) {
if (getBounds().contains(mousePoint))
currentColor = hoverColor;
else
currentColor = defaultColor;
} else {
currentColor = pressColor;
}
}
public void addButtonListener(ButtonListener btnListener) {
this.btnListener = btnListener;
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
if (enabled) {
if (btnListener != null) {
if (getBounds().contains(mousePoint)) {
ButtonID id = ButtonID.UNDETERMINABLE;
if (e.getButton() == MouseEvent.BUTTON1) id = ButtonID.LEFT;
if (e.getButton() == MouseEvent.BUTTON2) id = ButtonID.RIGHT;
btnListener.buttonClicked(new ButtonEvent(id, object, actionCommand));
}
}
}
}
public void mousePressed(MouseEvent e) {
if (getBounds().contains(mousePoint)) pressed = true;
}
public void mouseReleased(MouseEvent e) {
pressed = false;
}
public void setActionCommand(String actionCommand) {
this.actionCommand = actionCommand;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public void setDefaultColor(Color c) {
defaultColor = c;
}
public void setHoverColor(Color c) {
hoverColor = c;
}
public void setPressColor(Color c) {
pressColor = c;
}
public Collectable getObject() {
return object;
}
public void setObject(Collectable object) {
this.object = object;
}
public void destroy() {
canvasObject.removeMouseListener(this);
}
public void disable() {
enabled = false;
}
public void enable() {
enabled = true;
}
public boolean isEnabled() {
return enabled;
}
private BufferedImage darkenImage(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
image = deepCopy(image);
WritableRaster raster = image.getRaster();
for (int xx = 0; xx < width; xx++) {
for (int yy = 0; yy < height; yy++) {
int[] pixels = raster.getPixel(xx, yy, (int[]) null);
pixels[0] -= 50;
pixels[1] -= 50;
pixels[2] -= 50;
pixels[0] = Math.max(pixels[0], 0);
pixels[1] = Math.max(pixels[0], 0);
pixels[2] = Math.max(pixels[0], 0);
raster.setPixel(xx, yy, pixels);
}
}
return image;
}
private BufferedImage deepCopy(BufferedImage bi) {
ColorModel cm = bi.getColorModel();
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
WritableRaster raster = bi.copyData(null);
return new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
}
As you can probably see from the mouseClicked() method that a event is sent to the ButtonListener class.The ButtonListener interface is also declared in the package.
This button is drawn on the canvas itself.For example, in the levelmap, there is a button in the lower-right corner, which on clicking will open the inventory.Let this button be called btn_INV.
Until now,I have been taking inputs for moving the player through the keyboard.But I plan to change keyInput to mouse input where the player will move to the tile where the user clicks on.
For that I have to make a class , sayMouseInput that implements MouseListener.Now the problem is that when I click btn_INV , not only will the button generate an event, but also, since the button is actually drawn on the canvas, the MouseInput class will also get an event as to the tile to which the player wants to moving.Now, I thought that when the MouseInput class gets a MouseEvent, it will check with the buttons as whenever there is a mouse click on the canvas, the buttons always gets informed, although it may not generate a ButtonEvent, as you can see from the code.But this is a rather bad method and highly inefficient.So , I want another method for this.
NOTE : I thought of creating another canvas for displaying the HUD and the btn_INV and other such buttons, but that does not really solve the issue as much as bypass it.

I think there's two ways to solve this problem :
The first will be to divide your game screen into two parts : one for the buttons, the other for the tiles, so you can test whilecatching a MouseEvent if the click is positionned on the tiles or not. Note that the tiles can be placed on a new button. This solution is easy to implement but you won't be able to place buttons on your tile's area.
The second will be to create a "ButtonManager". Your game class will have this button manager that will listen for mouse event and send then to the buttons. Buttons won't listen directly to this event. They will say one after one if the click is on their bounds and if no button have been fired it means that the click occured on the tiles. This method is little bit more difficult to implement but allows you to create a priority beetween buttons and so buttons would be able to have thei bounds intersecting !
Hope it helped you !

Generally speaking, concepts like button listeners are used in applications, not games.
Games generally work with a game loop which will have an update and a draw phase. During the update phase, user input is caught (well... more checked than caught, but I'll get to that in a moment) and interpreted.
So you'd just have one big mouse listener that would then check to see which button might be pressed or if the mouse is within the game area so the character should get the move command.
Of course technically that mouse listener shouldn't directly do anything, as it is bound to the swing thread. It should just change some variables (like: "left mouse button is down, position is x, y") that will then be checked in your update phase, which will then actually work with it. That way, you're no longer dependant on the swing thread (which isn't ideal for games anyway) and - almost as important - your game logic is completely predictable.
However, you'd still have to make sure your draw method gets called regularly and actually paints what and when you want it to paint. That's besides the question though, so I won't go into more detail on it.
Keep in mind that even swing isn't just some magical construct. When the mouse is clicked, swing will also iterate through all elements you have and see if one of them should get an event. If you write your own Buttons, that's what you're gonna have to do, too.
Giving each button it's own Mouse Listener will only get confusing the bigger your game gets. It will also destroy your game loop as mouse events can be thrown and caught at any time regardless of what you're doing at the moment. If your game logic happens in a seperate thread from swing (which it should), it's even possible that you get an event while you're processing something. Which will screw up your result.
This may also lead to a couple of very odd one-time errors that you don't understand and can't reproduce. So be very careful with that.

Related

Delayed "Typing Effect" Title [duplicate]

How can I implement Marquee effect in Java Swing
Here's an example using javax.swing.Timer.
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
/** #see http://stackoverflow.com/questions/3617326 */
public class MarqueeTest {
private void display() {
JFrame f = new JFrame("MarqueeTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String s = "Tomorrow, and tomorrow, and tomorrow, "
+ "creeps in this petty pace from day to day, "
+ "to the last syllable of recorded time; ... "
+ "It is a tale told by an idiot, full of "
+ "sound and fury signifying nothing.";
MarqueePanel mp = new MarqueePanel(s, 32);
f.add(mp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
mp.start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MarqueeTest().display();
}
});
}
}
/** Side-scroll n characters of s. */
class MarqueePanel extends JPanel implements ActionListener {
private static final int RATE = 12;
private final Timer timer = new Timer(1000 / RATE, this);
private final JLabel label = new JLabel();
private final String s;
private final int n;
private int index;
public MarqueePanel(String s, int n) {
if (s == null || n < 1) {
throw new IllegalArgumentException("Null string or n < 1");
}
StringBuilder sb = new StringBuilder(n);
for (int i = 0; i < n; i++) {
sb.append(' ');
}
this.s = sb + s + sb;
this.n = n;
label.setFont(new Font("Serif", Font.ITALIC, 36));
label.setText(sb.toString());
this.add(label);
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
#Override
public void actionPerformed(ActionEvent e) {
index++;
if (index > s.length() - n) {
index = 0;
}
label.setText(s.substring(index, index + n));
}
}
I know this is a late answer, but I just saw another question about a marquee that was closed because it was considered a duplicate of this answer.
So I thought I'd add my suggestion which takes a approach different from the other answers suggested here.
The MarqueePanel scrolls components on a panel not just text. So this allows you to take full advantage of any Swing component. A simple marquee can be used by adding a JLabel with text. A fancier marquee might use a JLabel with HTML so you can use different fonts and color for the text. You can even add a second component with an image.
Basic answer is you draw your text / graphic into a bitmap and then implement a component that paints the bitmap offset by some amount. Usually marquees / tickers scroll left so the offset increases which means the bitmap is painted at -offset. Your component runs a timer that fires periodically, incrementing the offset and invalidating itself so it repaints.
Things like wrapping are a little more complex to deal with but fairly straightforward. If the offset exceeds the bitmap width you reset it back to 0. If the offset + component width > bitmap width you paint the remainder of the component starting from the beginning of the bitmap.
The key to a decent ticker is to make the scrolling as smooth and as flicker free as possible. Therefore it may be necessary to consider double buffering the result, first painting the scrolling bit into a bitmap and then rendering that in one go rather than painting straight into the screen.
Here is some code that I threw together to get you started. I normally would take the ActionListener code and put that in some sort of MarqueeController class to keep this logic separate from the panel, but that's a different question about organizing the MVC architecture, and in a simple enough class like this it may not be so important.
There are also various animation libraries that would help you do this, but I don't normally like to include libraries into projects only to solve one problem like this.
public class MarqueePanel extends JPanel {
private JLabel textLabel;
private int panelLocation;
private ActionListener taskPerformer;
private boolean isRunning = false;
public static final int FRAMES_PER_SECOND = 24;
public static final int MOVEMENT_PER_FRAME = 5;
/**
* Class constructor creates a marquee panel.
*/
public MarqueePanel() {
this.setLayout(null);
this.textLabel = new JLabel("Scrolling Text Here");
this.panelLocation = 0;
this.taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
MarqueePanel.this.tickAnimation();
}
}
}
/**
* Starts the animation.
*/
public void start() {
this.isRunning = true;
this.tickAnimation();
}
/**
* Stops the animation.
*/
public void stop() {
this.isRunning = false;
}
/**
* Moves the label one frame to the left. If it's out of display range, move it back
* to the right, out of display range.
*/
private void tickAnimation() {
this.panelLocation -= MarqueePanel.MOVEMENT_PER_FRAME;
if (this.panelLocation < this.textLabel.getWidth())
this.panelLocaton = this.getWidth();
this.textLabel.setLocation(this.panelLocation, 0);
this.repaint();
if (this.isRunning) {
Timer t = new Timer(1000 / MarqueePanel.FRAMES_PER_SECOND, this.taskPerformer);
t.setRepeats(false);
t.start();
}
}
}
Add a JLabel to your frame or panel.
ScrollText s= new ScrollText("ello Everyone.");
jLabel3.add(s);
public class ScrollText extends JComponent {
private BufferedImage image;
private Dimension imageSize;
private volatile int currOffset;
private Thread internalThread;
private volatile boolean noStopRequested;
public ScrollText(String text) {
currOffset = 0;
buildImage(text);
setMinimumSize(imageSize);
setPreferredSize(imageSize);
setMaximumSize(imageSize);
setSize(imageSize);
noStopRequested = true;
Runnable r = new Runnable() {
public void run() {
try {
runWork();
} catch (Exception x) {
x.printStackTrace();
}
}
};
internalThread = new Thread(r, "ScrollText");
internalThread.start();
}
private void buildImage(String text) {
RenderingHints renderHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
BufferedImage scratchImage = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_RGB);
Graphics2D scratchG2 = scratchImage.createGraphics();
scratchG2.setRenderingHints(renderHints);
Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);
FontRenderContext frc = scratchG2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
Rectangle2D textBounds = tl.getBounds();
int textWidth = (int) Math.ceil(textBounds.getWidth());
int textHeight = (int) Math.ceil(textBounds.getHeight());
int horizontalPad = 600;
int verticalPad = 10;
imageSize = new Dimension(textWidth + horizontalPad, textHeight
+ verticalPad);
image = new BufferedImage(imageSize.width, imageSize.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
g2.setRenderingHints(renderHints);
int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, imageSize.width, imageSize.height);
g2.setColor(Color.GREEN);
tl.draw(g2, 0, baselineOffset);
// Free-up resources right away, but keep "image" for
// animation.
scratchG2.dispose();
scratchImage.flush();
g2.dispose();
}
public void paint(Graphics g) {
// Make sure to clip the edges, regardless of curr size
g.setClip(0, 0, imageSize.width, imageSize.height);
int localOffset = currOffset; // in case it changes
g.drawImage(image, -localOffset, 0, this);
g.drawImage(image, imageSize.width - localOffset, 0, this);
// draw outline
g.setColor(Color.black);
g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
}
private void runWork() {
while (noStopRequested) {
try {
Thread.sleep(10); // 10 frames per second
// adjust the scroll position
currOffset = (currOffset + 1) % imageSize.width;
// signal the event thread to call paint()
repaint();
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
}
}
public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}
public boolean isAlive() {
return internalThread.isAlive();
}
}
This is supposed to be an improvement of #camickr MarqueePanel. Please see above.
To map mouse events to the specific components added to MarqueePanel
Override add(Component comp) of MarqueePanel in order to direct all mouse events of the components
An issue here is what do do with the MouseEvents fired from the individual components.
My approach is to remove the mouse listeners form the components added and let the MarqueePanel redirect the event to the correct component.
In my case these components are supposed to be links.
#Override
public Component add(Component comp) {
comp = super.add(comp);
if(comp instanceof MouseListener)
comp.removeMouseListener((MouseListener)comp);
comp.addMouseListener(this);
return comp;
}
Then map the component x to a MarqueePanel x and finally the correct component
#Override
public void mouseClicked(MouseEvent e)
{
Component source = (Component)e.getSource();
int x = source.getX() + e.getX();
int y = source.getY();
MarqueePanel2 marqueePanel = (MarqueePanel2) ((JComponent)e.getSource()).getParent();
double x2 = marqueePanel.getWidth();
double x1 = Math.abs(marqueePanel.scrollOffset);
if(x >= x1 && x <= x2)
{
System.out.println("Bang " + x1);
Component componentAt = getComponentAt(x+marqueePanel.scrollOffset, y);
if(comp instanceof MouseListener)
((MouseListener) componentAt).mouseClicked(e);
System.out.println(componentAt.getName());
}
else
{
return;
}
//System.out.println(x);
}

Drawing multiple connected lines in JApplet by using paintComponent

I am trying to create application where user can draw lines in Canvas. User can select direction from dropdown list and enter length of line. First line starts from center of Canvas, the next one starts where the previous one ended and so on - user can draw multiple lines one by one and all lines are connected.
I have two classes - TurtleApplet which creates GUI and Canvas with program logic:
public class TurtleApplet extends JApplet implements ActionListener
{
private JComboBox direction;
private JRadioButton activeButton, passiveButton;
private Button drawButton;
private ButtonGroup group;
private TextField pixels;
private Canvas canvas;
private JPanel panel;
private JPanel panelRadio;
private Button quitPr;
public void init()
{
//directions
String[] directionStrings = { "Right", "Left", "Up", "Down"};
direction = new JComboBox(directionStrings);
//direction.setSelectedIndex(4);
//Buttons
activeButton = new JRadioButton("Aktīvs");
passiveButton = new JRadioButton("Neaktīvs");
quitPr = new Button("Iziet");
//Canvas
canvas = new Canvas();
//canvas.setSize(600, 500);
//canvas.setBackground(Color.red);
canvas.setBorder(BorderFactory.createTitledBorder("Turtle drawing"));
//Panels
panel = new JPanel();
panelRadio =new JPanel();
panel.setLayout(new FlowLayout());
panelRadio.setLayout(new FlowLayout());
//actionListener
activeButton.addActionListener(this);
passiveButton.addActionListener(this);
activeButton.setSelected(true);
quitPr.addActionListener(this);
//Add radiobuttons
group = new ButtonGroup();
group.add(activeButton);
group.add(passiveButton);
//Add Buttons to panel
panelRadio.add(activeButton);
panelRadio.add(passiveButton);
//textfield
pixels = new TextField(12);
//Draw button
drawButton = new Button("Zīmēt");
drawButton.addActionListener(this);
direction.addActionListener(this);
panel.add(panelRadio);
panel.add(pixels);
panel.add(direction);
panel.add(drawButton);
panel.add(quitPr);
getContentPane().add(panel,"North");
getContentPane().add(canvas, "Center");
setSize(650,550);
}
public void actionPerformed( ActionEvent e)
{
if ( e.getSource() == activeButton ) {
drawButton.setVisible(true);
pixels.setEditable(true);
} else if (e.getSource() == passiveButton) {
drawButton.setVisible(false);
pixels.setEditable(false);
} else if(e.getSource() == quitPr){
System.exit(0);
}else if(e.getSource() == drawButton){
int y = Integer.parseInt(pixels.getText());
canvas.addPatt(direction.getSelectedIndex(), Integer.parseInt(pixels.getText()));
repaint();
}
//repaint();
}
}
public class Canvas extends JPanel {
private static final int RIGHT=0, LEFT=1, UP=2, DOWN=3;
public static final int WIDTH=600, HEIGHT=500;
private int direction = 0 ;
private int pixels;
//rivate List points;
public Polygon t = new Polygon();
//public Dimension d = getSize();
public int x = WIDTH/2;
public int y = HEIGHT/2;
public Canvas() {
setSize(WIDTH, HEIGHT);
}
public void addPatt(int pat, int lev) {
direction = pat;
pixels = lev;
}
public void paintComponent(Graphics g) {
switch (direction) {
case LEFT:
drawLineLeft(g, pixels);
break;
case RIGHT:
drawLineRight(g, pixels);
break;
case UP:
drawLineUp(g, pixels);
break;
case DOWN:
drawLineDown(g, pixels);
break;
}
}
private void drawLineLeft(Graphics g, int pix){
if(pix > 0){
g.drawLine(x, y, x-10*pix, y);//left
x =x -10*pix;
}
}
private void drawLineUp(Graphics g, int pix){
if(pix > 0){
g.drawLine(x, y, x, y-10*pix);//up
y = y-10*pix;
}
}
private void drawLineRight(Graphics g, int pix){
if(pix > 0){
g.drawLine(x, y, x+10*pix, y);//right
x = x+10*pix;
}
}
private void drawLineDown(Graphics g, int pix){
if(pix > 0){
g.drawLine(x, y, x, y+10*pix);// down
y = y+10*pix;
}
}
}
Applet works, but the problem is to save previous lines, when new one is drawn. When user enters direction and length of line and presses the button, new line appears on the screen, but the previous one disappears. I know that the problem is with paintComponent method, but I don't know how to exactly correct my code to make all lines visible. I was suggested to store point coordinates in array and then paint lines by looping throught array in paintComponent, but I don't know how to achieve this. Maybe there is better solution?
As I said in your last question, the case state is like an if-else, you only ever allow it to draw a single line. You need to maintain a List of "lines" which can be iterated over each time the paintComponent method is called
Because a line is represented by multiple properties, it's best to encapsulate that information into a simple class or POJO
public enum Direction {
UP, DOWN, LEFT, RIGHT
}
public class Line {
private Direction direction;
private int length;
public Line(Direction direction, int length) {
this.direction = direction;
this.length = length;
}
public Direction getDirection() {
return direction;
}
public int getLength() {
return length;
}
}
Here, I've separated the direction properties into a simple enum, this allows you to reference the properties more easily elsewhere within your program
Then you maintain a List of Lines, which when paintComponent is called, you simply re-iterate over and repaint...
public class Canvas extends JPanel {
public static final int WIDTH = 600, HEIGHT = 500;
public int x = WIDTH / 2;
public int y = HEIGHT / 2;
private List<Line> lines;
public Canvas() {
lines = new ArrayList<>(25);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
}
public void addPatt(Direction direction, int length) {
lines.add(new Line(direction, length));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Line line : lines) {
switch (line.getDirection()) {
case UP:
drawLineUp(g, line.getLength());
break;
case DOWN:
drawLineDown(g, line.getLength());
break;
case LEFT:
drawLineLeft(g, line.getLength());
break;
case RIGHT:
drawLineDown(g, line.getLength());
break;
}
}
}
private void drawLineLeft(Graphics g, int pix) {
if (pix > 0) {
g.drawLine(x, y, x - 10 * pix, y);//left
x = x - 10 * pix;
}
}
private void drawLineUp(Graphics g, int pix) {
if (pix > 0) {
g.drawLine(x, y, x, y - 10 * pix);//up
y = y - 10 * pix;
}
}
private void drawLineRight(Graphics g, int pix) {
if (pix > 0) {
g.drawLine(x, y, x + 10 * pix, y);//right
x = x + 10 * pix;
}
}
private void drawLineDown(Graphics g, int pix) {
if (pix > 0) {
g.drawLine(x, y, x, y + 10 * pix);// down
y = y + 10 * pix;
}
}
}
Remember, painting in Swing is destructive, you are expected to repaint the entire state of the component every time the paintComponent is called.
See Painting in AWT and Swing and Performing Custom Painting for more details about how painting works
I was suggested to store point coordinates in array and then paint lines by looping throught array in paintComponent, but I don't know how to achieve this.
It depends on your exact requirement.
If you need the ability to add/remove lines, then this might be the best approach.
If you only need the ability to add lines then you might want to paint the lines directly to a BufferedImage and then just display the BufferedImage as an Icon on a JLabel.
Check out Custom Painting Approaches which compares the two approaches and provides working examples of both approaches.

Resize 2 rectangles in a jpanel

I can not resize the two rectangles, but only the right one. What should I add to my code ?
I want also that when the lower edge of the left rectangle is moved, it also moves the upper edge of the right rectangle.
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
public class Resizing extends JPanel {
Rectangle rect = new Rectangle(100,100,150,150);
Rectangle rect2 = new Rectangle(300,100,150,150);
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(new Color(0, 0, 200));
g2.fill(rect);
g2.setColor(new Color(0, 0, 200));
g2.fill(rect2);
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Resizing essai = new Resizing();
Resizer1 rectangle = new Resizer1(essai);
essai.addMouseListener(rectangle);
essai.addMouseMotionListener(rectangle);
f.add(essai);
Resizing test2 = new Resizing();
Resizer2 rectangle2 = new Resizer2(test2);
test2.addMouseListener(rectangle2);
test2.addMouseMotionListener(rectangle2);
f.add(test2);
f.setSize(600,400);
f.setLocation(100,100);
f.setVisible(true);
}
}
class Resizer1 extends MouseAdapter {
Resizing component;
boolean dragging = false;
// Give user some leeway for selections.
final int PROX_DIST = 3;
public Resizer1(Resizing r) {
component = r;
}
#Override
public void mousePressed(MouseEvent e) {
if(component.getCursor() != Cursor.getDefaultCursor()) {
// If cursor is set for resizing, allow dragging.
dragging = true;
}
}
#Override
public void mouseReleased(MouseEvent e) {
dragging = false;
}
#Override
public void mouseDragged(MouseEvent e) {
if(dragging){
Point p = e.getPoint();
Rectangle r = component.rect;
int type = component.getCursor().getType();
int dy = p.y - r.y;
switch(type) {
case Cursor.N_RESIZE_CURSOR:
int height = r.height - dy;
r.setRect(r.x, r.y+dy, r.width, height);
break;
case Cursor.S_RESIZE_CURSOR:
height = dy;
r.setRect(r.x, r.y, r.width, height);
break;
default:
System.out.println("unexpected type: " + type);
}
component.repaint();
}
}
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
if(!isOverRect(p)) {
if(component.getCursor() != Cursor.getDefaultCursor()) {
// If cursor is not over rect reset it to the default.
component.setCursor(Cursor.getDefaultCursor());
}
return;
}
// Locate cursor relative to center of rect.
int outcode = getOutcode(p);
Rectangle r = component.rect;
switch(outcode) {
case Rectangle.OUT_TOP:
if(Math.abs(p.y - r.y) < PROX_DIST) {
component.setCursor(Cursor.getPredefinedCursor(
Cursor.N_RESIZE_CURSOR));
}
break;
case Rectangle.OUT_BOTTOM:
if(Math.abs(p.y - (r.y+r.height)) < PROX_DIST) {
component.setCursor(Cursor.getPredefinedCursor(
Cursor.S_RESIZE_CURSOR));
}
break;
default: // center
component.setCursor(Cursor.getDefaultCursor());
}
}
/**
* Make a smaller Rectangle and use it to locate the
* cursor relative to the Rectangle center.
*/
private int getOutcode(Point p) {
Rectangle r = (Rectangle)component.rect.clone();
r.grow(-PROX_DIST, -PROX_DIST);
return r.outcode(p.x, p.y);
}
/**
* Make a larger Rectangle and check to see if the
* cursor is over it.
*/
private boolean isOverRect(Point p) {
Rectangle r = (Rectangle)component.rect.clone();
r.grow(PROX_DIST, PROX_DIST);
return r.contains(p);
}
}
class Resizer2 extends MouseAdapter {
Resizing component;
boolean dragging = false;
// Give user some leeway for selections.
final int PROX_DIST = 3;
public Resizer2(Resizing r) {
component = r;
}
#Override
public void mousePressed(MouseEvent e2) {
if(component.getCursor() != Cursor.getDefaultCursor()) {
// If cursor is set for resizing, allow dragging.
dragging = true;
}
}
#Override
public void mouseReleased(MouseEvent e2) {
dragging = false;
}
#Override
public void mouseDragged(MouseEvent e2) {
if(dragging){
Point p = e2.getPoint();
Rectangle r = component.rect2;
int type = component.getCursor().getType();
int dy = p.y - r.y;
switch(type) {
case Cursor.N_RESIZE_CURSOR:
int height = r.height - dy;
r.setRect(r.x, r.y+dy, r.width, height);
break;
case Cursor.S_RESIZE_CURSOR:
height = dy;
r.setRect(r.x, r.y, r.width, height);
break;
default:
System.out.println("unexpected type: " + type);
}
component.repaint();
}
}
#Override
public void mouseMoved(MouseEvent e2) {
Point p = e2.getPoint();
if(!isOverRect(p)) {
if(component.getCursor() != Cursor.getDefaultCursor()) {
// If cursor is not over rect reset it to the default.
component.setCursor(Cursor.getDefaultCursor());
}
return;
}
// Locate cursor relative to center of rect.
int outcode = getOutcode(p);
Rectangle r = component.rect2;
switch(outcode) {
case Rectangle.OUT_TOP:
if(Math.abs(p.y - r.y) < PROX_DIST) {
component.setCursor(Cursor.getPredefinedCursor(
Cursor.N_RESIZE_CURSOR));
}
break;
case Rectangle.OUT_BOTTOM:
if(Math.abs(p.y - (r.y+r.height)) < PROX_DIST) {
component.setCursor(Cursor.getPredefinedCursor(
Cursor.S_RESIZE_CURSOR));
}
break;
default: // center
component.setCursor(Cursor.getDefaultCursor());
}
}
/**
* Make a smaller Rectangle and use it to locate the
* cursor relative to the Rectangle center.
*/
private int getOutcode(Point p) {
Rectangle r = (Rectangle)component.rect2.clone();
r.grow(-PROX_DIST, -PROX_DIST);
return r.outcode(p.x, p.y);
}
/**
* Make a larger Rectangle and check to see if the
* cursor is over it.
*/
private boolean isOverRect(Point p) {
Rectangle r = (Rectangle)component.rect2.clone();
r.grow(PROX_DIST, PROX_DIST);
return r.contains(p);
}
}
Problems I see on inspection of your code:
You are adding 2 JPanels to your JFrame, but only one will show since they're being added in a default fashion to a BorderLayout-using container. Edit: I see now why you're doing this, but as explained below, you should not be doing this. Only create one Resizing object and add it once to the JFrame. The one Resizing will show both rectangles, and the single MouseAdapter should be coded to allow you to interact with both rectangles.
You are not making your Rectangle fields private and are allowing outside classes (namely Resizer1) to directly access and minipulate your fields. You'd be better off using public methods that allow other classes to selectively query your class for its state or to change your class's state.
Don't use two Resizer classes, Resizer1 and Resizer2, and it is this as well as your adding two Resizing objects to the JFrame that are in fact the main reason for your problems. Instead, use just one Resizer class, and use it as a single MouseAdapter added to a single Resizing object. Then in this single class, allow both rectangles to be changed.
Please post comments if you have any questions.
You ask:
Thank you, but how can I allow both rectangles to be changed ? In class Resizer, there is only one component (r) ?
There is only one component, but it holds two rectangles, and it knows the difference between the two rectangles since it has two rectangle variables.
Edit 2
Consider:
Editing your original question and adding your new code to the bottom, deleting your redundant new question.
Creating a non-GUI object, say called MyRectangle, that holds a Rectangle object.
This new class can have methods that allow your to pass in a Point or an x and y positions, and return information to let the calling code know if the mouse is over the top or bottom edge (your code already does this, so this should be no problem for you).
This new class will have mutator (setter) methods that allow outside classes set its rectangle y position and height.
The new class will have a fill method that accepts a Graphics2D parameter and uses it to fill the Rectangle that it holds.
Then give your Resizer class a List<MyRectangle>, actually an ArrayList of them, say called myRectangleList, and you can add two MyRectangle objects to it
Then give Resizer a getMyRectangleList method that returns the list.
Then in your MouseAdapter, iterate through the List to see if the mouse is over an edge
etc...
e.g.,
class MyRectangle {
private Rectangle rect;
private String name;
public MyRectangle(int x, int y, int width, int height, String name) {
rect = new Rectangle(x, y, width, height);
this.name = name;
}
public void fill(Graphics2D g2) {
g2.fill(rect);
}
public int getOutcode(Point p) {
// return ... do what you need to figure tihs out
}
public boolean isOverRect(Point p) {
// return ... do what you need to figure tihs out
}
public String getName() {
return name;
}
#Override
public String toString() {
return name + " " + rect.toString();
}
}
And then something like:
public class Resizing2 extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = 400;
private static final Color RECT_COLOR = Color.blue;
private List<MyRectangle> rectangleList = new ArrayList<>();
public Resizing2() {
rectangleList.add(new MyRectangle(100, 100, 150, 150, "Rect 1"));
rectangleList.add(new MyRectangle(300, 100, 150, 150, "Rect 2"));
}
public List<MyRectangle> getRectangleList() {
return rectangleList;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(RECT_COLOR);
for (MyRectangle rect : rectangleList) {
rect.fill(g2);
}
}
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
// ..... etc...

How do i switch images using mouse events?

Im working on an assignment where an image moves around and when the user clicks on the image it will change and as soon as it moves again via timer class, the images chages back to the original. As of now I can click the image to change it, but it wont change back when it is time to move again. Is there a way to change back after it moves?
Here is my code
Main:
import java.awt.*;
import javax.swing.*;
public class Catch_The_Creature
{
public static void main(String[] args)
{
JFrame frame = new JFrame("Catch the Creature");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JOptionPane.showMessageDialog(frame, "Catch Pikachu!");
frame.getContentPane().add(new Creature());
frame.pack();
frame.setVisible(true);
}
}
Panel:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Creature extends JPanel
{
private static final int DELAY=900;
private Random generator = new Random();
private ImageIcon image, image1;
private Timer timer;
private int x, y;
private int catchCount=0;
public Creature()
{
image = new ImageIcon ("pikachu.png");
image1 = new ImageIcon ("pokeball.png");
timer = new Timer(DELAY, new MoveListener());
x = generator.nextInt( 1900 );
y = generator.nextInt(1000);
addMouseListener (new MouseClickedListener());
setBackground (Color.green);
setPreferredSize(new Dimension(1900,1000));
timer.start();
}
//Draws the image.
public void paintComponent(Graphics page)
{
super.paintComponent(page);
image.paintIcon (this, page, x, y);
page.drawString("Pikachus Captured: " + catchCount, 10, 35);
setFont(new Font("Arial", Font.BOLD,35));
}
//Method for moving the image.
public void move()
{
timer.start();
x = generator.nextInt( 1900 );
y = generator.nextInt(1000);
if (timer.isRunning())
{
x = generator.nextInt( 1900 );
y = generator.nextInt(1000);
}
repaint();
}
//Method for getting the number of times caught.
public int getCatchCount()
{
return catchCount;
}
//Makes the image move
private class MoveListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
move();
repaint();
}
}
//Records when the user clicks the image.
private class MouseClickedListener extends MouseAdapter
{
public void mouseClicked(MouseEvent event)
{
if((event.getButton() == MouseEvent.BUTTON1) && between(event.getX(), x, x + image.getIconWidth()) && between(event.getY(), y, y + image.getIconHeight()))
{
System.out.println("CAUGHT ONE!");
catchCount++;
move();
image=image1;
}
}
}
private static boolean between(int x, int lower, int upper)
{
return (x >= lower) && (x <= upper);
}
}
i hope i understand what do you try to achieve. first you need 3 Images:
private ImageIcon imageToDraw, image1, image2;
Creature will look like this now:
public Creature()
{
image1 = new ImageIcon ("pikachu.png");
image2 = new ImageIcon ("pokeball.png");
imageToDraw = image1;
...
}
in move() you should set the image to image1:
public void move()
{
imageToDraw = image1;
timer.start();
x = generator.nextInt( 1900 );
...
}
dont forget to change image to imageToDraw in paint():
public void paintComponent(Graphics page)
{
super.paintComponent(page);
imageToDraw.paintIcon (this, page, x, y);
...
}
remove move(); from onclick-event and change image to imageToDrwa in click-action:
public void mouseClicked(MouseEvent event)
{
if((event.getButton() == MouseEvent.BUTTON1) && between(event.getX(), x, x + image.getIconWidth()) && between(event.getY(), y, y + image.getIconHeight()))
{
System.out.println("CAUGHT ONE!");
catchCount++;
//move(); should be removed
imageToDraw=image1;
}
}
edit:move(); removed from onclick-event
You need a way to paint either Image. Right now you are just assigning image to image1 and when you do that the Image that is from "pikachu.png" disappears.
Without restructuring too much you can just make a flag for which one to paint:
/* as a field */
private boolean justCaptured;
Now you have to change your painting logic a little:
if (justCaptured) {
image1.paintIcon (this, page, x, y);
} else {
image.paintIcon (this, page, x, y);
}
Now the first thing you do is instead of reassigning image, set the flag to true:
System.out.println("CAUGHT ONE!");
catchCount++;
justCaptured = true;
move();
There's one last thing you need to do which is to set the flag back to false for the next draw. There are some different ways to approach this but the simplest I see without refactoring a number of things is to queue it for later:
System.out.println("CAUGHT ONE!");
catchCount++;
justCaptured = true;
move();
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
justCaptured = false;
}
});
Using invokeLater will enqueue it at the end of EventQueue and it will be changed after previously queued tasks complete. So after the mouse event exits and then after the repaint you've just requested in move.
"I post all of it in case anyone would like to give advice on other things general things."
You're calling timer.start in move and this doesn't appear necessary. Timers repeat by default.
move generates x and y twice. Since you call timer.start in the constructor, timer.isRunning will always be true.
MoveListener calls repaint after calling move but move also calls repaint.
"Also so people can test it themselves if they would like to."
We can't run it because we don't have your images. If you want to post it so we can run it change the code so it works without them. Otherwise we have to go and change it somehow ourselves. Here's a static method that returns a blank ImageIcon:
public static ImageIcon createBlankIcon(int w, int h, Color color) {
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(color);
g2d.fillRect(0, 0, w, h);
g2d.dispose();
return new ImageIcon(img);
}
You can substitute something like that for content, eg: image = getBlankIcon(50, 50, Color.RED);.

2D Game camera logic

I'm trying to implement a camera for a 2D game that I'm making... The goal will to have the cam keep the player in the center and the sprites relative to the camera.
To get the hang of normalocity's post, I tried starting off simple by making a Camera Test project, where I'd simulate a camera by drawing a sprite to a JPanel, and moving a "camera" object (which is the JPanel) around and setting the sprite's x,y relative to that.
The Camera, as I said, is the JPanel... and I've added a "world", which is a class with an x,y of 0,0, and w=1000, h=1000. I've included the sprite's location relative to the world as well as the camera. When I move the camera up, the sprite moves down and the player stays in the middle as expected..
But if I keep pressing down, the sprite seems to keep drawing over itself.
My questions are:
Am I on the right track in implementing a camera given the code below?
Why does the sprite start to draw over itself there? It should just disappear off the viewPort/JPanel
Thanks!
Now with PaintComponent(g) added, my JPanel bg color of gray now slides off. Is this supposed to happen?
EDIT: SSCCE of my program:
Main Class:
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class MainSSCCE extends JFrame {
static MainSSCCE runMe;
public MainSSCCE() {
JFrame f = new JFrame("Camera Test");
CameraSSCCE cam = new CameraSSCCE(0, 0, 500, 500);
f.add(cam);
f.setSize(cam.getWidth(), cam.getHeight());
f.setVisible(true);
f.setResizable(false);
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
f.setLocation( (screensize.width - f.getWidth())/2,
(screensize.height - f.getHeight())/2-100 );
}
public static void main(String[] args) {
runMe = new MainSSCCE();
}
}
Camera Class:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JPanel;
//Camera is the JPanel that will draw all objects... each object location will be in relation to the World
public class CameraSSCCE extends JPanel implements KeyListener {
//add world to camera...
private static final long serialVersionUID = 1L;
private int camX, camY, camH, camW;
private SpriteSSCCE sprite;
private PlayerSSCCE player;
private WorldSSCCE world;
public CameraSSCCE(int x, int y, int w, int h) {
camX = x;
camY = y;
camW = w;
camH = h;
sprite = new SpriteSSCCE(this, 300, 300, 20, 20);
player = new PlayerSSCCE(this, camW/2, camH/2, 25, 40);
world = new WorldSSCCE(this, 0, 0, 1000, 1000);
addKeyListener(this);
setFocusable(true);
}
public int getWidth() {
return camW;
}
public int getHeight() {
return camH;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//cam is 500 x 500
g.setColor(Color.gray);
g.fillRect(camX, camY, camW, camH);
//draw sprite at JPanel location if in camera sight
if (((sprite.getX()-camX) >= camX) && ((sprite.getX()-camX) <= (camX+camW)) && ((sprite.getY()-camY) >= camY) && ((sprite.getY()-camY) <= (camY+camH))) {
g.setColor(Color.green);
g.fillRect(sprite.getX()-camX, sprite.getY()-camY, 20, 20);
//Cam Sprite Location
g.setColor(Color.white);
g.drawString("Camera Sprite Location: (" + (sprite.getX()-camX) + ", " + (sprite.getY()-camY) + ")", sprite.getX()-camX, sprite.getY()-camY);
}
//Player location (center of Camera... Camera follows player)
g.setColor(Color.cyan);
g.fillRect(player.getX()-player.getWidth(), player.getY()-player.getWidth(), player.getWidth(), player.getHeight());
g.setColor(Color.white);
//World Sprite Location
g.drawString("World Sprite Location: (" + sprite.getX() + ", " + sprite.getY() + ")", sprite.getX(), sprite.getY());
//Cam Player Location
g.drawString("Cam Player Location: (" + (camW/2-player.getWidth()) + ", " + (camH/2-player.getHeight()) + ")", camW/2-player.getWidth(), camH/2-player.getHeight());
}
public void keyPressed(KeyEvent e) {
//move camera right in relation to World
if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
camX+=5;
}
//move camera left in relation to World
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
camX-=5;
}
//move camera up in relation to World
if (e.getKeyCode() == KeyEvent.VK_UP) {
camY-=5;
}
//move camera down in relation to World
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
camY+=5;
}
repaint();
}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
}
World Class:
public class WorldSSCCE {
private int x, y, w, h;
private CameraSSCCE camera;
public WorldSSCCE(CameraSSCCE cam, int x, int y, int w, int h) {
camera = cam;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public int getWidth() {
return this.w;
}
public int getHeight() {
return this.h;
}
}
Player Class:
import java.awt.Dimension;
public class PlayerSSCCE {
private int x, y, w, h;
private CameraSSCCE cam;
public PlayerSSCCE(CameraSSCCE cm, int x, int y, int w, int h) {
cam = cm;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public int getWidth() {
return this.w;
}
public int getHeight() {
return this.h;
}
public void setX(int val) {
this.x += val;
}
public void setY(int val) {
this.y += val;
}
}
Sprite Class:
import java.awt.Color;
import java.awt.Graphics;
public class SpriteSSCCE {
private int xLoc, yLoc, width, height;
private CameraSSCCE world;
public SpriteSSCCE(CameraSSCCE wld, int x, int y, int w, int h) {
xLoc = x;
yLoc = y;
width = w;
height = h;
world = wld;
}
public int getX() {
return xLoc;
}
public int getY() {
return yLoc;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public void paintComponent(Graphics g) {
g.setColor(Color.green);
g.fillRect(xLoc, yLoc, width, height);
}
}
1) You have not honored the paint chain by calling super.paintComponent(g) in paintComponent(..):
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//do drawing here
}
As per Java docs:
protected void paintComponent(Graphics g)
Further, if you do not invoker super's implementation you must honor
the opaque property, that is if this component is opaque, you must
completely fill in the background in a non-opaque color. If you do not
honor the opaque property you will likely see visual artifacts.
2) Also notice the #Override annotation I added and the fact that I changed public modifier to protected as thats what the access level is defined as in the implementation class which we should keep unless for a specific reason.
3) Also Swing uses Keybindings have a read on How to Use Key Bindings
4) Also have a read on Concurrency in Swing specifically on The Event Dispatch Thread which dictates all swing components be created on EDT via SwingUtillities.invokeXXX(..) block:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
//create and manipulate swing components here
}
});
5) You extend the JFrame class and create an instance, this is not what you want rather remove the extends JFrame from the class declaration:
public class MainSSCCE extends JFrame { //<-- Remove extends JFrame
public MainSSCCE() {
JFrame f = new JFrame("Camera Test");//<-- instance is created here
}
}
Your world is a virtual area larger than the screen (or your jpanel for what matters). All objects' positions are relative to the world. Let's call them absolute coordinates.
Your camera is a small rectangular portion of the world (your panel). By moving it you see different world portions. If you could move the camera like in the post you link to, then at some point you would not be able to see neither the player nor the other sprite.
Since your goal is to keep the player centered on the screen what does this mean for our world? This means that the player and the camera are moving together in relation to the world.
Given the above it does not make sense to draw a camera sprite as in your first screenshot. The camera sprite should be either invisible or it should be drawn in the same position with the player sprite. Nor it makes sense to change the camera's absolute coordinates without changing the player's. Those two are moving together. (take this into account in your keyPressed() methods)
Now when you are drawing, you are drawing from the camera's point of view (or in other words in the camera's coordinate system). From that point of view, the camera always see a rectangle of (0, 0, cameraWidth, cameraHeight). That's what you should use when clearing the area with gray color. This will fix your moving background issue. Since camera and player always have the same absolute coordinates the player will always be in the same place (this is what we want). The rest of the sprites will be seen relative to camera.
For each one of them you translate them in the camera's coordinate system when you do (sprite.x - cam.x) and (sprite.y - cam.y). Since they are translated, you only need to check if they are inside the camera's rectangle (0, 0, cameraWidth, cameraHeight). If they are you go ahead and draw them. If not ignore them.
I hope that helps
Note: cameraWidth, cameraHeight are your jpanel's dimensions

Categories