We're making a game in Android Studio and we got stuck. The resource (mana) used for specific spells should recover on time, e.g. 1 mana point per 5 minutes. We don't really get how to make it recover while the game is off. Is there a method to check current date/time and count the amount of mana replenished? Converting date and time to String and comparing it with the new date/time seems to be an "exciting" work to do, but we would bypass these mechanics if there is a way.
Thank you in advance.
The best way to do this in the background is to register a receiver in your manifest. This means the receiver will keep listening for broadcasts even if the app is off.
What you need is this particular action when registering your receiver Intent.ACTION_TIME_TICK
There is a more detailed answer about this matter here Time change listener
Another solution is to use the Calendar class in java. With it you can get the exact minutes passed from a point in the past to this moment. This way you don't have to worry about parsing dates and similar. I can't provide you specific examples because me myself have not used the Calendar class very much, but I'm sure you can find lots of stuff in the official documentation and on stackoverflow about it.
No need to work with Date objects, the simple usage of System.currentTimeMillis() should work. Here's a basic outline:
long mLastManaRefreshTime = System.currentTimeMillis();
void refreshMana()
{
long timeDelta = System.currentTimeMillis() - mLastManaRefreshTime;
mLastManaRefreshTime = System.currentTimeMillis();
float totalManaToRefresh = (float)AMOUNT_TO_REFRESH_IN_ONE_MINUTE * ((float)timeDelta / 60000f);
mMana += totalManaToRefresh;
if (mMana > MAX_MANA)
mMana = MAX_MANA;
}
This method is of course just an outline. You will need to call this once every update cycle. It will calculate how much time passed since the last time refreshMana was called, and replenish the required amount.
If you need this to work while the game is off, you can save the mLastManaRefreshTime to a SharedPreferences object and reload it when the game loads up again.
With System.currentTimeMillis() you can a current time-stamp in milliseconds.
You could save the latest time-stamp in your Preferences with every 5 min tick of the running game. For the other case, when your App comes back from a state where it does not do this (i.e. called the first time, woken up etc.).
Something like this:
int manacycles = ((int) (((System.currentTimeMillis() - oldtimestamp) / 1000) / 60) ) % 5;
would give you the number of Mana points you would have to add.
Alternately you could do the same thing with the Calendar class.
Also keep in mind players could cheat this way by simply changing their time. If your game is online you could get the time from the internet, with something like this:
try {
TimeTCPClient client = new TimeTCPClient();
try {
// Set timeout of 60 seconds
client.setDefaultTimeout(60000);
// Connecting to time server
// Other time servers can be found at : http://tf.nist.gov/tf-cgi/servers.cgi#
// Make sure that your program NEVER queries a server more frequently than once every 4 seconds
client.connect("nist.time.nosc.us");
System.out.println(client.getDate());
} finally {
client.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
Related
I try to access the accelerometer from the NDK. So far it works. But the way events are written to the eventqueue seems a little bit strange.
See the following code:
ASensorManager* AcquireASensorManagerInstance(void) {
typedef ASensorManager *(*PF_GETINSTANCEFORPACKAGE)(const char *name);
void* androidHandle = dlopen("libandroid.so", RTLD_NOW);
PF_GETINSTANCEFORPACKAGE getInstanceForPackageFunc = (PF_GETINSTANCEFORPACKAGE) dlsym(androidHandle, "ASensorManager_getInstanceForPackage");
if (getInstanceForPackageFunc) {
return getInstanceForPackageFunc(kPackageName);
}
typedef ASensorManager *(*PF_GETINSTANCE)();
PF_GETINSTANCE getInstanceFunc = (PF_GETINSTANCE) dlsym(androidHandle, "ASensorManager_getInstance");
return getInstanceFunc();
}
void init() {
sensorManager = AcquireASensorManagerInstance();
accelerometer = ASensorManager_getDefaultSensor(sensorManager, ASENSOR_TYPE_ACCELEROMETER);
looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
accelerometerEventQueue = ASensorManager_createEventQueue(sensorManager, looper, LOOPER_ID_USER, NULL, NULL);
auto status = ASensorEventQueue_enableSensor(accelerometerEventQueue,
accelerometer);
status = ASensorEventQueue_setEventRate(accelerometerEventQueue,
accelerometer,
SENSOR_REFRESH_PERIOD_US);
}
That's how I initialize everything. My SENSOR_REFRESH_PERIOD_US is 100.000 - so 10 refreshs per second. Now I have the following method to receive the events of the event queue.
vector<sensorEvent> update() {
ALooper_pollAll(0, NULL, NULL, NULL);
vector<sensorEvent> listEvents;
ASensorEvent event;
while (ASensorEventQueue_getEvents(accelerometerEventQueue, &event, 1) > 0) {
listEvents.push_back(sensorEvent{event.acceleration.x, event.acceleration.y, event.acceleration.z, (long long) event.timestamp});
}
return listEvents;
}
sensorEvent at this point is a custom struct which I use. This update method gets called via JNI from Android every 10 seconds from an IntentService (to make sure it runs even when the app itself is killed). Now I would expect to receive 100 values (10 per second * 10 seconds). In different tests I received around 130 which is also completly fine for me even it's a bit off. Then I read in the documentation of ASensorEventQueue_setEventRate that it's not forced to follow the given refresh period. So if I would get more than I wanted it would be totally fine.
But now the problem: Sometimes I receive like 13 values in 10 seconds and when I continue to call update 10 secods later I get the 130 values + the missing 117 of the run before. This happens completly random and sometimes it's not the next run but the fourth following or something like that.
I am completly fine with being off from the refresh period by having more values. But can anyone explain why it happens that there are so many values missing and they appear 10 seconds later in the next run? Or is there maybe a way to make sure I receive them in their desired run?
Your code is correct and as i see only one reason can be cause such behaviour. It is android system, for avoid drain battery, decreases frequency of accelerometer stream of events in some time after app go to background or device fall asleep.
You need to revise all axelerometer related logic and optimize according
Doze and App Standby
Also you can try to work with axelerometer in foreground service.
I'm using Action Script3 Flash pro cc. I have tried to write codes to my game so that it replenishes the player's lives over time. But I have had no success. For example, in Candy Crush, it's called 'lives'. You can have maximum of 5 lives. And once you start a level, it takes off 1 life from the 5 lives. And then the timer next to the lives label starts counting down from 20 minutes to 0. once it reaches 0, it gives you 1 life back so that you can start a level again. I tried to do this like this: Frame EventListener to count down from 20 minutes to 0 whenever the number of lives is under 5, and once it reaches 5, stop counting down numbers. It sounds simple and it is simple. But what if the player quits the game while the timer is counting down?? Let's say the player left the game at 3:10pm with 3 lives and 10 min left for a life replenish. And the player starts the game again at 3:30pm. Then the player would have 4 lives and 10 min left for a life replenish. I tried to accomplish this by using date class. But the remaining time gets messed up whenever I quit the game and restart the game.
Basically you need to keep track of the last time you gave a free life, then just compare the current time with the last time to see how many lives to give.
You'll need to store the lastFreeLifeTS timestamp somewhere, so you have 2 options:
Locally - Either a SharedObject or an XML file, depending on if you're on, say, the web, or using AIR for a mobile app. As #DodgerThud pointed out, this is easy enough to cheat, either by finding the file and modifying the value, or changing the system time. Candy Crush does this though, so you have to decide if this is an important enough issue for you
Online - Ideally, you would store this timestamp on a server, in which case, it's best to move this logic there as well, so it's the server that decides when you get a free live (either by telling the client, or the client can poll the server when it thinks it should have one)
Getting your timestamp is as simple as:
this.lastFreeLifeTS = ( new Date() ).time;
Call this when you either a) give a new free life, or b) use a life when you were at maximum (as then you want your next free life to come at the maximum time from that point).
Then you need to deal with 2 issues:
When the player should get a life in the game
When the player comes back after X amount of time
The first one is easy - just have a timer counting down (NOTE: you don't need to go once every frame - that's higher fidelity than's needed. Once every second or so is all you need). When your time is up, give the life:
var currTime:Number = ( new Date() ).time;
var diff:Number = ( currTime - this.lastFreeLifeTime );
var lifeTime:Number = 1000 * 60 * 20; // give a life every 20 minutes
if( diff > lifeTime )
{
giveFreeLife();
this.lastFreeLifeTime += lifeTime;
}
Seeing as you know when you're going to give a new life, you could almost replace it with a getTimer(), which would mean you don't need to keep creating new Date objects all the time.
// starting off
var lifeTime:Number = 1000 * 60 * 20; // 20 minutes
var msUntilNextLife:Number = ( ( new Date() ).time - this.lastFreeLifeTime ) + lifeTime;
this.nextFreeLifeTime = getTimer() + msUntilNextLife;
...
// check if we need to give a new life
if( getTimer() > this.nextFreeLifeTime )
{
giveFreeLife();
this.lastFreeLifeTime += lifeTime;
this.nextFreeLifeTime += lifeTime;
}
For the second one, you just need to check the timestamp when you start your game, and give any free lives necessary, then you're back to the first type of logic, above.
// load our SharedObject/XML and set our lastFreeLifeTime
...
// check how much time has passed and give any lives necessary
var currTime:Number = ( new Date() ).time;
var diff:Number = currTime - this.lastFreeLifeTime;
var lifeTime:Number = 1000 * 60 * 20; // 20 minutes
var numLives:int = int( diff / lifeTime );
if( numLives > 5 )
numLives = 5; // only give a maximum number of lives
if( numLives > 0 )
{
giveFreeLives( numLives );
// update our lastFreeLifeTime only if we didn't give the max number of lives
// (as otherwise we don't need it)
// NOTE: because we're adding (lifeTime * numLives), it handles the time between
// lives nicely. E.g. If we get a life every 20m and we come back after 30m, then
// our lastFreeLifeTime will be (20 * i), meaning a 10m difference between the
// current time, meaning we only have to wait 10m to get our next one
if( numLives < 5 )
this.lastFreeLifeTime += lifeTime * numLives;
}
Update - getting time from the internet
The easiest way to get timestamp from the net it to control the source yourself; i.e. you own the server/webpage that you're asking the timestamp from.
A simple solution is to host the following php code in a file somewhere:
<?php
echo round( microtime( true ) * 1000 );
?>
All this does is print the current timestamp to the page. You should be able then read the time using a URLLoader:
// NOTE: add all the other listeners, like IOErrorEvent, and SecurityErrorEvent
var urlLoader:URLLoader = new URLLoader( new URLRequest( "http://myserver.com/timestamp.php" ) );
urlLoader.addEventListener( Event.COMPLETE, onGetTime );
function onGetTime( e:Event ):void
{
// remove our event listener so we can clean up
var urlLoader:URLLoader = ( e.target as URLLoader );
urlLoader.removeEventListener( Event.COMPLETE, onGetTime );
// the data is the timestamp returned by php, as a string, so convert it
var timestamp:Number = Number( urlLoader.data );
if ( isNaN( timestamp ) )
{
trace( "Couldn't get the timestamp from the server! Returned details: " + urlLoader.data );
timestamp = ( new Date() ).time; // fallback to client
}
// do something with the time
}
Problems with this approach
It's slow; you're making a web request every time you need to know the current time. The time you get won't be the actual current time, due to latency. For most cases, this isn't a problem, though
You need internet connection; if the user's turned theirs off, this won't work
You may run into security problems and need to host a crossdomain.xml (maybe)
It's a lot of hassle; it's a lot more awkward than a simple (new Date()).time; you now need to host this file somewhere; if your game is successful, your server can get slammed pretty quickly depending on how many players you have and how many times you're calling this
It's up to you to decide whether or not it's worth it. If your game already has a server component, then this generally isn't a problem. I'd advise you to get it working just on the client first, and come back to it if necessary (i.e. you have a ton of players cheating)
I need stopWatch and I used http://www.goldb.org/stopwatchjava.html
It did not work well so I tried write out the value every 1000ms:
stopWatch.start();
HandlerScrollBar.postDelayed(TtScroll, 1000);
private Runnable TtScroll = new Runnable() {
public void run() {
long time = stopWatch.getElapsedTime();
HandlerScrollBar.postDelayed(TtScroll,(long) 1000);
Log.d(TAG, Long.toString(time));
}
};
I can see value of time every second in CatLog and this is result:
Real time is max +5ms but in Column it is at least +3 seconds! How is it possible? It is the same with
new Date().getTime().
Is there some StopWatch class which will pass this test as expected?
Thank you.
If you are measuring elapsed time, and you want it to be correct, you must use System.nanoTime(). You cannot use System.currentTimeMillis(), unless you don't mind your result being wrong.
The purpose of nanoTime is to measure elapsed time, and the purpose of currentTimeMillis is to measure wall-clock time. You can't use the one for the other purpose. The reason is that no computer's clock is perfect; it always drifts and occasionally needs to be corrected.
Since nanoTime's purpose is to measure elapsed time, it is unaffected by any of these small corrections.I would suggest to pick the nanoTime() as it has better accuracy in those microcalculations.
for extremely precise measurements of elapsed time. From its javadoc:
long startTime = System.nanoTime();
// ... the code being measured ...
long estimatedTime = System.nanoTime() - startTime;
Seems impossible. I've never had System.currentTimeMillis() act that way. Also, you're logging out as Log.d() but the logcat you show indicates a Log.e(). You sure that's the right logcat?
I am writing a code for an app that will "beep" when the current speed is more than the user set warning speed limit. The code shown below is written inside onLocationChanged(), but for some logical reason, it beeps only once and then stops, which tells me that it goes through the loop once and after which the pastTime and curTime loses track and the if condition is not logically true after and hence skipping the loop. The reason I wanted a delay of 5 seconds is to have enough time delay between the beeps and not have them overlap. I intialized pasTime with 0 at the very beginning of the activity. Any suggestion on a fix for this is appreciated. Also curTime = c.getTimeInMillis() each time there is a location change in the location listener.
if (Activity2.mySpeedmph > mySpeed_max & curTime > pastTime+5000)
{
player = MediaPlayer.create(Activity2.this, R.raw.beep);
player.setLooping(false);
player.setVolume(100,100);
player.start();
pastTime = curTime;
}
This is a very specific problem and I couldn't find anything related to this. I know its a very simple issue for an expert.
Not meaning to be condescending here (this answer sounds like your classic IT helpdesk "Have you tried turning it off and on again?"), just covering all bases.
Are you updating curTime? What you're describing is what you'd see if pastTime was originally set far in the past and curTime is set once outside the loop and not updated.
Failing that, you probably need to take some samples of the variables involved in that if statement (before the if) to see what values they're set to.
Or, force one of the conditions to be true beforehand (such as with Activity.mySpeedmph = mySpeed_max + 1; so as to check the operation of the other condition.
I'm reading timestamp values from SensorEvent data but I can't work out the reference time for these values. Android documentation just says "The time in nanosecond at which the event happened" As an example:
My current Android device date, October 14th 2011 23:29:56.421 (GMT+2)
System.currentTimeMillis * 1000000 (nanosec) = 1318627796431000000 (that's ok)
sensorevent.timestamp (nanosec) = 67578436328000 = 19 hours 46 min ????
May you help me?
thanks
It appears that what you are dealing with is the number of nanoseconds since the operating system started, also known as "uptime".
Further info on the issue: http://code.google.com/p/android/issues/detail?id=7981
I should add that the linked question SensorEvent.timestamp to absolute (utc) timestamp? deals with the same issue and is where I found the answer.
I know that it's a very old question, but, I'm also struggling for converting SensorEvent.timestamp to a human readable time. So I'm writing here what I've understood so far and how I'm converting it in order to get better solutions from you guys. Any comments will be welcomed.
As I understood, SensorEvent.timestamp is an elapsed time since the device's boot-up. So I have to know the uptime of the device. So if there is an API returning device's boot-up, it will be very easy, but, I haven't found it.
So I'm using SystemClock.elapsedRealtime() and System.currentTimeMillis() to 'estimate' a device's uptime. This is my code.
private long mUptimeMillis; // member variable of the activity or service
...
atComponentsStartUp...() {
...
/* Call elapsedRealtime() and currentTimeMillis() in a row
in order to minimize the time gap */
long elapsedRealtime = SystemClock.elapsedRealtime();
long currentTimeMillis = System.currentTimeMillis();
/* Get an uptime. It assume that elapsedRealtime() and
currentTimeMillis() are called at the exact same time.
Actually they don't, but, ignore the gap
because it is not a significant value.
(On my device, it's less than 1 ms) */
mUptimeMillis = (currentTimeMillis - elapsedRealtime);
....
}
...
public void onSensorChanged(SensorEvent event) {
...
eventTimeMillis = ((event.timestamp / 1000000) + mUptimeMillis);
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(eventTimeMillis);
...
}
I think this works for Apps that a millisecond time error is okey. Please, leave your ideas.