WIP: Tilting ball maze game in 3D – Contest Entry

nice job :), and how did you do your timer? looks pretty awesome

The timer is just a little helper class. It’s not thread safe, but it’s only accessed from update()-thread.

[java]

public class StopWatch

{

private long startTime;

private long accumulatedTime = 0;

private boolean running = false;



public void start()

{

if (!this.running)

{

this.startTime = System.currentTimeMillis();

this.running = true;

}

}



public long stop()

{

if (this.running)

{

this.accumulatedTime += System.currentTimeMillis() - this.startTime;

this.running = false;

}



return this.accumulatedTime;

}



public long getAccumulatedTime()

{

if (this.running)

{

return this.accumulatedTime + System.currentTimeMillis() - this.startTime;

}

else

{

return this.accumulatedTime;

}

}



public void reset()

{

this.accumulatedTime = 0;

this.running = false;

}



public boolean isRunning()

{

return this.running;

}



@Override

public String toString()

{

long time = this.getAccumulatedTime();

long min = time / 60000;

long sec = time / 1000 % 60;

long hsec = time / 10 % 100;

return String.format("%d:%02d:%02d", min, sec, hsec);

}

}

[/java]



Updating the time label in ScreenController:

[java]

public void bind(Nifty nifty, Screen screen) {

this.nifty = nifty;

this.screen = screen;

this.timeLabel = this.screen.findElementByName(“timeLabel”).getRenderer(TextRenderer.class);

this.congratsTextLabel = this.screen.findElementByName(“congratsTextLabel”).getRenderer(TextRenderer.class);

}



void updateTime(String timeString)

{

this.timeLabel.setText(timeString);

}

[/java]

1 Like

Looks nice, maybe it could be made an AppState that can manage multiple timers based on an object hash map, so you go like timerState.start(this); timer.stop(this); etc.

Hmm, not everything is an AppState. But I must admit I might be out because my guild is going for epix loot now. :smiley:

http://www.youtube.com/watch?v=pu84VmXnyzo

@survivor said:
The timer is just a little helper class. It's not thread safe, but it's only accessed from update()-thread.
[java]
public class StopWatch
{

......

}
[/java]

ah cool thanks :) i actually meant about the awesome font, and gradient effect :P

Ah, ok, I did it with the tool Hiero 2.0. If you want an offline version, just download and put all the files mentioned in the hiero.jnlp into one directory and create a start script. For Windows it would look like this.

[java]“c:\Program Files (x86)\Java\jre6\bin\javaw.exe” -cp “.;hiero.jar;slick.jar;lwjgl.jar;natives-win32.jar” org.newdawn.slick.tools.hiero.Hiero[/java]

Make sure to use a 32 bit jre. Hiero has a bug that sometimes flips the PNG file. Just load the PNG into an image editor and “flip vertically”. Good luck!

1 Like

sweet man, thanks!

About that time stuff, have you already used the Timer class? I don’t know if it’s a better approach than your StopWatch class, I’m just curious!

The jme3 timer is more or less a frame rate counter. It can’t be paused and resumed which I need if someone enters the menu. All other timer classes Netbeans’ syntax completion offers to me are some kind of real timer with a concurrent callback or event. That’s also not what I need. I need what the name of the class says, a StopWatch.

...are some kind of real timer with a concurrent callback or event....


Yeah. Your timer really doesn't need to be synchronized with the "real time". I had to use the Timer class because in my game I had to synchronize the game with the music.

I’ve just found a very useful awesome pausable Stopwatch on the internet xD



[java]

/*

*


* Antelmann.com Java Framework by Holger Antelmann
* Copyright (c) 2005 Holger Antelmann <info@antelmann.com>
* For details, see also http://www.antelmann.com/developer/
*
*/
package org.jscience.util;

import java.io.Serializable;
import java.util.Date;

/**
* Stopwatch is a convenient implementation to bench just about anything.
* Todo: provide support for internationalization
*
* @author Holger Antelmann
*/
public class Stopwatch implements Serializable {
static final long serialVersionUID = -6958796021257440232L;

private long start;
private boolean paused;
private long halted;
private long end;

/**
* initializes a running Stopwatch starting now
*/
public Stopwatch() {
start = System.currentTimeMillis();
}

/**
* This constructor starts a Stopwatch starting at the current time
* minus the milliseconds given. I.e. a negative value would mean that
* the StopWach counts towards zero until zero is reached and then
* continues to count forward.
*/
public Stopwatch(long milliseconds) {
start = System.currentTimeMillis() - milliseconds;
}

/**
* This constructor starts a Stopwatch starting at the given point in time.
* I.e. a time in the future would mean that the StopWach counts towards
* zero until zero is reached and then continues to count forward.
*/
public Stopwatch(Date time) {
start = time.getTime();
}

/**
* This constructor generates a new Stopwatch synchronized
* with the given timer
*/
public Stopwatch(Stopwatch timer) {
synchronize(timer);
}

/**
* If the boolean parameter is true, this call is equivalent
* to the default constructor; if the parameter is false,
* the StopWach will be initialized but is halted with
* no elapsed time so far.
*/
public Stopwatch(boolean isRunning) {
this();
if (!isRunning) {
pause();
reset();
}
}

public Stopwatch(long milliseconds, boolean isRunning) {
if (isRunning) {
start = System.currentTimeMillis() - milliseconds;
} else {
end = System.currentTimeMillis();
start = end - milliseconds;
paused = true;
}
}

/**
* synchronize() will make this Stopwatch equivalent
* to the passed timer
*/
public synchronized void synchronize(Stopwatch timer) {
synchronized (timer) {
start = timer.getStartTime();
paused = timer.isPaused();
end = timer.getEnd();
halted = timer.getHalted();
}
}

/**
* returns the time this Stopwatch was constructed or when any of
* the reset() or restart() methods were called last
*/
public long getStartTime() {
return start;
}

/**
* returns the initial start time of the Stopwatch.
* This time may have been changed through synchronize() or restart().
*/
public Date getStartDate() {
return new Date(start);
}

/**
* returns true if Stopwatch is paused.
* isPaused() <==> !isRunning()
*/
public boolean isPaused() {
return (paused);
}

/**
* returns true if Stopwatch is running.
* isPaused() <==> !isRunning()
*/
public boolean isRunning() {
return (!paused);
}

/**
* pause() stops the timer and maintains the elapsed time;
* it does nothing if the timer is already paused.
*/
public synchronized void pause() {
if (!paused) {
end = System.currentTimeMillis();
paused = true;
}
}

/**
* resume() will reactivate a suspended timer (suspended through
* either stop() or pause()). The beginning time as well as the
* suspended time is maintained, so that the elapsed time remains
* accurate. If the StopWach is currently running, the function doesn't
* do anything.
*/
public synchronized void resume() {
if (paused) {
halted = halted + (System.currentTimeMillis() - end);
end = 0;
paused = false;
}
}

/**
* start() maintains the beginning time and sets
* the elapsed time to zero (through halted time);
* time will be running.
*/
public synchronized void start() {
halted = (System.currentTimeMillis() - start);
paused = false;
end = 0;
}

/**
* stop() returns the elapsed time and also performs
* a reset().
*/
public synchronized long stop() {
long t = elapsed();
pause();
reset();
return t;
}

/**
* reset() will set the start to the current time and set
* the halted time to zero. The running status (paused or not)
* of the timer is maintained.
*/
public synchronized void reset() {
start = System.currentTimeMillis();
if (paused) {
end = start;
}
halted = 0;
}

/**
* sets the given time as elapsed time; run/pause status remains
* and halted time is reset
*/
public synchronized void reset(long milliseconds) {
if (paused) {
start = System.currentTimeMillis() - milliseconds;
end = start + milliseconds;
halted = 0;
} else {
restart(milliseconds);
}
}

/**
* restart() reinitializes the timer equivalent to it
* just being construced with the default constructor (time is running).
*/
public synchronized void restart() {
start = System.currentTimeMillis();
halted = 0;
end = 0;
paused = false;
}

/**
* restart() reinitializes the timer with the passed
* milliseconds interpreted as already elapsed time.
*/
public synchronized void restart(long milliseconds) {
start = System.currentTimeMillis();
halted = -milliseconds;
end = 0;
paused = false;
}

/**
* returns the elapsed time in milliseconds
*/
public synchronized long elapsed() {
if (paused) {
return ((end - start) - halted);
} else {
return ((System.currentTimeMillis() - start) - halted);
}
}

/**
* returns a string representing the elapsed time with
* - if applicable - days, hours, minutes and seconds
* (down to the milliseconds).
*/
public String elapsedAsString() {
return timeAsString(elapsed());
}

/**
* displays the elapsed time as timeAsStringShort(elapsed())
*
* @see #timeAsStringShort(long)
*/
public String display() {
return timeAsStringShort(elapsed());
}

/**
* takes milliseconds and converts them into a String
* using all relevant time measures up to days
*/
public static String timeAsString(long milliSecs) {
String s = "";
int days = (int) (milliSecs / (1000 * 60 * 60 * 24));
int hours = (int) (milliSecs % (1000 * 60 * 60 * 24)) / 3600000;
int minutes = (int) (milliSecs % 3600000) / 60000;
double seconds = (double) (milliSecs % 60000) / 1000;
if (days != 0) {
s += days + " days, ";
}
if (hours != 0) {
s += hours + " h, ";
}
if (minutes != 0) {
s += minutes + " min, ";
}
s += seconds + " sec";
return s;
}

/**
* takes milliseconds and converts them into a short String.
* The format is <pre>h:mm:ss</pre>.
*/
public static String timeAsStringShort(long milliSecs) {
int hours = Math.abs((int) milliSecs / 3600000);
int minutes = Math.abs((int) (milliSecs % 3600000) / 60000);
int seconds = Math.abs((int) (milliSecs % 60000) / 1000);
String s = hours + ":";
s += ((minutes < 10) ? ("0" + minutes) : String.valueOf(minutes));
s += ":";
s += ((seconds < 10) ? ("0" + seconds) : String.valueOf(seconds));
if (milliSecs < 0) s = "-" + s;
return s;
}

/**
* returns the elapsed time in a convenient format
* including elapsedAsString() and tells
* whether the Stopwatch is currently running or not
*/
public String toString() {
String s = "elapsed time: ";
s += elapsedAsString();
if (paused) {
s += " (time paused)";
} else {
s += " (time running)";
}
return (s);
}

protected long getHalted() {
return halted;
}

protected long getEnd() {
return end;
}
}
[/java]

I was needing a *pausable* stopwatch like that xD!
1 Like

you should probably rather make an AppState for that using tpf as the tpf doesn’t necessarily reflect realtime.



There should be a wiki page for this considering the confusion the timer has caused in the past..
@normen said:
you should probably rather make an AppState for that using tpf as the tpf doesn't necessarily reflect realtime.


I need a *real time* stopwatch :). Because I was needing to schedule my musics, because I need to *pause* the music, and *play* it again from where it *stopped*. For example, I have a sound editor, and when the player press *key_space*, the music pauses, and when the player press space again, the music plays, but when I play it when it's paused, I have to use the method audioNode.setTimeOffset(), then I use:

[java]
//1000 because the time is in seconds
audioNode.setTimeOffset(stopwatch.elapsed() / 1000);
audioNode.play();
[/java]

Then I play / pause / stop the stopwatch accordling to the music. The TestMusicPlayer.java still doesn't have this feature, the *slider* doesn't move accordling to the music.

alright ^^

1 Like

If I’m wrong please tell me ;).

Well as long as its not correlated to in-scene animations or anything its fine.

The community demanded more bounciness.



Update:

  • even more epic bounciness
  • small input inertia
  • invisible ceiling plane to prevent too much bouncy bouncy
  • timer starts when ball hits floor
  • music by dj chixXxterminator :smiley:
  • soundfx by freesound.org

    — different sounds for ball → wall and ball → floor

    — sound volume based on physics impulse
  • sound settings menu item

    — sound can be toggled on and off

    — music and soundfx volume can be adjusted
  • credits panel with different music fading in (atm a placeholder, waiting for credits song from a mate)



    http://www.nabyrinth.com/NabyBallMaze/



    Questions:
  1. Is it possible to clear all pending physics events to start a new game? At the moment, I have to trash my bulletAppState and create a new one (incl. PhysicsSpace and all objects). This takes a quite long time. But if I only reset the position of the ball, I get old pending collision between ball and floor on next update, which is my trigger to start the timer and add the invisible ceiling.


  2. Is there already way to fade volume? I made it myself now, but would be nice to know. Also, is there a way to structure volume hierarchically? Like this:



    “master volume”

    |

    → “music volume”

    |

    → “soundfx volume”



    The hierarchical AudioNode structure seems to be only for spatial purposes.


  3. The creation of AudioNodes from “large” (3-5 MB) OGG files takes rather long (that’s why the game takes so long to start). Is that normal.



    A3: Ok, it seems when “stream = false” (default), hte AudioNode decodes the OGG into a single large buffer, probably to be able to apply effects and filters. However, setting “stream = true” results in “setLooping(true)” not working as expected. It is just looping the current small buffer and not flipping to the next buffer (double buffering). Is this behavior intended? Wiki says so. I’ve tried to make a manual workaround and re-play() the AudioNode when it has finished. But it doesn’t seem to work. I have to re-create the AudioNode. Quite ugly.