I keep getting this error when I compile and run my Java program using the ACM library
Exception in thread "Thread-3" java.lang.NullPointerException
at SpaceTravel.getBlackHoleDistance(SpaceTravel.java:148)
at SpaceTravel.gameOverBlackHole(SpaceTravel.java:132)
at BlackHole.oneTimeStep(BlackHole.java:84)
at BlackHole.run(BlackHole.java:45)
at java.lang.Thread.run(Unknown Source)
This is the game class:
import acm.graphics.*;
import acm.program.*;
import acm.util.*;
import java.awt.*;
public class SpaceTravel extends GraphicsProgram {
// specify the size of the window
public static int APPLICATION_WIDTH = 1280;
public static int APPLICATION_HEIGHT = 600;
// class constants
private static final double
PLANET_SIZE = 80,
BLACK_HOLE_SIZE = 100,
STAR_SIZE = 2;
// instance variables
private GOval ice, fire, iron;
private GPoint lastPoint;
private boolean isDragging = false;
private GLabel gameOverText, goal, win;
private BlackHole blackhole1, blackhole2;
private RandomGenerator rand = new RandomGenerator();
// init method, draw the graphics objects
public void init() {
setBackground(Color.BLACK);
// call the randomly colored stars method
drawStars();
// call 1 instance of BlackHole class
blackhole1 = new BlackHole(BLACK_HOLE_SIZE, 5, 2, this);
add(blackhole1, APPLICATION_WIDTH-400, APPLICATION_HEIGHT/2 );
new Thread(blackhole1).start();
// call 1 instance of BlackHole class, but name it differently
blackhole2 = new BlackHole(BLACK_HOLE_SIZE, 3, -4, this);
add(blackhole2, APPLICATION_WIDTH-200, APPLICATION_HEIGHT/2 );
new Thread(blackhole2).start();
// draw fire planet
fire = drawCircleCentered(100, 400, PLANET_SIZE);
add(fire);
fire.setFilled(true);
fire.setColor(Color.RED);
// draw ice planet
ice = drawCircleCentered(100, 100, PLANET_SIZE);
add(ice);
ice.setFilled(true);
ice.setColor(Color.BLUE);
// draw iron planet
iron = drawCircleCentered(100, 250, PLANET_SIZE);
add(iron);
iron.setFilled(true);
Color grey = new Color(34, 34, 34);
iron.setColor(grey);
// game over text
gameOverText = new GLabel ("GAME OVER", APPLICATION_WIDTH/2 - 250, APPLICATION_HEIGHT/2);
gameOverText.setColor(Color.RED);
gameOverText.setFont(new Font("DEFAULT_FONT", Font.BOLD, 90));
// goal text
goal = new GLabel ("GOAL", APPLICATION_WIDTH-150, APPLICATION_HEIGHT/2);
goal.setColor(Color.RED);
goal.setFont(new Font("DEFAULT_FONT", Font.BOLD, 20));
add(goal);
// win text
win = new GLabel ("WINRAR IS YOU!", APPLICATION_WIDTH/2 - 350, APPLICATION_HEIGHT/2);
win.setColor(Color.RED);
win.setFont(new Font("DEFAULT_FONT", Font.BOLD, 90));
}
// checker method if the ice and fire plantes touch, call the game over method below.
private void checkFireIce(GOval fire, GOval ice) {
if(getDistance(fire, ice) < PLANET_SIZE ) {
gameOver(fire);
}
}
// checker method for when fire planet gets to the goal text, call the game winning method below
private void checkPlanetsGoal() {
if(fire.getBounds().intersects(goal.getBounds())) {
winGame();
}
}
// start dragging if the ball is pressed
public void mousePressed(GPoint point) {
if (ice.contains(point)) {
isDragging = true;
lastPoint = point;
}
}
// move the ball when it is dragged, and call checking methods for game winning or game over conditions
public void mouseDragged(GPoint point) {
checkFireIce(fire, ice);
checkPlanetsGoal();
if (isDragging) {
ice.move(point.getX()-lastPoint.getX(),
point.getY()-lastPoint.getY());
lastPoint = point;
// bump the planets
bump(ice, iron);
bump(iron, fire);
}
}
// checking method for if any of the planets have touched an instance of black hole
public void gameOverBlackHole(BlackHole blackhole) {
double a = getBlackHoleDistance(fire, blackhole);
double b = getBlackHoleDistance(ice, blackhole);
double c = getBlackHoleDistance(iron, blackhole);
if(a < BLACK_HOLE_SIZE/2 + PLANET_SIZE/2) {
gameOver(fire);
}
if(b < BLACK_HOLE_SIZE/2 + PLANET_SIZE/2) {
gameOver(ice);
}
if(c < BLACK_HOLE_SIZE/2 + PLANET_SIZE/2) {
gameOver(iron);
}
}
// get distance between a black hole instance and a planet
private double getBlackHoleDistance(GOval planet, BlackHole blackhole) {
return GMath.distance(planet.getX()+PLANET_SIZE/2, planet.getY()+PLANET_SIZE/2,
blackhole.getX(), blackhole.getY());
}
// bump helper method, calculates how much to move a tangent planet by when it is being bumped by another
private void bump(GOval planet1, GOval planet2) {
double offset = PLANET_SIZE+1.5 - getDistance(planet1, planet2);
if (offset > 0) {
planet2.move(offset*(planet2.getX()-planet1.getX())/PLANET_SIZE,
offset*(planet2.getY()-planet1.getY())/PLANET_SIZE);
}
}
// a helper method, compute the distance between the centers of the balls
private double getDistance(GOval planet1, GOval planet2) {
return GMath.distance(planet1.getX()+PLANET_SIZE/2, planet1.getY()+PLANET_SIZE/2,
planet2.getX()+PLANET_SIZE/2, planet2.getY()+PLANET_SIZE/2);
}
// a helper method, draw a circle centered at the given location
private GOval drawCircleCentered(double centerX, double centerY, double size) {
return new GOval(centerX-size/2, centerY-size/2, size, size);
}
// a helper method, draw randomly colored stars
private void drawStars() {
for (int i = 0; i < 1000; i++) {
GOval star = drawCircleCentered(rand.nextDouble(0, APPLICATION_WIDTH), rand.nextDouble(0, APPLICATION_HEIGHT), STAR_SIZE);
add(star);
star.setFilled(true);
star.setColor(rand.nextColor());
}
}
// helper method to switch dragging off when mouse is released from window
public void mouseReleased(GPoint point) {
isDragging = false;
}
// helper method to switch dragging off, remove the planet that touched the goal, and display game over text
public void gameOver(GOval planet) {
isDragging = false;
remove(planet);
add(gameOverText);
}
// helper method to switch dragging off, remove planets, and display win text
private void winGame() {
isDragging = false;
add(win);
remove(fire);
remove(ice);
remove(iron);
remove(goal);
}
}
And this is the class that creates blackholes which can end the game.
// import libraries
import acm.program.*;
import acm.graphics.*;
import acm.util.*;
import java.awt.*;
import java.util.*;
public class BlackHole extends GCompound implements Runnable {
// instance variables
private double size, xSpeed, ySpeed;
private SpaceTravel game;
private boolean stopHammerTime = false;
// constructor for BlackHole
public BlackHole(double size, double xSpeed, double ySpeed, SpaceTravel game) {
// save the parameters size, xSpeed, ySpeed, centerX, centerY, and game
this.size = size;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.game = game;
// call method drawBlackhole
drawBlackhole(0, 0, size, 3, 40);
}
// run method, move the black hole until it hits a planet || stopHammerTime = true
public void run() {
while(!stopHammerTime) {
oneTimeStep();
}
}
// helper method, creates a black hole
private void drawBlackhole(double centerX, double centerY, double size, double gap, int layers) {
for (int i = 0; i < layers; i++) {
// this gradient color will lighten each time the for loop completes
Color gradient = new Color(0 + 5*i, 0 + 5*i, 0 + 5*i);
GOval ring = drawCircleCentered(centerX, centerY, size-gap*2*i);
add(ring);
ring.setFilled(true);
ring.setColor(gradient);
}
}
// a helper method, draw a circle centered at the given location
private GOval drawCircleCentered(double centerX, double centerY, double size) {
return new GOval(centerX-size/2, centerY-size/2, size, size);
}
// a helper method, move the blackHole in oneTimeStep
private void oneTimeStep() {
double x = getX();
double y = getY();
// if the black hole hits the left or the right wall, reverse the x-speed
if (x < size/2 || x+size/2 > game.getWidth()) xSpeed = -xSpeed;
// if the black hole hits the top or the bottom wall, reverse the y-speed
if (y < size/2 || y+size/2 > game.getHeight()) ySpeed = -ySpeed;
// move the black hole by a small interval, incorporating changes from if statements
move(xSpeed, ySpeed);
// check if a planet has touched a blackhole
game.gameOverBlackHole(this);
// delay
pause(20);
}
}
I'm pretty sure that I have the calling of the classes correct, but for some reason, the blackhole2 that is called just crashes.
Exception in thread "Thread-3" java.lang.NullPointerException
It is NullPointerException happened while executing thread "Thread-3"
At line 148 in SpaceTravel.java file.
It seems while Thread-3 executing, line 148 is trying to do some operation on null reference, which results in NullPointerException.
The code that apparently crashes looks like this:
return GMath.distance(planet.getX()+PLANET_SIZE/2, planet.getY()+PLANET_SIZE/2,
blackhole.getX(), blackhole.getY());
Most likely either planet or blackhole parameter is null. Use debugger or println() to figure out which one. This piece of code is called from three places:
double a = getBlackHoleDistance(fire, blackhole);
double b = getBlackHoleDistance(ice, blackhole);
double c = getBlackHoleDistance(iron, blackhole);
Your line numbers are a bit off but I dare to say it's the first line, so either fire or blackhole is null.
A Quick Analysis:
gameOverBlackHole method gets called form BlackHole class this is causing problems because remove happens inside that method. If remove happens while one thread is executing then there will NPE
This a can be a example of what might go wrong if you share state of object between multiple threads
Related
I am currently working on a 3 cushion billiards game project. I have added two balls on the table so far. I am trying to move one of the balls but I am having a hard time doing that. Should I use a timer? If so then could you tell me an effective way to use the timer on my code so I can move my balls?
Your help would be much appreciated.
Thanks in advance.
Farhan Hasan
I have tried to create a move function for the class balls. But I am not sure what I should put inside the function, I have added the xSpeed and ySpeed. The xLocation and the yLocation changes depending on the xSpeed and ySpeed.
public class Balls
{
private Color ballFillColor;
private Color ballBorderColor;
private int ballX = 0;
private int ballY = 0;
private int xSpeed = 5;
private int ySpeed = 0;
private int ballWidth = 0;
private int ballHeight = 0;
Timer t;
public boolean fillBall = false;
private static Balls ballArray[]; //Required for drawMultipleBalls
Balls(){ //Constructor
ballBorderColor = Color.black;
}
Balls(int ballX, int ballY, int ballWidth, int ballHeight, Color ballBorderColor, JFrame window){ //Constructor
// X , Y , Width, Height, Border Colour, container
this.setBallBorderColor(ballBorderColor);
this.setBallWidth(ballWidth);
this.setBallHeight(ballHeight);
this.setBallX(ballX);
this.setBallY(ballY);
this.drawBall(window);
}
//Here is the move function. I am not really sure what to do here.
public void move()
{
if(this.ballX < 1000 - this.ballWidth)
{
this.ballX += this.xSpeed;
}
try
{
Thread.sleep(1);
}
catch(Exception e)
{
}
}
//GET AND SET FUNCTIONS HERE
//HERE ARE THE FUNCTIONS WHICH ARE RESPONSIBLE FOR DRAWING MY BALLS IN JFRAME
public void drawBall(JFrame frame)
{
frame.getContentPane().add(new MyComponent());
}
public void drawMultipleBalls(JFrame frame, Balls[] balls)
{
ballArray = balls;
frame.getContentPane().add(new MyComponent2());
}
private class MyComponent extends JComponent{
public void paintComponent(Graphics g){
if (fillBall) //Fill first, and then draw outline.
{
g.setColor(ballFillColor);
g.fillOval(getBallX(),getBallY(), getBallHeight(),getBallWidth());
}
g.setColor(getBallBorderColor());
g.drawOval(getBallX(),getBallY(), getBallHeight(),getBallWidth());
}
}
private class MyComponent2 extends JComponent{
public void paintComponent(Graphics g){
for (int i = 0; i < ballArray.length; i++)
{
if (ballArray[i].fillBall) //Fill first, and then draw outline.
{
g.setColor(ballArray[i].ballFillColor);
g.fillOval(ballArray[i].getBallX(),ballArray[i].getBallY(), ballArray[i].getBallHeight(),ballArray[i].getBallWidth());
}
g.setColor(ballArray[i].getBallBorderColor());
g.drawOval(ballArray[i].getBallX(),ballArray[i].getBallY(), ballArray[i].getBallHeight(),ballArray[i].getBallWidth());
}
}
}
Hopefully, I can have two movable balls for the game, the should bounce back as the hit the edge of the screen and they should be able to slow down over time. For that, I am thinking to use a damper (I will multiply the xSpeed and ySpeed with a number less than 1, eventually it will slow down the ball)
Here is a simple example I came up with to show a ball moving and bouncing off the edges.
The direction changes based on the boundary. Left and top edges just check for 0. Bottom and right edges need to include the diameter of the ball.
The x and y increments are independent. And these amounts in conjunction with the timer can change the movement. Notice however, that to have objects bounce off of each other (as in a pool game) is more complicated due to angle of trajectories, etc. And the distances bounced will vary and slow with time based on frictional values. Everything else is documented in the Java API.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MovementDemo extends JPanel implements ActionListener {
JFrame frame = new JFrame("Movement Demo");
int size = 500;
int x = 50;
int y = 200;
int diameter = 50;
int yinc = 2;
int xinc = 2;
int xdirection = 1;
int ydirection = 1;
public MovementDemo() {
setPreferredSize(new Dimension(size, size));
frame.add(this);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new MovementDemo().start());
}
public void start() {
Timer timer = new Timer(100, this);
timer.setDelay(5);
timer.start();
}
public void actionPerformed(ActionEvent ae) {
if (x < 0) {
xdirection = 1;
}
else if (x > size - diameter) {
xdirection = -1;
}
if (y < 0) {
ydirection = 1;
}
else if (y > size - diameter) {
ydirection = -1;
}
x = x + xdirection * xinc;
y = y + ydirection * yinc;
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLUE);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillOval(x, y, diameter, diameter);
}
}
It seems in general there are a few things you need to figure out:
has the ball collided with another ball
has the ball collided with a wall
otherwise just figure out what is the ball's new position based on its velocity
Below is some sample code that stubs some of this out. You can first compare the current ball's position to all others (not including the current ball of course). If there are any equal positions, process a collision with a ball. If the ball is at the window border i.e it hit a wall, process a collision with a wall. Otherwise just calculate its new position based on its current velocity.
The process collision part is just to apply physics mechanics to whatever degree of complexity you require. One general suggested change would be to update the velocity of the balls then apply it to the position after. The specific calculations for velocity changes you could apply as needed and as you can imagine it can get pretty involved which is why I suggest using a separate method and possibly a sub class for velocity instead of managing each part of the velocity vector in the ball itself. I used the wall as an object because of this. The composition, weights, velocities etc of the object's colliding can affect the resulting collision, but how complex you want that processing to be is up to you.
Sorry I'm no physics expert but I hope this sends you in the right direction in terms of code! Also this might help with the specific calculations you might want to use:
https://www.khanacademy.org/science/physics/one-dimensional-motion/displacement-velocity-time/v/calculating-average-velocity-or-speed
public void move()
{
// check if balls are on same position not including this ball
for(Ball b: ballArray){
if (this.position == b.position && this != b){
processCollision(this, b, null);
} else{
// if the ball hasn't collided with anything process its movement based on speed
// this assumes a 1000 x 1000 window for keeping objects inside it
if(this.ballX < 1000 - this.ballWidth && this.ballY < 1000 - this.ballHeight){
this.ballX += this.xSpeed;
this.ballY += this.ySpeed;
}else {
processCollision(this, null, new Wall());
}
}
}
try
{
Thread.sleep(1);
}
catch(Exception e)
{
}
}
public void processCollision(Ball b1, Ball b2, Wall w){
// if ball hasn't collided with a wall, process a ball - ball collision
if(w == null){
// apply physics mechanics according the complexity desired for ball collisions
b1.xSpeed -= b2.xSpeed;
b1.ySpeed -= b2.ySpeed;
// ball 2 would end up slowing down
b2.xSpeed -= b1.xSpeed;
b2.ySpeed -= b1.ySpeed;
}
// if ball hasn't collided with a ball, process a ball - wall collision
if(b2 == null){
// apply physics mechanics for hitting a wall
// e.g as below: just send ball in opposite direction
b1.xSpeed = b1.xSpeed * -1;
b1.ySpeed = b1.ySpeed * -1;
}
// either way, process ball's new position based on its new speed
b1.ballX += b1.xSpeed;
b1.ballY += b1.ySpeed;
b2.ballX += b2.xSpeed;
b2.ballY += b2.ySpeed;
}
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 4 years ago.
I'm working on a pong game with Processing. The game works just fine with a single class, however, I have to add multiple classes. Every time I get a Null Pointer Exception. This is part of the main class which extends PApplet
PApplet app = new PApplet();
void MAINMENU() {
// Start the Menu Song
if (musicStartMenu) {
MENUsong.loop();
musicStartMenu = false;
}
// Stop the Level song if needed
if (!musicStart) {
BGsong.stop();
musicStart = true;
}
// Resetting player scores
ScoreP1 = ScoreP2 = 0;
// Creating the Background for this scene
image(menuBG, 0, 0);
textFont(SC);
// Setting the title
text("PONG", width / 2 - 100, 150);
// Creating the buttons for this scene
Button Play= new Button(width / 2 - 150, height / 2 - 70, 300, 100, "PLAY", width / 2 - 100, height / 2 + 10, 1, 1,MAIN,app);
Button Exit= new Button(width / 2 - 150, height / 2 + 70, 300, 100, "EXIT", width / 2 - 100, height / 2 + 150, 3, 2,MAIN,app);
Play.Create();
Exit.Create();
}
and this is the button class:
import processing.core.PApplet;
public class Button {
int Bx,By, width, height;
String label;
int labelW, labelH, action, style, MAIN;
PApplet app;
public Button(int Bx, int By, int width, int height, String label, int labelW, int labelH, int action, int style, int MAIN, PApplet app) {
this.Bx = Bx;
this.By = By;
this.width = width;
this.height = height;
this.label = label;
this.labelW = labelW;
this.labelH = labelH;
this.action = action;
this.style = style;
this.MAIN = MAIN;
this.app = app;
}
void Create(){
// Check if we hover the mouse over and select a style
if (ButtonBorder(Bx, By, height, width)) {
if (style == 1)
// light green
app.fill(100, 155, 100);
else if (style == 2)
// light red
app.fill(255, 100, 100);
} else app.fill(0, 50); // black transparent
// Nobody likes borders
app.stroke(0, 0);
// Create the button box
app.rect(Bx, By, width, height);
// CHeck if the mouse is pressed and is hovering above the button
if (app.mousePressed && ButtonBorder(Bx, By, height, width)) {
// Select scene on click
MAIN = action;
}
// SET the fill of the label and create the label
app.fill(255);
app.text(label, labelW, labelH);
}
boolean ButtonBorder(int xB, int yB, int ButtonHeight, int ButtonWidth) {
// returns true if the mouse pointer is located inside the button
if (!(app.mouseX >= xB && app.mouseX <= xB + ButtonWidth))
return false;
if (!(app.mouseY >= yB && app.mouseY <= yB + ButtonHeight))
return false;
return true;
}
If anyone has any clue it would be of much help. I want to say that the code is working just fine in a method inside the main class but for some reason that I can't seem to find it doesn't work in another class.
Trace:
java.lang.NullPointerException
at processing.core.PApplet.fill(PApplet.java:14521)
at ButtonB.Create(ButtonB.java:41)
at Pong.MAINMENU(Pong.java:205)
at Pong.draw(Pong.java:161)
at processing.core.PApplet.handleDraw(PApplet.java:2429)
at processing.awt.PSurfaceAWT$12.callDraw(PSurfaceAWT.java:1557)
at processing.core.PSurfaceNone$AnimationThread.run(PSurfaceNone.java:313)
Your code has a few problems, because you aren't using Processing the way it was designed to be used.
First, it doesn't make sense to create an instance of PApplet directly like this:
PApplet app = new PApplet();
Instead, you want to extend the PApplet class to create your own sketch, like this:
import processing.core.PApplet;
public class MySketch extends PApplet{
public void settings(){
size(500, 500);
}
public void draw(){
ellipse(mouseX, mouseY, 50, 50);
}
public void mousePressed(){
background(64);
}
public static void main(String[] args){
String[] processingArgs = {"MySketch"};
MySketch mySketch = new MySketch();
PApplet.runSketch(processingArgs, mySketch);
}
}
Secondly, you have to make sure you don't call Processing's functions (like fill() and rect()) until after the setup() function of your sketch class has been called.
Also, some feedback: please try to use standard naming conventions in your code. Classes and constructors should start with an upper-case letter, and functions and variables should start with a lower-case letter. Following this convention will make your code much easier to read.
Shameless self-promotion: here is a tutorial on using Processing as a Java library.
I have been trying to code a half built simple java game i was given to by a friend (I wanted to try learning coding so he gave me this old assignment from his 1st year of programming class).
He told me to try and build a game using the 2 classes he gave me, a main class and a sub class.
So far I have got everything to work as intended except for finding a way to get my enemy class to move towards the goal regardless of its position. I can get it to always move in a fixed direction towards the goal if I set it up perfectly but I want to be able to find the position of the goal regardless and make its way towards it.
The code need to be written in the peformAction method under Enemy.
If anyone would be kind enough to explain how I can go about this, it would be much appropriated, or even if you can just link me to another post that explains how to do what I'm trying to do.
Thank you in advance.
GameManager (main) Class:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;
public class GameManager extends JFrame implements KeyListener {
private int canvasWidth;
private int canvasHeight;
private int borderLeft;
private int borderTop;
private BufferedImage canvas;
private Stage stage;
private Enemy[] enemies;
private Player player;
private Goal goal;
private Graphics gameGraphics;
private Graphics canvasGraphics;
private int numEnemies;
private boolean continueGame;
public static void main(String[] args) {
// During development, you can adjust the values provided in the brackets below
// as needed. However, your code must work with different/valid combinations
// of values.
GameManager managerObj = new GameManager(1920, 1280, 30);
}
public GameManager(int preferredWidth, int preferredHeight, int maxEnemies) {
this.borderLeft = getInsets().left;
this.borderTop = getInsets().top;
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
if (screenSize.width < preferredWidth)
this.canvasWidth = screenSize.width - getInsets().left - getInsets().right;
else
this.canvasWidth = preferredWidth - getInsets().left - getInsets().right;
if (screenSize.height < preferredHeight)
this.canvasHeight = screenSize.height - getInsets().top - getInsets().bottom;
else
this.canvasHeight = preferredHeight - getInsets().top - getInsets().bottom;
setSize(this.canvasWidth, this.canvasHeight);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
addKeyListener(this);
Random rng = new Random(2);
this.canvas = new BufferedImage(this.canvasWidth, this.canvasHeight, BufferedImage.TYPE_INT_RGB);
// Create a Stage object to hold the background images
this.stage = new Stage();
// Create a Goal object with its initial x and y coordinates
this.goal = new Goal(this.canvasWidth / 2, Math.abs(rng.nextInt()) % this.canvasHeight);
// Create a Player object with its initial x and y coordinates
this.player = new Player(this.canvasWidth - (Math.abs(rng.nextInt()) % (this.canvasWidth / 2)),
(Math.abs(rng.nextInt()) % this.canvasHeight));
// Create the Enemy objects, each with a reference to this (GameManager) object
// and their initial x and y coordinates.
this.numEnemies = maxEnemies;
this.enemies = new Enemy[this.numEnemies];
for (int i = 0; i < this.numEnemies; i++) {
this.enemies[i] = new Enemy(this, Math.abs(rng.nextInt()) % (this.canvasWidth / 4),
Math.abs(rng.nextInt()) % this.canvasHeight);
}
this.gameGraphics = getGraphics();
this.canvasGraphics = this.canvas.getGraphics();
this.continueGame = true;
while (this.continueGame) {
updateCanvas();
}
this.stage.setGameOverBackground();
updateCanvas();
}
public void updateCanvas() {
long start = System.nanoTime();
// If the player is alive, this should move the player in the direction of the
// key that has been pressed
// Note: See keyPressed and keyReleased methods in the GameManager class.
this.player.performAction();
// If the enemy is alive, the enemy must move towards the goal. The goal object
// is obtained
// via the GameManager object that is given at the time of creating an Enemy
// object.
// Note: The amount that the enemy moves by must be much smaller than that of
// the player above
// or else the game becomes too hard to play.
for (int i = 0; i < this.numEnemies; i++) {
this.enemies[i].performAction();
}
if ((Math.abs(this.goal.getX() - this.player.getX()) < (this.goal.getCurrentImage().getWidth() / 2))
&& (Math.abs(this.goal.getY() - this.player.getY()) < (this.goal.getCurrentImage().getWidth() / 2))) {
for (int i = 0; i < this.numEnemies; i++) {
// Sets the image of the enemy to the "dead" image and sets its status to
// indicate dead
this.enemies[i].die();
}
// Sets the image of the enemy to the "dead" image and sets its status to
// indicate dead
this.goal.die();
// Sets the background of the stage to the finished game background.
this.stage.setGameOverBackground();
this.continueGame=false;
}
// If an enemy is close to the goal, the player and goal die
int j=0;
while(j<this.numEnemies) {
if ((Math.abs(this.goal.getX() - this.enemies[j].getX()) < (this.goal.getCurrentImage().getWidth() / 2))
&& (Math.abs(this.goal.getY() - this.enemies[j].getY()) < (this.goal.getCurrentImage().getWidth()/ 2))) {
this.player.die();
this.goal.die();
this.stage.setGameOverBackground();
j=this.numEnemies;
this.continueGame=false;
}
j++;
}
try {
// Draw stage
this.canvasGraphics.drawImage(stage.getCurrentImage(), 0, 0, null);
// Draw player
this.canvasGraphics.drawImage(player.getCurrentImage(),
this.player.getX() - (this.player.getCurrentImage().getWidth() / 2),
this.player.getY() - (this.player.getCurrentImage().getHeight() / 2), null);
// Draw enemies
for (int i = 0; i < this.numEnemies; i++) {
this.canvasGraphics.drawImage(this.enemies[i].getCurrentImage(),
this.enemies[i].getX() - (this.enemies[i].getCurrentImage().getWidth() / 2),
this.enemies[i].getY() - (this.enemies[i].getCurrentImage().getHeight() / 2), null);
}
// Draw goal
this.canvasGraphics.drawImage(this.goal.getCurrentImage(),
this.goal.getX() - (this.goal.getCurrentImage().getWidth() / 2),
this.goal.getY() - (this.goal.getCurrentImage().getHeight() / 2), null);
} catch (Exception e) {
System.err.println(e.getMessage());
}
// Draw everything.
this.gameGraphics.drawImage(this.canvas, this.borderLeft, this.borderTop, this);
long end = System.nanoTime();
this.gameGraphics.drawString("FPS: " + String.format("%2d", (int) (1000000000.0 / (end - start))),
this.borderLeft + 50, this.borderTop + 50);
}
public Goal getGoal() {
return this.goal;
}
public void keyPressed(KeyEvent ke) {
// Below, the setKey method is used to tell the Player object which key is
// currently pressed.
// The Player object must keep track of the pressed key and use it for
// determining the direction
// to move.
if (ke.getKeyCode() == KeyEvent.VK_LEFT)
this.player.setKey('L', true);
if (ke.getKeyCode() == KeyEvent.VK_RIGHT)
this.player.setKey('R', true);
if (ke.getKeyCode() == KeyEvent.VK_UP)
this.player.setKey('U', true);
if (ke.getKeyCode() == KeyEvent.VK_DOWN)
this.player.setKey('D', true);
if (ke.getKeyCode() == KeyEvent.VK_ESCAPE)
this.continueGame = false;
}
#Override
public void keyReleased(KeyEvent ke) {
// Below, the setKey method is used to tell the Player object which key is
// currently released.
// The Player object must keep track of the pressed key and use it for
// determining the direction
// to move.
if (ke.getKeyCode() == KeyEvent.VK_LEFT)
this.player.setKey('L', false);
if (ke.getKeyCode() == KeyEvent.VK_RIGHT)
this.player.setKey('R', false);
if (ke.getKeyCode() == KeyEvent.VK_UP)
this.player.setKey('U', false);
if (ke.getKeyCode() == KeyEvent.VK_DOWN)
this.player.setKey('D', false);
}
#Override
public void keyTyped(KeyEvent ke) {
}
}
My Enemy (sub) Class:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Enemy {
//variables
private BufferedImage imageRunning;
private BufferedImage imageOver;
private BufferedImage imageCurrent;
int valX = 200;
int valY = 200;
public Enemy (GameManager gameManager, int x, int y) {
try {
this.imageRunning = ImageIO.read(new File("C:/Users/HUS/Desktop/Assigment222/images/enemy-alive.png"));
this.imageOver = ImageIO.read(new File("C:/Users/HUS/Desktop/Assigment222/images/enemy-dead.png"));
} catch (IOException e) {
e.printStackTrace();
}
this.imageCurrent = this.imageRunning;
}
public BufferedImage getCurrentImage() {
return this.imageCurrent;
}
public void performAction() {
valX += 1;
valY += 1;
return;
}
public void die() {
// TODO Auto-generated method stub
this.imageCurrent = this.imageOver;
}
public int getX() {
// TODO Auto-generated method stub
return this.valX;
}
public int getY() {
// TODO Auto-generated method stub
return this.valY;
}
}
My Goal (sub) class:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Goal {
private BufferedImage imageRunning;
private BufferedImage imageOver;
private BufferedImage imageCurrent;
int posX = 500;
int poxY = 500;
public Goal(int x, int y) {
try {
this.imageRunning = ImageIO.read(new File("C:/Users/HUS/Desktop/Assigment222/images/goal-alive.png"));
this.imageOver = ImageIO.read(new File("C:/Users/HUS/Desktop/Assigment222/images/goal-dead.png"));
} catch (IOException e) {
e.printStackTrace();
}
this.imageCurrent = this.imageRunning;
}
public BufferedImage getCurrentImage() {
return this.imageCurrent;
}
public int getX() {
return this.posX;
}
public int getY() {
return this.poxY;
}
public void die() {
this.imageCurrent = this.imageOver;
}
}
It's impossible with the current code if you want to restrict the modifications only to the performAction method.
1) The Enemy has no way of accessing (seeing) the coordinates of the goal. This means the code can't figure out the way to the goal because it doesn't have the X and Y of where the destination is.
2) To fix that you would need Enemy to be able to see the X and Y of the Goal. The quickest way would be to use getters and setters in the GameManager class to return the stored Goal object. Then you can modify your Enemy constructor to store the reference to the GameManager that created it.
GameManager:
public Goal getGoal() {
return goal;
}
Enemy:
private GameManager gameManager;
public Enemy (GameManager gameManager, int x, int y) {
...
this.gameManager = gameManager;
}
The last step would be getting the Goal object from the stored gameManager, and using its X and Y to do some logic (this would be performed inside the performAction method):
int goalX = gameManager.getGoal().getX();
int goalY = gameManager.getGoal().getY();
...some logic to calculate the direction here...
I'm sure you'll know what to do with the numbers and figure out how to move towards the goal from there.
#Edit
If not - one way would be comparing Xs and Ys of both the Enemy and the Goal and then incrementing/decrementing valX and/or valY accordingly.
A very simple implementation:
public void performAction()
{
valX += Math.signum(gameManager.getGoal().getX() - valX);
valY += Math.signum(gameManager.getGoal().getX() - valX);
}
In your method performAction you are currently changing the x and y position respectively by incrementing them by 1 each time the method gets called.
This will move your enemy exactly 1 unit in direction x and 1 unit in direction y.
You instead want to move your character in the direction of the Goal. That's more of a math problem than a programming issue but let's give it a go:
You have 2 positions:
Your Enemy has a position of form and the same goes for your goal with position
You now want to move your Enemy in the direction of the goal. So you calculate a vector connecting these two points with
You then change the length of the vector to the speed your enemy can move with (maybe define this with a field?) by normalizing the vector and doing a scalar multiplication with your speed.
So with a given speed S this gives you the full formula:
Now let us transform this into code. In the constructor of your Enemy class save the GameManager as a field so you can call the getGoal method on it.
So we add a field private GameManager gameManager and in the Constructor we do this.gameManager = gameManager to store the reference.
Also we adda field private double speed = 2 for our calculation.
And then we change the performAction method to implement our formula. Something like this:
public void performAction()
{
// Calculate our P_g - P_e
double dX = gameManager.getGoal().getX() - valX;
double dY = gameManager.getGoal().getX() - valX;
// Calculate its length to normalize it
double divider = Math.sqrt(dX * dX + dY * dY);
// Normalize it
dX /= divider;
dY /= divider;
// Do a scalar multiplication with our speed
dX *= speed;
dY *= speed;
valX += dX;
valY += dY;
}
The only problem we are running into here is that we are adding double values to our integer values valX and valY so probably change those to double values generally or else you might run into problems where your enemy will never move at all.
I'm teaching myself LibGdx and was following the simple game tutorial, unfortunately majority of the code is in one class. I want to refactor the code so I can use multiple textures for the rain that falls based on a random number.
I'll attach the Code for the main program and then the class I got started on.
So far everything worked except the Rain texture/img does not show on the screen.
public class GameScreen implements Screen {
public static FruitHarvest game;
protected final Texture dropImage;
//protected final Texture dropImage2;
private final Texture bucketImage;
public static Rectangle bucket;
public static Sound dropSound;
//private static Music rainMusic;
private final OrthographicCamera camera;
public static Array<Rectangle> raindrops;
private long lastDropTime;
public static int dropsGathered;
// private int random = MathUtils.random(0,1);
private Drops drop;
//Iterator<Rectangle> iterator = raindrops.iterator();
public GameScreen(final FruitHarvest game) {
this.game = game;
// load the images for the droplet and the bucket, 64x64 pixels each
dropImage = new Texture(Gdx.files.internal("droplet.png"));
//dropImage2 = new Texture(Gdx.files.internal("droplet1.png"));
bucketImage = new Texture(Gdx.files.internal("bucket.png"));
// load the drop sound effect and the rain background "music"
dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
//rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));
//rainMusic.setLooping(true);
// create the camera and the SpriteBatcher
camera = new OrthographicCamera();
camera.setToOrtho(false, 800, 480);
// create a Rectangle to logically represent the bucket
bucket = new Rectangle();
bucket.x = 800 / 2 - 64 / 2; // Center the bucket horizontally
bucket.y = 20; // Bottom left corner of the bucket is 20 pixels above the bottom screen edge;
bucket.width = 64;
bucket.height = 64;
// Create the raindrops array and spawn the first raindrop
raindrops = new Array<Rectangle>();
long delta = 0;
drop = new Drops(dropImage, 64, 64, raindrops, delta);
}
#Override
public void render(float delta) {
// clear the screen with a dark blue color. The arguments to glClearColor are the
// red, green, blue, and alpha component in the range [0,1] of the color to be
// used to clear the screen
Gdx.gl.glClearColor(0, 0, .5f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// tell the camera to update its matrices.
camera.update();
// tell the SpriteBatch to render in the coordinate system specified by the camera.
game.batch.setProjectionMatrix(camera.combined);
// begin a new batch and draw the bucket and all drops
game.batch.begin();
game.font.draw(game.batch, "Drops collected: " + dropsGathered, 0, 480);
game.batch.draw(bucketImage, bucket.x, bucket.y, bucket.width, bucket.height);
// Draws the Items Falling
for (Rectangle raindrop : raindrops) {
game.batch.draw(dropImage, raindrop.x, raindrop.y);
}
game.batch.end();
// process user input
if (Gdx.input.isTouched()) {
Vector3 touchPos = new Vector3();
touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
camera.unproject(touchPos);
bucket.x = touchPos.x - 64 / 2;
}
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime();
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();
// make sure the bucket stays within the screen bounds
if (bucket.x < 0) bucket.x = 0;
if (bucket.x > 800 - 64) bucket.x = 800 - 64;
// check if we need to create a new raindrop
if (TimeUtils.nanoTime() - drop.getLastDropTime() > 1000000000) {
drop.spawnRaindrop();
}
// move the raindrops, remove any that are beneath the bottom edge of the screen
// or that hit the bucket. In the later case we increase the value our drops counter
// and add a sound effect.
Iterator<Rectangle> iter = raindrops.iterator();
drop.update(delta);
// while (iter.hasNext()) {
// Rectangle raindrop = iter.next();
// raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
// if (raindrop.y + 64 < 0) iter.remove();
// if (raindrop.overlaps(bucket)) {
// dropsGathered++;
// dropSound.play();
// iter.remove();
// }
// }
}
private void spawnRaindrop() {
Rectangle raindrop = new Rectangle();
raindrop.x = MathUtils.random(0, 800 - 64);
raindrop.y = 480;
raindrop.width = 64;
raindrop.height = 64;
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
// public void randomDrop(int value, float dropX, float dropY) {
// switch (value) {
// case 0:
// game.batch.draw(dropImage, dropX, dropY);
// break;
// case 1:
// //game.batch.draw(dropImage2, dropX, dropY);
// break;
// default:
// game.batch.draw(dropImage, dropX, dropY);
// break;
// }
// }
#Override
public void resize(int width, int height) {
}
#Override
public void show() {
// start the playback of the background music when the screen is shown
//rainMusic.play();
}
#Override
public void hide() {
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void dispose() {
dropImage.dispose();
bucketImage.dispose();
dropSound.dispose();
//rainMusic.dispose();
}
}
Heres my class for the drops
public class Drops {
private Rectangle raindrop;
private int imageHeight, imageWidth, x, y;
private Array<Rectangle> raindrops;
private long lastDropTime;
private Texture dropImage = new Texture(Gdx.files.internal("droplet.png"));
Iterator<Rectangle> iter = GameScreen.raindrops.iterator();
private float runTime = 0;
public Drops(Texture img, int imageHeight, int imageWidth, Array<Rectangle> drop, float delta) {
this.imageHeight = imageHeight;
this.imageWidth = imageWidth;
this.raindrops = drop;
this.dropImage = img;
}
public void update(float delta) {
while (iter.equals(true)) {
raindrop = iter.next();
raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
if (raindrop.y + 64 < 0) iter.remove();
onCollision();
}
}
public void onCollision() {
if (raindrop.overlaps(bucket)) {
GameScreen.dropsGathered++;
GameScreen.dropSound.play();
iter.remove();
}
}
public void spawnRaindrop() {
Rectangle raindrop = new Rectangle();
raindrop.x = MathUtils.random(0, 800 - 64);
raindrop.y = 480;
raindrop.width = imageWidth;
raindrop.height = imageHeight;
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
public long getLastDropTime() {
return lastDropTime;
}
}
By drop.spawnRaindrop(); you add drops to Array<Rectangle> raindrops; in your Drops class but for drawing you use
for (Rectangle raindrop : raindrops) {
game.batch.draw(dropImage, raindrop.x, raindrop.y);
}
Which will loop trough raindrop array list in your GameScreen which is empty.
So either draw the array list in drops or populate array list in GameScreen.
You need to be more careful as you refactor. You left behind your original Array of drop rectangles in your screen class, and you're drawing that (which is now empty). Then in your Drops class you are referencing the iterator for the now useless array in the screen class. And you're updating that empty array in the screen's render method.
Basically, the drops need to be handled in one place, but you're handling redundant arrays of drops in two different classes and getting them all mixed up.
It's not clear to me why you even have a class called Drops that tries to handle collisions with a bucket. There's no reason to move top-level game logic into a separate class, as that just complicates the code. If you had a more complicated game, it might make sense to have separate classes for tracking and updating various aspects of the game.
Incidentally, you're leaking a texture you load in this line:
private Texture dropImage = new Texture(Gdx.files.internal("droplet.png"));
because you never dispose of it before replacing the reference with another one in the constructor. In LibGDX, any object that implements Disposable must be disposed before its reference is lost, or it will leak native memory.
The straight-forward way to allow multiple drop images:
1) Go back to your original single class with all the game logic in the screen class.
2) Load your drop images into an array for easier access.
private final Array<Texture> dropImages = new Array<Texture>(); // replaces your dropImage declaration
//...
// in constructor:
dropImages.add(new Texture(Gdx.files.internal("droplet.png")));
dropImages.add(new Texture(Gdx.files.internal("droplet1.png")));
// etc. as many variations as you like
// don't forget to dispose of them:
#Override
public void dispose() {
for (Texture dropImage : dropImages) dropImage.dispose();
bucketImage.dispose();
dropSound.dispose();
}
3) Create a class Drop that extends Rectangle and has an additional parameter for the image type. You probably also want to make these sortable by image index to avoid swapping between Textures multiple times as you draw them, which causes batch flushes since you're not using a TextureAtlas.
public class Drop extends Rectangle implements Comparable<Drop>{
public int imageIndex;
public Drop (){
super();
}
public int compareTo(Drop otherDrop) {
return (int)Math.signum(imageIndex - otherDrop.imageIndex);
}
}
4) Change your Array<Rectangle> to Array<Drop>. When you spawn a drop, also give it a random image index:
private void spawnRaindrop() {
Drop raindrop = new Drop ();
raindrop.x = MathUtils.random(0, 800 - 64);
raindrop.y = 480;
raindrop.width = 64;
raindrop.height = 64;
raindrop.imageIndex = MathUtils.random(dropImages.size); // <-- HERE
raindrops.add(raindrop);
lastDropTime = TimeUtils.nanoTime();
}
5) When drawing your drops, use the drop's imageIndex to pull the correct texture. You can sort them first to avoid swapping the Texture back and forth:
// Draws the Items Falling
raindrops.sort();
for (Drop raindrop : raindrops) {
game.batch.draw(dropImages.get(raindrop.imageIndex), raindrop.x, raindrop.y);
}
i'm trying to implement a function to calculate the area of the polygon. this code allows you yo draw a polygon but when it comes to display the area, i am not sure how to do that. i tried in a couple ways but i am still a beginner at programming, so i'd appreciate any help. here is the code:
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.*;
import javax.swing.*;
import java.awt.Color;
import java.awt.Polygon;
public class DrawPolygons extends JApplet implements ActionListener, MouseListener
{
private static final int NUMPOINTS = 500; //Up to 500 points can be chosen
private JButton finish; //Button to indicate user is done entering points
private Polygon shape; //polygon object to be drawn
private boolean isDrawn; //boolean flag for when the user is finished drawing
private int count; //how many points the user has clicked
private Color color; //color of the polygon after user finalizes points
private int[] x; //x coordinates of each point user picks
private int[] y; //y coordinates of each point user picks
private float sum;
private double area;
public void init() //set up GUI
{
setLayout(new FlowLayout());
addMouseListener(this); //adds MouseListener for mouse clicks
isDrawn = false; //isDrawn is initially false
count = 0; //count starts as 0
x = new int[NUMPOINTS]; //allows for up to 500 points to be chosen
y = new int[NUMPOINTS]; //allows for up to 500 points to be chosen
finish = new JButton("Finalize points"); //creates finish button
finish.addActionListener(this); //adds finish button to ActionListener
add(finish); //adds finish button to GUI
color = Color.BLACK; //color is intially black, and will remain
// so if user cancels the color chooser
//JOptionPane.showMessageDialog(null, "Click points that will make up the polygon. After each" +
// "point is entered, press the Finalize Points button");
shape = new Polygon(); //creates the Polygon shape
}
public void paint(Graphics g) //draws the Polylines, Polygons, and sets the color
{
super.paint(g);
g.drawPolyline(x, y, count); //draws the polyline specified by user
// mouseclick
g.setColor(color); //sets the color to the user chosen color
if(isDrawn) //if finalize button is clicked
{
g.fillPolygon(x, y, count); //finalizes polylines into
} // polygon and fills shape
}
public void actionPerformed(ActionEvent a) //decides what to do when finalize button is pressed
{
if(a.getSource() == finish) //if the finalize button is pressed
{
isDrawn = true; //isDrawn is set to true, ending the users ability
// to add more points, and fills the polygon
color = Color.red;
//JColorChooser.showDialog(this, "Choose a color", color); //color is set to users choice
repaint();
JOptionPane.showMessageDialog(null, "The area is ");
}
}
public void mouseClicked(MouseEvent e) //save coordinates of clicks
{
if(isDrawn == false && count < NUMPOINTS) //if the finalize button is not pressed
{ // the user can add additional points
x[count] = e.getX(); //adds the x point at the current mouse x coordinate
y[count] = e.getY(); //adds the y point at the current mouse y coordinate
count++; //count increases with each mouse click
repaint();
}
else if (e.isShiftDown()) {
// Clear the applet. (This only requires a repaint.)
// Also, set count to zero to start a new polygon.
count = 0;
isDrawn = false;
repaint();
}
}
private float getPolygonArea(int[] x, int[] y, int count)
{
float sum_but_no_result=0;
for(int i=0;i<(count-1);i++) // count is point number of polygon
{
sum_but_no_result+=x[i]*y[i+1] + y[i]*x[i+1];
}
sum_but_no_result+=x[count-1]*y[0] + y[count-1]*x[0];
float sum = (float)Math.abs(sum_but_no_result) / 2.0f;
return sum;
}
//Empty Implementation provided here so we can implement MouseListener (needed because we must
// provide concrete forms of all methods of an interface to implement it
public void mousePressed(MouseEvent e){};
public void mouseReleased(MouseEvent e){};
public void mouseEntered(MouseEvent e){};
public void mouseExited(MouseEvent e){};
}
float x[N],y[N]; // point coordinates as x1,y1 x2,y2 ....
...
...
...
...
float sum_but_no_result=0;
for(int i=0;i<(N-1);i++) // N is point number of polygon
{
sum_but_no_result+=x[i]*y[i+1] + y[i]*x[i+1];
}
sum_but_no_result+=x[N-1]*y[0] + y[N-1]*x[0];
float sum= (float)Math.abs(sum_but_no_result) / 2.0f;
http://www.mathopenref.com/coordpolygonarea.html
For self-intersecting polygons, you may add an intersection-coord-finder and polygon-adder algorithm to find all sub-polygons produced by intersecting, recursively.
In Kotlin, using an extension on java.awt.Polygon :
import java.awt.Polygon
fun Polygon.area(): Float {
var sum = 0f
for (i in 0 until npoints - 1) {
sum += xpoints[i] * ypoints[i + 1] + ypoints[i] * xpoints[i + 1]
}
sum += xpoints[npoints - 1] * ypoints[0] + ypoints[npoints - 1] * xpoints[0]
return sum / 2.0f
}