I am trying do develop may first android game in 2d. I intend to build it from scratch without the use of an engine. I have manage to create the following thread:
import android.graphics.Canvas;
import android.view.SurfaceHolder;
public class MainThread extends Thread {
private int FPS = 30;
private double averageFPS;
private GamePanel gamePanel;
private SurfaceHolder surfaceHolder;
private boolean running;
public static Canvas canvas;
public MainThread(SurfaceHolder surfaceHolder, GamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#Override
public void run() {
long startTime;
long timeMillis;
long waitTime;
long totalTime = 0;
int frameCount = 0;
// how many milliseconds it take to run through the loop
long targetTime = 1000 / FPS;
while (running) {
startTime = System.nanoTime();
canvas = null;
// try to lock the canvas for pixel editing
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
// the main game loop
this.gamePanel.update();
this.gamePanel.draw(canvas);
}
} catch (Exception e) {
} finally {
if (canvas != null) {
try {
surfaceHolder.unlockCanvasAndPost(canvas);
} catch (Exception e) {
e.printStackTrace();
}
}
}
timeMillis = (System.nanoTime() - startTime) / 1000000;
waitTime = targetTime - timeMillis;
try {
System.out.println(waitTime);
this.sleep(waitTime);
} catch (Exception e) {
}
totalTime += System.nanoTime() - startTime;
frameCount++;
if (frameCount == FPS) {
averageFPS = 1000 / ((totalTime / frameCount) / 1000000);
frameCount = 0;
totalTime = 0;
System.out.println(averageFPS);
}
}
}
The problem I have is that the averageFPS keeps dropping when adding stuff to my game. With only the background it works at 30 on my Nexus 5 but upon adding character and obstacles it drops to 22-21.
Is there anyway I can do to optimize this ?
Thank you
UPDATE: my GamePanel looks like this:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.ArrayList;
import java.util.Random;
public class GamePanel extends SurfaceView implements SurfaceHolder.Callback {
public static final int WIDTH = 1920;
public static final int HEIGHT = 1080;
public static float MOVESPEED = -12;
private Random rand = new Random();
private MainThread thread;
private Background bg;
private Treadmill tm;
private Player player;
private ArrayList<Box> boxes;
private int minDistance = 600;
private int score;
public GamePanel(Context context) {
super(context);
// add callback service to the holder to intercept events
getHolder().addCallback(this);
// make gamePanel focusable so it can handle events
setFocusable(true);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
while (retry) {
try {
thread.setRunning(false);
thread.join();
retry = false;
thread = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// instantiate objects
thread = new MainThread(getHolder(), this);
bg = new Background(BitmapFactory.decodeResource(getResources(), R.drawable.background));
tm = new Treadmill(BitmapFactory.decodeResource(getResources(), R.drawable.ground));
player = new Player(BitmapFactory.decodeResource(getResources(), R.drawable.character));
boxes = new ArrayList<Box>();
//start game loop
thread.setRunning(true);
thread.start();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (player.jump == false) {
player.setVelocity(-14f);
player.setJump(true);
}
return true;
}
return super.onTouchEvent(event);
}
public void update() {
bg.update();
tm.update();
player.update();
if (boxes.size() == 0) {
boxes.add(new Box(BitmapFactory.decodeResource(getResources(), R.drawable.box),
WIDTH));
} else if (boxes.get(boxes.size() - 1).getX() < WIDTH) {
if (WIDTH - boxes.get(boxes.size() - 1).getX() < minDistance) {
boxes.add(new Box(BitmapFactory.decodeResource(getResources(), R.drawable.box),
rand.nextInt(400 - 50) + WIDTH + minDistance));
}
}
for (int i = 0; i < boxes.size(); i++) {
if (boxes.get(i).getX() <= player.getX() && boxes.get(i).getX() >= player.getX() - 12) {
score++;
MOVESPEED -= 0.2;
System.out.println(MOVESPEED);
}
if (collision(boxes.get(i), player)) {
boxes.remove(i);
break;
}
if (boxes.get(i).getX() < -100) {
boxes.remove(i);
break;
}
}
for (int i = 0; i < boxes.size(); i++) {
boxes.get(i).update();
}
}
#Override
public void draw(Canvas canvas) {
final float scaleFactorX = getWidth() / (WIDTH * 1.f);
final float scaleFactorY = getHeight() / (HEIGHT * 1.f);
if (canvas != null) {
final int saveState = canvas.save();
canvas.scale(scaleFactorX, scaleFactorY);
// draw the background
bg.draw(canvas);
tm.draw(canvas);
player.draw(canvas);
for (Box bx : boxes) {
bx.draw(canvas);
}
drawText(canvas);
canvas.restoreToCount(saveState);
}
}
public boolean collision(GameObject a, GameObject b) {
if (Rect.intersects(a.getRectangle(), b.getRectangle())) {
return true;
}
return false;
}
public void drawText(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setTextSize(50);
paint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
canvas.drawText("Score: " + (score), 15, HEIGHT - 20, paint);
}
Thank you for all the help so far
The problem doesn't seem to be in the Main Thread Activity, you followed tutorial fine (Except for the line - System.out.println(waitTime); - but i don't think thats the problem cause.)
The problem is most likely in the GamePanel. ;)
Don't ever use Thread.sleep() in game loops. It is not guaranteed to sleep for the specified amount of time.
Anyway, I think that this is pretty much expected behaviour when using Canvas. If I were you I'd consider using OpenGL ES for anything like graphics rendering. Definitely, this makes it much more complex (even though Android has a lot of things already done for you). But the benefit is quite obvious - you get actual real-time graphics performance.
Related
Im using the MLKit combined with the CameraX Image-Analysis Use Case to build an App, that lets you control a cursor, which gets drawn onto an overlay based on the position of the face (head based pointing essentially).
I have two activities:
Activity 1 previews the camera feed and draws the facial landmarks (in my case, just the nosetip) onto the overlay as well as the cursor. As I said before, the cursor position is being calculated based on the nosetip position in the image -> No problems here
Activity 2 runs a very simplistic game in a separate thread, which spawns obstacles that need to be avoided with the cursor. No live camera feed, "just" the face detection, the face -> cursor screen mapping, a custom gameView and the overlay for the cursor ontop.
My problem:
After some time, the face detector just stops detecting faces (thus the cursor gets "stuck") without any errors and oddly enough, the game incrementally speeds up (but still registers collision with the cursor).
I already followed the instructions for optimizing face detection in real-time by only enabling landmark-mode, enabled tracking and setting the performance mode to fast.
My guess was that running the face detection, mapping the cursor to a point on the screen, drawing onto the overlay for the cursor and having a game running at the same time might be just too much work, but I read somewhere that MLKit runs its detection on its own thread.
It must have something to do with my game, because as already mentioned above, in Activity 1 (Live camera preview + calculating cursor position + drawing landmarks & cursor on the overlay canvas) I never faced (no pun intended) a crash.
My custom GameView:
public class GameView extends SurfaceView implements SurfaceHolder.Callback{
private final int difficultyTime = 5;
private GameThread thread;
private long startTime;
private long timer;
private long difficultyTimer;
private long lastSpawned = 0;
private Paint textPaint;
private Random random;
private Pointer pointer;
private GameManager game;
public GameView(Context context) {
super(context);
init();
}
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GameView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void init() {
getHolder().addCallback(this);
thread = new GameThread(getHolder(), this);
game = new GameManager();
setFocusable(true);
textPaint = new Paint();
textPaint.setColor(Color.RED);
textPaint.setTextSize(70);
Paint collisionPaint = new Paint();
collisionPaint.setColor(Color.BLUE);
Paint pointerPaint = new Paint();
pointerPaint.setColor(Color.GREEN);
startTime = System.currentTimeMillis();
lastSpawned = System.currentTimeMillis();
difficultyTimer = System.currentTimeMillis() + difficultyTime * 1000;
random = new Random();
}
public void update() {
timer = (System.currentTimeMillis() - startTime) / 1000;
long currentTimeMS = System.currentTimeMillis();
if(currentTimeMS > lastSpawned + (game.getCooldown() * 1000L)) {
lastSpawned = System.currentTimeMillis();
game.setMayDraw(true);
}
if(checkCollision()) {
gameover();
}
// Raise difficulty
if(difficultyTimer <= System.currentTimeMillis()) {
difficultyTimer = System.currentTimeMillis() + difficultyTime * 1000;
game.advanceDifficulty();
}
}
#Override
public void draw(Canvas canvas) {
super.draw(canvas);
int bgColor = Color.WHITE;
canvas.drawColor(bgColor);
// Spawn elements
if(game.getMayDraw()) {
game.setMayDraw(false);
spawnObstacles(canvas);
}
// Update existing elements
game.updateObstacles(canvas);
// Draw the timer
drawTimer(canvas);
// Draw difficulty text
drawDifficulty(canvas);
}
private void drawTimer(Canvas canvas) {
canvas.drawText(Long.toString(timer), (getWidth() / 2), 80, textPaint);
invalidate();
}
private void drawDifficulty(Canvas canvas) {
canvas.drawText("Difficulty: " + game.getCurrentDifficultyIndex(), (getWidth() / 2) + 250, 80, textPaint);
}
private void spawnObstacles(Canvas canvas) {
int random_number;
int last_random = 0;
for(int i = 0; i < game.getMAX_SPAWN(); i++) {
do {
random_number = random.nextInt(4) + 1;
}while(random_number == last_random);
switch(random_number) {
case 1:
game.spawnTop(canvas);
break;
case 2:
game.spawnBottom(canvas);
break;
case 3:
game.spawnLeft(canvas);
break;
case 4:
game.spawnRight(canvas);
break;
}
last_random = random_number;
}
}
public void gameover() {
startTime = System.currentTimeMillis();
game.getObstacles().clear();
game.resetDifficulty();
difficultyTimer = System.currentTimeMillis() + difficultyTime * 1000;
}
public boolean checkCollision() {
List<Pair<GameManager.Direction, Rect>> collidables = game.getObstacles();
for (Pair<GameManager.Direction, Rect> r : collidables) {
if(intersects(r.second, pointer.getCollisionRect()) || intersects(pointer.getCollisionRect(), r.second)) {
return true;
}
}
return false;
}
public static boolean intersects(Rect rect, Rect otherRect) {
return rect.left <= otherRect.right && otherRect.left <= rect.right
&& rect.top <= otherRect.bottom && otherRect.top <= rect.bottom;
}
#Override
public void surfaceCreated(#NonNull SurfaceHolder surfaceHolder) {
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceChanged(#NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
System.out.println("Surface changed - Format: " + format + " W/H: ("+width +"/" +height+")");
}
#Override
public void surfaceDestroyed(#NonNull SurfaceHolder surfaceHolder) {
boolean retry = true;
while (retry) {
try {
thread.setRunning(false);
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
retry = false;
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
GameThread:
public class GameThread extends Thread{
private final SurfaceHolder surfaceHolder;
private final GameView gameView;
private boolean running;
public static Canvas canvas;
public GameThread(SurfaceHolder surfaceHolder, GameView gameView) {
super();
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
#Override
public void run() {
long startTime;
long timeMillis;
long waitTime;
long totalTime = 0;
int frameCount = 0;
int targetFPS = 15;
long targetTime = 1000 / targetFPS;
while (running) {
startTime = System.nanoTime();
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized(surfaceHolder) {
this.gameView.update();
this.gameView.draw(canvas);
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
if (canvas != null) {
try {
surfaceHolder.unlockCanvasAndPost(canvas);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
timeMillis = (System.nanoTime() - startTime) / 1000000;
waitTime = targetTime - timeMillis;
try {
if(waitTime > 0) {
this.sleep(waitTime);
}
} catch (Exception e) {
e.printStackTrace();
}
totalTime += System.nanoTime() - startTime;
frameCount++;
if (frameCount == targetFPS) {
double averageFPS = 1000 / ((totalTime / frameCount) / 1000000);
frameCount = 0;
totalTime = 0;
Log.d("GameFPS", "" + averageFPS);
}
}
}
public void setRunning(boolean isRunning) {
running = isRunning;
}
FaceDetector:
public class Detector {
private final FaceDetector faceDetector;
private final GraphicOverlay overlay;
private final Pointer pointer;
private final ClickHandler clickHandler;
private final boolean drawFaces;
public Detector(GraphicOverlay overlay, Context context, boolean drawFaces) {
this.overlay = overlay;
this.drawFaces = drawFaces;
pointer = new Pointer(overlay, context);
clickHandler = new ClickHandler(context, this.pointer);
FaceDetectorOptions options =
new FaceDetectorOptions.Builder()
//.setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
.setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
/* disable for better performance *///.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
.enableTracking()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.build();
faceDetector = FaceDetection.getClient(options);
}
public void detectFace(ImageProxy image) {
if(image == null) {
return;
}
InputImage inputImage = getInputImage(image);
int rotationDegrees = image.getImageInfo().getRotationDegrees();
if(rotationDegrees == 0 || rotationDegrees == 180) {
overlay.setImageSourceInfo(image.getWidth(), image.getHeight(), true);
}else{
overlay.setImageSourceInfo(image.getHeight(), image.getWidth(), true);
}
if(inputImage == null) {
return;
}
Task<List<Face>> result =
this.faceDetector.process(inputImage)
// When detection succeeded
.addOnSuccessListener(
faces -> {
overlay.clear();
if(faces.size() > 0) {
for(Face face: faces) {
if(drawFaces) {
// Adding faces to overlay
FaceGraphic g = new FaceGraphic(overlay, face);
overlay.add(g);
}
// Update Pointer/Cursor
pointer.update(face);
}
}
})
// When detection failed
.addOnFailureListener(
e -> Log.d("Processing failed", e.getMessage()))
// When detection is completed
.addOnCompleteListener(
task -> image.close()
);
}
#SuppressLint("UnsafeOptInUsageError")
private InputImage getInputImage(ImageProxy image) {
int rotation = image.getImageInfo().getRotationDegrees();
Image mediaImage = image.getImage();
if(mediaImage != null) {
return InputImage.fromMediaImage(mediaImage, rotation);
}else{
return null;
}
}
GameActivity:
public class GameActivity extends AppCompatActivity {
private GraphicOverlay mOverlay;
private GameView gameView;
private Camera camera;
private Pointer pointer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_game);
gameView = findViewById(R.id.gameView);
mOverlay = findViewById(overlay_game);
initCamera();
}
private void initCamera() {
camera = new Camera(this, mOverlay);
camera.setDrawFaces(false);
camera.setAnalysisUseCase();
camera.setAnalyzer();
camera.build();
pointer = camera.getDetector().getPointer();
gameView.setPointer(pointer);
}
#Override
protected void onResume() {
super.onResume();
}
#Override
protected void onPause() {
super.onPause();
}
The face detection "crash" can occur anywhere from 1 second to 5 minutes playing the game. I would gladly appreciate any hints or suggestions. Also im fairly new to java and mobile-app development, so please excuse my bad code.
If you need any further information/code i'll happily provide it.
Thanks in advance
Before I start, here is my UML diagram:
I'm trying to animate my player's character, of a gif but split into 5 separate png files. I'd rather work when the 5 frames directly because it's not a detailed image, just an 8-bit character sprite. Also, don't know how to use many Photo editing software, so I'm using the separate files themselves.
I've tried quite a few examples off Stack Overflow and other tutorials but nothing seems to match what I'm trying to do on the framework I have to build for my game. It's for an end of semester project but I'm planning to build upon in after the due date and keep it open source.
Here is some of my code.
Driver. Loads the game loop and other functions.
package cactus;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
public class Driver extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;
private Thread t;
private boolean running;
private Controller controller;
public Driver() {
controller = new Controller();
/*--- Add Game Objects ---------------------------------------*/
controller.addObject(new Travis(500, 675, ID.Travis));
/*------------------------------------------------------------*/
}
public synchronized void start() {
t = new Thread(this);
t.start();
running = true;
}
public synchronized void stop() {
try {
t.join();
running = false;
} catch (Exception e) {
e.printStackTrace();
}
}
public void run() {
this.requestFocus();
long lastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis();
int frames = 0;
while(running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >= 1) {
tick();
delta--;
}
if(running) {
render();
}
frames++;
if(System.currentTimeMillis() - timer > 1000) {
timer += 1000;
System.out.println("FPS:" + frames);
frames = 0;
}
}
stop();
}
private void tick() {
controller.tick();
}
private void render() {
BufferStrategy buffer = this.getBufferStrategy();
if (buffer == null) {
this.createBufferStrategy(3);
return;
}
Graphics g = buffer.getDrawGraphics();
g.drawImage(background.getCurrentBG(), 0, 0, Frame.getWidth(), Frame.getHeight(), null);
controller.render(g);
g.dispose();
buffer.show();
}
public static void main(String[] args) {
new Driver();
}
}
Controller. Loops through game objects to execute some of their render functions, which displays what they look like, and their tick functions which controls their locations and some future actions.
package cactus;
import java.awt.Graphics;
import java.util.LinkedList;
public class Controller {
LinkedList<Objects> gameObj = new LinkedList<Objects>();
public void tick() {
for (int i = 0; i < gameObj.size(); i++) {
Objects currentObject = gameObj.get(i);
if (currentObject.getId() == ID.ShootFire && currentObject.getX() > Frame.getWidth() + 30) {
removeObject(currentObject);
System.out.println("Fire Object Removed");
}
currentObject.tick();
}
}
public void render(Graphics g) {
for (int i = 0; i < gameObj.size(); i++) {
Objects currentObject = gameObj.get(i);
currentObject.render(g);
}
}
public void addObject(Objects o) {
this.gameObj.add(o);
}
public void removeObject(Objects o) {
this.gameObj.remove(o);
}
}
Player class. Gets methods from Abstract Objects class
package cactus;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
// import javax.swing.Timer;
public class Travis extends Objects implements ActionListener{
ArrayList<BufferedImage> animation;
// Timer animationTimer;
Graphics g;
// int i = 0;
public Travis(int x, int y, ID id) {
super(x, y, id);
this.animation = createAnimation();
// this.animationTimer = createTimer(animationTimer);
// animationTimer.start();
}
private synchronized ArrayList<BufferedImage> createAnimation() {
animation = new ArrayList<BufferedImage>();
for (int i = 0; i < 6; i++) {
try {
animation.add(ImageIO.read(new File("./src/resources/image/travis/frame_" + i + ".png")));
} catch (IOException e) {e.printStackTrace(); System.exit(0);}
}
return animation;
}
// private synchronized Timer createTimer(Timer animationTimer) {
// animationTimer = new Timer(15, this);
// animationTimer.setDelay(15*1000);
// return animationTimer;
// }
#Override
public void tick() {
x += velocityX;
}
/*
* drawImage(img, posX, posY, observe [null])
*
* (non-Javadoc)
* #see cactus.GameObjects#render(java.awt.Graphics)
*/
#Override
public void render(Graphics g) {
for (int i = 0; i < 6; i++) {
g.drawImage(animation.get(i), x, y, null);
}
if (i == 6)
i = 0;
}
#Override
public void actionPerformed(ActionEvent e) {
}
}
I want each object of the ArrayList (although likely will change it to Map), to play with different delays between frames.
* frame_0: 1s
* frame_1: 2s
* frame_2: 2s
* frame_3: 1s
* frame_4: 2s
* frame_5: 5s
I've tried using Timer and some other things and I'm not able to animate it, it just displays the last frame.
I don't have enough rep to post photographs.
The simplest approach is to yield the rendering thread by sleeping it for a few milliseconds between frames:
private static final long PERIOD = 1000 / FRAMES_PER_SECOND;
...
long now = System.currentTimeMillis();
while(running) {
// Perform frame
...
// Wait for next frame
now += PERIOD;
final long duration = now - System.currentTimeMillis();
if(duration > 0) {
Thread.sleep(duration);
}
}
where FRAMES_PER_SECOND is the desired number of frames-per-second.
You could also add a counter to calculate the frame-rate if required.
There are lots of other (and better) ways of creating a render loop but this should do as a starter.
As you can see I added the a mouse listener to the game.
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
public class Game implements Runnable
{
private Display display;
public int width, height;
public String title;
private boolean running = false;
private Thread thread;
private BufferStrategy bs;
private Graphics g;
// States
public static State gameState;
// Input
private InputManager inputManager;
private MouseHandler mouseHandler;
public Game(String title, int width, int height)
{
this.width = width;
this.height = height;
this.title = title;
inputManager = new InputManager();
mouseHandler = new MouseHandler();
}
/**
* Initializes all of the graphics
*/
private void initialize()
{
display = new Display(title, width, height);
display.getFrame().addKeyListener(inputManager);
display.getFrame().addMouseListener(mouseHandler);
Assets.loadAssets();
gameState = new GameState(this, 1);
State.setState(gameState);
}
/**
* Updates everything in the game loop
*/
private void update()
{
if (State.getState() != null)
State.getState().update();
}
private void render()
{
bs = display.getCanvas().getBufferStrategy();
// If this is the first time running initialize the buffer strategy
if (bs == null)
{
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
// Clear the screen
g.clearRect(0, 0, width, height);
// Drawing
if (State.getState() != null)
State.getState().render(g);
// End drawing
bs.show();
g.dispose();
}
public void run()
{
initialize();
// Updates the game loop 60 times every 1 second = 1,000,000,000
// nanoseconds
int fps = 60;
double timePerUpdate = 1000000000 / fps;
double timeElapsed = 0;
long now;
// Current time of computer in nanoseconds
long lastTime = System.nanoTime();
// Game loop
while (running)
{
now = System.nanoTime();
timeElapsed += (now - lastTime) / timePerUpdate;
lastTime = now;
if (timeElapsed >= 1)
{
update();
render();
timeElapsed--;
}
}
stop();
}
public synchronized void start()
{
// Do not make a new thread if it is already running
if (running)
return;
// Starts the game
running = true;
thread = new Thread(this); // this = Game class
thread.start(); // Goes to run
}
public synchronized void stop()
{
// In case stop gets called and it is already not running
if (!running)
return;
// Stops the game
running = false;
try
{
thread.join();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
public InputManager getInputManager()
{
return inputManager;
}
public MouseHandler getMouseHandler()
{
return mouseHandler;
}
public static void main(String[] args)
{
Game game = new Game("Game", 1024, 768);
game.start();
}
}
This is my mouse adapter class, basically all I want is the x,y coordinates of where the mouse is pressed
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class MouseHandler extends MouseAdapter
{
public int x, y;
public MouseHandler()
{
}
public void MousePressed(MouseEvent e)
{
x = e.getX();
y = e.getY();
}
}
When I try to get the X, and Y coordinates they are always 0 instead of where the mouse actually clicked. I have no clue why.
public int x, y;
int variables are always initialized to 0.
public void MousePressed(MouseEvent e)
Method names are case sensitive.
Your code is never executed since the method to override should be mousePressed(...). Fix the typo in your code.
Always use code like:
#Override
public void mousePressed(MouseEvent e)
Then if you make a typo the compiler will tell you.
When I try to get the X, and Y coordinates they are always 0
Since your code is never executed the default value is returned.
This is my Game Class:
package Game;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game extends JPanel implements Runnable {
Thread thread;
private boolean running = true;
private double FPS = 1.0/60.0;
private Level level;
public Game(){
level = new LevelOne();
level.setBackground(Color.BLACK);
add(level);
}
public synchronized void start() {
thread = new Thread(this, "Game");
thread.start();
}
public synchronized void stop() {
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
#Override
public void run() {
double firstTime = 0;
double lastTime = System.nanoTime() / 1000000000.0;
double passedTime = 0;
double updateTime = 0;
double timer = System.nanoTime() / 1000000000.0;
int rendered = 0;
int updates = 0;
while (running) {
firstTime = System.nanoTime() / 1000000000.0;
passedTime = firstTime - lastTime;
lastTime = firstTime;
updateTime += passedTime;
while(updateTime > FPS){
updates++;
update();
updateTime -= FPS;
}
render();
rendered++;
if((System.nanoTime() / 1000000000) - timer > 1){
timer += 1;
System.out.println("FPS:"+rendered+" Updates:"+updates);
updates = 0;
rendered = 0;
}
}
}
private void update() {
}
private void render() {
this.repaint();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
level.repaint();
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setLocationRelativeTo(null);
frame.setSize(300, 300);
frame.setVisible(true);
frame.setTitle("Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Game game = new Game();
frame.add(game);
game.start();
}
}
My LevelOne class:
package Game;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class LevelOne extends Level {
private BufferedImage spriteSheet;
private BufferedImage[] player = new BufferedImage[4]; //0 = down, 1 = right, 2 = up, 3 = left
private int dir = 0;
private String UP = "W";
private String DOWN = "S";
private String LEFT = "A";
private String RIGHT = "D";
private int speedX = 0;
private int speedY = 0;
public LevelOne(){
try {
spriteSheet = ImageIO.read(new File("images/sprites.png"));
} catch (IOException e) {
e.printStackTrace();
}
for (int i = 0; i < 4; i++) {
player[i] = spriteSheet.getSubimage((i * 18), 0, 18, 28);
}
this.addKeyListener(new KeyListener() {
#Override
public void keyPressed(KeyEvent e) {
String key = e.getKeyText(e.getKeyCode());
if (key.equals(UP)) {
dir = 2;
speedY = -5;
} else if (key.equals(LEFT)) {
dir = 3;
speedX = -5;
} else if (key.equals(RIGHT)) {
dir = 1;
speedX = 5;
} else if (key.equals(DOWN)) {
dir = 0;
speedY = 5;
}
}
#Override
public void keyReleased(KeyEvent e) {
String key = e.getKeyText(e.getKeyCode());
if (key.equals(UP)) {
speedY = 0;
} else if (key.equals(LEFT)) {
speedX = 0;
} else if (key.equals(RIGHT)) {
speedX = 0;
} else if (key.equals(DOWN)) {
speedY = 0;
}
}
#Override
public void keyTyped(KeyEvent e) {
}
});
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(player[dir], 0, 0, 18, 28, null);
}
}
LevelOne extends Level, which is currently empty(And extends JPanel), I'm not sure if I need to add anything to Level. I just dont get what I need to do to make the player image show up...
And sorry if my code is sloppy, I'm trying to get into higher level Java, and im not sure if I am just approaching it wrong.
Thanks.
Six things...
Don't call level.repaint from within the paintComponent method of Game, this could cause an infinite loop of repaint requests which is going to screw with your frame rate. Consider calling it within your render method
paintComponent should never public, there's no reason for anybody to ever call it directly.
Use key bindings over KeyListener, they will solve focus related issues. How to Use Key Bindings
Move frame.setVisible AFTER frame.add(game);, in fact, it really should the last thing you do
Make sure your images are loading properly
Set the layout manager for Game to BorderLayout
I'm using the Java AWT class to make a wall made up of rectangles on both sides. The wall class extends rectangles, and uses the same variables to draw to the screen.
Walls Class:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
public class Walls extends Rectangle {
public Walls(int startX, int startY, int width, int height ){
//super(startX, startY, width, height);
//
//Checking with this way for the collision detection
this.x = startX;
this.y = startY;
this.width = width;
this.height = height;
//this.wallStartX = startX;
//this.wallStartY = startY;
//this.wallWidth = width;
//this.wallHeight = height;
}
public void draw(Graphics g){
g.setColor(Color.GRAY);
//g.fillRect( this.wallStartX, this.wallStartY, this.wallWidth, this.wallHeight);
//Trying with the rectangle methods
g.fillRect( this.x, this.y, this.width, this.height);
}
I used the rectangle.intersects method to check for collision prediction. It works with the left wall, but it seems to collide with the right wall all the time, even when the player square is not touching. I've used the same code for the left and right wall, and I made sure that the arguments for the wall are the same for the rectangle in the class above.
Based on Worms game from Killer Code Programming in Java
RacerPanel:
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.text.DecimalFormat;
import java.util.Random;
import java.awt.event.KeyEvent; //Should be for Key Usage
import java.awt.event.KeyListener;
public class RacingPanel extends GamePanel implements KeyListener {
///KeyListener from https://www.youtube.com/watch?v=5UaEUrbpDPE
//Q: Serial ID? http://stackoverflow.com/questions/285793/what-is-a- serialversionuid-and-why-should-i-use-it
private static final long serialVersionUID = 1825086766957972644L;
private DecimalFormat df = new DecimalFormat("0.##"); // 2 dp
private Main_Racer mrTop;
private Racer bob;
private Walls josephine;
private Walls jeremy;
private static Random randomGenerator = new Random();
boolean wallBuilder;
int leftCreateorDestroy = 100;
int rightCreateorDestroy = 100;
//private ArrayList()[] Walls kinderland;
//Q
private Font font;
private FontMetrics metrics;
//Q: definitely find out what this method is doing
public RacingPanel( Main_Racer rc, long period) {
super(period);
mrTop = rc;
this.addKeyListener(this);
}
//In case we want to integrate mice
//Q: Why void? Why protected?
protected void mousePress( int x, int y){
}
//Q: in the worm game, it calls
//fred.move(), presumably to move the worm
//wcTop.setTimeSpent(timeSpentInGame);
//Probably a counter
protected void simpleUpdate(){
}
//Q: Assuming that this should be things the
//game starts off with, like the block,
//the course. Also, font
protected void simpleInitialize(){
//T: Maybe I need to initialize the racer
bob = new Racer(PWIDTH, PHEIGHT);
josephine = new Walls(0, 35, 100, 1);
jeremy = new Walls(PWIDTH-100, 35, 100,1);
//That did it!
// so the arguments to the racer update change
//where the square was rendered
//TJ: I need to loop in such a way that will draw random variables
// set up message font
font = new Font("SansSerif", Font.BOLD, 24);
metrics = this.getFontMetrics(font);
}
protected void simpleRender(Graphics dbg) {
if(!gameOver){
//These two seem pretty straight forward
dbg.setColor(Color.blue);
dbg.setFont(font);
// report frame count & average FPS and UPS at top left
// dbg.drawString("Frame Count " + frameCount, 10, 25);
dbg.drawString("Average FPS/UPS: " + df.format(averageFPS) + ", "
+ df.format(averageUPS), 20, 25); // was (10,55)
dbg.setColor(Color.black);
// draw game elements: the obstacles and the worm
//This'll probably be the block and the
//course instead
//obs.draw(dbg);
//fred.draw(dbg);
bob.draw(dbg);
//jeremy.draw(dbg);
//See if we can draw this thing over and over
for(int i = 35; i < PHEIGHT; i++){
wallBuilder = randomGenerator.nextBoolean();
if(wallBuilder == false){
if ( leftCreateorDestroy < PWIDTH/2 - 50)
leftCreateorDestroy++;
}
else{
if(leftCreateorDestroy > 0){
leftCreateorDestroy--;
}
}
josephine = new Walls(0, i, leftCreateorDestroy, 1);
josephine.draw(dbg);
wallBuilder = randomGenerator.nextBoolean();
//This wall keeps colliding with the other wall
//Also, it isn't set to the other side of the screen due to conventions
if(wallBuilder == false){
//if ( (PWIDTH - rightCreateorDestroy) > (PWIDTH/2-150))
if ( rightCreateorDestroy > 0)
rightCreateorDestroy--;
}
else{
if((PWIDTH/2 + 50) < PWIDTH - rightCreateorDestroy){
rightCreateorDestroy++;
}
}
//PWIDTH - rightCreateorDestroy is the length of the
jeremy = new Walls((PWIDTH - rightCreateorDestroy), i, rightCreateorDestroy, 1);
//jeremy = new Walls((PWIDTH - rightCreateorDestroy), i, 100, 1);
//Let's see if it will draw negative; will not;
jeremy.draw(dbg);
//Now to put up collision detection
if(bob.intersects(josephine) ){
System.out.println("Ouch!");
gameOver = true;
}
//System.out.println(bob.);
else if( bob.intersects(jeremy) ){
Rectangle Sektor = bob.intersection(jeremy);
System.out.println(Sektor);
System.out.println(jeremy);
}
//System.out.println("Yow!");
//Problems seem to be with the right side; let's take out the right and check.
//For some reason, intersecting all of the time.
}
}
//1msecond is too long. Starts clipping;
/* try{
Thread.sleep(1000);
}catch ( InterruptedException exc ){
System.out.println("No work");
} */
}
//So this does draw over and over again
//FPS/UPS Still good
//Pretty straight forward as well. Just need
//to see when it is called
protected void gameOverMessage(Graphics g)
// center the game-over message in the panel
{
//Keeps updating after death...
String msg = "Game Over. Your Score: " + timeSpentInGame;
int x = (PWIDTH - metrics.stringWidth(msg)) / 2;
int y = (PHEIGHT - metrics.getHeight()) / 2;
g.setColor(Color.red);
g.setFont(font);
g.drawString(msg, x, y);
} // end of gameOverMessage()
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_RIGHT){
//System.out.println("Right");
//Should set x position to current position plus 1
bob.setRacerPositionX(bob.getRacerPositionX() + 1);
}
if (e.getKeyCode() == KeyEvent.VK_LEFT){
// System.out.println("Left");
bob.setRacerPositionX(bob.getRacerPositionX() - 1);
}
if (e.getKeyCode() == KeyEvent.VK_UP){
//System.out.println("Up");
bob.setRacerPositionY(bob.getRacerPositionY() - 1);
}
if (e.getKeyCode() == KeyEvent.VK_DOWN){
//System.out.println("Down");
bob.setRacerPositionY(bob.getRacerPositionY() + 1);
}
}
Game Panel Class:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.text.DecimalFormat;
import javax.swing.JPanel;
public abstract class GamePanel extends JPanel implements Runnable {
private static final long serialVersionUID = 1863596360846514344L;
private static long MAX_STATS_INTERVAL = 1000000000L;
// private static long MAX_STATS_INTERVAL = 1000L;
// record stats every 1 second (roughly)
private static int NUM_FPS = 10;
// number of FPS values stored to get an average
protected static final int PWIDTH = 500; // size of panel
protected static final int PHEIGHT = 400;
protected static final int NO_DELAYS_PER_YIELD = 16;
/*
* Number of frames with a delay of 0 ms before the animation thread yields
* to other running threads.
*/
protected static int MAX_FRAME_SKIPS = 5; // was 2;
// no. of frames that can be skipped in any one animation loop
// i.e the games state is updated but not rendered
private Thread animator; // the thread that performs the animation
protected boolean running = false; // used to stop the animation thread
protected boolean isPaused = false;
protected long period; // period between drawing in _nanosecs_
// used for gathering statistics
private long statsInterval = 0L; // in ns
private long prevStatsTime;
private long totalElapsedTime = 0L;
private long gameStartTime;
protected int timeSpentInGame = 0; // in seconds
private long frameCount = 0;
private double fpsStore[];
private long statsCount = 0;
protected double averageFPS = 0.0;
private long framesSkipped = 0L;
private long totalFramesSkipped = 0L;
private double upsStore[];
protected double averageUPS = 0.0;
private DecimalFormat df = new DecimalFormat("0.##"); // 2 dp
// used at game termination
protected boolean gameOver = false;
// off screen rendering
private Graphics dbg;
private Image dbImage = null;
public GamePanel(long period) {
this.period = period;
setBackground(Color.white);
setPreferredSize(new Dimension(PWIDTH, PHEIGHT));
setFocusable(true);
requestFocus(); // the JPanel now has focus, so receives key events
readyForTermination();
simpleInitialize();
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
mousePress(e.getX(), e.getY());
}
});
// initialise timing elements
fpsStore = new double[NUM_FPS];
upsStore = new double[NUM_FPS];
for (int i = 0; i < NUM_FPS; i++) {
fpsStore[i] = 0.0;
upsStore[i] = 0.0;
}
} // end of GamePanel()
/**
* Sets up a listener to kill the game loop if the user attempts to quit
*/
private void readyForTermination() {
addKeyListener(new KeyAdapter() {
// listen for esc, q, end, ctrl-c on the canvas to
// allow a convenient exit from the full screen configuration
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if ((keyCode == KeyEvent.VK_ESCAPE)
|| (keyCode == KeyEvent.VK_Q)
|| (keyCode == KeyEvent.VK_END)
|| ((keyCode == KeyEvent.VK_C) && e.isControlDown())) {
running = false;
}
}
});
} // end of readyForTermination()
/**
* Triggers the animation loop to start
*/
public void addNotify()
// wait for the JPanel to be added to the JFrame before starting
{
super.addNotify(); // creates the peer
startGame(); // start the thread
}
/**
* Starts the animation loop
*/
private void startGame()
// initialise and start the thread
{
if (animator == null || !running) {
animator = new Thread(this);
animator.start();
}
} // end of startGame()
// ------------- game life cycle methods ------------
// called by the JFrame's window listener methods
public void resumeGame()
// called when the JFrame is activated / deiconified
{
isPaused = false;
}
public void pauseGame()
// called when the JFrame is deactivated / iconified
{
isPaused = true;
}
public void stopGame()
// called when the JFrame is closing
{
running = false;
}
// ----------------------------------------------
public void run()
/* The frames of the animation are drawn inside the while loop. */
{
long beforeTime, afterTime, timeDiff, sleepTime;
long overSleepTime = 0L;
int noDelays = 0;
long excess = 0L;
gameStartTime = System.nanoTime();
prevStatsTime = gameStartTime;
beforeTime = gameStartTime;
running = true;
while (running) {
gameUpdate();
//T Entering Game Render
System.out.println("Entering Game Render");
gameRender();
paintScreen();
afterTime = System.nanoTime();
timeDiff = afterTime - beforeTime;
sleepTime = (period - timeDiff) - overSleepTime;
if (sleepTime > 0) { // some time left in this cycle
try {
Thread.sleep(sleepTime / 1000000L); // nano -> ms
} catch (InterruptedException ex) {
}
overSleepTime = (System.nanoTime() - afterTime) - sleepTime;
} else { // sleepTime <= 0; the frame took longer than the period
excess -= sleepTime; // store excess time value
overSleepTime = 0L;
if (++noDelays >= NO_DELAYS_PER_YIELD) {
Thread.yield(); // give another thread a chance to run
noDelays = 0;
}
}
beforeTime = System.nanoTime();
/*
* If frame animation is taking too long, update the game state
* without rendering it, to get the updates/sec nearer to the
* required FPS.
*/
int skips = 0;
while ((excess > period) && (skips < MAX_FRAME_SKIPS)) {
excess -= period;
gameUpdate(); // update state but don't render
skips++;
}
framesSkipped += skips;
storeStats();
}
printStats();
System.exit(0); // so window disappears
} // end of run()
/**
* Renders to the backbuffer
*/
private void gameRender() {
if (dbImage == null) {
dbImage = createImage(PWIDTH, PHEIGHT);
if (dbImage == null) {
System.out.println("dbImage is null");
return;
} else
dbg = dbImage.getGraphics();
//T
System.out.println("dbg set");
}
// clear the background
dbg.setColor(Color.white);
dbg.fillRect(0, 0, PWIDTH, PHEIGHT);
//T
simpleRender(dbg);
if (gameOver)
gameOverMessage(dbg);
} // end of gameRender()
/**
* Paints the back buffer
*/
private void paintScreen()
// use active rendering to put the buffered image on-screen
{
Graphics g;
try {
g = this.getGraphics();
if ((g != null) && (dbImage != null))
g.drawImage(dbImage, 0, 0, null);
g.dispose();
} catch (Exception e) {
System.out.println("Graphics context error: " + e);
}
} // end of paintScreen()
/**
* Should be update the game state
*/
private void gameUpdate() {
if (!isPaused && !gameOver)
simpleUpdate();
} // end of gameUpdate()
private void storeStats() {
frameCount++;
statsInterval += period;
if (statsInterval >= MAX_STATS_INTERVAL) { // record stats every
// MAX_STATS_INTERVAL
long timeNow = System.nanoTime();
timeSpentInGame = (int) ((timeNow - gameStartTime) / 1000000000L);
long realElapsedTime = timeNow - prevStatsTime; // time since last
// stats collection
totalElapsedTime += realElapsedTime;
totalFramesSkipped += framesSkipped;
double actualFPS = 0; // calculate the latest FPS and UPS
double actualUPS = 0;
if (totalElapsedTime > 0) {
actualFPS = (((double) frameCount / totalElapsedTime) * 1000000000L);
actualUPS = (((double) (frameCount + totalFramesSkipped) / totalElapsedTime) * 1000000000L);
}
// store the latest FPS and UPS
fpsStore[(int) statsCount % NUM_FPS] = actualFPS;
upsStore[(int) statsCount % NUM_FPS] = actualUPS;
statsCount = statsCount + 1;
double totalFPS = 0.0; // total the stored FPSs and UPSs
double totalUPS = 0.0;
for (int i = 0; i < NUM_FPS; i++) {
totalFPS += fpsStore[i];
totalUPS += upsStore[i];
}
if (statsCount < NUM_FPS) { // obtain the average FPS and UPS
averageFPS = totalFPS / statsCount;
averageUPS = totalUPS / statsCount;
} else {
averageFPS = totalFPS / NUM_FPS;
averageUPS = totalUPS / NUM_FPS;
}
framesSkipped = 0;
prevStatsTime = timeNow;
statsInterval = 0L; // reset
}
} // end of storeStats()
private void printStats() {
System.out.println("Frame Count/Loss: " + frameCount + " / "
+ totalFramesSkipped);
System.out.println("Average FPS: " + df.format(averageFPS));
System.out.println("Average UPS: " + df.format(averageUPS));
System.out.println("Time Spent: " + timeSpentInGame + " secs");
} // end of printStats()
/**
* Should implement game specific rendering
*
* #param g
*/
protected abstract void simpleRender(Graphics g);
/**
* Should display a game specific game over message
*
* #param g
*/
protected abstract void gameOverMessage(Graphics g);
protected abstract void simpleUpdate();
/**
* This just gets called when a click occurs, no default behavior
*/
protected abstract void mousePress(int x, int y);
/**
* Should be overridden to initialize the game specific components
*/
protected abstract void simpleInitialize();
} // end of GamePanel class
Racer Class:
import java.awt.*;
import java.awt.geom.*;
public class Racer extends Rectangle {
//Only method that will likely be common between
//the two areas. This presumably draws to screen
//Size of Car
private static final int carSize = 20;
//Changing racer position from array to x and y coordinates
private int pWidth, pHeight; // panel dimensions
private long startTime; // in ms
public Racer (int pW, int pH){
this.width = pW;
this.height = pH;
//Meant to start racer in the center of the screen
//But it does not
this.x = pW/2;
this.y = pW/2;
}
public void draw(Graphics g){
g.setColor(Color.blue);
g.fillRect( x, y, carSize, carSize);
}
public int getRacerPositionX() {
return x;
}
public void setRacerPositionX(int x) {
this.x = x;
}
public int getRacerPositionY() {
return y;
}
public void setRacerPositionY(int y) {
this.y = y;
}
}
Main Racer:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Main_Racer extends JFrame implements WindowListener
{
private static int DEFAULT_FPS = 80;
private RacingPanel rp;
//Let's leave now and make the panel
//Q: Just a value; why JTextField instead of int?
private JTextField jtfBox;
//Q: Just a value; why JTextField instead of int?
private JTextField jtfTime;
//Main_Racer Constructor
public Main_Racer(long period)
{
//Q: Gotta find this constructor
super("The Main Racder");
//Q:What does the argument do?
makeGUI(period);
addWindowListener( this);
pack();
setResizable(false);
setVisible(true);
}
//Q: Warrants an in depth look
private void makeGUI(long period)
{
Container c = getContentPane(); // default BorderLayout used
rp = new RacingPanel(this, period);
c.add(rp, "Center");
JPanel ctrls = new JPanel(); // a row of textfields
ctrls.setLayout( new BoxLayout(ctrls, BoxLayout.X_AXIS));
//Keeps track of time. Might still be useful
jtfTime = new JTextField("Time Spent: 0 secs");
jtfTime.setEditable(false);
ctrls.add(jtfTime);
c.add(ctrls, "South");
} // end of makeGUI()
//Pretty straing forward
public void setTimeSpent(long t)
{ jtfTime.setText("Time Spent: " + t + " secs"); }
// QQQ -----------------Going to copy the window listening methods, as probably n
//need em all
public void windowActivated(WindowEvent e)
{ rp.resumeGame(); }
public void windowDeactivated(WindowEvent e)
{ rp.pauseGame(); }
public void windowDeiconified(WindowEvent e)
{ rp.resumeGame(); }
public void windowIconified(WindowEvent e)
{ rp.pauseGame(); }
public void windowClosing(WindowEvent e)
{ rp.stopGame(); }
public void windowClosed(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
//=================================================================================
public static void main(String args[])
{
int fps = DEFAULT_FPS;
if (args.length != 0)
fps = Integer.parseInt(args[0]);
long period = (long) 1000.0/fps;
System.out.println("fps: " + fps + "; period: " + period + " ms");
new Main_Racer(period*1000000L); // ms --> nanosecs
}
} // end of WormChase class
Any reason why collision detection only works on one side?