I want to create an ArrayList of ball objects, which should be in a loop until there are 100 pieces.
Now my problem: I must implement a function hitTest, so that when you click on a ball it gets removed. In the same position, there should appear two balls then, which go into a different direction.
Can someone help me? I am so lost...
Here's my code so far:
Ball b;
ArrayList<Ball> balls;
void setup() {
size(800, 800);
balls = new ArrayList<Ball>();
for (int i = 0; i<100; i++) {
drawBall();
}
}
void draw() {
background(255);
//b.update();
for (int i= 0; i<balls.size(); i++) {
balls.get(i).update();
}
}
void drawBall() {
Ball b = new Ball();
balls.add(b);
}
class Ball {
private float x;
private float y;
private float ballSize;
private float dirX;
private float dirY;
private boolean moving = true;
Ball() {
this.x = width/2;
this.y = height/2;
this.ballSize = random(10.0, 30.0);
this.dirX = random(-3.0, 3.0);
this.dirY = random(-3.0, 3.0);
if (this.dirX<1.0 && this.dirX>1.0) //1 statt -1 macht zufälliger { this.dirX = 1.0; }
if (this.dirY<1.0 && this.dirY>1.0) {
this.dirY = 1.0;
}
}
public void update() {
stroke(255);
fill(random(255), random(255), random(255), random(255));
ellipse( this.x, this.y, this.ballSize, this.ballSize);
if (this.moving == true) {
this.x += this.dirX;
this.y += this.dirY;
}
if (this.x+ this.ballSize/2> width ||this.x- this.ballSize/2<0) {
this.dirX= dirX*-1;
}
if (this.y+ this.ballSize/2> height ||this.y- this.ballSize/2<0) {
this.dirY= dirY*-1;
}
}
}
Break your problem down into smaller, simpler steps.
e.g.
when you click on a ball, it gets removed. In the same position, there should appear two balls then, which go into a different direction.
when you click on a ball: you can mix the dist() function (to check if the distance between the mouse and a ball is smaller then the radius) with mouseClicked() (or mousePressed() / mouseReleased())
it gets removed: you already called balls.add(). Similarly you can call balls.remove() passing the ball object or index to remove (depending on the case)
same position: you need to remember (store the coordinates) of the ball that was clicked to add two balls at the same position
different direction: you already do that in the Ball() constructor: you can apply the same logic on each new ball.
Here's a basic sketch to illustrate point 1, using dist():
void draw(){
background(255);
int ballX = 50;
int ballY = 50;
int ballRadius = 35;
if(dist(ballX, ballY, mouseX, mouseY) < ballRadius){
fill(0,192,0);
}else{
fill(192,0,0);
}
ellipse(ballX,ballY, ballRadius * 2, ballRadius * 2);
}
Paste this in a new sketch, run it and you should get the hang of using dist() in the context of your problem.
Regarding points 2,3,4 here's a modified version of your sketch with comments and a slightly different approach: instead of removing a ball to add a new one at the exact location with a different direction, simply randomise the direction. Visually it will look similar to a new ball (except the random size/colour). With the clicked ball being re-used, only a second one is added:
Ball b;
ArrayList<Ball> balls;
void setup() {
size(800, 800);
balls = new ArrayList<Ball>();
for (int i = 0; i<100; i++) {
drawBall();
}
}
void draw() {
background(255);
//b.update();
for (int i= 0; i<balls.size(); i++) {
// pass the mouse coordinates to each ball to check if it's hovered or not
balls.get(i).update(mouseX, mouseY);
}
}
// on mouse pressed
void mousePressed(){
for (int i= 0; i<balls.size(); i++) {
// make current ball reusable in this loop
Ball ball = balls.get(i);
// if ball is hovered
if(ball.isHovered){
// randomize direction of current ball
ball.setRandomDirection();
// add a new ball from the current location
balls.add(ball.copy());
}
}
}
void drawBall() {
Ball b = new Ball();
balls.add(b);
}
class Ball {
private float x;
private float y;
private float ballSize;
private float dirX;
private float dirY;
private boolean moving = true;
private color fillColor;
// property to keep track if the ball is hovered or not
private boolean isHovered;
Ball() {
this.x = width/2;
this.y = height/2;
this.ballSize = random(10.0, 30.0);
this.setRandomDirection();
this.fillColor = color(random(255), random(255), random(255), random(255));
}
// extract random direction calls into a re-usable function (handy for click / collision)
void setRandomDirection(){
this.dirX = random(-3.0, 3.0);
this.dirY = random(-3.0, 3.0);
if (this.dirX<1.0 && this.dirX>1.0) { //1 statt -1 macht zufälliger { this.dirX = 1.0; }
if (this.dirY<1.0 && this.dirY>1.0) {
this.dirY = 1.0;
}
}
}
public void update(int x, int y) {
// euclidean distance between this ball's coordinates a given x y position (e.g. mouse)
isHovered = dist(this.x, this.y, x, y) < this.ballSize / 2;
// optional: use stroke color to visually display highlighted ball
if(isHovered){
stroke(0);
}else{
stroke(255);
}
fill(fillColor);
ellipse( this.x, this.y, this.ballSize, this.ballSize);
if (this.moving == true) {
this.x += this.dirX;
this.y += this.dirY;
}
if (this.x + this.ballSize / 2 > width ||
this.x - this.ballSize / 2 < 0) {
this.dirX= dirX*-1;
}
if (this.y + this.ballSize / 2 > height ||
this.y - this.ballSize / 2 < 0) {
this.dirY= dirY*-1;
}
}
// utility function: simply copies this ball's x,y position to the new one
Ball copy(){
Ball clone = new Ball();
clone.x = this.x;
clone.y = this.y;
return clone;
}
}
The copy() method is flexible enough that it's ease to remove one ball to add two more if that is an absolute must. For example:
// on mouse pressed
void mousePressed(){
for (int i= 0; i<balls.size(); i++) {
// make current ball reusable in this loop
Ball ball = balls.get(i);
// if ball is hovered
if(ball.isHovered){
// add two new balls from the current location
balls.add(ball.copy());
balls.add(ball.copy());
// remove ball
balls.remove(i);
}
}
}
Related
My title probably does not make much sense which is why I am having a bit of an issue googling my problem.
I am trying to move a shape on the screen from one set of X/Y coordinates to another in a direct line.
So for example,
This is the class method for setting the new target direction.
void setTargetPosition(int targetX, int targetY) {
xTar = targetX;
yTar = targetY;
if (xPos > xTar)
xDir = -1;
else
xDir = 1;
if (yPos > yTar)
yDir = -1;
else
xDir = 1;
}
This would set the Direction of the X/Y variables and the following code would move the player on the screen.
void drawPlayer() {
fill(circleColour);
circle(xPos,yPos,35);
//stops player from moving once target destination has been reached
if (xPos == xTar)
xDir = 0;
if (yPos == yTar)
yDir = 0;
xPos += xDir;
yPos += yDir;
}
The above code does work mostly as intended but I need to find a way to proportionally change the X/Y coordinate so that it's more of a 'direct line' to the destination.
Sorry if this does not make sense. I don't know the right terms to use.
You have to use floating point values for the computation of the movement rather than integral values:
float xTar;
float yTar;
float xPos;
float yPos;
setTargetPosition just set xTar and yTar:
void setTargetPosition(float targetX, float targetY) {
xTar = targetX;
yTar = targetY;
}
In drawPlayer you have to compute the direction vector (PVector) from the objects position to the target:
PVector dir = new PVector(xTar - xPos, yTar - yPos);
if the length of the vector (mag()) is greater than 0, the you have to move the object:
if (dir.mag() > 0.0) {
// [...]
}
If you have to move the object, then compute the Unit vector by normalize(). Note, the length of a unit vector is 1. Multiply the vector by a certain speed by mult(). This scales the vector to a certain length. Ensure that the length of the vector is not greater than the distance to the object (min(speed, dir.mag())). Finally add the components of the vector to the position of the object:
dir.normalize();
dir.mult(min(speed, dir.mag()));
xPos += dir.x;
yPos += dir.y;
See the example:
class Player {
float xTar;
float yTar;
float xPos;
float yPos;
color circleColour = color(255, 0, 0);
Player(float x, float y)
{
xTar = xPos = x;
yTar = yPos = y;
}
void setTargetPosition(float targetX, float targetY) {
xTar = targetX;
yTar = targetY;
}
void drawPlayer() {
fill(circleColour);
circle(xPos,yPos,35);
float speed = 2.0;
PVector dir = new PVector(xTar - xPos, yTar - yPos);
if (dir.mag() > 0.0) {
dir.normalize();
dir.mult(min(speed, dir.mag()));
xPos += dir.x;
yPos += dir.y;
}
}
}
Player player;
void setup() {
size(500, 500);
player = new Player(width/2, height/2);
}
void draw() {
background(255);
player.drawPlayer();
}
void mousePressed() {
player.setTargetPosition(mouseX, mouseY);
}
As a self-project, I'm trying to make the game 'Asteroids'.
Currently, I'm stuck on trying to figure out how to make it so the lasers fired from my ship appear from the tip of the ship. So far, I've tried experimenting with using the Shape object's .getBounds2D().getX() methods, but because getBounds2D() draws a rectangle around the polygon, the lasers end up appearing from the corner of the imaginary 'box' around my Polygon ship.
Here's a gif of what I have so far.
Is there a way to 'get' a specific point from a Shape object; where, in this case, that specific point is the tip of the ship.
Main Class:
public class AsteroidGame implements ActionListener, KeyListener{
public static AsteroidGame game;
public Renderer renderer;
public boolean keyDown = false;
public int playerAngle = 0;
public boolean left = false;
public boolean right = false;
public boolean go = false;
public boolean back = false;
public boolean still = true;
public double angle = 0;
public int turnRight = 5;
public int turnLeft = -5;
public Shape transformed;
public ArrayList<Laser> lasers;
public ArrayList<Shape> transformedLasers;
public final int WIDTH = 1400;
public final int HEIGHT = 800;
public Ship ship;
public Rectangle shipHead;
public Shape shipHeadTrans;
public Point headPoint;
public AffineTransform transform = new AffineTransform();
public AffineTransform lasTransform = new AffineTransform();
public AffineTransform headTransform = new AffineTransform();
public AsteroidGame(){
JFrame jframe = new JFrame();
Timer timer = new Timer(20, this);
renderer = new Renderer();
jframe.add(renderer);
jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jframe.setSize(WIDTH, HEIGHT);
jframe.setVisible(true);
jframe.addKeyListener(this);
jframe.setResizable(false);
int xPoints[] = {800, 780, 800, 820};
int yPoints[] = {400, 460, 440, 460};
//(800, 400) is the initial location of the 'tip' of the ship'.
headPoint = new Point(800, 400);
lasers = new ArrayList<Laser>();
transformedLasers = new ArrayList<Shape>();
ship = new Ship(xPoints, yPoints, 4, 0);
transformed = transform.createTransformedShape(ship);
shipHead = new Rectangle(headPoint);
shipHeadTrans = transform.createTransformedShape(shipHead);
//shipHeadTrans.getBounds2D().
timer.start();
}
public void repaint(Graphics g){
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
Graphics2D g2d = (Graphics2D)g;
//drawing the ship
g2d.setColor(Color.WHITE);
g2d.draw(transformed);
//drawing lasers
g2d.setColor(Color.RED);
for (int i = 0; i < transformedLasers.size(); i++){
System.out.println(i);
g2d.draw(transformedLasers.get(i));
}
}
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
/*The for if and else if statements are just to send the ship
* to the other side of the canvas if it ever leaves the screen
*/
if (transformed.getBounds2D().getMinX() > WIDTH){
double tempAng = ship.getAng();
double diff = 90-tempAng;
transform.rotate(Math.toRadians(diff), ship.getCenterX(), ship.getCenterY());
transform.translate(0,WIDTH);
transform.rotate(Math.toRadians(-diff), ship.getCenterX(), ship.getCenterY());
}
else if (transformed.getBounds2D().getX() < 0){
double tempAng = ship.getAng();
double diff = 90-tempAng;
transform.rotate(Math.toRadians(diff), ship.getCenterX(), ship.getCenterY());
transform.translate(0,-WIDTH);
transform.rotate(Math.toRadians(-diff), ship.getCenterX(), ship.getCenterY());
}
else if (transformed.getBounds2D().getY() > HEIGHT){
double tempAng = ship.getAng();
double diff = 180-tempAng;
transform.rotate(Math.toRadians(diff), ship.getCenterX(), ship.getCenterY());
transform.translate(0,HEIGHT);
transform.rotate(Math.toRadians(-diff), ship.getCenterX(), ship.getCenterY());
}
else if (transformed.getBounds2D().getY() < 0){
double tempAng = ship.getAng();
double diff = 180-tempAng;
transform.rotate(Math.toRadians(diff), ship.getCenterX(), ship.getCenterY());
transform.translate(0,-HEIGHT);
transform.rotate(Math.toRadians(-diff), ship.getCenterX(), ship.getCenterY());
}
if (right){
ship.right();
//rotating the ship
transform.rotate(Math.toRadians(turnRight), ship.getCenterX(), ship.getCenterY());
//rotating the 'tip' of the ship.
headTransform.rotate(Math.toRadians(turnRight), ship.getCenterX(), ship.getCenterY());
}
else if (left){
ship.left();
//rotating the ship
transform.rotate(Math.toRadians(turnLeft), ship.getCenterX(), ship.getCenterY());
//rotating the 'tip' of the ship
headTransform.rotate(Math.toRadians(turnLeft), ship.getCenterX(), ship.getCenterY());
}
if (go){
ship.go();
}
else if (back){
ship.reverse();
}
//moving and shaping each individual laser that had been shot
for (int i = 0; i < transformedLasers.size(); i++){
lasers.get(i).move();
lasTransform = new AffineTransform();
lasTransform.rotate(Math.toRadians(lasers.get(i).getAng()), transformed.getBounds2D().getX(), transformed.getBounds2D().getY());
transformedLasers.set(i, lasTransform.createTransformedShape(lasers.get(i)));
}
//moving the ship
ship.move();
//moving the 'tip'
shipHead.y -= ship.getSpeed();
transformed = transform.createTransformedShape(ship);
shipHeadTrans = headTransform.createTransformedShape(shipHead);
renderer.repaint();
}
//defining a new laser
public void fireLaser(){
Laser tempLaser = new Laser((int)transformed.getBounds2D().getX(), (int)transformed.getBounds2D().getY(), 5, 10, ship.getAng());
lasers.add(tempLaser);
lasTransform = new AffineTransform();
lasTransform.rotate(Math.toRadians(ship.getAng()), transformed.getBounds2D().getX(), transformed.getBounds2D().getY());
transformedLasers.add(lasTransform.createTransformedShape(tempLaser));
}
public static void main(String[] args){
game = new AsteroidGame();
}
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode() == KeyEvent.VK_RIGHT){
right = true;
keyDown = true;
}else if (e.getKeyCode() == KeyEvent.VK_LEFT){
left = true;
keyDown = true;
}
else if (e.getKeyCode() == KeyEvent.VK_UP){
go = true;
}
else if (e.getKeyCode() == KeyEvent.VK_DOWN){
back = true;
}
//fire laser
if (e.getKeyCode() == KeyEvent.VK_SPACE){
fireLaser();
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode() == KeyEvent.VK_RIGHT){
right = false;
}
if (e.getKeyCode() == KeyEvent.VK_LEFT){
left = false;
}
if (e.getKeyCode() == KeyEvent.VK_UP){
go = false;
}
if (e.getKeyCode() == KeyEvent.VK_DOWN){
back = false;
}
still = true;
keyDown = false;
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
Ship Class (I don't think it's relevant though)
package asteroidGame;
import java.awt.Polygon;
import java.util.Arrays;
public class Ship extends Polygon{
/**
*
*/
private double currSpeed = 0;
private static final long serialVersionUID = 1L;
public double angle;
public int[] midX;
public int[] midY;
public Ship(int[] x, int[] y, int points, double angle){
super(x, y, points);
midX = x;
midY = y;
this.angle= angle;
}
public void right(){
angle += 5;
}
public void left(){
angle -= 5;
}
public void move(){
for (int i = 0; i < super.ypoints.length; i++){
super.ypoints[i] -= currSpeed;
//System.out.println(super.ypoints[i]);
//System.out.println(super.xpoints[i]);
}
//System.out.println(Arrays.toString(super.ypoints));
}
public double getSpeed(){
return currSpeed;
}
public void reverse(){
if (currSpeed > -15) currSpeed -= 0.2;
}
public void go(){
if (currSpeed < 25) currSpeed += 0.5;
}
public int getCenterX(){
return super.xpoints[2];
}
public int getCenterY(){
return super.ypoints[2];
}
public double getAng(){
return angle;
}
public void test(){
for (int x = 0; x < super.ypoints.length; x++){
super.ypoints[x] += 1000;
}
}
/*
public void decrement(){
if(currSpeed == 0){}
else if (currSpeed > 0 && currSpeed < 15){
currSpeed -= 0.05;
}
else if (currSpeed < 0 && currSpeed > -15){
currSpeed += 0.05;
}
System.out.println("losing speed");
}
*/
}
Laser Class (I don't think this is relevant either, but here ya go.)
package asteroidGame;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
public class Laser extends Rectangle{
private double angle;
public Laser(int x, int y , int width, int height, double ang){
super(x, y, width, height);
angle = ang;
Rectangle tst = new Rectangle();
}
public void move(){
super.y -= 35;
}
public double getAng(){
return angle;
}
public boolean intersects (Rectangle2D r){
//if intersects
if (super.intersects(r)){
return true;
}
else{
return false;
}
}
}
I was thinking of maybe turning the the Shape object transformed back into a Polygon to get the point, but I'm not sure how or if that would work.
You can use AffineTransform.transform(Point2D, Point2D) to transform a single point on your polygon.
Things would be a lot simpler for you if instead of trying to move the ship by using a rotation transform you kept a single (x,y) location of where the ship is. You'd move the ship's location in move() instead of trying to translate the polygon. Then when you want to paint the ship you e.g. do:
// Optionally copying the Graphics so the
// transform doesn't affect later painting.
Graphics2D temp = (Graphics2D) g2d.create();
temp.translate(ship.locX, ship.locY);
temp.rotate(ship.angle);
temp.draw(ship);
To move a point based on speed you can do this to find the movement vector:
double velX = speed * Math.cos(angle);
double velY = speed * Math.sin(angle);
locX += timeElapsed * velX;
locY += timeElapsed * velY;
That is essentially a conversion from polar to Cartesian coordinates. The x and y velocities are the legs of a triangle whose hypotenuse is speed and whose known angle is angle:
/|
/ |
/ |
/ |
speed / |
/ |
/ |velY
/ angle |
/)_______|
velX
There's an example of doing movement this way in an answer of mine here: https://stackoverflow.com/a/43692434/2891664.
For your comments:
Are you saying that, unlike my initial move function, just to make ship hold a single point, and thus I would only translate that instead?
More or less, yes. You'd still have a polygon to hold the ship's shape, but the points on the polygon would be relative to (0,0).
Suppose the following definitions:
Each (x,y) point on the polygon is pi. (In other words, one of p0, p1, p2 and p3.)
The (x,y) coordinates of the translation are T
Then, after translating the Graphics2D, each pi coordinate becomes pi+T on the panel. So if your polygon points are defined relative to (0,0) then translating to the ship's (locX,locY) will move the polygon to a location relative to (locX,locY).
It could be simplest then to define the point which is the tip of the polygon as being (0,0) so that after the translation the tip of the ship is the ship's location:
// Your original points:
int xPoints[] = {800, 780, 800, 820};
int yPoints[] = {400, 460, 440, 460};
// Become these points relative to (0,0):
int xPoints[] = {0, -20, 0, 20};
int yPoints[] = {0, 60, 40, 60};
And to e.g. start the ship in the same place, you would initialize its location to (800,400).
I was thinking about this again and realized the rotation is a little more complicated, because you probably don't want to rotate the ship around the tip. You probably want to rotate the ship around its center.
So, here's an MCVE demonstrating how to do all of this.
package mcve.game;
import javax.swing.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.GraphicsConfiguration;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
public class MovementExample implements ActionListener {
public static void main(String[] args) {
SwingUtilities.invokeLater(MovementExample::new);
}
final int fps = 60;
final int period = 1000 / fps;
final JFrame frame;
final GamePanel panel;
final Controls controls;
final Ship ship;
final List<Bullet> bullets = new ArrayList<>();
MovementExample() {
frame = new JFrame("Movement Example");
Dimension size = getMaximumWindowSize(frame);
size.width /= 2;
size.height /= 2;
frame.setPreferredSize(size);
panel = new GamePanel();
frame.setContentPane(panel);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
controls = new Controls();
ship = new Ship(panel.getWidth() / 2,
panel.getHeight() / 2);
new Timer(period, this).start();
}
#Override
public void actionPerformed(ActionEvent e) {
double secondsElapsed = 1.0 / fps;
ship.update(secondsElapsed);
bullets.forEach(b -> b.update(secondsElapsed));
Rectangle bounds = panel.getBounds();
bullets.removeIf(b -> !bounds.contains(b.locX, b.locY));
panel.repaint();
}
class GamePanel extends JPanel {
GamePanel() {
setBackground(Color.WHITE);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if (ship != null) {
ship.draw(g2);
}
bullets.forEach(b -> b.draw(g2));
g2.dispose();
}
}
abstract class AbstractGameObject {
double maxSpeed;
double rotationAngle;
double locX;
double locY;
double velX;
double velY;
AbstractGameObject(double initialX, double initialY) {
locX = initialX;
locY = initialY;
}
abstract void update(double secondsElapsed);
abstract void draw(Graphics2D g2);
}
class Ship extends AbstractGameObject {
Polygon shape;
double rotationRate;
Ship(double initialX, double initialY) {
super(initialX, initialY);
maxSpeed = 128; // pixels/second
rotationAngle = Math.PI * 3 / 2;
rotationRate = (2 * Math.PI) / 2; // radians/second
int xPoints[] = {0, -20, 0, 20};
int yPoints[] = {0, 60, 40, 60};
shape = new Polygon(xPoints, yPoints, 4);
}
Point2D.Double getTip() {
Point2D.Double center = getCenter();
// The tip is at (0,0) and it's already centered
// on the x-axis origin, so the distance from the
// tip to the center is just center.y.
double distance = center.y;
// Then find the location of the tip, relative
// to the center.
double tipX = distance * Math.cos(rotationAngle);
double tipY = distance * Math.sin(rotationAngle);
// Now find the actual location of the center.
center.x += locX;
center.y += locY;
// And return the actual location of the tip, relative
// to the actual location of the center.
return new Point2D.Double(tipX + center.x, tipY + center.y);
}
Point2D.Double getCenter() {
// Returns the center point of the ship,
// relative to (0,0).
Point2D.Double center = new Point2D.Double();
for (int i = 0; i < shape.npoints; ++i) {
center.x += shape.xpoints[i];
center.y += shape.ypoints[i];
}
center.x /= shape.npoints;
center.y /= shape.npoints;
return center;
}
#Override
void update(double secondsElapsed) {
// See my answer here: https://stackoverflow.com/a/43692434/2891664
// for a discussion of why this logic is the way it is.
double speed = 0;
if (controls.isUpHeld()) {
speed += maxSpeed;
}
if (controls.isDownHeld()) {
speed -= maxSpeed;
}
velX = speed * Math.cos(rotationAngle);
velY = speed * Math.sin(rotationAngle);
locX += secondsElapsed * velX;
locY += secondsElapsed * velY;
double rotation = 0;
if (controls.isLeftHeld()) {
rotation -= rotationRate;
}
if (controls.isRightHeld()) {
rotation += rotationRate;
}
rotationAngle += secondsElapsed * rotation;
// Cap the angle so it can never e.g. get so
// large that it loses precision.
if (rotationAngle > 2 * Math.PI) {
rotationAngle -= 2 * Math.PI;
}
if (controls.isFireHeld()) {
Point2D.Double tipLoc = getTip();
Bullet bullet = new Bullet(tipLoc.x, tipLoc.y, rotationAngle);
bullets.add(bullet);
}
}
#Override
void draw(Graphics2D g2) {
Graphics2D copy = (Graphics2D) g2.create();
copy.setColor(Color.RED);
// Translate to the ship's location.
copy.translate(locX, locY);
// Rotate the ship around its center.
Point2D.Double center = getCenter();
// The PI/2 offset is necessary because the
// polygon points are defined with the ship
// already vertical, i.e. at an angle of -PI/2.
copy.rotate(rotationAngle + (Math.PI / 2), center.x, center.y);
copy.fill(shape);
}
}
class Bullet extends AbstractGameObject {
Ellipse2D.Double shape = new Ellipse2D.Double();
Bullet(double initialX, double initialY, double initialRotation) {
super(initialX, initialY);
maxSpeed = 512;
rotationAngle = initialRotation;
velX = maxSpeed * Math.cos(rotationAngle);
velY = maxSpeed * Math.sin(rotationAngle);
double radius = 3;
shape.setFrame(-radius, -radius, 2 * radius, 2 * radius);
}
#Override
void update(double secondsElapsed) {
locX += secondsElapsed * velX;
locY += secondsElapsed * velY;
}
#Override
void draw(Graphics2D g2) {
Graphics2D copy = (Graphics2D) g2.create();
copy.setColor(Color.BLACK);
copy.translate(locX, locY);
copy.fill(shape);
}
}
// See https://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
class Controls {
final Set<Integer> keysHeld = new HashSet<>();
Controls() {
bind(KeyEvent.VK_A, "left");
bind(KeyEvent.VK_D, "right");
bind(KeyEvent.VK_W, "up");
bind(KeyEvent.VK_S, "down");
bind(KeyEvent.VK_SPACE, "fire");
}
boolean isLeftHeld() { return keysHeld.contains(KeyEvent.VK_A); }
boolean isRightHeld() { return keysHeld.contains(KeyEvent.VK_D); }
boolean isUpHeld() { return keysHeld.contains(KeyEvent.VK_W); }
boolean isDownHeld() { return keysHeld.contains(KeyEvent.VK_S); }
boolean isFireHeld() { return keysHeld.contains(KeyEvent.VK_SPACE); }
void bind(int keyCode, String name) {
bind(keyCode, name, true);
bind(keyCode, name, false);
}
void bind(int keyCode, String name, boolean isOnRelease) {
KeyStroke stroke = KeyStroke.getKeyStroke(keyCode, 0, isOnRelease);
name += isOnRelease ? ".released" : ".pressed";
panel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(stroke, name);
panel.getActionMap()
.put(name, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
if (isOnRelease) {
keysHeld.remove(keyCode);
} else {
keysHeld.add(keyCode);
}
}
});
}
}
// This returns the usable size of the display which
// the JFrame resides in, as described here:
// http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsEnvironment.html#getMaximumWindowBounds--
static Dimension getMaximumWindowSize(JFrame frame) {
GraphicsConfiguration config = frame.getGraphicsConfiguration();
Dimension size = config.getBounds().getSize();
Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(config);
size.width -= insets.left + insets.right;
size.height -= insets.top + insets.bottom;
return size;
}
}
There are other ways the tip of the ship could be calculated, but the way I did it in the MCVE is this:
Get the center point of the ship, relative to (0,0).
Get the distance from the center point to the tip. The tip is at (0,0) so this is just the y-coordinate of the center.
Then calculate the (x,y) location of the tip, relative to the center. This is done in a very similar way to the figure above for speed and velocity, but the hypotenuse is the distance between the center and the tip of the ship.
Translate the center to be relative to the ship's location.
Translate the location of the tip (which is relative to the center) to be relative to the ship's location.
It could also all be done with an AffineTransform, similar to what you are doing in the code in the question, but you'd set it on every update. Something like this:
AffineTransform transform = new AffineTransform();
#Override
void update(double secondsElapsed) {
...
// Clear the previous translation and rotation.
transform.setToIdentity();
// Set to current.
transform.translate(locX, locY);
Point2D.Double center = getCenter();
transform.rotate(rotationAngle + (Math.PI / 2), center.x, center.y);
if (controls.isFireHeld()) {
Point2D.Double tip = new Point2D.Double(0, 0);
transform.transform(tip, tip);
Bullet bullet = new Bullet(tip.x, tip.y, rotationAngle);
bullets.add(bullet);
}
}
You could still use a transform to do calculations that way, but you don't end up with any strangeness from depending on the transform for movement. (In the code in the question, the ship is e.g. only ever moved along the y-axis. The apparent sideways movement is due to the series of rotation concatenations.)
Hi I want to draw new circles when I enter new radius for them and click button. I am supposed to position it with mouse. However after I draw circle I either can't draw new one or i can't position it mouse too.
import interfascia.*;
int numCircles = 500;
Circle[] circles = new Circle[numCircles]; // define the array
int k=0;
GUIController c;
IFButton b1;
IFTextField tn;
float bx;
float by;
boolean overBox = false;
boolean locked = false;
float xOffset = 0.0;
float yOffset = 0.0;
float r;
void setup() {
size(1439,800);
smooth();
noStroke();
bx=width/2;
by=height/2;
c = new GUIController (this);
b1 = new IFButton ("Click to draw", 600, 220, 100);
tn=new IFTextField("", 100,20,50);
c.add(b1);
c.add(tn);
}
void draw() {
background(205);
if (mouseX > bx-r && mouseX < bx+r &&
mouseY > by-r && mouseY < by+r) {
overBox = true;
if(!locked) {
}
} else {
overBox = false;
}
circles[k] = new Circle(bx,by,r);
circles[k].display(); // display all the circles
}
void actionPerformed (GUIEvent e) {
if (e.getSource() == b1) {
r=float(tn.getValue());
}
}
class Circle {
float x,y,r; // location
color c; // color
Circle(float x, float y, float r) {
this.x = x;
this.y = y;
this.r = r;
c = color(random(255));
}
void display() {
ellipse(x,y,10,10); // a circle at position xy
}
}
void mousePressed() {
if(overBox) {
locked = true;
} else {
locked = false;
}
xOffset = mouseX-bx;
yOffset = mouseY-by;
}
void mouseDragged() {
if(locked) {
bx = mouseX-xOffset;
by = mouseY-yOffset;
}
}
void mouseReleased() {
locked = false;
overBox=false;
}
You have two main options:
Option 1: Stop calling the background() function from your draw() function. This is what's clearing out old frames. If you remove that (or move it to setup(), then your old frames will never be cleared out.
Option 2: Store your state in a set of data structures. You can think of using arrays to store the position and radius of each of your circles. (Or better yet, create a Circle class and store it instances of that in an ArrayList.) Then to draw your scene, just iterate over your data structure and draw all of your circles.
Another option is to use an off-screen PGraphics buffer.
I am making a 2d rpg game in java and I have run into a problem. I can make the player move around the stage and I have rocks, trees, walls, etc. on the stage as well. I don't know how to detect the collision and make it to where the player can't move through the object. The code that reads map file and draws image on the canvas is as follows:
public void loadLevel(BufferedImage levelImage){
tiles = new int[levelImage.getWidth()][levelImage.getHeight()];
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
Color c = new Color(levelImage.getRGB(x, y));
String h = String.format("%02x%02x%02x", c.getRed(),c.getGreen(),c.getBlue());
switch(h){
case "00ff00"://GRASS Tile - 1
tiles[x][y] = 1;
break;
case "808080"://Stone -2
tiles[x][y] = 2;
break;
case "894627"://Dirt -3
tiles[x][y] = 3;
break;
case "404040"://Rock on Grass -4
tiles[x][y] = 4;
break;
case "00b700"://Tree -5
tiles[x][y] = 5;
break;
case"000000"://Wall -6
tiles[x][y] = 6;
break;
case "cccccc"://Rock on stone -7
tiles[x][y] = 7;
break;
default:
tiles[x][y] = 1;
System.out.println(h);
break;
}
}
}
}
And the player class is as follows:
public class Player {
private int x,y;
public int locx,locy;
private Rectangle playerR;
private ImageManager im;
public boolean up =false,dn = false,lt=false,rt=false,moving = false,canMove = true;
private final int SPEED =2;
public Player(int x, int y, ImageManager im){
this.x = x;
this.y = y;
this.im = im;
locx = x;
locy = y;
playerR = new Rectangle(x,y,16,16);
}
public void tick(){
if (up) {
if(canMove){
y -= SPEED;
locx = x;
locy = y;
playerR.setLocation(locx, locy);
moving = true;
}
else{
y += 1;
canMove=true;
}
}
if (dn) {
y +=SPEED;
locx = x;
locy = y;
moving = true;
}
}
if (lt) {
x -= SPEED;
locx = x;
locy = y;
moving = true;
}
if (rt) {
x+=SPEED;
locx = x;
locy = y;
moving = true;
}
}
if(moving){
System.out.println("PLAYER\tX:"+locx+" Y:"+locy);
moving = false;
}
}
public void render(Graphics g){
g.drawImage(im.player, x, y, Game.TILESIZE*Game.SCALE, Game.TILESIZE*Game.SCALE, null);
}
}
I don't really know how to do collision, but i googled it and people said to make a rectangle for the player and all the objects that the player should collide with, and every time the player moves, move the player's rectangle. Is this the right way to do this?
EDIT EDIT EDIT EDIT
code for when collision is true:
if (rt) {
x+=SPEED;
locx = x;
locy = y;
playerR.setLocation(locx, locy);
for(int i = 0;i<Level.collisions.size();i++){
if(intersects(playerR,Level.collisions.get(i))==true){
x-=SPEED;
locx = x;
playerR.setLocation(locx, locy);
}
}
moving = true;
}
And the intersects method is as follows:
private boolean intersects(Rectangle r1, Rectangle r2){
return r1.intersects(r2);
}
I'm going to focus on your tick method since that is where most of this logic is going. There are a couple changes here. Most notably, we only move the rectangle before checking for collisions. Then loop through all the collideable objects in your level. Once one is found, we reset our x and y and break out of the loop (no sense in looking at any of the other objects since we already found the one we collided with). Then we update our player position. By doing it this way, I centralized the code so it is not being repeated. If you ever see yourself repeating code, there is a pretty good chance that it can be pulled out to a common place, or to a method.
public void tick() {
if (up) {
y -= SPEED;
} else if (dn) {
y += SPEED;
} else if (lt) {
x -= SPEED;
} else if (rt) {
x += SPEED;
}
playerR.setLocation(x, y);
for (Rectangle collideable : Level.collisions) {
if (intersects(playerR, collideable)) {
x = locx;
y = locy;
playerR.setLocation(x, y);
break;
}
}
locx = x;
locy = y;
}
There are different ways to do that. As you talk about a "rpg" i think your view is Isometric (45° top down).
I would do the collision detection in pure 90° top down, as it is easier and, imho, more realistic.
We have 2 possibilities:
Move your Player to the next position. If there is a collision, reset his position.
Calculate the next position, if there would be a collision don't move.
If you want to have a "gliding" collision response, you have to check in which axis the collision will happen, and stop / reset movement for this axis only.
To have a more efficient collision detection only check near objects, which will possibly collide.
Do this by comparing a squared "dangerRadius" with the squared distance between your player and the object:
if ((player.x - object.x)² + (player.y - object.y)² <= dangerRadius²)
// Check for intersection
This will sort out most of the objects by using a simple calculation of:
2 subtractions
1 addition
3 multiplications (the ²)
1 compare (<=)
In your game you should sepparate the logic and the view. So basicly you don't detect, if the two images overlapp, but you check, if the objects in your logic overlap. Then you draw the images on the right position.
Hope this helps.
EDIT: Important: If you update your character, depending on the time between the last and this frame (1/FPS) you have to limit the max timestep. Why? Because if for some reason (maybe slow device?) the FPS are really low, it is possible, that the character moves verry far between 2 frames and for that he could go through an object in 1 frame.
Also if you simply reset the movement on collision or just don't move the distance between you and the object could be big for low FPS. For normal FPS and not to high movementspeed this won't happen/ be noticeable.
I personally am fairly new to Java, though I have worked with C# in the past. I am making a similar game, and for collision detection I just check the locations of the player and objects:
if (z.gettileX() == p.gettileX()){
if (z.gettileY() == p.gettileY()){
System.out.println("Collision!");
}
}
If the player (p) has equal X coordinates and Y coordinates to z(the bad guy), it will send this message and confirm that the two have, in fact, collided. If you can make it inherent in the actual class behind z to check if the coordinates a equal, you can create an unlimited number of in-game objects that detect collision and react in the same way, i.e. walls.
This is probably what your looking for. I've made this class spicificly for collision of multiple objects and for individual side collisions.
abstract class Entity {
private Line2D topLine;
private Line2D bottomLine;
private Line2D leftLine;
private Line2D rightLine;
private Rectangle rectangle;
private Entity entity;
protected boolean top;
protected boolean bottom;
protected boolean left;
protected boolean right;
protected int x;
protected int y;
protected int width;
protected int height;
public Entity(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
updateLinesAndRects();
}
public void updateLinesAndRects() {
topLine = new Line(x + 1, y, width - 2, 0);
bottomLine = new Line(x + 1, y + height, width - 2, height);
leftLine = new Line(x, y + 1, 0, height - 2);
rightLine = new Line(x + width, y + 1, 0, height - 2);
rectangle = new Rectangle(x, y, width, height)
}
public void setCollision(Entity entity) {
this.entity = entity;
top = isColliding(new Line2D[]{topLine, bottomLine, leftLine, rightLine});
bottom = isColliding(new Line2D[]{bottomLine, topLine, leftLine, rightLine});
left = isColliding(new Line2D[]{leftLine, topLine, bottomLine, rightLine});
right = isColliding(new Line2D[]{rightLine, topLine, bottomLine, leftLine});
}
public void updateBounds() {
if(top) y = entity.y + entity.height;
if(bottom) y = entity.y - height;
if(left) x = entity.x + entity.width;
if(right) x = entity.x - width;
}
public boolean isColliding() {
return rectangle.intersects(entity.rect);
}
private boolean isLinesColliding(Line2D[] lines) {
Rectangle rect = entity.getRectangle();
return lines[0].intersects(rect) && !lines[1].intersects(rect) && !lines[2].intersects(rect) && !lines[3].intersects(rect);
}
private Line2D line(float x, float y, float width, float height) {
return new Line2D(new Point2D.Float(x, y), new Point2D.Float(x + width, x + height));
}
public Rectangle getRectangle() {
return rectangle;
}
}
Example:
class Player extends Entity{
Entity[] entities;
public Player(int x, int y, int width, int height) {
super(x, y, width, height);
}
public void update() {
updateLinesAndRects();
for(Entity entity : entities) {
setCollision(entity);
if(top) system.out.println("player is colliding from the top!");
if(isColliding()) system.out.println("player is colliding!");
updateBounds(); // updates the collision bounds for the player from the entities when colliding.
}
}
public void setEntities(Entity[] entities) {
this.entities = entities;
}
}
I'm writing a program using Processing but I keep getting Expecting TRIPLE_DOT, found ';'.
What could be wrong?
class Collision {
Ball ball = new Ball();
Block block = new Block();
int ball_xpos;
int ball_rad;
int ball_ypos;
int block_width;
int block_height;
int block_control;
Collision(ball.xpos, ball.rad, ball.ypos, block.width, block.height, block.control){
//
}
void detect_() {
//not done yet
}
}
Ball class:
class Ball {
int rad = 30; // Width of the shape
float xpos, ypos; // Starting position of shape
float xspeed = 2.8; // Speed of the shape
float yspeed = 2.2; // Speed of the shape
int xdirection = 1; // Left or Right
int ydirection = 1; // Top to Bottom
Ball() {
ellipseMode(RADIUS);
// Set the starting position of the shape
xpos = width/2;
ypos = height/2;
}
void display() {
ellipseMode(CENTER);
ellipse(xpos, ypos, 410, 40);
}
void move() {
// Update the position of the shape
xpos = xpos + ( xspeed * xdirection );
ypos = ypos + ( yspeed * ydirection );
// Test to see if the shape exceeds the boundaries of the screen
// If it does, reverse its direction by multiplying by -1
if (xpos > width-rad || xpos < rad) {
xdirection *= -1;
}
if (ypos > height-rad || ypos < rad) {
ydirection *= -1;
}
// Draw the shape
ellipse(xpos, ypos, rad, rad);
}
}
In the parameter names in your constructor, the dots (.) should be replaced with _. And you should give types to those parameters:
Collision(int ball_xpos, int ball_rad, ... so on){
//
}
If you use ball.xpos, then compiler expects a var-args after the 1st dot(.) after ball.
But it seems like want to pass attributes of Ball class, to initialize the fields with the Ball class attribute. In that case, you should just pass a single parameter, that is a reference to Ball:
Collision(Ball ball) {
this.ball = ball;
}
But I don't see why at all you are having those fields (ball_xpos, ball_ypos) in Collision class, given that you also have a Ball type field. You can remove them, and just set ball reference to the reference passed in the above constructor.
Same thing for Block type reference. You are simply having copy of the fields of Block and Ball in Collision class again. Not needed.