I have an animation class ready for my game. I think there are some optimization issues in this classroom.
Let's say there are more than 10 living entities with the same animations in my game.
Although they all have different animation times, the pictures of their animations are the same. When the code is like this, instead of keeping 2 pictures in RAM, wouldn't 20 pictures be kept? Code;
Animation idle;
BufferedImage idle1,idle2;
ArrayList<Soldier> enemyArmyArray = new ArrayList<Soldier>();
ArrayList<Animation> armyIdleAnimations = new ArrayList<Animation>();
w_idleImage1 = ImageIO.read(getClass().getResource("/Data/idle1.png"));
w_idleImage2 = ImageIO.read(getClass().getResource("/Data/idle2.png"));
for(int z = 0 ; z <= 10 ; z++) {
Soldier s = new Soldier();
armyArray.add(s);
idle = new Animation();
idle.addFrame(idle1, 400);
idle.addFrame(idle2, 400);
armyIdleAnimations.add(idle);
}
and this is my animation class:
public class Animation {
private ArrayList frames;
private int currentFrame;
private long animTime; // long takes up more memory than int but can hold
// more accurate numbers.
private long totalDuration;
public Animation() {
frames = new ArrayList();
totalDuration = 0;
synchronized (this) {
animTime = 0;
currentFrame = 0;
}
}
public synchronized void addFrame(BufferedImage image, long duration) {
totalDuration += duration;
frames.add(new AnimFrame(image, totalDuration));
}
public synchronized void update(long elapsedTime) {
if (frames.size() > 1) {
animTime += elapsedTime;
if (animTime >= totalDuration) {
animTime = animTime % totalDuration;
currentFrame = 0;
}
while (animTime >= getFrame(currentFrame).endTime) {
currentFrame++;
}
}
}
public synchronized BufferedImage getImage() {
if (frames.size() == 0) {
return null;
} else {
return getFrame(currentFrame).image;
}
}
private AnimFrame getFrame(int i) {
return (AnimFrame) frames.get(i);
}
private class AnimFrame {
BufferedImage image;
long endTime;
public AnimFrame(BufferedImage image, long endTime) {
this.image = image;
this.endTime = endTime;
}
}
public int getCurrentFrame() {
return currentFrame;
}
public void setCurrentFrame(int currentFrame) {
this.currentFrame = currentFrame;
}
public long getAnimTime() {
return animTime;
}
public void setAnimTime(long animTime) {
this.animTime = animTime;
}
}
Related
I have a class extending Thread. Inside this run I have a few method working in while loop. Now I want one of this methods gameSurface.updateBitmapObjects() to work with delay. How to achieve this? Here is my code:
GameThread.java:
public class GameThread<T extends GameSurface> extends Thread {
private boolean isRunning;
private long startTime, loopTime;
private long delay = 33;
private SurfaceHolder surfaceHolder;
private T gameSurface;
public GameThread(SurfaceHolder surfaceHolder, T gameSurface) {
this.surfaceHolder= surfaceHolder;
this.gameSurface = gameSurface;
isRunning = true;
}
#Override
public void run() {
while(isRunning) {
startTime = SystemClock.uptimeMillis();
Canvas canvas = surfaceHolder.lockCanvas(null);
if(canvas != null) {
synchronized (surfaceHolder) {
gameSurface.updateBitmapObjects();
gameSurface.drawMapBitmap(canvas);
((GameMainSurface)gameSurface).drawCoinAndCoins();
((GameMainSurface)gameSurface).drawFieldLines(canvas);
((GameMainSurface)gameSurface).updatePlayersLabels(canvas);
if (((GameMainSurface)gameSurface).isGameMainFragment()) {
((GameMainSurface)gameSurface).setSomeValues();
}
gameSurface.drawObjects(canvas);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
loopTime = SystemClock.uptimeMillis() - startTime;
if(loopTime < delay) {
try {
Thread.sleep(delay - loopTime);
}
catch (InterruptedException e) {
Log.e("Interupted ex", e.getMessage());
}
}
}
}
}
GameMainSurface.java:
(...)
public void updateBitmapObjects() {
this.chibi1.update();
(...)
}
MovingObject.java:
// I want this method to work with delay let's say 100 ms
public void update() {
this.colUsing++;
if (colCount == 13) {
if (colUsing >= 9) {
if (rowUsing == ROW_TOP_TO_BOTTOM_CHIBI || rowUsing == ROW_BOTTOM_TO_TOP_CHIBI)
colUsing = 1;
else
colUsing = 0;
}
}
if (colCount == 4) {
if (colUsing >= 4) {
this.colUsing = 0;
}
}
}
else if(left || right) {
if(currentAction != WALKING) {
currentAction = WALKING;
animation.setFrames(sprites.get(WALKING));
animation.setDelay(40);
width = 30;
Here is the code I am having issues with. namely the animation.setFrames and setDelay the setFrames uses a BufferedImage array and the setDelay is a long variable. The two errors that come up are
The method setFrames(BufferedImage[]) is undefined for the type Animation
and
The method setDelay(Duration) in the type Animation is not applicable for the arguments (int)
public void setFrames(BufferedImage[] frames) {
this.frames = frames;
currentFrame = 0;
startTime = System.nanoTime();
playedOnce = false;
this is the code for the setFrames and the code of delay's setter is just
public void setDelay(long d) {
delay = d;
}
Any assistance is welcome.
There were none of these errors in the tutorial
EDIT: I have created a new Animation class in the constructor but it did not solve it. Added Animation class
package Entity;
import java.awt.image.BufferedImage;
public class Animation {
private BufferedImage[] frames;
private int currentFrame;
private long startTime;
private long delay;
private boolean playedOnce; played; e.g. an attack so it does not
public void Animation() {
playedOnce = false;
}
public void setFrames(BufferedImage[] frames) {
this.frames = frames;
currentFrame = 0;
startTime = System.nanoTime();
playedOnce = false;
}
public void setDelay(long d) {
delay = d;
}
public void setFrame(int i) {
currentFrame = i;
}
public void update() {
if (delay == -1)
return;
long elapsed = (System.nanoTime() - startTime) / 1000000;
if (elapsed > delay) {
currentFrame++;
playedOnce = true;
}
}
public int getFrame() {
return currentFrame;
}
public BufferedImage getImage() {
return frames[currentFrame];
}
public boolean hasPlayedOnce() {
return playedOnce;
}
}
Thank you to Bandreid for the answer.
It need the object to be added and a new object created.
For a project in school we got a simple simulator for a parking lot, and it is our job to add features to this simulator. I'm trying to add a GUI to the simulator and so that you can fill in your values and that the simulation bases its simulation based upon those values. But i first made a simple button to go from 1 frame to the other but when i run the Frame1 file with the following code:
JButton btnSubmit = new JButton("Submit");
btnSubmit.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
frame.dispose();
Simulator mysimulator = new Simulator();
mysimulator.run();
}
it doesn't load, it just gives me a blank window.
the view of the simulator is made in the following code:
//package Parkeersimulator;
import javax.swing.*;
import java.awt.*;
public class SimulatorView extends JFrame {
public JFrame frame;
private CarParkView carParkView;
private int numberOfFloors;
private int numberOfRows;
private int numberOfPlaces;
private int numberOfOpenSpots;
private Car[][][] cars;
public SimulatorView(int numberOfFloors, int numberOfRows, int numberOfPlaces) {
this.numberOfFloors = numberOfFloors;
this.numberOfRows = numberOfRows;
this.numberOfPlaces = numberOfPlaces;
this.numberOfOpenSpots =numberOfFloors*numberOfRows*numberOfPlaces;
cars = new Car[numberOfFloors][numberOfRows][numberOfPlaces];
carParkView = new CarParkView();
Container contentPane = getContentPane();
contentPane.add(carParkView, BorderLayout.CENTER);
pack();
setVisible(true);
updateView();
}
public void updateView() {
carParkView.updateView();
}
public int getNumberOfFloors() {
return numberOfFloors;
}
public int getNumberOfRows() {
return numberOfRows;
}
public int getNumberOfPlaces() {
return numberOfPlaces;
}
public int getNumberOfOpenSpots(){
return numberOfOpenSpots;
}
public Car getCarAt(Location location) {
if (!locationIsValid(location)) {
return null;
}
return cars[location.getFloor()][location.getRow()][location.getPlace()];
}
public boolean setCarAt(Location location, Car car) {
if (!locationIsValid(location)) {
return false;
}
Car oldCar = getCarAt(location);
if (oldCar == null) {
cars[location.getFloor()][location.getRow()][location.getPlace()] = car;
car.setLocation(location);
numberOfOpenSpots--;
return true;
}
return false;
}
public Car removeCarAt(Location location) {
if (!locationIsValid(location)) {
return null;
}
Car car = getCarAt(location);
if (car == null) {
return null;
}
cars[location.getFloor()][location.getRow()][location.getPlace()] = null;
car.setLocation(null);
numberOfOpenSpots++;
return car;
}
public Location getFirstFreeLocation() {
for (int floor = 0; floor < getNumberOfFloors(); floor++) {
for (int row = 0; row < getNumberOfRows(); row++) {
for (int place = 0; place < getNumberOfPlaces(); place++) {
Location location = new Location(floor, row, place);
if (getCarAt(location) == null) {
return location;
}
}
}
}
return null;
}
public Car getFirstLeavingCar() {
for (int floor = 0; floor < getNumberOfFloors(); floor++) {
for (int row = 0; row < getNumberOfRows(); row++) {
for (int place = 0; place < getNumberOfPlaces(); place++) {
Location location = new Location(floor, row, place);
Car car = getCarAt(location);
if (car != null && car.getMinutesLeft() <= 0 && !car.getIsPaying()) {
return car;
}
}
}
}
return null;
}
public void tick() {
for (int floor = 0; floor < getNumberOfFloors(); floor++) {
for (int row = 0; row < getNumberOfRows(); row++) {
for (int place = 0; place < getNumberOfPlaces(); place++) {
Location location = new Location(floor, row, place);
Car car = getCarAt(location);
if (car != null) {
car.tick();
}
}
}
}
}
private boolean locationIsValid(Location location) {
int floor = location.getFloor();
int row = location.getRow();
int place = location.getPlace();
if (floor < 0 || floor >= numberOfFloors || row < 0 || row > numberOfRows || place < 0 || place > numberOfPlaces) {
return false;
}
return true;
}
private class CarParkView extends JPanel {
private Dimension size;
private Image carParkImage;
/**
* Constructor for objects of class CarPark
*/
public CarParkView() {
size = new Dimension(0, 0);
}
/**
* Overridden. Tell the GUI manager how big we would like to be.
*/
public Dimension getPreferredSize() {
return new Dimension(800, 500);
}
/**
* Overriden. The car park view component needs to be redisplayed. Copy the
* internal image to screen.
*/
public void paintComponent(Graphics g) {
if (carParkImage == null) {
return;
}
Dimension currentSize = getSize();
if (size.equals(currentSize)) {
g.drawImage(carParkImage, 0, 0, null);
}
else {
// Rescale the previous image.
g.drawImage(carParkImage, 0, 0, currentSize.width, currentSize.height, null);
}
}
public void updateView() {
// Create a new car park image if the size has changed.
if (!size.equals(getSize())) {
size = getSize();
carParkImage = createImage(size.width, size.height);
}
Graphics graphics = carParkImage.getGraphics();
for(int floor = 0; floor < getNumberOfFloors(); floor++) {
for(int row = 0; row < getNumberOfRows(); row++) {
for(int place = 0; place < getNumberOfPlaces(); place++) {
Location location = new Location(floor, row, place);
Car car = getCarAt(location);
Color color = car == null ? Color.white : car.getColor();
drawPlace(graphics, location, color);
}
}
}
repaint();
}
/**
* Paint a place on this car park view in a given color.
*/
private void drawPlace(Graphics graphics, Location location, Color color) {
graphics.setColor(color);
graphics.fillRect(
location.getFloor() * 260 + (1 + (int)Math.floor(location.getRow() * 0.5)) * 75 + (location.getRow() % 2) * 20,
60 + location.getPlace() * 10,
20 - 1,
10 - 1); // TODO use dynamic size or constants
}
}
}
and the simultion self is made in the following code:
//package Parkeersimulator;
import java.util.Random;
import javax.swing.*;
public class Simulator {
private static final String AD_HOC = "1";
private static final String PASS = "2";
private CarQueue entranceCarQueue;
private CarQueue entrancePassQueue;
private CarQueue paymentCarQueue;
private CarQueue exitCarQueue;
private SimulatorView simulatorView;
private int day = 0;
private int hour = 0;
private int minute = 0;
private int tickPause = 100;
int weekDayArrivals= 100; // average number of arriving cars per hour
int weekendArrivals = 200; // average number of arriving cars per hour
int weekDayPassArrivals= 50; // average number of arriving cars per hour
int weekendPassArrivals = 5; // average number of arriving cars per hour
int enterSpeed = 3; // number of cars that can enter per minute
int paymentSpeed = 7; // number of cars that can pay per minute
int exitSpeed = 5; // number of cars that can leave per minute
public static void main(String[] args){
Simulator mySimulator = new Simulator();
mySimulator.run();
}
public Simulator() {
entranceCarQueue = new CarQueue();
entrancePassQueue = new CarQueue();
paymentCarQueue = new CarQueue();
exitCarQueue = new CarQueue();
simulatorView = new SimulatorView(3, 6, 30);
}
public void run() {
for (int i = 0; i < 10000; i++) {
tick();
}
}
private void tick() {
advanceTime();
handleExit();
updateViews();
// Pause.
try {
Thread.sleep(tickPause);
} catch (InterruptedException e) {
e.printStackTrace();
}
handleEntrance();
}
private void advanceTime(){
// Advance the time by one minute.
minute++;
while (minute > 59) {
minute -= 60;
hour++;
}
while (hour > 23) {
hour -= 24;
day++;
}
while (day > 6) {
day -= 7;
}
}
private void handleEntrance(){
carsArriving();
carsEntering(entrancePassQueue);
carsEntering(entranceCarQueue);
}
private void handleExit(){
carsReadyToLeave();
carsPaying();
carsLeaving();
}
private void updateViews(){
simulatorView.tick();
// Update the car park view.
simulatorView.updateView();
}
private void carsArriving(){
int numberOfCars=getNumberOfCars(weekDayArrivals, weekendArrivals);
addArrivingCars(numberOfCars, AD_HOC);
numberOfCars=getNumberOfCars(weekDayPassArrivals, weekendPassArrivals);
addArrivingCars(numberOfCars, PASS);
}
private void carsEntering(CarQueue queue){
int i=0;
// Remove car from the front of the queue and assign to a parking space.
while (queue.carsInQueue()>0 &&
simulatorView.getNumberOfOpenSpots()>0 &&
i<enterSpeed) {
Car car = queue.removeCar();
Location freeLocation = simulatorView.getFirstFreeLocation();
simulatorView.setCarAt(freeLocation, car);
i++;
}
}
private void carsReadyToLeave(){
// Add leaving cars to the payment queue.
Car car = simulatorView.getFirstLeavingCar();
while (car!=null) {
if (car.getHasToPay()){
car.setIsPaying(true);
paymentCarQueue.addCar(car);
}
else {
carLeavesSpot(car);
}
car = simulatorView.getFirstLeavingCar();
}
}
private void carsPaying(){
// Let cars pay.
int i=0;
while (paymentCarQueue.carsInQueue()>0 && i < paymentSpeed){
Car car = paymentCarQueue.removeCar();
// TODO Handle payment.
carLeavesSpot(car);
i++;
}
}
private void carsLeaving(){
// Let cars leave.
int i=0;
while (exitCarQueue.carsInQueue()>0 && i < exitSpeed){
exitCarQueue.removeCar();
i++;
}
}
private int getNumberOfCars(int weekDay, int weekend){
Random random = new Random();
// Get the average number of cars that arrive per hour.
int averageNumberOfCarsPerHour = day < 5
? weekDay
: weekend;
// Calculate the number of cars that arrive this minute.
double standardDeviation = averageNumberOfCarsPerHour * 0.3;
double numberOfCarsPerHour = averageNumberOfCarsPerHour + random.nextGaussian() * standardDeviation;
return (int)Math.round(numberOfCarsPerHour / 60);
}
private void addArrivingCars(int numberOfCars, String type){
// Add the cars to the back of the queue.
switch(type) {
case AD_HOC:
for (int i = 0; i < numberOfCars; i++) {
entranceCarQueue.addCar(new AdHocCar());
}
break;
case PASS:
for (int i = 0; i < numberOfCars; i++) {
entrancePassQueue.addCar(new ParkingPassCar());
}
break;
}
}
private void carLeavesSpot(Car car){
simulatorView.removeCarAt(car.getLocation());
exitCarQueue.addCar(car);
}
}
please do note that i only just began programming and i have little to no knowledge at all.
You need a add the button to a panel.
JPanel panel = new JPanel();
JButton btnSubmit = new JButton("Submit");
Container content = this.getContentPane(); // Get the content pane
content.add(panel, BorderLayout.CENTER);
content.add(btnSubmit, BorderLayout.SOUTH);
setVisible(true); // Display the window
I'm currently making a space invaders-esque game for my software engineering course. I've already got everything working that satisfies the requirements, so this isn't a 'solve my homework' kind of question. My problem is that the game will lag (at what seems like random times & intervals) to the point where it becomes too frustrating to play. Some things I think might be causing this - though I'm not positive - are as follows:
Problem with timer event every 10 ms (I doubt this because of the very limited resources required for this game).
Problem with collision detection (checking for collision with every visible enemy every 10 ms seems like it would take up a large chunk of resources)
Problem with repainting? This seems unlikely to me however...
#SuppressWarnings("serial")
public class SIpanel extends JPanel {
private SIpanel panel;
private Timer timer;
private int score, invaderPace, pulseRate, mysteryCount, distanceToEdge;
private ArrayList<SIthing> cast;
private ArrayList<SIinvader> invaders, dead;
private ArrayList<SImissile> missileBase, missileInvader;
private SIinvader[] bottomRow;
private SIbase base;
private Dimension panelDimension;
private SImystery mysteryShip;
private boolean gameOver, left, right, mysteryDirection, space, waveDirection;
private boolean runningTimer;
private Music sound;
private void pulse() {
pace();
processInputs();
if (gameOver) gameOver();
repaint();
}
private void pace() {
// IF invaders still live
if (!invaders.isEmpty()) {
invaderPace++;
// Switch back manager
if (distanceToEdge <= 10) {
switchBack();
pulseRate = (pulseRate >= 16) ? (int) (pulseRate*(0.8)) : pulseRate;
waveDirection = !waveDirection;
distanceToEdge = calculateDistanceToEdge();
}
// Move invaders left/right
else if (invaderPace >= pulseRate) {
invaderPace = 0;
distanceToEdge = calculateDistanceToEdge();
moveAI();
invadersFire();
if (!dead.isEmpty()) removeDead();
if (mysteryCount < 1) tryInitMysteryShip();
}
// All invaders are kill, create new wave
} else if (missileBase.isEmpty() && missileInvader.isEmpty() && !cast.contains(mysteryShip)) {
// System.out.println("New Wave!");
newWave();
}
// Every pace
if (!missileBase.isEmpty()) moveMissileBase();
// Every two paces
if (invaderPace % 2 == 0) {
if (!missileInvader.isEmpty()) moveMissileInvader();
if (mysteryCount > 0) moveMysteryShip();
}
}
private void processInputs() {
if (left) move(left);
if (right) move(!right);
if (space) fireMissile(base, true);
}
protected void fireMissile(SIship ship, boolean isBase) {
if(isBase && missileBase.isEmpty()) {
base.playSound();
SImissile m = new SImissile(ship.getX()+(ship.getWidth()/2), ship.getY()-(ship.getHeight()/4));
missileBase.add(m);
cast.add(m);
} else if (!isBase && missileInvader.size()<3) {
base.playSound();
SImissile m = new SImissile(ship.getX()+(ship.getWidth()/2), ship.getY()+(ship.getHeight()/4));
missileInvader.add(m);
cast.add(m);
}
}
private void newWave() {
pulseRate = 50;
int defaultY=60, defaultX=120, defaultWidth=30, defaultHeight=24;
for(int i=0; i<5; i++) {
for(int j=0; j<10; j++) {
if (i<1) invaders.add(new SItop((j*defaultWidth)+defaultX, (i*defaultHeight)+defaultY, defaultWidth, defaultHeight));
else if (i<3) invaders.add(new SImiddle((j*defaultWidth)+defaultX, (i*defaultHeight)+defaultY, defaultWidth, defaultHeight));
else if (i<5) invaders.add(new SIbottom((j*defaultWidth)+defaultX, (i*defaultHeight)+defaultY, defaultWidth, defaultHeight));
}
}
for (SIinvader s: invaders) {
cast.add(s);
}
if (!cast.contains(base)) {
cast.add(base);
}
bottomRow = getBottomRow();
}
private void tryInitMysteryShip() {
Random rand = new Random();
int x=rand.nextInt(1000);
if (x<=3) {
mysteryCount = 1;
if (rand.nextBoolean()) {
mysteryDirection = true;
}
if (mysteryDirection) {
mysteryShip = new SImystery(0, 60, 36, 18);
} else {
mysteryShip = new SImystery(480, 60, 36, 18);
}
cast.add(mysteryShip);
}
}
private void moveMysteryShip() {
int distance = 0;
if (mysteryDirection) {
mysteryShip.moveRight(5);
distance = getWidth() - mysteryShip.getX();
} else {
mysteryShip.moveLeft(5);
distance = 30+mysteryShip.getX()-mysteryShip.getWidth();
}
if (distance <= 5) {
dead.add(mysteryShip);
mysteryShip = null;
mysteryCount = 0;
}
}
private void removeDead() {
#SuppressWarnings("unchecked")
ArrayList<SIinvader> temp = (ArrayList<SIinvader>) dead.clone();
dead.clear();
for (SIinvader s : temp) {
invaders.remove(s);
cast.remove(s);
}
bottomRow = getBottomRow();
}
private void invadersFire() {
int[] p = new int[bottomRow.length];
for (int i=0; i<p.length; i++) {
for (int j=0; j<p.length; j++) {
p[j] = j;
}
Random rand = new Random();
int a=rand.nextInt(101);
if (a>=20) {
int b=rand.nextInt(p.length);
fireMissile(bottomRow[b], false);
}
}
}
private int calculateDistanceToEdge() {
int distance = 0;
SIinvader[] outliers = getOutliers();
if (waveDirection) {
distance = getWidth() - outliers[0].getX()-outliers[0].getWidth();
} else {
distance = outliers[1].getX();
}
return distance;
}
private SIinvader[] getOutliers() {
SIinvader leftMost = invaders.get(0), rightMost = invaders.get(0);
for (SIinvader s : invaders) {
if (s.getX() < leftMost.getX()) {
leftMost = s;
}
if (s.getX() > rightMost.getX()) {
rightMost = s;
}
}
return new SIinvader[] { rightMost, leftMost };
}
private SIinvader[] getBottomRow() {
SIinvader[] x = new SIinvader[(invaders.size()>10)?10:invaders.size()];
for (int i=0; i<x.length; i++) {
x[i] = invaders.get(i);
for (SIinvader s:invaders) {
if (s.getX() == x[i].getX()) {
if (s.getY() > x[i].getY()) {
x[i] = s;
}
}
}
}
return x;
}
private void move(boolean b) {
int defaultX = 5;
if (b) base.moveLeft(defaultX);
else base.moveRight(defaultX);
}
private void moveAI() {
for(SIinvader s : invaders) {
s.changeImage();
int defaultX = 5;
if (waveDirection) s.moveRight(defaultX);
else s.moveLeft(defaultX);
}
}
private void moveMissileBase() {
if (invaders.isEmpty()) return;
int movement = -5, bound = 0;
SImissile missile = missileBase.get(0);
missile.moveDown(movement);
SIinvader lowestInvader = getLowestInvader();
if (missile.getY() < (lowestInvader.getY() + lowestInvader.getHeight())) {
for (SIinvader s:bottomRow) {
if (checkCollision(missile, s)) {
s.setHit();
dead.add(s);
cast.remove(missile);
missileBase.clear();
score += s.value;
return;
}
}
if (mysteryCount > 0) {
if (checkCollision(missile, mysteryShip)) {
mysteryShip.setHit();
dead.add(mysteryShip);
cast.remove(missile);
missileBase.clear();
score += mysteryShip.value;
return;
}
}
if (missile.getY() < bound) {
missileBase.remove(missile);
cast.remove(missile);
}
}
}
private SIinvader getLowestInvader() {
SIinvader lowest = bottomRow[0];
for (SIinvader invader : bottomRow) {
if (invader.getY() > lowest.getY()) {
lowest = invader;
}
}
return lowest;
}
private void moveMissileInvader() {
int movement = 5, bound = (int) panelDimension.getHeight();
for (SImissile missile : missileInvader) {
missile.moveDown(movement);
if(missile.getY() >= base.getY()) {
if (checkCollision(missile, base)) {
base.setHit();
gameOver = true;;
missileInvader.remove(missile);
cast.remove(missile);
return;
} else if (missile.getY() >= bound-25) {
missileInvader.remove(missile);
cast.remove(missile);
return;
}
}
}
}
private boolean checkCollision(SIthing missile, SIthing ship) {
Rectangle2D rect1 = new Rectangle2D.Double(
missile.getX(),
missile.getY(),
missile.getWidth(),
missile.getHeight()
);
Rectangle2D rect2 = new Rectangle2D.Double(
ship.getX(),
ship.getY(),
ship.getWidth(),
ship.getHeight()
);
return rect1.intersects(rect2);
}
private void switchBack() {
int defaultY = 12;
for (SIinvader s : invaders) {
if (s.getY() > getHeight()) {
gameOver = true;
return;
}
s.moveDown(defaultY);
}
}
private void gameOver() {
pause(true);
SI.setGameOverLabelVisibile(true);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.GREEN);
Font font = new Font("Arial", 0, 20);
setFont(font);
String score = "Score: "+this.score;
Rectangle2D rect = font.getStringBounds(score, g2.getFontRenderContext());
int screenWidth = 0;
try { screenWidth = (int) panelDimension.getWidth(); }
catch (NullPointerException e) {}
g2.setColor(Color.GREEN);
g2.drawString(score, (int) (screenWidth - (10 + rect.getWidth())), 20);
for(SIthing a:cast) {
a.paint(g);
}
}
public SIpanel() {
super();
setBackground(Color.BLACK);
cast = new ArrayList<SIthing>();
missileBase = new ArrayList<SImissile>();
score = invaderPace = mysteryCount = pulseRate = 0;
sound = new Music("AmbientMusic.wav");
panel = this;
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT : left = true; break;
case KeyEvent.VK_RIGHT : right = true; break;
case KeyEvent.VK_SPACE : space = true; break;
}
}
#Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT : left = false; break;
case KeyEvent.VK_RIGHT : right = false; break;
case KeyEvent.VK_SPACE : space = false; break;
}
}
});
setFocusable(true);
timer = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
pulse();
}
});
}
public void reset() {
SI.setGameOverLabelVisibile(false);
score = invaderPace = mysteryCount = 0;
pulseRate = 50;
cast = new ArrayList<SIthing>();
invaders = new ArrayList<SIinvader>();
dead = new ArrayList<SIinvader>();
missileBase = new ArrayList<SImissile>();
missileInvader = new ArrayList<SImissile>();
base = new SIbase(230, 370, 26, 20);
waveDirection = true;
gameOver = false;
sound.stop();
sound.loop();
panelDimension = SI.getFrameDimensions();
bottomRow = getBottomRow();
newWave();
timer.start();
runningTimer=true;
}
public SIpanel getPanel() {
return this.panel;
}
public void pause(boolean paused) {
if (paused) timer.stop();
else timer.start();
}
}
I believe that collision detection may be the reason for lagging and you should simply investigate it by trying to increase and decrease count of enemies or missiles drastically to see if that makes a difference.
Consider garbage collector your enemy. In your checkCollision method you are instantiating two (very simple) objects. It may not seem like a lot, but consider that your might be creating them for each collision check, and that at 60fps it adds up until it may reach critical mass when GC says "stop the world" and you see noticeable lag.
If that is the case, possible solution to that would be to not instantiate any objects in a method called so frequently. You may create Rectangle2D once, and then update its position, instead of creating a new one each time, so you will avoid unnecessary memory allocation.
Within the scope of a paper I am writing at high school I chose to make my own audio-file-to-spectrogram-converter from scratch in order to create landscapes out of these spectrograms.
I already do have my implementation of an FFT and of using that to make a heightmap, a spectrogram. But I often get weird artifacts in the form of vertical stripes when the frequencies get dense, as you can see in the image below.
The example is right at the beginning with a window length of 2048 and on a log^2-scale. The FFT I am using is flawless, I've already compared it to others and they produce the same result.
This is the function which transforms the amplitudes into frequencies and stores them in a 2D-array:
private void transform(int from, int until) {
double val, step;
for(int i=from; i<until; i++) {
for(int j=0; j<n; j++)
chunk[j] = data[0][i*n+j+start];
fft.realForward(chunk);
for(int j=0; j<height; j++) {
val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);
map[i][j] = val;
}
}
}
Now my Question: Where do these vertical stripes come from and how do I get rid of them?
I currently don't employ a window function and every calculation is stringed to one another, which means there is no overlapping. It is the simplest way you can think of making a spectrogram. Could it help introducing a window function or doing each calculation independent of whether the frame was already involved in a previous calculation, that is to say overlapping the frame-windows?
Also, what other ways are there to improve on my basic approach in order to get a better result?
This is the whole class. I feed it the data and all the necessary information from an audio file:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import org.jtransforms.fft.DoubleFFT_1D;
public class Heightmap extends JFrame implements WindowListener{
public static final int LOG_SCALE = 0;
public static final int LOG_SQUARE_SCALE = 1;
public static final int SQUARE_SCALE = 2;
public static final int LINEAR_SCALE = 3;
private BufferedImage heightmap;
private FileDialog chooser;
private JMenuBar menuBar;
private JMenu fileMenu;
private JMenuItem save, close;
private DoubleFFT_1D fft;
private int[][] data;
private double[][] map;
private double[] chunk;
private int width, height, n, start, scale;
private String name;
private boolean inactive;
public Heightmap(int[][] data, int resolution, int start,
int width, int height, int scale, String name) {
this.data = data;
this.n = resolution;
this.start = start;
this.width = width;
this.height = height;
this.scale = scale;
this.name = name;
fft = new DoubleFFT_1D(n);
map = new double[width][height];
heightmap = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
chunk = new double[n];
System.out.println("Starting transformation...");
long time;
time = System.currentTimeMillis();
transform();
time = System.currentTimeMillis() - time;
System.out.println("Time taken for calculation: "+time+" ms");
time = System.currentTimeMillis();
makeHeightmap();
initComponents();
time = System.currentTimeMillis() - time;
System.out.println("Time taken for drawing heightmap: "+time+" ms");
}
private void initComponents() {
this.setSize(width, height);
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.setResizable(false);
this.setLocationRelativeTo(null);
this.setTitle(name);
createMenuBar();
chooser = new FileDialog(this, "Choose a directory", FileDialog.SAVE);
chooser.setDirectory("/Users/<user>/Desktop");
this.addMouseListener(new HeightmapMouseListener());
this.addKeyListener(new HeightmapKeyListener());
this.addWindowListener(this);
this.setVisible(true);
}
private void createMenuBar() {
menuBar = new JMenuBar();
fileMenu = new JMenu();
fileMenu.setText("File");
save = new JMenuItem("Save...", KeyEvent.VK_S);
save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.META_DOWN_MASK));
save.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
chooser.setVisible(true);
String fileName = chooser.getFile();
String dir = chooser.getDirectory();
chooser.setDirectory(dir);
if(fileName != null) {
try {
File outputfile = new File(dir + fileName + ".png");
ImageIO.write(heightmap, "png", outputfile);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Saved "+fileName+".png to "+dir);
}
}
});
close = new JMenuItem("Close", KeyEvent.VK_C);
close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_DOWN_MASK));
close.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
dispose();
}
});
fileMenu.add(save);
fileMenu.addSeparator();
fileMenu.add(close);
menuBar.add(fileMenu);
this.setJMenuBar(menuBar);
}
public void paint(Graphics g) {
g.drawImage(heightmap, 0, 0, null);
}
private void transform() {
transform(0, width);
}
private void transform(int from, int until) {
double max = Double.MIN_VALUE;
double min = Double.MAX_VALUE;
double val, step;
for(int i=from; i<until; i++) {
for(int j=0; j<n; j++) {
chunk[j] = data[0][i*n+j+start];
}
fft.realForward(chunk);
for(int j=0; j<height; j++) {
val = Math.sqrt(chunk[2*j]*chunk[2*j] + chunk[2*j+1]*chunk[2*j+1]);
if(val > max)
max = val;
if(val < min)
min = val;
map[i][j] = val;
}
if(min != 0) {
step = max/(max-min);
for(int j=0; j<height; j++)
map[i][j] = (map[i][j]-min)*step;
}
}
}
/*
* Paints heightmap into the BufferedImage
*/
private void makeHeightmap() {
double max = 0;
switch(scale) {
case LOG_SCALE: max = Math.log(findMax(map)+1); break;
case LOG_SQUARE_SCALE: max = Math.pow(Math.log(findMax(map)+1), 2); break;
case SQUARE_SCALE: max = Math.sqrt(findMax(map)); break;
case LINEAR_SCALE: max = findMax(map); break;
default: max = Math.pow(Math.log(findMax(map)+1), 2); break;
}
double stepsize = 255.0/max;
int val, rgb;
for(int x=0; x<width; x++)
for(int y=0; y<height; y++) {
switch(scale) {
case LOG_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); break;
case LOG_SQUARE_SCALE: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
case SQUARE_SCALE: val = (int) (Math.sqrt(map[x][y])*stepsize); break;
case LINEAR_SCALE: val = (int) (map[x][y]*stepsize); break;
default: val = (int) (Math.log(map[x][y]+1)*stepsize); val *= val; break;
}
rgb = 255<<24 | val<<16 | val<<8 | val;
heightmap.setRGB(x, height-y-1, rgb);
}
}
private double findMax(double[][] data) {
double max = 0;
for(double[] val1: data)
for(double d: val1)
if(d > max)
max = d;
return max;
}
private class HeightmapKeyListener implements KeyListener {
boolean busy = false;
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_RIGHT && !busy && start < data[0].length-width*n) {
busy = true;
for(int x=0; x<width-1; x++)
map[x] = map[x+1].clone();
start += n;
transform(width-1, width);
makeHeightmap();
repaint();
busy = false;
}
else if(e.getKeyCode() == KeyEvent.VK_LEFT && !busy && start > 0) {
busy = true;
for(int x=width-1; x>0; x--)
map[x] = map[x-1];
start -= n;
transform(0, 1);
makeHeightmap();
repaint();
busy = false;
}
}
public void keyReleased(KeyEvent e) { }
public void keyTyped(KeyEvent e) { }
}
private class HeightmapMouseListener implements MouseListener {
public void mouseClicked(MouseEvent e) {
if(inactive) {
inactive = false;
return;
}
long time = System.currentTimeMillis();
int posX = e.getX();
int diff = posX - width/2; //difference between old and new center in pixels
int oldStart = start;
start = start + diff*n;
if(start < 0) start = 0;
int maxFrame = data[0].length-width*n;
if(start > maxFrame) start = maxFrame;
if(start == oldStart) return;
System.out.println("Changing center...");
int absDiff = Math.abs(diff);
if(start < oldStart) { //shift the start backward, recalculate the start
for(int x=width-1; x>=absDiff; x--)
map[x] = map[x-absDiff].clone();
transform(0, absDiff);
}
else if(start > oldStart) { //shift the back forward, recalculate the back
for(int x=0; x<width-absDiff; x++)
map[x] = map[x+absDiff].clone();
transform(width-absDiff, width);
}
makeHeightmap();
repaint();
System.out.println("Time taken: "+(System.currentTimeMillis()-time)+" ms");
}
public void mousePressed(MouseEvent e) { }
public void mouseReleased(MouseEvent e) { }
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
}
public void windowActivated(WindowEvent arg0) { }
public void windowClosed(WindowEvent arg0) { }
public void windowClosing(WindowEvent arg0) { }
public void windowDeactivated(WindowEvent arg0) {
inactive = true;
}
public void windowDeiconified(WindowEvent arg0) { }
public void windowIconified(WindowEvent arg0) { }
public void windowOpened(WindowEvent arg0) { }
}
EDIT:
Implementing a window function improved the result drastically. I really didn't understand what a window function would do and therefore underestimated its effect.
However, after doing so I tried mapping a cosine wave with a frequency of 10kHz which (again) produced some strange artifacts:
What could be the cause of this one? I implemented a overflow protection by clipping everything under 0 to 0 and over 255 to 255 with no change whatsoever.
This type of artifact can be due to overflow or otherwise exceeding parameter bounds before or in your color mapping function, or perhaps with some function (log?) returning NaN values. You might be able to find this by putting in some asserts for out-of-range or illegal values.