Basically what I just do to create a timer is float timer += 1 * deltaTime and it will add 1 every second, but I'm having a problem on using it.
if(timer < 2){
//do something
}
I want the if statement to stop running the code when timer is at 2 seconds, buy since I cannot do if(timer != 2f) because it will not even detect 2 seconds because it's too fast. This means I have to put a condition timer < 2f, which is not accurate and always gives me inacurate results.
Instead of using a hand made timer why not use a Task and a boolean?
Pseudo code below;
boolean stop = false;
Timer.schedule(new Task() {
#Override
public void run() {
stop = true;
}
}, 2f);
Then in your render method;
render() {
if (!stop) {
// Freefall
}
}
If I understand your question correctly, you want your code to process for X seconds and no more (where X can be a floating point value such as 12.556 seconds).
I'm going to propose a single-threaded alternative where you have a custom Timer class that manages your freefall logic as shown below. It watches the progress and if it sees something greater than the duration, it "lies" to the internal logic so that your logic is only executed for the amount of time specified (within a configurable margin of error since we're playing with floats).
The example below demonstrates this sort of thing without using libGDX (just in case folks using other libraries are interested), but it would be trivial to swap out my simulated getDeltaTime() method with Gdx.graphics.getDeltaTime().
package tech.otter.timing;
/**
* Created by john on 11/20/16.
*/
public class TimingExample {
public static void main(String... args) {
boolean complete = false;
Timer t = new Timer(15f) {
// You could implement your logic here.
#Override
void action(float delta) {
System.out.println(progress);
}
};
while(!complete) {
complete = t.update(getDeltaTime());
}
assert t.progress < t.duration;
assert t.progress + t.errorMargin > t.duration;
}
/**
* Simulates processing time by returning 0-100ms.
* #return The number of milliseconds that have allegedly elapsed since the last call.
*/
public static float getDeltaTime() {
return (float)(Math.random() / 10);
}
abstract static class Timer {
private float duration;
protected float progress;
private float errorMargin;
public Timer(float duration) {
this(duration, 0.0001f);
}
public Timer(float duration, float errorMargin) {
this.duration = duration;
this.errorMargin = errorMargin;
this.progress = 0f;
}
/**
* Update the timer based on how long since the last call.
* #param delta The amount of time since the last call.
* #return Whether the timer's progressed has met the duration.
*/
public boolean update(float delta) {
// This if-statement "caps" the delta so that we will never exceed the duration.
if(progress + delta > duration) {
delta = duration - progress;
}
progress += delta;
action(delta);
// Return "true" if the progress is equal to the duration (+/- a small margin just in case since we use floats).
return progress + errorMargin > duration && progress - errorMargin < duration;
}
/**
* Override this method with your game logic.
* You should not call it directly.
* #param delta The amount of time that has elapsed since the timer was last updated.
*/
abstract void action(float delta);
}
}
Related
Background
Many similar questions seem to have been asked on SO before (most notably android google maps not loading the map when using GoogleMap.AnimateCamera() and How can I smoothly pan a GoogleMap in Android?), but none of the answers or comments posted throughout those threads have given me a firm idea of how to do this.
I initially thought that it would be as simple as just calling animateCamera(CameraUpdateFactory.newLatLng(), duration, callback) but like the OP of the first link above, all I get is a gray or very blurry map until the animation completes, even if I slow it down to tens of seconds long!
I've managed to find and implement this helper class that does a nice job of allowing the tiles to render along the way, but even with a delay of 0, there is a noticeable lag between each animation.
Code
OK, time for some code. Here's the (slightly-modified) helper class:
package com.coopmeisterfresh.googlemaps.NativeModules;
import android.os.Handler;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.GoogleMap;
import java.util.ArrayList;
import java.util.List;
public class CameraUpdateAnimator implements GoogleMap.OnCameraIdleListener {
private final GoogleMap mMap;
private final GoogleMap.OnCameraIdleListener mOnCameraIdleListener;
private final List<Animation> cameraUpdates = new ArrayList<>();
public CameraUpdateAnimator(GoogleMap map, GoogleMap.
OnCameraIdleListener onCameraIdleListener) {
mMap = map;
mOnCameraIdleListener = onCameraIdleListener;
}
public void add(CameraUpdate cameraUpdate, boolean animate, long delay) {
if (cameraUpdate != null) {
cameraUpdates.add(new Animation(cameraUpdate, animate, delay));
}
}
public void clear() {
cameraUpdates.clear();
}
public void execute() {
mMap.setOnCameraIdleListener(this);
executeNext();
}
private void executeNext() {
if (cameraUpdates.isEmpty()) {
mOnCameraIdleListener.onCameraIdle();
} else {
final Animation animation = cameraUpdates.remove(0);
new Handler().postDelayed(() -> {
if (animation.mAnimate) {
mMap.animateCamera(animation.mCameraUpdate);
} else {
mMap.moveCamera(animation.mCameraUpdate);
}
}, animation.mDelay);
}
}
#Override
public void onCameraIdle() {
executeNext();
}
private static class Animation {
private final CameraUpdate mCameraUpdate;
private final boolean mAnimate;
private final long mDelay;
public Animation(CameraUpdate cameraUpdate, boolean animate, long delay) {
mCameraUpdate = cameraUpdate;
mAnimate = animate;
mDelay = delay;
}
}
}
And my code to implement it:
// This is actually a React Native Component class, but I doubt that should matter...?
public class NativeGoogleMap extends SimpleViewManager<MapView> implements
OnMapReadyCallback, OnRequestPermissionsResultCallback {
// ...Other unrelated methods removed for brevity
private void animateCameraToPosition(LatLng targetLatLng, float targetZoom) {
// googleMap is my GoogleMap instance variable; it
// gets properly initialised in another class method
CameraPosition currPosition = googleMap.getCameraPosition();
LatLng currLatLng = currPosition.target;
float currZoom = currPosition.zoom;
double latDelta = targetLatLng.latitude - currLatLng.latitude;
double lngDelta = targetLatLng.longitude - currLatLng.longitude;
double latInc = latDelta / 5;
double lngInc = lngDelta / 5;
float zoomInc = 0;
float minZoom = googleMap.getMinZoomLevel();
float maxZoom = googleMap.getMaxZoomLevel();
if (lngInc > 15 && currZoom > minZoom) {
zoomInc = (minZoom - currZoom) / 5;
}
CameraUpdateAnimator animator = new CameraUpdateAnimator(googleMap,
() -> googleMap.animateCamera(CameraUpdateFactory.zoomTo(
targetZoom), 5000, null));
for (double nextLat = currLatLng.latitude, nextLng = currLatLng.
longitude, nextZoom = currZoom; Math.abs(nextLng) < Math.abs(
targetLatLng.longitude);) {
nextLat += latInc;
nextLng += lngInc;
nextZoom += zoomInc;
animator.add(CameraUpdateFactory.newLatLngZoom(new
LatLng(nextLat, nextLng), (float)nextZoom), true);
}
animator.execute();
}
}
Question
Is there a better way to accomplish this seemingly-simple task? I'm thinking that perhaps I need to move my animations to a worker thread or something; would that help?
Thanks for reading (I know it was an effort :P)!
Update 30/09/2021
I've updated the code above in line with Andy's suggestions in the comments and although it works (albeit with the same lag and rendering issues), the final algorithm will need to be a bit more complex since I want to zoom out to the longitudinal delta's half-way point, then back in as the journey continues.
Doing all these calculations at once, as well as smoothly rendering all the necessary tiles simultaneously, seems to be way too much for the cheap mobile phone that I'm testing on. Or is this a limitation of the API itself? In any case, how can I get all of this working smoothly, without any lag whatsoever between queued animations?
Here's my attempt using your utility frame player.
A few notes:
The zoom value is interpolated based on the total steps (set at 500 here) and given the start and stop values.
A Google Maps utility is used to compute the next lat lng based on a fractional distance: SphericalUtil.interpolate.
The fractional distance should not be a linear function to reduce the introduction of new tiles. In other words, at higher zooms (closer in) the camera moves in shorter distances and the amount of camera movement increases exponentially (center-to-center) while zooming out. This requires a bit more explanation...
As you can see the traversal is split into two - reversing the exponential function of the distance movement.
The "max" zoom (bad name) which is the furthest out can be a function of the total distance - computed to encompass the whole path at the midpoint. For now it's hard coded to 4 for this case.
Note the maps animate function cannot be used as it introduces its own bouncing ball effect on each step which is undesirable. So given a fair number of steps the move function can be used.
This method attempts to minimize tile loading per step but ultimately the TileLoader is the limiting factor for viewing which cannot monitored (easily).
animateCameraToPosition
// flag to control the animate callback (at completion).
boolean done = false;
private void animateCameraToPosition(LatLng targetLatLng, float targetZoom) {
CameraPosition currPosition = gMap.getCameraPosition();
LatLng currLatLng = currPosition.target;
//meters_per_pixel = 156543.03392 * Math.cos(latLng.lat() * Math.PI / 180) / Math.pow(2, zoom)
int maxSteps = 500;
// number of steps between start and midpoint and midpoint and end
int stepsMid = maxSteps / 2;
// current zoom
float initz = currPosition.zoom;
//TODO maximum zoom (can be computed from overall distance) such that entire path
// is visible at midpoint.
float maxz = 4.0f;
float finalz = targetZoom;
CameraUpdateAnimator animator = new CameraUpdateAnimator(gMap, () -> {
if (!done) {
gMap.animateCamera(CameraUpdateFactory.
zoomTo(targetZoom), 5000, null);
}
done = true;
});
// loop from start to midpoint
for (int i = 0; i < stepsMid; i++) {
// compute interpolated zoom (current --> max) (linear)
float z = initz - ((initz - maxz) / stepsMid) * i;
// Compute fractional distance using an exponential function such that for the first
// half the fraction delta advances slowly and accelerates toward midpoint.
double ff = (i * (Math.pow(2,maxz) / Math.pow(2,z))) / maxSteps;
LatLng nextLatLng =
SphericalUtil.interpolate(currLatLng, targetLatLng, ff);
animator.add(CameraUpdateFactory.newLatLngZoom(
nextLatLng, z), false, 0);
}
// loop from midpoint to final
for (int i = 0; i < stepsMid; i++) {
// compute interpolated zoom (current --> max) (linear)
float z = maxz + ((finalz - maxz) / stepsMid) * i;
double ff = (maxSteps - ((i+stepsMid) * ( (Math.pow(2,maxz) / Math.pow(2,z)) ))) / (double)maxSteps;
LatLng nextLatLng =
SphericalUtil.interpolate(currLatLng, targetLatLng, ff);
animator.add(CameraUpdateFactory.newLatLngZoom(
nextLatLng, z), false, 0);
}
animator.add(CameraUpdateFactory.newLatLngZoom(
targetLatLng, targetZoom), true, 0);
//
animator.execute();
}
Test Code
I tested with these two points (and code) from Statue Of Liberty to a point on the west coast:
gMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(40.68924, -74.04454), 13.0f));
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
animateCameraToPosition(new LatLng(33.899832, -118.020450), 13.0f);
}
}, 5000);
CameraUpdateAnimator Mods
I modified the camera update animator slightly:
public void execute() {
mMap.setOnCameraIdleListener(this);
executeNext();
}
private void executeNext() {
if (cameraUpdates.isEmpty()) {
mMap.setOnCameraIdleListener(mOnCameraIdleListener);
mOnCameraIdleListener.onCameraIdle();
} else {
final Animation animation = cameraUpdates.remove(0);
// This optimization is likely unnecessary since I think the
// postDelayed does the same on a delay of 0 - execute immediately.
if (animation.mDelay > 0) {
new Handler().postDelayed(() -> {
if (animation.mAnimate) {
mMap.animateCamera(animation.mCameraUpdate);
} else {
mMap.moveCamera(animation.mCameraUpdate);
}
}, animation.mDelay);
} else {
if (animation.mAnimate) {
mMap.animateCamera(animation.mCameraUpdate);
} else {
mMap.moveCamera(animation.mCameraUpdate);
}
}
}
}
Before Sample
Using
// assume initial (40.68924, -74.04454) z=13.0f
gMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(33.899832,-118.020450), 13.0f), 30000, null);
After Samples
These are recorded from an emulator. I also sideloaded onto my phone (Samsumg SM-G960U) with similar results (using 1000 steps 0 delay).
So I don't think this meets your requirements entirely: there are some "ambiguous tiles" as they are brought in from the west.
Statue of Liberty - to - somewhere near San Diego
500 Steps 0 delay
100 Steps 0 delay
50 Steps 100MS delay
Diagnostics
It is in some ways useful to have insight into what Maps is doing with tiles. Insight can be provided by installing a simple UrlTileProvider and log the requests. This implementation fetches the google tiles though they are lower resolution that is normally seen.
To do this the following is required:
// Turn off this base map and install diagnostic tile provider
gMap.setMapType(GoogleMap.MAP_TYPE_NONE);
gMap.addTileOverlay(new TileOverlayOptions().tileProvider(new MyTileProvider(256,256)).fadeIn(true));
And define the diagnostic file provider
public class MyTileProvider extends UrlTileProvider {
public MyTileProvider(int i, int i1) {
super(i, i1);
}
#Override
public URL getTileUrl(int x, int y, int zoom) {
Log.i("tiles","x="+x+" y="+y+" zoom="+zoom);
try {
return new URL("http://mt1.google.com/vt/lyrs=m&x="+x+"&y="+y+"&z="+zoom);
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}
You'll notice right away that tile layers are always defined in integral units (int). The fractional zooms which are supplied in the zoom (e.g. LatLngZoom work strictly with the in-memory images - good to know.'
Here's a sample for completeness:
// initial zoom
x=2411 y=3080 zoom=13
x=2410 y=3080 zoom=13
x=2411 y=3081 zoom=13
x=2410 y=3081 zoom=13
x=2411 y=3079 zoom=13
x=2410 y=3079 zoom=13
And at max:
x=9 y=12 zoom=5
x=8 y=12 zoom=5
x=9 y=11 zoom=5
x=8 y=11 zoom=5
x=8 y=13 zoom=5
x=9 y=13 zoom=5
x=7 y=12 zoom=5
x=7 y=11 zoom=5
x=7 y=13 zoom=5
x=8 y=10 zoom=5
x=9 y=10 zoom=5
x=7 y=10 zoom=5
Here's a chart of the zooms (y-axis) at each invocation of tiler (x-axis). Each zoom layer are roughly the same count which imo is what is desired. The full-out zoom appears twice as long because that's the midpoint repeating. There are a few anomalies though which require explaining (e.g. at around 110).
This is a chart of "zoom" as logged by the tile provider. So each x-axis point would represent a single tile fetch.
I am currently developing a 2D game using Swing components.
Each time I run my game it stutters randomly at some points. This is my game loop code:
public class FixedTSGameLoop implements Runnable
{
private MapPanel _gamePanel;
public FixedTSGameLoop(MapPanel panel)
{
this._gamePanel = panel;
}
#Override
public void run()
{
long lastTime = System.nanoTime(), now;
double amountOfTicks = 60.0;
double amountOfRenders = 120.0;
double nsTick = 1000000000 / amountOfTicks;
double nsRender = 1000000000 / amountOfRenders;
double deltaTick = 0;
double deltaRender = 0;
while (this._gamePanel.isRunning())
{
now = System.nanoTime();
deltaTick += (now - lastTime) / nsTick;
deltaRender += (now - lastTime) / nsRender;
lastTime = now;
while (deltaTick >= 1)
{
tick();
deltaTick--;
}
while (deltaRender >= 1)
{
render();
deltaRender--;
}
}
}
private void tick()
{
/**
* Logic goes here:
*/
this._gamePanel.setLogic();
}
private void render()
{
/**
* Rendering the map panel
*/
this._gamePanel.repaint();
}
}
I have tried multiple times to omit certain code parts, thinking that they cause lag, but I have found nothing that caused it particularly, so I think the problem lies within my game loop mechanism.
Thank you for your help!
Your game loop must contain a "Thread.sleep" on order to sleep the amount of time needed to respect your target FPS.
The main loop is supposed to contain 1 tick() and 1 render().
Your current implementation is flooding the paint manager, slowdowns will appear when the underlying buffers will be full and when the garbage collector will do its job.
While it's good that you've split your render and logic in two different methods, the problem lies in that they exist in the same thread.
What needs to happen to reduce the lag is to have them in separate threads. The render thread would request a snapshot of the current state from the logic thread (to prevent concurrent modification) and render that snapshot.
Right now, if one render takes too long, or if one logic step takes too long, the other check is going to have to wait for it to finish before it can begin working.
I have this update method shown below:
#Override
public void update(Input input, int delta) {
/* if UP, move player 5 tiles upwards */
if (input.isKeyPressed(Input.KEY_UP) {
y -= 5;
setY(y);
}
/* if DOWN, move player 5 tiles downwards */
if (input.isKeyPressed(Input.KEY_DOWN) {
y += 5;
setY(y);
}
/* if LEFT, move player 5 tiles to the left */
if (input.isKeyPressed(Input.KEY_LEFT) {
x -= 5;
setX(x);
}
/* if RIGHT, move player 5 tiles to the right */
if (input.isKeyPressed(Input.KEY_RIGHT) {
x += 5;
setX(x);
}
}
My update loop from my World class:
public void update(Input input, int delta)
throws SlickException {
// Update every sprite eg. Player, Blocks etc.
for (Sprite sprite : list) {
sprite.update(input, delta);
}
}
Where setX() and setY() are just setters from my class, which handle how many pixels the player should move in terms of tiles. Where each tile is 32 pixels.
So far this moves my player from one location to another location 5 tiles down, up, left or right. I was wondering if their was a way to make the player move one tile every 0.25 seconds to its destination? As in, every 0.25 seconds, the player would move 32 pixels in the left, right, down or up direction. I want this to be added so it looks like the player is sliding across tiles instead of teleporting straight to its location.
How could I use a timer to achieve this? Could I use delta to do this? Any sort of help would be appreciated.
See this answer:
Java slick2d moving an object every x seconds
You should definitly use delta for this since you want framerate independent motion in your game.
Keep a variable outside of the update loop that contains a timestamp.
int lastTimestamp;
Now, in your loop, make a conditional that checks if 250 milliseconds has passed since the time in lastTimetamp.
// Use System.nanoTime() / 1000000L to get current time
if(currentTime >= (lastTimestamp + 250)) {
movePlayer();
lastTimestamp = currentTime;
}
Now this condition should pass every 0.25 seconds.
I'd add a Runnable class MoveAnimation that you give to a
ScheduledExecutorService.
class MoveAnimation { // the full player movement
public boolean animationFinished();
public void animate() {
if (animationFinished()) {
throw new Exception(); // there is probably a cleaner way to remove this from the executor
}
}
}
class AnimationControl {
private final ScheduledExecutorService animationSchedule = Executors.newScheduledThreadPool(1);
public void addMovement(MoveAnimation animation) {
animationSchedule.scheduleAtFixedRate(animation::animate, 0, 250, TimeUnit.MILLISECONDS);
}
}
// in the event listener
if (input.keyIsPressed(Input.KEY_LEFT)) {
animationControl.addMovement(new MoveLeftAnimation(from, to));
}
I have recently started on developing my own game engines for fun. I implemented a method to load Blender .OBJ models from files, and I am successfully rendering them. However, when doing a stress-test I ran into an unusual predicament with my Delta-time based fps system. While running at 1500FPS, it takes me 4 seconds to look from one end of a wall of models to another. If I cap the FPS at 120FPS, however, it only takes my 0.84 seconds to look from one end of the wall to another. As I explored further it would seem that, in fact, the game-movement speed decreases as FPS increases.
Here is my Timer class:
class Timer
{
long lastFrame;
int fps;
long lastFPS;
int delta;
int fpsToReturn;
public Timer()
{
lastFPS = getTime();
}
public long getTime()
{
return (Sys.getTime() * 1000) / Sys.getTimerResolution();
}
public int getDelta()
{
long time = getTime();
delta = (int) (time - lastFrame);
lastFrame = time;
return delta;
}
public void updateFPS()
{
if (getTime() - lastFPS > 1000)
{
fpsToReturn = fps;
fps = 0;
lastFPS += 1000;
}
fps++;
}
public int getFPS()
{
return fpsToReturn;
}
}
And of course movement is just something along the lines of:
camera.rotX += (0.5 * timer.getDelta());
Does anyone have any ideas? Is this how delta-time is supposed to work? When running at 16FPS it returns around 65, at 120FPS Delta is returning around 8-9, while uncapped FPS it always returns 0 or 1 if that makes a difference or helps you spot if something is wrong with the uncapped FPS. I really appreciate any help, thank you guys.
Solved my own question, and glad I learned in the process.
My issue, which I later discovered when I was implementing angular movement, was that I was using the method getDelta() and getFPS() every time i needed it more than once-per-frame, which was throwing off the delta variable. I solved the issue by using a static variable, one for FPS and one for Delta, and updating each variable at the end of each frame.
Pseudo Code:
public static double globalDelta;
while(nextFrame) //Loops once per frame
{
updateGame();
globalDelta = calculateDelta();
}
I' m currently writing a simple 2D game in java from scratch (for learning purposes)
I want to control the rate at which a player can shoot. The method done there works, but it could be improved. The method is getting called if the user presses/holds the left mouse button. It works when the user is holding the button pressed, but when he/she releases the mouse button, waits (more then the rateOfFire time) and tries to shoot it may or may not work, because the roftC value isn' t getting updated when the player doesn' t shoot.
I tried then to put it into my update() method (which gets called 60 times a second). The problem still exists. I really have no idea how to solve this. Here is my code:
/**
* Used to control the rate of fire
*/
private int roftC = 0;
/**
* Shoot a Projectile
*/
protected void shoot(int x, int y, double dir) {
Projectile p = new Bomb(x, y, dir);
if (roftC % p.getRateOfFire() == 0) {
level.addProjectile(p);
}
if (roftC > 6000) {
roftC = 0;
}
roftC++; // Whether it is here or down there doesn' t make a diffrence
}
/**
*
*/
#Override
public void update() {
// roftC++;
}
One idea would be to introduce a minimum delay between shots. Something like this:
static final long MINIMUM_DELAY = 1000/30; // So we can have 30 shots per second
long lastShotTimestamp;
protected void shoot(int x, int y, double dir) {
long now = System.currentTimeMillis();
if (now - lastShotTimestamp > MINIMUM_DELAY) {
level.addProjectile(new Bomb(x, y, dir));
lastShotTimestamp = now;
}
}
Such an approach is actually close to the physics - a gun needs some time to reload between consecutive shots.