1

I am currently making a tile based game. Everything so far is working fine. However, I want the player to be able to add objects, like stone or wood to the screen when he/she presses the mouse button. I attempted this myself but it is not working. Here is what I have done, but is not working:

This is my KeyInput class, where all the keyboard and mouse events take place.

    public static ArrayList<StoneTile> sTile = new ArrayList<StoneTile>();

public KeyInput(Handler handler) {
    this.handler = handler;

}


public void tick(LinkedList<Square> object) {}


public void mousePressed(MouseEvent e){
    int mx = e.getX();
    int my = e.getY();
    System.out.println("Pressed (X,Y): " + mx + " " + my);

    sTile.add(new StoneTile(1,mx,my));

    if(sTile.add(new StoneTile(1,mx,my))){
        System.out.println("ADDED");
    }
}
public void mouseReleased(MouseEvent e){
    System.out.println("Released");
}

Here is my StoneTile class, this is what I want to add to screen:

    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Rectangle;
    import java.util.LinkedList;

    public class StoneTile extends Tile {

Textures tex;
public StoneTile(int id,int x,int y) {
    super(Textures.stoneArray[0], id);
    Tile.x = x;
    Tile.y = y;
}

public static void main(String[] args) {

}


public Rectangle getBounds(){
    return new Rectangle(x,y,Tile.TILEWIDTH,Tile.TILEHEIGHT);
  }

}

The Textures.stoneArray[0] is simply the image that I want to add to the screen. The Tile.(instance variable, like x, y, TILEWIDTH, and TILEHEIGHT) is simply a Tile class that contains all the render methods for the tiles (grass, stone, etc). If anything is unclear I will clarify or if you need any code provided, then I will add it in.

Note - The ArrayList was just an idea that I had in mind, if there are more efficient ways of doing this or any better ideas, I am open to all of them.

Here is where I set the MouseListener. I set it in an init() method and then called in a run() method (last line):

private void init() {
    BufferedImageLoader loader = new BufferedImageLoader();

    level = loader.loadImage("level.png");

    world = new worldLoader("res/worlds/world1.txt");

    handler = new Handler();
    WIDTH = getWidth();
    HEIGHT = getHeight();

    cam = new Camera(handler, Game.WIDTH / 2, Game.HEIGHT / 2);
    setWIDTH(getWidth());
    setHEIGHT(getHeight());
    tex = new Textures();
    //backGround = loader.loadImage("/background.jpg");


    handler.addObject(new Coin(100, 100, handler, ObjectId.Coin));
    handler.addObject(new newStoneTile(20,20,ObjectId.newStoneTile));
    handler.addObject(new player_Square(100,100, handler, ObjectId.player_Square));
    //handler.addObject(new OneUp(300, 150, handler, ObjectId.OneUp));

    this.addKeyListener(new KeyInput(handler));
    this.addMouseListener(new KeyInput(handler));
}

jcomponent, is this what you meant?

public class Window {   

private static final long serialVersionUID = -6482107329548182911L;
static final int DimensionX = 600;
static final int DimensionY = 600;

public Window(int w, int h, String title, Game game) {
    game.setPreferredSize(new Dimension(w, h));
    game.setMaximumSize(new Dimension(w, h));
    game.setMinimumSize(new Dimension(w, h));
    JFrame frame = new JFrame();
    frame.add(game);
    frame.pack();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);

    game.start();




}

}

  • Please could you add the code for where you set the `MouseListener` and how you update the relevant `JComponent` (e.g. the overridden `paintComponent(Graphics g)`. I assume you're using Swing. – d.j.brown Jul 01 '17 at 09:23
  • I added the area where I set the MouseListener, but I was not too sure what you mean by "update the relevant JComponent" –  Jul 01 '17 at 13:07
  • @d.j.brown could you help –  Jul 01 '17 at 17:24
  • I assume somewhere you're painting all this by overriding `paintComponent`, if so, when the mouse is pressed (and you update the state), you need to call `repaint` to trigger a new paint cycle – MadProgrammer Jul 01 '17 at 21:51
  • 1) For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). 2) One way to get image(s) for an example is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). – Andrew Thompson Jul 02 '17 at 04:31

1 Answers1

0

Perhaps the best way to try and answer this question is to give a small example.

Essentially what needs to happen is the following (assuming my understanding of the problem is correct):

  1. User clicks on the JComponent/JPanel to determine where to place a Tile. This will cause a MouseEvent that needs to be listened for and handled.
  2. The JComponent/JPanel needs a MouseListener implementation which will create a new Tile object and add it to the List of the Tile objects. Once this is complete the JComponent/JPanel needs to know to repaint(). You do not override repaint() but rather paintComponent(Graphics g), which will be called by repaint() (eventually).
  3. The paintComponent(Graphics g) method will iterate over the List of Tile objects, drawing them to the JComponent/JPanel using the Graphics context for the component.

To illustrate this I have simplified your problem. Note this isn't the best way to solve the problem since the Model (game logic) and the GUI should be separated, ideally using Model View Controller / Observer pattern.

First and most importantly is the GamePanel class, which extends JPanel. It's sole role in this example is to display the game graphically and handle mouse clicks. i.e. handling the list of tasks noted above.

GamePanel

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JPanel;

public class GamePanel extends JPanel {

    private List<Tile> tiles; // Stores the Tile objects to be displayed

    /**
     * Sole constructor for GamePanel.
     *
     * @param width the width of the game panel
     * @param height the height of the game panel
     */
    public GamePanel(int width, int height) {
        tiles = new ArrayList<>();

        setPreferredSize(new Dimension(width, height));

        // Implement mouse events for when the JPanel is 'clicked', only using the
        // mouse released operation in this case.
        addMouseListener(new MouseListener() {

            @Override
            public void mouseReleased(MouseEvent e) {
                // On mouse release, add a StoneTile (in this case) to the tiles List
                tiles.add(new StoneTile(e.getX(), e.getY()));

                // Repaint the JPanel, calling paint, paintComponent, etc.
                repaint();
            }

            @Override
            public void mouseClicked(MouseEvent e) {
                // Do nothing
            }

            @Override
            public void mousePressed(MouseEvent e) {
                // Do nothing
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                // Do nothing
            }

            @Override
            public void mouseExited(MouseEvent e) {
                // Do nothing
            }
        });
    }

    /**
     * Draws the Tiles to the Game Panel.
     *
     * @param g the Graphics context in which to paint
     */
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g); // Make sure you do this

        // For this example, using black as the color to draw
        g.setColor(Color.BLACK);

        // Iterate over the tile list and draw them to the JPanel
        for (Tile tile : tiles) {
            Rectangle tileRect = tile.getBounds();
            g.fillRect(tileRect.x, tileRect.y, tileRect.width, tileRect.height);
        }
    }
}

Second is the GameFrame, which extends JFrame. This is just a basic JFrame which adds a GamePanel object. I've also included the main method which will ensure the GUI is initialized on the Event Dispatch Thread.

GameFrame

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class GameFrame extends JFrame {

    private static final String TITLE = "Tile Game"; // Game Frame Window Title

    private final JPanel gamePanel;

    /**
     * Sole constructor for GameFrame.
     *
     * @param width the width of the game in pixels
     * @param height the height of the game in pixels
     */
    public GameFrame(int width, int height) {
        gamePanel = new GamePanel(width, height);
    }

    /**
     * Performs final configuration and shows the GameFrame.
     */
    public void createAndShow() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setTitle(TITLE);
        add(gamePanel);
        pack();
        setVisible(true);
    }

    /**
     * Entry point for the program.
     *
     * @param args not used
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                GameFrame gameFrame = new GameFrame(640, 480);
                gameFrame.createAndShow();
            }
        });
    }

}

Finally, the other classes used in the example for completeness, Tile and StoneTile. Personally I don't see much benefit from using Rectangle inside the model, but each to their own and I wanted to keep the example somewhat similar to your currently implementation.

Tile

import java.awt.Rectangle;

public abstract class Tile {

    private int x, y, width, height;

    public Tile(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    public Rectangle getBounds() {
        return new Rectangle(x, y, width, height);
    }

}

StoneTile

public class StoneTile extends Tile {

    public StoneTile(int x, int y) {
        super(x, y, 100, 100);
    }
}

One final comment. I notice that your sTile ArrayList is static, this should probably not be the case as it belongs to the class rather than a particular instance of the class.

d.j.brown
  • 1,822
  • 1
  • 12
  • 14
  • Ok, so it works to an extent, which I appreciate, so thank you! However, instead of drawing on block of 64 by 64, it draws a whole bunch of them which cover the screen, except for other entities like the player. I believe this is because I commented the super.paintComponent(g); (paintComponent method). I know it shouldn't be but it came up with an error "The method paintComponent(Graphics) is undefined for the type Object" If this error comes up because I have not extended JPanel, then I have a problem because my current Game class already extends Canvas. –  Jul 03 '17 at 19:32
  • I can't comment on the exact cause of the issue but `Canvas` is part of the AWT library and it can result in strange results mixing AWT and Swing components. I'd suggest changing your `Canvas` to a `JPanel`. If you insist on using `Canvas` then you should override the `paint` method and not `paintComponent`. – d.j.brown Jul 03 '17 at 20:04
  • I got the program it work with JPanel thank you! Is it possible to make the block stay in its position and when I press the mouse key, another block is added. Like in minecraft when the player places a block and then another, the first block does not disappear, so you're able to build stuff. I want to essentially be able to 'draw' things when I place blocks –  Jul 04 '17 at 14:35
  • @CodeLife if you repaint the `List` of `Tile` objects then all the existing `Tile` objects should be drawn in the same position assuming that 1) you do not alter their position, 2) you maintain all the `Tile` to repaint in some form of Collection. The basic example in the answer produces the behaviour you desire. – d.j.brown Jul 04 '17 at 17:36
  • I take it you are very experienced in programming and do not wish to undermine that, but are you sure it produces the output that I need? As I made two classes called GamePanel and GameFrame and copy/paste (just to make sure I did not do anything wrong in my code), and when I ran it, I clicked the mouse button and a black block was drawn to the screen (as written in your example) and then I clicked the mouse button again in a different position and the block from before disappeared and appeared in the area I had just clicked. Look, I do not expect you to help me any further or even reply. –  Jul 04 '17 at 19:19
  • Thank you though! –  Jul 04 '17 at 19:19