I have created an abstract shape class for 2d game but i am getting an error in both the shape classes. The error is something to do with super(). There maybe other errors as well. i have also shown where i get the error in the code. IS super() suitable to use.
Shape class
public abstract class Shape {
int Y;
int WIDTH;
int HEIGHT;
int DIAMETER;
public Shape(int Y, int WIDTH, int HEIGHT, int DIAMETER) {
this.Y = Y;
this.WIDTH = WIDTH;
this.HEIGHT = HEIGHT;
this.DIAMETER = DIAMETER;
}
public abstract void paint(Graphics g);
}
Racquet class
public class Racquet extends Shape {
int x = 0;
int xa = 0;
private Game game;
public Racquet(int Y, int WIDTH, int HEIGHT) {
super(Y, WIDTH, HEIGHT); // <- **Error Here**
}
public void move() {
if (x + xa > 0 && x + xa < game.getWidth() - this.WIDTH)
x = x + xa;
}
public void paint(Graphics r) {
r.setColor(new java.awt.Color(229, 144, 75));
r.fillRect(x, Y, this.WIDTH, this.HEIGHT);
}
public Rectangle getBounds() {
return new Rectangle(x, this.Y, this.WIDTH, this.HEIGHT);
}
public int getTopY() {
return this.Y - this.HEIGHT;
}
}
Ball Class
import java.awt.*;
public class Ball extends Shape {
int x = 0;
int y = 0;
int xa = 1;
int ya = 1;
private Game game;
public Ball(Integer DIAMETER) {
super(DIAMETER); // <- **Error Here**
}
void move() {
if (x + xa < 0)
xa = game.speed;
if (x + xa > game.getWidth() - this.DIAMETER)
xa = -game.speed;
if (y + ya < 0)
ya = game.speed;
if (y + ya > game.getHeight() - this.DIAMETER)
game.CheckScore();
if (collision()) {
ya = -game.speed;
y = game.racquet.getTopY() - this.DIAMETER;
game.speed++;
}
x = x + xa;
y = y + ya;
}
private boolean collision() {
return game.racquet.getBounds().intersects(getBounds());
}
public void paint(Graphics b) {
b.setColor(new java.awt.Color(237, 238, 233));
b.fillOval(x, y, this.DIAMETER, this.DIAMETER);
}
public Rectangle getBounds() {
return new Rectangle(x, y, this.DIAMETER, this.DIAMETER);
}
}
Thanks a lot.
By calling super(...), you are actually calling constructor of super class. In the super class you have only one constructor which expects 4 parameters: Shape(int Y, int WIDTH, int HEIGHT, int DIAMETER), so you either have to provide 4 parameters when calling super(...), or provide needed constructors in the super class, with 3 parameters and with 1 parameter
Your Shape class doesn't have constructor with three parameters or one parameters.
You may want to use;
in recquet class
super(Y, WIDTH, HEIGHT, 0);
in Ball class
super(0, 0, 0, DIAMETER);
Shape doesn't have constructors that fit the parameters you are using in Racquet and Ball.
From a "best-practice" perspective, since Ball and Racquet should logically be constructed differently, it may be better to use composition, rather than inheritance.
Related
I want to change the height of my texture to a random height (with a specified range), and I almost figured out how, but the problem I have is that the texture (tower) is now above the ground. I want to stretch the texture while it remains on the same position, but changes height length.
I have a Tower class and a Scrollable class. In the Tower class I generate a random height in the reset method, but the problem is that I don't know what exactly I have to add or write to put the texture on the correct position (so that it isn't above the ground).
Here is the Tower class:
public class Tower extends Scrollable {
private Random r;
// When Tower's constructor is invoked, invoke the super (Scrollable)
// constructor
public Tower(float x, float y, int width, int height, float scrollSpeed) {
super(x, y, width, height, scrollSpeed);
// Initialize a Random object for Random number generation
r = new Random();
}
#Override
public void reset(float newX) {
// Call the reset method in the superclass (Scrollable)
super.reset(newX); // newX
// Change the height to a random number
Random r = new Random();
int low = 0;
int high = 15;
int result = r.nextInt(high-low) + low;
height = result;
}
}
And here's the Scrollable class:
public class Scrollable {
protected Vector2 position;
protected Vector2 velocity;
protected int width;
protected int height;
protected boolean isScrolledLeft;
public Scrollable(float x, float y, int width, int height, float scrollSpeed) {
position = new Vector2(x, y);
velocity = new Vector2(scrollSpeed, 0);
this.width = width;
this.height = height;
isScrolledLeft = false;
}
public void update(float delta) {
position.add(velocity.cpy().scl(delta));
// If the Scrollable object is no longer visible:
if (position.x + width < 0) {
isScrolledLeft = true;
}
}
// Reset: Should Override in subclass for more specific behavior.
public void reset(float newX) {
position.x = newX;
isScrolledLeft = false;
}
public boolean isScrolledLeft() {
return isScrolledLeft;
}
public float getTailX() {
return position.x + width;
}
public float getX() {
return position.x;
}
public float getY() {
return position.y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
}
Maybe it's important to know that I have a GameRenderer class which has a drawTowers() method, which is then used in a render() method.
That's my drawTowers() method:
private void drawTowers() {
batcher.draw(AssetLoader.texture1, tower1.getX(), tower1.getY() + tower1.getHeight(),
tower1.getWidth(), midPointY - (tower1.getHeight()));
batcher.draw(AssetLoader.texture2, tower2.getX(), tower2.getY() + tower2.getHeight(),
tower2.getWidth(), midPointY - (tower2.getHeight()));
batcher.draw(AssetLoader.texture3, tower3.getX(), tower3.getY() + tower3.getHeight(),
tower3.getWidth(), midPointY - (tower3.getHeight()));
batcher.draw(AssetLoader.texture4, tower4.getX(), tower4.getY() + tower4.getHeight(),
tower4.getWidth(), midPointY - (tower4.getHeight()));
}
You are drawing the tower too high, you need to be adding half the height rather than the whole height.
Here in drawTowers():
batcher.draw(AssetLoader.texture1, tower1.getX(), tower1.getY() + tower1.getHeight() / 2, tower1.getWidth(), midPointY - (tower1.getHeight()));
Do the same for the other towers.
This may not be perfectly correct but it shouldn't be far off.
Ok so I am very new to Java Swing and a beginner in Java in general. My current problem is I have designed a "cityscape". I am working on a UFO flying around, but my randomly generated buildings continue to get regenerated. I am wondering if there is a way to save my instance of buildings to an ArrayList as I have attempted, and paint that selection from that list each time paint is called. I tried what I thought of and I believe it just crashed it when run, because it didn't even open a JFrame and instead produced errors upon errors. Here is what I have:
CityScape class (the main class):
import java.awt.*;
import javax.swing.*;
public class CityScape extends JPanel
{
Buildings a = new Buildings ();
UFO b = new UFO();
#Override
public void paint (Graphics g)
{
//RememberBuildings.buildingList.get(1).paint(g);
a.paint(g);
b.paint(g);
}
public void move()
{
b.move();
}
public static void main(String[] args) throws InterruptedException
{
JFrame frame = new JFrame("Frame");
CityScape jpe = new CityScape();
frame.add(jpe);
frame.setSize(800, 750);
frame.setBackground(Color.BLACK);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
System.out.println(frame.getContentPane().getSize());
while (true)
{
jpe.move(); //Updates the coordinates
jpe.repaint(); //Calls the paint method
Thread.sleep(10); //Pauses for a moment
}
}
}
Buildings class (the class that generates the buildings):
import java.awt.*;
public class Buildings
{
private int maxX = 784;
private int maxY = 712;
private int width = (int)(Math.random()*100+100);
private int height = (int)(Math.random()*350+100);
private int rows = Math.round((height)/25);
private int columns = Math.round(width/25);
public void addBuilding()
{
RememberBuildings.addBuilding();
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
Color transYellow = new Color (255, 255, 0, 59);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, maxX, maxY);
g2d.setColor(Color.WHITE);
g2d.fillRect(5, 5, 25, 25);
int a = 0;
for (int i =10; i<634; i+=(a+10))//buildings
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY-height, width, height);
g2d.fillRect(i, maxY-height, width, height);
rows = Math.round((height)/25);
columns = Math.round(width/25);
for (int j = 1; j<=columns; j++)//windows
{
for (int k = 1; k<=rows; k++)
{
g2d.setColor(Color.BLACK);
g2d.drawRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
if (Math.random()<0.7)
{
g2d.setColor(Color.YELLOW);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
else
{
g2d.setColor(Color.BLACK);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
g2d.setColor(transYellow);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
}
}
addBuilding();
a = width;
height = (int)(Math.random()*462+100);
width = (int)(Math.random()*100+100);
}
}
}
RememberBuildings class (the point of this is to add an instance to an ArrayList):
import java.util.*;
public class RememberBuildings
{
public static ArrayList<Buildings> buildingList = new ArrayList<Buildings>();
public static void addBuilding()
{
buildingList.add(new Buildings());
}
}
And finally my UFO class (creates the UFO flying by):
import java.awt.*;
import javax.swing.*;
public class UFO extends JPanel
{
private int x = 20; //x and y coordinates of the ball
private int y = 20;
private int xa = 1;
public void move() //Increase both the x and y coordinates
{
if (x + xa < 0) {
xa = 1;
}
if (x + xa > 784-75)
{
xa = -1;
}
x = x + xa;
}
public void paint(Graphics g)
{
super.paint(g); //Clears the panel, for a fresh start
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillOval(x,y,75,25); //Draw the ball at the desired point
}
}
Avoid overriding paint, use paintComponent instead. Always call the super paint method before you do any custom painting to ensure that the paint chain is maintained. See Painting in AWT and Swing and Performing Custom Painting for more details
Beware, Swing is not thread safe and it's unwise to update any component (or any variable that a component may rely on) from outside the context of the Event Dispatching Thread. A simple solution might be to use a Swing Timer instead of a while (true) loop and Thread.sleep. See How to use Swing Timers for more details.
You should also only create and modify UI components from within the context of the event dispatching thread, see Initial Threads for more details
If you have a problem with your code not working, you should consider providing a runnable example which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses. Providing code which is not runnable and is missing classes makes it difficult to know why it's not working and how to fix it.
A few things here:
To address the paintComponent note and view an example, check out this other thread: Concerns about the function of JPanel: paintcomponent()
There seems to be a bit of a disconnect between the logic you've got going and the object-oriented programming logic that I think will help sort things out (for general info on OOP: https://en.wikipedia.org/wiki/Object-oriented_programming):
What You've Got:
The Structure you've got going is as follows:
CityScape :: here's where you've extended JPanel and setup the main function
UFO :: an object class that represents 1 UFO
Building :: a class that has methods for drawing randomized buildings and calling methods in RememberBuildings
RememberBuildings :: I think this is intended to track buildings that have been drawn
The issue here is that your Building class's paint method continually draws multiple newly randomized buildings instead of a set building that retains its structure.
My Suggestion:
There are plenty of solutions to this issue and different ways to implement each solution, but my recommendation is to remodel your Building class in an OOP fashion, meaning that it would represent 1 single building (truer to the name of the class). This would contain a constructor that initializes all of the randomized dimensions of that single building once and draws that single building on the jpanel. Then you would need to keep an array or list of some sort in the cityscape that contains buildings that are part of the cityscape, eliminating the need for a "RememberBuildings" class. so roughly:
CityScape extends JPanel:
variables:
Building[] buildings; //might be useful to use an arraylist/stack/queue instead of an array depending on implementation
UFO craft;
constructor:
setup new Building objects and add to list buildings
initialize craft to new UFO
paintComponent:
calls the paint methods for each building & the ufo craft
Building:
variables:
int x, y; // position of building
int height, width; // of this building
constructor:
initializes x, y // probably needs to be inputed from CityScape with this setup
calc height and width randomly // stored in this.height/width
paint:
paints single building based on it's variables
//side-note, you'll probably need getters for the x/y/width to build each building from CityScape
Everything else should be much the same.
Good Luck !
So, every time Buildings#paint is called, it regenerates all the builds, which is done randomly.
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Color transYellow = new Color(255, 255, 0, 59);
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, maxX, maxY);
g2d.setColor(Color.WHITE);
g2d.fillRect(5, 5, 25, 25);
int a = 0;
for (int i = 10; i < 634; i += (a + 10))//buildings
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY - height, width, height);
g2d.fillRect(i, maxY - height, width, height);
rows = Math.round((height) / 25);
columns = Math.round(width / 25);
for (int j = 1; j <= columns; j++)//windows
{
for (int k = 1; k <= rows; k++) {
g2d.setColor(Color.BLACK);
g2d.drawRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
if (Math.random() < 0.7) {
g2d.setColor(Color.YELLOW);
g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
} else {
g2d.setColor(Color.BLACK);
g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
g2d.setColor(transYellow);
g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
}
}
}
addBuilding();
a = width;
height = (int) (Math.random() * 462 + 100);
width = (int) (Math.random() * 100 + 100);
}
}
There's two ways you might be able to solve this, which you use will depend on what you want to achieve. You could render the buildings directly to a BufferedImage and simply paint that on each paint cycle or you could cache the information you need in order to re-create the buildings.
The BufferedImage approach is quicker, but can't be animated, so if you want to animate the buildings in some way (make the lights flicker), you will need to build up a series of information which allows you to simply repaint them.
I'm going for the second, as you've asked about painting assets from a ArrayList.
I started by translating your "paint" code into a single concept of a virtual building, which has also has information about it's own lights.
public class Building {
protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);
private int x, y, width, height;
private List<Light> lights;
public Building(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
lights = new ArrayList<>(25);
int rows = Math.round((height) / 25);
int columns = Math.round(width / 25);
for (int j = 1; j <= columns; j++)//windows
{
for (int k = 1; k <= rows; k++) {
Color color = null;
if (Math.random() < 0.7) {
color = Color.YELLOW;
} else {
color = TRANS_YELLOW;
}
lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
}
}
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.GRAY);
g2d.drawRect(x, y, width, height);
g2d.fillRect(x, y, width, height);
for (Light light : lights) {
light.paint(g2d);
}
}
public class Light {
private int x, y;
private Color color;
public Light(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(x, y, 20, 20);
g2d.setColor(color);
g2d.fillRect(x, y, 20, 20);
}
}
}
This allows you to generate the primary parameters for the Building and simple cache the results and when needed, simply paint it.
For example...
public class Buildings {
private int maxX = 784;
private int maxY = 712;
private List<Building> buildings;
public Buildings() {
buildings = new ArrayList<>(25);
for (int i = 10; i < 634; i += 10)//buildings
{
int width = (int) (Math.random() * 100 + 100);
int height = (int) (Math.random() * 350 + 100);
int x = i;
int y = maxY - height;
buildings.add(new Building(x, y, width, height));
}
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
for (Building building : buildings) {
building.paint(g2d);
}
}
}
I also changed your UFO class so it no longer extends from JPanel, as it just doesn't need to and is probably the primary cause of confusion with your painting.
I then updated your paint method in your CityScape to use paintComponent instead...
public class CityScape extends JPanel {
Buildings a = new Buildings();
UFO b = new UFO();
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
a.paint(g);
b.paint(g);
}
As a runnable example...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class CityScape extends JPanel {
Buildings a = new Buildings();
UFO b = new UFO();
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
a.paint(g);
b.paint(g);
}
public void move() {
b.move();
}
public static void main(String[] args) throws InterruptedException {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Frame");
CityScape jpe = new CityScape();
frame.add(jpe);
frame.setSize(800, 750);
frame.setBackground(Color.BLACK);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
System.out.println(frame.getContentPane().getSize());
Timer timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
jpe.move(); //Updates the coordinates
jpe.repaint(); //Calls the paint method
}
});
timer.start();
}
});
}
public class Buildings {
private int maxX = 784;
private int maxY = 712;
private List<Building> buildings;
public Buildings() {
buildings = new ArrayList<>(25);
for (int i = 10; i < 634; i += 10)//buildings
{
int width = (int) (Math.random() * 100 + 100);
int height = (int) (Math.random() * 350 + 100);
int x = i;
int y = maxY - height;
buildings.add(new Building(x, y, width, height));
}
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
for (Building building : buildings) {
building.paint(g2d);
}
}
}
public static class Building {
protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);
private int x, y, width, height;
private List<Light> lights;
public Building(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
lights = new ArrayList<>(25);
int rows = Math.round((height) / 25);
int columns = Math.round(width / 25);
for (int j = 1; j <= columns; j++)//windows
{
for (int k = 1; k <= rows; k++) {
Color color = null;
if (Math.random() < 0.7) {
color = Color.YELLOW;
} else {
color = TRANS_YELLOW;
}
lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
}
}
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.GRAY);
g2d.drawRect(x, y, width, height);
g2d.fillRect(x, y, width, height);
for (Light light : lights) {
light.paint(g2d);
}
}
public class Light {
private int x, y;
private Color color;
public Light(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(x, y, 20, 20);
g2d.setColor(color);
g2d.fillRect(x, y, 20, 20);
}
}
}
public class UFO {
private int x = 20; //x and y coordinates of the ball
private int y = 20;
private int xa = 1;
public void move() //Increase both the x and y coordinates
{
if (x + xa < 0) {
xa = 1;
}
if (x + xa > 784 - 75) {
xa = -1;
}
x = x + xa;
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.LIGHT_GRAY);
g2d.fillOval(x, y, 75, 25); //Draw the ball at the desired point
}
}
}
I'm stuck on something that is usually really simple. I'm getting a NullPointerException when calling this simple class's constructor:
import java.awt.geom.*;
public class Brick extends ColorShape {
private int xPos = 0;
private int yPos = 0;
private int width = 0;
private int height = 0;
private Rectangle2D.Double shape;
// constructor
public Brick(int x, int y, int w, int h) {
super(new Rectangle2D.Double(x, y, w, h));
//set brick x, y, width, and height
xPos = x;
yPos = y;
width = w;
height = h;
// update shape
shape.setRect((double)xPos, (double)yPos, (double)width, (double)height);
}
public int getX() {
return xPos;
}
public Rectangle2D.Double getShape() {
return shape;
}
}
It gets called this way:
for (int i = 0; i < numCols; i++) {
for (int j = 0; j < numRows; j++) {
// initialize bricks[i][j]
bricks[i][j].setLocation((double)(i*brickWidth), (double)(j*brickHeight));
bricks[i][j].setSize((double)brickWidth, (double)brickHeight);
//bricks[i][j] = new Brick(i*brickWidth, j*brickHeight, brickWidth, brickHeight);
//bricks[i][j] = new Brick(0, 0, 0, 0);
}
}
No matter what I try, I always get a NullPointerException trying to initialize that class.
EDIT:
Tristan's suggestions along with changing the nested for loops to the code below fixed it
// create new bricks and store them in bricks array
for (int i = 0; i < numCols; i++) {
for (int j = 0; j < numRows; j++) {
// initialize bricks[i][j]
//bricks[i][j].setLocation((double)(i*brickWidth), (double)(j*brickHeight));
//bricks[i][j].setSize((double)brickWidth, (double)brickHeight);
bricks[i][j] = new Brick(i*brickWidth, j*brickHeight, brickWidth, brickHeight);
//bricks[i][j] = new Brick(0, 0, 0, 0);
}
}
I think you are accidentally redeclaring shape as an uninitialized field. The shape you are calling setRect on has not been initialized the way you think it has.
If you have a shape in the parent class that you are trying to access, simply set its modifier to protected and remove the private shape declaration in the class you posted.
/* REMOVE THIS */
private Rectangle2D.Double shape; // uninitialized!
// constructor
public Brick(int x, int y, int w, int h) {
super(new Rectangle2D.Double(x, y, w, h));
//set brick x, y, width, and height
xPos = x;
yPos = y;
width = w;
height = h;
// update shape
// This now references a protected instance variable inherited from the parent.
shape.setRect((double)xPos, (double)yPos, (double)width, (double)height);
}
However looking through this constructor, it seems rather off. If it is the case that the parent class has a shape itself, why do you need to set the rectangle any differently than the way you set it in the parent class?
For example, this code appears to be logically equivalent.
// Call the parents constructor to set the shape of this brick..
public Brick(int x, int y, int w, int h) {
super(new Rectangle2D.Double(x, y, w, h));
}
As mentioned by Tristan, the problem with the initial brick constructor is that shape has been declared but not instantiated.
That said, it is simple to instantiate shape:
public Brick(int x, int y, int w, int h) {
super(new Rectangle2D.Double(x, y, w, h));
//set brick x, y, width, and height
xPos = x;
yPos = y;
width = w;
height = h;
// update shape
// This now references a protected instance variable inherited from the parent.
shape = (Rectangle2D.Double)super.shape;
shape.setRect(xPos, yPos, width, height);
}
I´m coding a little app that allows the user to draw multiple shapes and then remove or resize them. It´s working perfectly on rectangles and ovals, but I´m having issues with lines.
Here´s a method that I wrote to find if the clicked spot on the screen is part of a specific line:
public boolean containsLocation(int x, int y) {
int m = (getY2() - getY()) / (getX2() - getX());
int b = getY() - (m * getX());
if (y == (m * x) + b) {
return true;
}
return false;
I´m using the famous y = mx + b formula and replacing y and x, wich are the coordinates on the spot clicked, to find if the clicked spot is part of the line.
The original coordinates are determined using the getters getX(), getY() and getX2(), getY2()
The problem is when I click on the screen to remove the line, it only works if I click on the very fist coordinate (x,y) where the line starts.
Nothing happens when I click anywhere else along the line.
Since math is not a strongest suit, can anyone shed a light on what I´m doing wrong?
Here´s my Line full class:
public class Line extends Shape{
private int x2, y2;
public Line (int x, int y, int x2, int y2, Color lineColor) {
super(x, y, lineColor);
this.x2 = x2;
this.y2 = y2;
}
public void draw(Graphics g) {
g.setColor(getLineColor());
g.drawLine(getX(), getY(), getX2(), getY2());
}
#Override
public boolean containsLocation(int x, int y) {
int m = (getY2() - getY()) / (getX2() - getX());
int b = getY() - (m * getX());
if (y == (m * x) + b) {
return true;
}
return false;
}
public int getX2() {
return x2;
}
public void setX2(int x2) {
this.x2 = x2;
}
public int getY2() {
return y2;
}
public void setY2(int y2) {
this.y2 = y2;
}
}
Here´s the Shape class that is being extended by Line:
public abstract class Shape {
private int x, y;
private Color lineColor;
public Shape(int x, int y, Color lineColor) {
this.x = x;
this.y = y;
this.lineColor = lineColor;
}
public abstract void draw(Graphics g);
public abstract boolean containsLocation(int x, int y);
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public Color getLineColor() {
return lineColor;
}
public void setLineColor(Color lineColor) {
this.lineColor = lineColor;
}
}
Here´s the method that calls containsLocation:
public Shape shapeFinder(int x, int y){
for (int i = shapes.size()-1; i >=0; i--){
if (shapes.get(i).containsLocation(x, y)){
return shapes.get(i);
}
}
return null;
}
And here´s the method that should remove the line (it´s working for Ovals and Rectangles):
public void mousePressed(MouseEvent e) {
if (model.getAction() == Model.REMOVE) {
startX = e.getX();
startY = e.getY();
shape = model.shapeFinder(startX, startY);
if (shape != null) {
model.getShape().remove(model.shapeFinder(startX, startY));
}
You're using integer division to calculate your slope. I used my example of (100,100) to (120, 153) and it gave me a slope of 2. It should be a slope of 2.65.
But regardless, you'll never find any integer points in the middle of my line - there are no points along my line where both x and y are integers. If you calculate the slope correctly you'll be able to get the endpoints recognized but you need to find a different way of calculating points in the middle. Maybe introduce some sort of epsilon to your method?
Actually, you need to compute the direction of the line (its angle in radians) using something like Math.atan2. Then, you can apply an inverse rotation to one of the points of the line, considering the other point as the center, and also to the mouse coordinate. Your line is now a horizontal one, so checking if the mouse is over it is straight-forward.
See rotation matrices.
public class LineDemo {
public static void main(String[] args) {
System.out.println(containsLocation(50, 75, 50, 50, 50, 100));
}
public static boolean containsLocation(int x, int y, int x1, int y1, int x2, int y2) {
double dy = y2 - y1;
double dx = x2 - x1;
double dist = Math.sqrt(dx*dx + dy*dy);
double angle = Math.atan2(dy, dx);
double cos = Math.cos(-angle);
double sin = Math.sin(-angle);
double xRot = (x - x1) * cos - (y - y1) * sin;
double yRot = (x - x1) * sin + (y - y1) * cos;
// Actually, I only rotated the mouse point, since
// I can use the first point of the line and its
// euclidian distance to know where the rotated
// second point would end.
if (0 <= xRot && xRot <= dist) {
double tolerance = 3; // distance tolerance in pixels
if (Math.abs(yRot) <= tolerance) {
return true;
}
}
return false;
}
}
I have class which represents every object in my simple game (player, enemy, beam etc - they all have many commons like speed, position, dmg). So i made class named Thing. Here is how it looks like:
public abstract class Thing {
private Image image;
private float x;
private float y;
private float speed;
private final int WIDTH;
private final int HEIGHT;
public Thing(String filename, float x, float y, float speed) {
try {
Image image = ImageIO.read(new File(filename));
} catch (Exception e) {}
this.x = x;
this.y = y;
this.speed = speed;
WIDTH = image.getWidth(null);
HEIGHT = image.getHeight(null);
}
//Zwraca ksztalt do sprawdzania czy contains...
public Rectangle2D getShade() {
return new Rectangle2D.Float(x, y, WIDTH, HEIGHT);
}
public Image getImage() {
return image;
}
public Point2D getPoint() {
return new Point2D.Float(x, y);
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
I have extended the class Player:
public class Player extends Thing {
public Player(String filename, float x, float y, float speed) {
super(filename, x, y, speed);
}
public void moveToPoint(Point2D targetPoint) {
int targetX = (int)targetPoint.getX();
int targetY = (int)targetPoint.getY();
if ( ((int)x+20 < targetX+3) && ((int)x+20 > targetX-3) ) {
return;
}
float distanceX = targetX - x;
float distanceY = targetY - y;
//Dodanie 20px wymiarow statku
distanceX -= 20;
distanceY -= 20;
//Ustalenie wartosci shiftow
float shiftX = speed;
float shiftY = speed;
if (abs(distanceX) > abs(distanceY)) {
shiftY = abs(distanceY) / abs(distanceX) * speed;
}
if (abs(distanceY) > abs(distanceX)) {
shiftX = abs(distanceX) / abs(distanceY) * speed;
}
//Zmiana kierunku shifta w zaleznosci od polozenia
if (distanceX < 0) {
shiftX = -shiftX;
}
if (distanceY < 0) {
shiftY = -shiftY;
}
//Jezeli statek mialby wyjsc poza granice to przerywamy
if ( (((int)x+shiftX < 0) || ((int)x+shiftX > 260)) || ((y+shiftY < 0) || (y+shiftY > 360)) ) {
return;
}
//Zmiana pozycji gracza
x += shiftX;
y += shiftY;
}
}
And here is the problem because my IDE underlines x, y and speed fields red and tells they cannot be accessed from Player class. I tried to change them into private and default but there appears an error after that. What am I doing wrong? When i create new object from class which extends Thing I want to copy all fields and init them as it is said in constructor. So how to repair it?
You need to use getX(), getY() etc., because x,y, speed are private variables for class Thing.
The fact that Player extends Thing doesn't mean Player can access private fields. Thing provided public get... set... to access its private variables.
Change the variables x, y, and speed to protected, or use the accessors getX(), getY(), getSpeed() (getSpeed() needs to be added in this case) to solve the access issues.
The error that appeared after you changed them to default was the fact that you're calling abs(...) instead of Math.abs(...). Change all instances of abs(...) to Math.abs(...) to get rid of the new errors.