I'm making a tiled map and I came across this problem:
When i'm moving my character it's going off the map and then falls (due to gravity)
How do I make this map infinite?
And also, how do I store which blocks are destroyed and which not? So that i can repaint the screen with the same map and when you walk back to the starting point the brocken blocks are still there.
Just Tell me if I need to provide Code.
I'll give you my world.java
package game.test.src;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import javax.swing.ImageIcon;
public class World {
public Rectangle[] blocks;
public boolean[] isSolid;
public Image[] blockImg;
public final int arrayNum = 500;
//Block Images
public Image BLOCK_GRASS, BLOCK_DIRT, BLOCK_STONE, BLOCK_SKY;
private int x, y, xDirection, yDirection;;
//map navigation
static final int PAN_UP = 0, PAN_DOWN = 1, PAN_LEFT= 2, PAN_RIGHT = 3;
public World(){
BLOCK_GRASS = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_grass.png").getImage();
BLOCK_DIRT = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_dirt.png").getImage();
BLOCK_STONE = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_stone.png").getImage();
BLOCK_SKY = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/tile_sky.png").getImage();
blocks = new Rectangle[500];
blockImg = new Image[500];
isSolid = new boolean[arrayNum];
loadArrays();
}
private void loadArrays(){
for(int i = 0; i < arrayNum; i++){
if(x >= 500){
x = 0;
y += 20;
}
if(i >= 0 && i < 100){
blockImg[i] = BLOCK_SKY;
isSolid[i] = false;
blocks[i] = new Rectangle(x, y, 20, 20);
}
if(i >= 100 && i < 125){
blockImg[i] = BLOCK_GRASS;
isSolid[i] = true;
blocks[i] = new Rectangle(x, y, 20, 20);
}
if(i >= 125 && i < 225){
blockImg[i] = BLOCK_DIRT;
isSolid[i] = true;
blocks[i] = new Rectangle(x, y, 20, 20);
}
if(i >= 225 && i < 500){
blockImg[i] = BLOCK_STONE;
isSolid[i] = true;
blocks[i] = new Rectangle(x, y, 20, 20);
}
x += 20;
}
}
public void draw(Graphics g){
for(int i = 0; i < arrayNum; i++){
g.drawImage(blockImg[i], blocks[i].x, blocks[i].y, null);
}
}
public void moveMap(){
for(Rectangle r : blocks){
r.x += xDirection;
r.y += yDirection;
}
}
public void stopMoveMap(){
setXDirection(0);
setYDirection(0);
}
private void setXDirection(int dir){
xDirection = dir;
}
private void setYDirection(int dir){
yDirection = dir;
}
public void navigateMap(int nav){
switch(nav){
default:
System.out.println("default case entered... Doing nothing.");
break;
case PAN_UP:
setYDirection(-1);
break;
case PAN_DOWN:
setYDirection(1);
break;
case PAN_LEFT:
setXDirection(-1);
break;
case PAN_RIGHT:
setXDirection(1);
break;
}
}
}
here is my Player.java
package game.test.src;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
public class Player {
static final int MOVE_UP = 0, MOVE_DOWN = 1, MOVE_LEFT= 2, MOVE_RIGHT = 3;
private World world;
private Rectangle playerRect;
private Image playerImg;
//Block Variables
private int hoverX, hoverY;
private boolean hovering = false;
protected static int xDirection;
protected static int yDirection;
private Weapon weapon;
public Player(World world){
this.world = world;
playerImg = new ImageIcon("H:/2D game test/Game test 2/src/game/test/src/images/Character.png").getImage();
playerRect = new Rectangle(50, 0, 10, 36);
weapon = new Weapon(weapon.PICKAXE);
}
private static void setXDirection(int d){
xDirection = d;
}
private static void setYDirection(int d){
yDirection = d;
}
public void update()
{
move();
checkForCollision();
}
private void checkForCollision() {
}
private void move()
{
playerRect.x += xDirection;
playerRect.y += yDirection;
gravity();
}
private void gravity()
{
for(int i=0;i<world.arrayNum; i++)
{
if(!world.isSolid[i])
{
setYDirection(1);
}
else if(world.isSolid[i] && playerRect.intersects(world.blocks[i]))
{
setYDirection(0);
}
}
}
//MotionEvents
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mouseClicked(MouseEvent e)
{
}
public void mouseMoved(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
int px = playerRect.x;
int py = playerRect.y;
for(int i = 0; i < world.arrayNum; i++)
{
if(weapon.isEquipped(Weapon.PICKAXE) &&
x > world.blocks[i].x && x < world.blocks[i].x + world.blocks[i].width &&
y > world.blocks[i].x && y < world.blocks[i].y + world.blocks[i].height && world.isSolid[i] &&
(world.blocks[i].x + (world.blocks[i].width / 2) ) <= (px + playerRect.width/2) + weapon.WEAPON_RADIUS &&
(world.blocks[i].x + (world.blocks[i].width / 2) ) >= (px + playerRect.width/2) - weapon.WEAPON_RADIUS &&
(world.blocks[i].y + (world.blocks[i].height / 2) ) <= (py + playerRect.height/2) + weapon.WEAPON_RADIUS &&
(world.blocks[i].y + (world.blocks[i].height / 2) ) >= (py + playerRect.height/2) - weapon.WEAPON_RADIUS)
{
hovering = true;
hoverX = world.blocks[i].x;
hoverY = world.blocks[i].y;
break;
}
else
hovering = false;
}
}
public void mouseDragged(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
//Drawing Methods
public void draw(Graphics g)
{
g.drawImage(playerImg, playerRect.x, playerRect.y, null);
if(hovering)
drawBlockOutline(g);
}
private void drawBlockOutline(Graphics g)
{
g.setColor(Color.black);
g.drawRect(hoverX, hoverY, world.blocks[0].width,world.blocks[0].height);
}
private class Weapon
{
public static final int UNARMED = 0;
public static final int PICKAXE = 1;
public static final int GUN = 2;
public int CURRENT_WEAPON;
public int WEAPON_RADIUS;
public Weapon(int w)
{
switch(w)
{
default:
System.out.println("No weapon selected");
break;
case UNARMED:
CURRENT_WEAPON = UNARMED;
WEAPON_RADIUS = 100;
break;
case PICKAXE:
CURRENT_WEAPON = PICKAXE;
WEAPON_RADIUS = 100;
break;
case GUN:
CURRENT_WEAPON = GUN;
WEAPON_RADIUS = 100;
break;
}
}
public void selectWeapon(int w)
{
switch(w)
{
default:
System.out.println("No weapon selected");
break;
case UNARMED:
CURRENT_WEAPON = UNARMED;
WEAPON_RADIUS = 100;
break;
case PICKAXE:
CURRENT_WEAPON = PICKAXE;
WEAPON_RADIUS = 100;
break;
case GUN:
CURRENT_WEAPON = GUN;
WEAPON_RADIUS = 100;
break;
}
}
public boolean isEquipped(int w)
{
if(w == CURRENT_WEAPON)
{
return true;
}
else
return false;
}
}
public void moveMap(){
for(Rectangle r : world.blocks){
r.x += xDirection;
r.y += yDirection;
}
}
public static void stopMoveMap(){
setXDirection(0);
setYDirection(0);
}
private static void setXDirection1(int dir){
xDirection = dir;
}
private static void setYDirection1(int dir){
yDirection = dir;
}
public static void navigatePlayer(int nav){
switch(nav){
default:
System.out.println("default case entered... Doing nothing.");
break;
case MOVE_UP:
setYDirection1(-1);
break;
case MOVE_DOWN:
setYDirection1(1);
break;
case MOVE_LEFT:
setXDirection1(-1);
break;
case MOVE_RIGHT:
setXDirection1(1);
break;
}
}
}
Thanks for the help!
At a basic level you need a 3 dimensional array to store every single block. The problem is, that won't get you an "Infinite" world, it will get you one limited to memory.
Notch solved it by using "Chunks"--which are 3D arrays of a fixed size that can be swapped to disk when necessary.
You should also learn about how bits can be used to pack storage, for anything large you will need it--For your example, each block can be held in 3 bits, 2 for the blocks and one more for "broken". If you used this instead of a byte array you would use less than 1/2 the storage, which means you could maybe go twice as far in your world before needing to read another chunk from disk.
If you want an easier introduction to writing this kind of app, look into writing a minecraft mod using Bukkit--much of the detail work is handled for you and you can actually pick up a lot of knowledge about how stuff is done before trying to write a Minecraft clone from scratch.
So what you need is essentially a two-dimensional data structure which can be extended indefinitely (or until memory runs out) into both dimensions.
There are myriads of ways to solve this problem.
One way would be a two dimensional double-linked list (double-linked net?) where each map tile has a reference to the four adjacent tiles. That means you keep track of the tile in the center of the viewport and render the scene by iterating into all four directions until you leave the screen. When you hit an un-initialized tile, it's time to generate it.
Related
I have created a game similar to snake in which the user is first prompted with a jpanel asking which difficulty they want, and whatever JButton they pick influences the size of the map as well as the delay between the snake movements. I have the map size working just fine, but the delay variable never seems to change. I suspect it has something to do with the way the timer is being casted, but I have no idea how to fix it. I am also wondering how when the program is first ran it seems some of the variables don't update, but the second time it is ran all of them are updated. Here is my class with the original variables and collision detection:
import java.util.Random;
import javax.swing.JLabel;
public class GameEngine extends JPanel implements ActionListener{
//creates the size of the panel as well as creating the resolution for all objects, including the players and food.
static final int sWidth = 600;
static final int sHeight = 600;
public static int size = 24;
static int objectSize = (sHeight*sWidth) / size;
public static int delay = 100;
final int playerX[] = new int[objectSize];
final int playerY[] = new int[objectSize];
int bodySize = 4;
int score = 0;
int appleX;
int appleY;
char direction = 'D';
boolean started = false;
Random random;
Timer timer;
boolean easy;
JLabel score1;
public static String difficulty;
GameEngine(){
random = new Random();
this.setPreferredSize(new Dimension(sWidth,sHeight));
this.setBackground(Color.black);
this.setFocusable(true);
this.addKeyListener(new UserMovement());
gameStart();
}
public void gameStart() {
newApple();
started = true;
timer = new Timer(delay,this);
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void drawHead(Graphics g) {
g.setColor(new Color(100,252,0));
g.fillRect(playerX[0], playerY[0], size, size);
}
public void draw(Graphics g) {
if(started) {
//draws the apples
g.setColor(Color.red);
g.fillOval(appleX, appleY, size, size);
for (int i = 0; i < bodySize; i++) {
if(i == 0) {
drawHead(g);
}
else {
g.setColor(new Color(60,180,0));
g.fillRect(playerX[i], playerY[i], size, size);
}
}
g.setColor(Color.white);
g.setFont(new Font("Bold", Font.BOLD, 20));
FontMetrics metrics = getFontMetrics(g.getFont());
g.drawString("Score: " + score,(sWidth - metrics.stringWidth("Score: " + score))/2,g.getFont().getSize());
}
}
public void newApple(){
appleX = random.nextInt((int)(sWidth/size))*size;
appleY = random.nextInt((int)(sHeight/size))*size;
}
//moves the player by using and modifying their coordinates
public void move() {
for (int i = bodySize; i > 0; i--) {
playerX[i] = playerX[i-1];
playerY[i] = playerY[i-1];
}
switch(direction) {
case 'W':
playerY[0] = playerY[0] - size;
break;
case 'S':
playerY[0] = playerY[0] + size;
break;
case 'A':
playerX[0] = playerX[0] - size;
break;
case 'D':
playerX[0] = playerX[0] + size;
break;
}
}
public void checkFood() {
if(playerX[0] == appleX && playerY[0] == appleY)
{
bodySize++;
score++;
newApple();
}
}
public void checkCol() {
//checks for head collision with the body
for(int i = bodySize; i > 0; i--) {
if((playerX[0] == playerX[i]) && (playerY[0] == playerY[i])) {
started = false;
}
}
//checks if head touches any of the walls of the program
if(playerX[0] < 0) {
started = false;
}
if(playerX[0] > sWidth) {
started = false;
}
if(playerY[0] < 0) {
started = false;
}
if(playerY[0] > sHeight) {
started = false;
}
if(started != true) {
timer.stop();
}
}
public void actionPerformed(ActionEvent e){
if(started == true) {
move();
checkFood();
checkCol();
}
repaint();
}
public class UserMovement extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_LEFT:
if(direction != 'D') {
direction = 'A';
}
break;
case KeyEvent.VK_RIGHT:
if(direction != 'A') {
direction = 'D';
}
break;
case KeyEvent.VK_UP:
if(direction != 'S') {
direction = 'W';
}
break;
case KeyEvent.VK_DOWN:
if(direction != 'W') {
direction = 'S';
}
break;
}
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
}
}
and here is the code calling and changing the delay and size variables:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class StartMenu extends JPanel {
StartMenu()
{
JButton easy = new JButton();
JButton hard = new JButton();
this.setPreferredSize(new Dimension(350,240));
this.setLayout(null);
this.setBackground(Color.black);
this.setFocusable(true);
easy.setBounds(75,40,200,40);
hard.setBounds(75,120,200,40);
this.add(easy);
this.add(hard);
easy.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
setVisible(false);
new SnakeStart();
GameEngine.size = 48;
GameEngine.delay = 140;
}
});
hard.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
setVisible(false);
new SnakeStart();
GameEngine.size = 24;
GameEngine.delay = 70;
}
});
}
}
I assume your GameEngine instance is created before the StartMenu action listeners are executed. If that assumption is correct, that means that GameEngine.timer with the default value of delay is created in the GameEngine constructor and is not updated after the delay is changed.
You need to make sure that you explicitly update your timer with the new delay value after StartMenu actions are called.
Look I'm going to be up front about this. My assignment is due soon and i've spent way too many hours trying to fix this problem with no success at all. I'm essentially clueless at what the issue is and I really dont know where to look. I have 5 classes, I will try and post them all to ensure I get the answer, I am unable to change GameManager or Goal but I am allowed to change any other class.
The problem lines are this.canvasGraphics.drawImage(player.getCurrentImage() where drawImage says it isnt applicable for the arguments
and
this.enemies[i].getX() - (this.enemies[i].getCurrentImage().getWidth() / 2),
this.enemies[i].getY() - (this.enemies[i].getCurrentImage().getHeight() / 2), null);
where getWidth and getHeight show an almost identical error
thanks in advance
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.awt.Font;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
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.
int choice;
do{
GameManager managerObj = new GameManager(1920, 1080);
choice=JOptionPane.showConfirmDialog(null,"Play again?", "", JOptionPane.OK_CANCEL_OPTION);
}while(choice==JOptionPane.OK_OPTION);
System.exit(0);
}
public GameManager(int preferredWidth, int preferredHeight) {
int maxEnemies;
try{
maxEnemies=Integer.parseInt(JOptionPane.showInputDialog("How many enemies? (Default is 5)"));
if (maxEnemies<0)
maxEnemies=5;
}
catch (Exception e){
maxEnemies=5;
}
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();
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((Math.abs(rng.nextInt()) % (this.canvasWidth)),
(Math.abs(rng.nextInt()) % this.canvasHeight));
// Create a Player object with its initial x and y coordinates
this.player = new Player((Math.abs(rng.nextInt()) % (this.canvasWidth)),
(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),
Math.abs(rng.nextInt()) % this.canvasHeight);
}
this.gameGraphics = getGraphics();
this.canvasGraphics = this.canvas.getGraphics();
this.continueGame = true;
long gameStartTime=System.nanoTime();
while (this.continueGame) {
updateCanvas();
}
this.stage.setGameOverBackground();
double gameTime=(System.nanoTime()-gameStartTime)/1000000000.0;
updateCanvas();
this.gameGraphics.setFont(new Font(this.gameGraphics.getFont().getFontName(), Font.PLAIN, 50));
if (gameTime<1)
this.gameGraphics.drawString("Oops! Better luck next time...", this.canvasWidth/3, this.canvasHeight/2 - 50);
else
this.gameGraphics.drawString("You survived " + String.format("%.1f", gameTime)+ " seconds with "+this.numEnemies+" enemies!",
this.canvasWidth/4, this.canvasHeight/2 - 50);
return;
}
public void updateCanvas() {
long start = System.nanoTime();
this.goal.performAction();
// 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 Player. The Player 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 background of the stage to the finished game background.
this.stage.setGameOverBackground();
this.continueGame = false;
}
// If an enemy is close to the player or the goal, the player and goal die
int j = 0;
while (j < this.numEnemies) {
if ((Math.abs(this.player.getX() - this.enemies[j].getX()) < (this.player.getCurrentImage().getWidth() / 2))
&& (Math.abs(this.player.getY() - this.enemies[j].getY()) < (this.player.getCurrentImage().getWidth()
/ 2))) {
this.player.die();
this.goal.die();
this.stage.setGameOverBackground();
j = this.numEnemies;
this.continueGame = false;
}
else 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 goal
this.canvasGraphics.drawImage(this.goal.getCurrentImage(),
this.goal.getX() - (this.goal.getCurrentImage().getWidth() / 2),
this.goal.getY() - (this.goal.getCurrentImage().getHeight() / 2), 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);
}
} 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.setFont(new Font(this.gameGraphics.getFont().getFontName(), Font.PLAIN, 15));
this.gameGraphics.drawString("FPS: " + String.format("%2d", (int) (1000000000.0 / (end - start))),
this.borderLeft + 50, this.borderTop + 75);
return;
}
public Player getPlayer() {
return this.player;
}
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.
// Important: The setKey method in Player must not move the Player.
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;
return;
}
#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.
// Important: The setKey method in Player must not move the Player.
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);
return;
}
#Override
public void keyTyped(KeyEvent ke) {
return;
}
}
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.util.*;
public class Goal {
private int x;
private int y;
private BufferedImage imageCurrent;
private BufferedImage imageRunning;
private BufferedImage imageOver;
private int stepSize;
private Random rng; // Tip: Code that students write must not use randomness
public Goal(int x, int y) {
try {
this.imageRunning = ImageIO.read(new File("goal-alive.png"));
this.imageOver = ImageIO.read(new File("goal-dead.png"));
} catch (IOException e) {
e.printStackTrace();
}
this.x = x;
this.y = y;
this.stepSize = 10;
this.rng = new Random(x + y); // Tip: Code that students write (elsewhere) must not use any randomness.
this.imageCurrent = this.imageRunning;
return;
}
public void performAction() {
// The code below shows how the Goal can be moved by manipulating its x and y
// coordinates.
// Tip: Code that students write (elsewhere) must not use any randomness.
this.x += this.rng.nextInt() % stepSize;
this.y += this.rng.nextInt() % stepSize;
return;
}
public int getY() {
return this.y;
}
public int getX() {
return this.x;
}
public BufferedImage getCurrentImage() {
return this.imageCurrent;
}
public void die() {
this.imageCurrent = this.imageOver;
return;
}
}
import java.awt.Image;
public class Enemy {
private Image CurrentImage;
private int x;
private int y;
public Enemy(GameManager gameManager, int x, int y) {
}
public void performAction() {
}
public void die() {
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public Image getCurrentImage() {
return CurrentImage;
}
}
import java.awt.Dimension;
public class Player {
private Dimension CurrentImage;
private int x;
private int y;
public Player(int x1, int y1) {
}
public void performAction() {
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public Dimension getCurrentImage() {
return CurrentImage;
}
public void die() {
}
public void setKey(char c, boolean b) {
}
}
import java.awt.Image;
public class Stage {
public void setGameOverBackground() {
}
public Image getCurrentImage() {
return null;
}
}
Player.getCurrentImage() returns a Dimension, not an Image.
I made a program to display interference patterns for light waves. I did this by using the paint method on a JPanel to draw 2 sources and then drawing concentric circles around them. This would be double slit interference, so I allowed one of the sources to move around to experiment with the slit width.
The problem is that when I run this, my computer says it is using 80% of my CPU! There's really not much to it. Circle in the middle, circles around it, and it moves. My code follows.
main class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class main {
static final int UP = -1;
static final int DOWN = 1;
static final int RIGHT = 1;
static final int LEFT = -1;
static final int NOMOVEMENT = 0;
static int verticalMovement = NOMOVEMENT;
static int horizontalMovement = NOMOVEMENT;
public static void main(String[] args) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Point s1 = new Point((int)(screenSize.getWidth()/3), 50);
Point s2 = new Point((int)(2*screenSize.getWidth()/3), 50);
JFrame frame = new JFrame("Physics Frame");
frame.setPreferredSize(screenSize);
PhysicsPane pane = new PhysicsPane(screenSize, 15, s1, s2);
pane.setPreferredSize(screenSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.pack();
Timer time = new Timer(1000 / 20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
switch (verticalMovement){
case UP:
s2.changeY(UP);
break;
case DOWN:
s2.changeY(DOWN);
break;
default:
break;
}
switch (horizontalMovement){
case RIGHT:
s2.changeX(RIGHT);
break;
case LEFT:
s2.changeX(LEFT);
break;
default:
break;
}
pane.repaint();
}
});
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==37){
horizontalMovement = LEFT;
} else if (e.getKeyCode()==38){
verticalMovement = UP;
} else if (e.getKeyCode()==39){
horizontalMovement = RIGHT;
} else if (e.getKeyCode()==40){
verticalMovement = DOWN;
}
if(e.getKeyChar()=='a'){
pane.setWaveLength(2);
}
if(e.getKeyChar()=='s'){
pane.setWaveLength(-2);
}
}
#Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()){
case 37:
horizontalMovement = NOMOVEMENT;
break;
case 38:
verticalMovement = NOMOVEMENT;
break;
case 39:
horizontalMovement = NOMOVEMENT;
break;
case 40:
verticalMovement = NOMOVEMENT;
break;
}
}
});
frame.setVisible(true);
time.start();
}
}
Panel class. If there's an inefficiency with the drawing method, it would be here.
import javax.swing.*;
import java.awt.*;
public class PhysicsPane extends JPanel {
private Dimension size;
private Point[] pointArray = new Point[2];
private double waveLength;
public PhysicsPane(Dimension size, double wavelength, Point source1, Point source2) {
this.size = size;
pointArray[0] = source1;
pointArray[1] = source2;
setPreferredSize(size);
this.waveLength = wavelength;
}
#Override
public void paintComponent(Graphics g){
for (int i = 0; i < 2; i++) {
g.setColor(Color.black);
double x = this.pointArray[i].getX();
double y = this.pointArray[i].getY();
g.fillOval((int)x, (int)y, 2, 2);
for (int j = (int)waveLength; j < 1500; j+=waveLength) {
g.setColor(Color.red);
g.drawOval((int)x-(j/2+1), (int)y-(j/2+1), 2 + j, 2 + j);
}
}
}
public void setWaveLength(double increment){
this.waveLength+=increment;
}
}
Point Class: Really just puts x and y coordinates into one construct. Not important particularly
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public void changeX(double dX){
this.x+=dX;
}
public void changeY(double dY){
this.y+=dY;
}
}
So what is wrong with my program? Why is such a simple animation consuming so much of my processor?
UPDATED CODE SECTION:
#Override
public void paintComponent(Graphics g){
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
Graphics graphics = bi.getGraphics();
for (int i = 0; i < 2; i++) {
graphics.setColor(Color.black);
double x = this.pointArray[i].getX();
double y = this.pointArray[i].getY();
graphics.fillOval((int)x, (int)y, 2, 2);
for (int j = (int)waveLength; j < 1500; j+=waveLength) {
graphics.setColor(Color.red);
graphics.drawOval((int)x-(j/2+1), (int)y-(j/2+1), 2 + j, 2 + j);
}
}
g.drawImage(bi, 0, 0, new ImageObserver() {
#Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
return false;
}
});
}
The improvement that I recommend is removal of unnecessary tasks being performed, notably how your code updates the pane being drawn on, even when there aren't changes.
The following update reduced CPU usage from 12% to 0% (static frame):
Timer time = new Timer(1000 / 20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
boolean refresh = false;
switch (verticalMovement) {
case UP:
s2.changeY(UP);
refresh = true;
break;
case DOWN:
s2.changeY(DOWN);
refresh = true;
break;
default:
break;
}
switch (horizontalMovement) {
case RIGHT:
s2.changeX(RIGHT);
refresh = true;
break;
case LEFT:
s2.changeX(LEFT);
refresh = true;
break;
default:
break;
}
if (refresh == true) {
pane.repaint();
}
}
});
I have never tried to implement shooting in any kind of game before and when I try to remove and element of the ArrayList I'm using it throws and index out of bounds exception. I want to be able to press the enter key, store the current direction that the player is facing and then shoot a projectile in that current direction. Then, once a projectile has reached its destination to remove itself from the ArrayList and not be rendered anymore. However, when it reaches the end of the map (x == Map.mapSizeX), the bullet freezes and that's when the exception is thrown. (Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Index: 1, Size: 1).
Code for the shooting class (Projectile.java)
package Engine;
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Random;
public class Projectile {
public static ArrayList<Integer> bullets = new ArrayList<Integer>(3);
private static DirectionalVector dir = new DirectionalVector();
private static int index = 0;
private static int x,y;
public Projectile(int direction, int x, int y){
dir.applyDirection(direction);
bullets.add(index);
index += 1;
this.x = x;
this.y = y;
}
public void updatePos(){
if (dir.right){
x += 1;
dir.left = false;
dir.down = false;
}
if (dir.left){
x -= 1;
dir.right = false;
dir.down = false;
}
if (dir.down){
y += 1;
dir.left = false;
dir.right = false;
}
if (dir.down){
System.out.println("DOWN");
}
if (dir.right){
System.out.println("RIGHT");
}
if (dir.left){
System.out.println("LEFT");
}
if (x > Map.mapSizeX){
bullets.remove(index);
}
if (x < 0){
bullets.remove(index);
}
if (y > Map.mapSizeY){
bullets.remove(index);
}
if (y < 0){
bullets.remove(index);
}
}
public static void render(Graphics2D g2){
int r = new Random().nextInt(256);
int g = new Random().nextInt(256);
int b = new Random().nextInt(256);
if (r < 50){
r = new Random().nextInt(256);
}
if (g < 50){
g = new Random().nextInt(256);
}
if (b < 50){
b = new Random().nextInt(256);
}
g2.setColor(new Color(r,g,b));
g2.fillRect(x * Map.tileSize, y * Map.tileSize, Map.tileSize, Map.tileSize);
}
Code for the DirectionalVector class
package Engine;
public class DirectionalVector {
public boolean left = false, right = true, down = false;
public DirectionalVector(){
}
public void applyDirection(int id){
if (id == 0){
right = true;
}
if (id == 1){
left = true;
}
if (id == 2){
down = true;
}
}
}
Code for the Player class
package Engine;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Player implements KeyListener, Runnable{
public static int x = 0,y = 0;
private static boolean right = false, left = false, falling = false, jumping = false;
private Thread thread = new Thread(this, "GAME LOOP");
private static int lastDirectionPressed = 0;
public static Projectile bullet;
public Player(){
thread.start();
}
public void renderPlayer(boolean value, Graphics2D g2){
if (value){
g2.setColor(Color.blue);
g2.fillRect(x * Map.tileSize, y * Map.tileSize, Map.tileSize, Map.tileSize);
}
}
#Override
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
if (c == KeyEvent.VK_RIGHT && right){
x += 1;
lastDirectionPressed = 0;
}
if (c == KeyEvent.VK_LEFT && left){
x -= 1;
lastDirectionPressed = 1;
}
if (c == KeyEvent.VK_SPACE && jumping){
y -= 1;
}
if (c == KeyEvent.VK_ENTER){
bullet = new Projectile(lastDirectionPressed, x, y);
}
}
public void update(){
if (x != Map.mapSizeX){
right = Map.getTile(x + 1, y) == 0;
}
if (x > 0){
left = Map.getTile(x - 1, y) == 0;
}
if (x == 0){
left = false;
}
if (x == Map.mapSizeX){
right = false;
}
if (y != Map.mapSizeY){
falling = Map.getTile(x, y + 1) == 0;
}
if (y == Map.mapSizeY){
falling = false;
}
if (falling){
y += 1;
}
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void run(){
while (true){
try{
Thread.sleep(100);
update();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
Code for the ContentPane class (Where all the rendering occurs)
package Engine;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
#SuppressWarnings({"serial", "static-access"})
public class ContentPane extends JPanel{
private Map map = new Map();
public Player player = new Player();
public ContentPane(){
}
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g2);
map.renderMap(true, g2);
player.renderPlayer(true, g2);
if (player.bullet.bullets.size() > 0){
player.bullet.updatePos();
}
for (int i = 0; i < player.bullet.bullets.size(); i++){
player.bullet.render(g2);
}
System.out.println(player.bullet.bullets.size());
repaint();
}
}
I am almost done with my first little java game for my final project. It is a sidescroller where you have to shoot/avoid asteroids. My last problem is figuring out how to make my array of asteroids collide with the player's lasers. Here's what I have so far, there's an "AWT-EventQueue-0" java.lang.NullPointerException" on line 137, that I can't deal with. Any help is appreciated.
Edit: I added in my other classes, I realize it would be hard to judge the functionality of my code if I didn't show you where it came from.
package ShooterGame;
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.*;
public class Board extends JPanel implements ActionListener
{
Enemy[] baddies = new Enemy[10000];
Player p;
Image img;
int y;
Timer time;
boolean lost = false;
static Font font = new Font("SanSerif", Font.BOLD, 24);
public AudioClip theme, bang, laser;
static ArrayList<Enemy> enemies;
public static int score = 0;
public static int lives = 5;
public Board()
{
p = new Player();
addKeyListener(new ActionListener());
setFocusable(true);
ImageIcon i = new ImageIcon("images/background.png");
img = i.getImage();
time = new Timer(5, this);
time.start();
for(int j = 0; j < baddies.length; j++)
{
Random ran = new Random();
y = ran.nextInt(365)+1;
baddies[j]= new Enemy((100*j + 700), y, "images/asteroid.gif");
}
theme = Applet.newAudioClip(getClass().getResource("theme.mid"));
theme.play();
bang = Applet.newAudioClip(getClass().getResource("bang.wav"));
}
public void actionPerformed(ActionEvent e)
{
checkCollisions();
ArrayList<?> bullets = Player.getBullets();
for(int i = 0; i < bullets.size(); i++)
{
Bullet b = (Bullet)bullets.get(i);
if(b.isVisible() == true)
{
b.move();
}
else
{
bullets.remove(i);
}
}
p.move();
for(int i = 0; i < baddies.length; i++)
{
if(p.x > 400)
{
baddies[i].move(p.getdx());
}
}
repaint();
}
public void paint(Graphics g)
{
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
if(lost)
{
g2d.drawString("You Lose!", 300, 300);
}
if((p.getX() - 590) % 2400 == 0 || (p.getX() - 590) % 2400 == 1)
{
p.nx = 0;
}
if((p.getX() - 1790) % 2400 == 0 ||(p.getX() - 1790) % 2400 == 1)
{
p.nx2 = 0;
}
g2d.drawImage(img, 685-p.nx2, 0, null);
if(p.getX() >= 590)
{
g2d.drawImage(img, 685-p.nx, 0, null);
}
g2d.drawImage(p.getImage(), p.edge, p.getY(), null);
ArrayList<?> bullets = Player.getBullets();
for(int i = 0; i < bullets.size(); i++)
{
Bullet b = (Bullet)bullets.get(i);
g2d.drawImage(b.getImg(), b.getX(), b.getY(), null);
}
for(int i = 0; i < baddies.length; i++)
{
if(baddies[i].isAlive == true)
{
g2d.drawImage(baddies[i].getImg(), baddies[i].getX(), baddies[i].getY(), null);
}
}
g2d.setColor(Color.white);
g2d.drawString("Score: " + score, 0, 320);
g2d.drawString("Lives: " + lives, 80, 320);
}
public void checkCollisions()
{
Rectangle[] rect = new Rectangle[baddies.length];
for(int i = 0; i < baddies.length; i++)
{
Enemy e = (Enemy)baddies[i];
rect[i] = e.getBounds();
}
ArrayList<?> bullets = Player.getBullets();
for (int i = 0; i < bullets.size(); i++)
{
Bullet b = (Bullet) bullets.get(i);
Rectangle b1 = b.getBounds();
if (rect[i].intersects(b1) && baddies[i].isAlive())
{
score++;
baddies[i].isAlive = false;
baddies[i].isVisible = false;
bang.play();
}
Rectangle h = p.getBounds();
if (h.intersects(rect[i]))
{
if(baddies[i].isAlive() == true)
{
lives--;
if(lives < 0)
{
lost = true;
theme.stop();
System.exit(1);
}
}
}
}
}
private class ActionListener extends KeyAdapter
{
public void keyReleased(KeyEvent e)
{
p.keyReleased(e);
}
public void keyPressed(KeyEvent e)
{
p.keyPressed(e);
}
}
}
Enemy
package ShooterGame;
import java.awt.*;
import javax.swing.ImageIcon;
public class Enemy
{
int x, y;
Image img;
boolean isAlive = true;
boolean isVisible = true;
public Enemy(int startX, int startY, String location)
{
x = startX;
y = startY;
ImageIcon l = new ImageIcon(location);
img = l.getImage();
}
public Rectangle getBounds()
{
return new Rectangle(x, y, 60, 60);
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public boolean isAlive()
{
return isAlive;
}
public boolean isVisible()
{
return isVisible;
}
public Image getImg()
{
return img;
}
public void move(int dx)
{
x = x - dx;
}
}
Bullet
package ShooterGame;
import java.applet.Applet;
import java.awt.*;
import javax.swing.ImageIcon;
import java.applet.AudioClip;
public class Bullet
{
int x, y;
Image img;
boolean visible;
public Bullet(int startX, int startY)
{
x = startX;
y = startY;
ImageIcon newBullet = new ImageIcon("images/bullet.gif");
img = newBullet.getImage();
visible = true;
}
public Rectangle getBounds()
{
return new Rectangle(x, y, 9, 5);
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public Image getImg()
{
return img;
}
public boolean isVisible()
{
return visible;
}
public void move()
{
x = x + 2;
if(x > 700)
{
visible = false;
}
}
public void setVisible(boolean isVisible)
{
visible = isVisible;
}
}
Player
package ShooterGame;
import java.applet.Applet;
import java.applet.AudioClip;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import javax.swing.ImageIcon;
public class Player
{
int x, dx, y, dy, nx2, nx, edge, ceiling;
Image player;
ImageIcon ib = new ImageIcon("images/player1back.gif");
ImageIcon i = new ImageIcon("images/playerstill.gif");
ImageIcon f = new ImageIcon("images/playerforward.gif");
ImageIcon up = new ImageIcon("images/playerup.gif");
ImageIcon down = new ImageIcon("images/playerdown.gif");
public AudioClip laser;
static ArrayList<Bullet> bullets;
public Player()
{laser = Applet.newAudioClip(getClass().getResource("laser.wav"));
player = ib.getImage();
x = 75;
nx = 0;
nx2 = 685;
y = 172;
edge = 150;
ceiling = 0;
bullets = new ArrayList<Bullet>();
}
public static ArrayList<Bullet> getBullets()
{
return bullets;
}
public void fire()
{
Bullet z = new Bullet((edge + 60), (y+17));
bullets.add(z);
}
public Rectangle getBounds()
{
return new Rectangle(edge, y, 43, 39);
}
public void move()
{
y = y + dy;
if(y < ceiling)
{
y = ceiling;
}
if(y > 290)
{
y = 290;
}
if(dx != -1)
{
if(edge + dx <= 151)
{
edge = edge + dx;
}
else
{
x = x + dx;
nx2 = nx2 + dx;
nx = nx + dx;
}
}
else
{
if(edge + dx > 0)
{
edge = edge + dx;
}
}
}
public int getX()
{
return x;
}
public int getdx()
{
return dx;
}
public int getY()
{
return y;
}
public Image getImage()
{
return player;
}
public void keyPressed(KeyEvent e)
{
int key = e.getKeyCode();
if(key == KeyEvent.VK_RIGHT)
{
dx = 2;
player = f.getImage();
}
if(key == KeyEvent.VK_UP)
{
dy = -1;
player = up.getImage();
}
if(key == KeyEvent.VK_DOWN)
{
dy = 1;
player = down.getImage();
}
if(key == KeyEvent.VK_SPACE)
{
fire();
laser.play();
}
}
public void keyReleased(KeyEvent e)
{
int key = e.getKeyCode();
if(key == KeyEvent.VK_RIGHT)
{
dx = 1;
player = ib.getImage();
}
if(key == KeyEvent.VK_UP)
{
dy = 0;
player = ib.getImage();
}
if(key == KeyEvent.VK_DOWN)
{
dy = 0;
player = ib.getImage();
}
}
}
Frame
package ShooterGame;
import javax.swing.*;
public class Frame
{
public AudioClip theme;
public Frame()
{
JFrame frame = new JFrame();
frame.add(new Board());
frame.setTitle("SideShooter");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(700,365);
frame.setVisible(true);
frame.setLocationRelativeTo(null);
}
public static void main(String[] args)
{
new Frame();
}
}
Ok, so the problem is the line mentioned in the other answer, but I believe it is that all the enemies may not be initialised before it checks collisions. Because you are making 10000 of them to start with, I think your action performed method may be checking collisions before they have all be created.
One thing to try could be to bring down the amount of enemies you have and see if it still keeps happening, try 100 or 1000, but this still won't fix the issue.
You really want to be change your game to run in it's own loop though, at the moment you are only checking collisions when the player performs an action. so if the player stops moving, no collision detection...
I would suggest that you find a book called 'Killer Game Programming in Java', there are free ebook version, and just read the first 2 chapters about making a game loop. The book is a bit old, but the basics of the loop are very good.
This question here also contains a very simple loop, and some suggestions in the comments about how to make it better.
The error is on the line
rect[i] = e.getBounds();
Are you not initializing the bounds of your Enemy class correctly? Alternatively, the Enemy you pulled out of the array could be null.