Game loop on redux-saga - react-native

I'm playing with redux-saga to create a version of the snake game in react native, and I'm not sure how to go about the game loop implementation. So far I have a working version, but it feels clunky.. the snake moves at different speeds and the game is not very smooth.
Here is what I've got so far:
function *tickSaga() {
while (true) {
yield call(updateGameSaga)
const game = yield select(getGame)
if (game.crashed)
break
yield delay(1000)
}
}
The updateGameSaga basically gets the state from the store, process some logic (finds out if the snake next move will be a crash, move to an empty tile or move to a food tile and grow) and dispatches the corresponding action for it.
I'm slightly aware of things like requestAnimationFrame which I know I should be using, but I'm not sure how to make it work within the saga (maybe I don't need too do it in the saga, hence this question).
Any ideas on how to improve the smoothness of the game loop would be welcome.
UPDATE
I've included redux-saga-ticker, which internally uses setInterval (code here) to periodically send updates to the channel.
function *tickSaga() {
const channel = Ticker(1000); // game tick every 1000ms
while (true) {
yield take(channel);
yield call(updateGameSaga);
const game = yield select(getGame)
if (game.crashed)
break
}
}
It works better now and I like the channel aproach, but I still feel requestAnimationFrame is the way to go, although I'm not yet sure on how to implement it.

How about this.
let lastTime = 0;
function *tickSaga() {
while (true) {
var time = +new Date();
var delayTime = Math.max(0, 1000 - (time - lastTime));
lastTime = time + delayTime;
yield call(updateGameSaga)
const game = yield select(getGame)
if (game.crashed)
break
yield delay(delayTime)
}
}
If you need 60 fps, replace 1000 to 16 (1000/60).
For each tick you have a correction of the interval (time - lastTime) so the ticker interval should be uniform.

Related

Synchronizing TWEEN, CCapture, and THREE.js rendering code to all be on the same clock

I'm using a library called CCapture to capture equally time-spaced frames. This code seems to be able to hook the clock and control it so that is slows down my rendering loop when it is capturing. My code uses clock.getDelta() to get the time and it uses this time to calculate the positions of moving transit vehicles. When CCapture controls the rendering rate, the vehicles are correctly positioned.
I recently started using the TWEEN library in my code.
function animate() {
renderer.setAnimationLoop( renderFrame );
}
function renderFrame() {
const delta = clock.getDelta()
timeSinceStart += delta;
repositionTransitVehicles(timeSinceStart);
renderer.render(scene, camera);
if (capturer) {
capturer.capture( renderer.domElement );
}
orbitControls.enabled = false;
TWEEN.update();
orbitControls.enabled = true;
orbitControls.update();
},
With the no-arguments version of TWEEN.update(), tweening works but always proceeds at real time, which is too fast when I'm using CCapture. If I use...
TWEEN.update(timeSinceStart)
...then tweening does not work at all.
Does anyone know the secret to making TWEEN.update() operate using the same clock as the rest of the model?
The timer that TWEEN uses is a millisecond timer, an thus the units of the time that need to be passed into TWEEN.update() need to be in units of milliseconds. The units of THREE.Clock() are seconds. So use...
TWEEN.update(timeSinceStart*1000)
Wherever you use TWEEN's .start() method to initiate your tweens, you will also need to pass in the argument timeSinceStart*1000 as well.

How to change the current time in the progress control?

Is it possible to manualy set the current time position on the timeline? I need this because I have several separated videos which represent one video and are playing in different players.
For example, the actual current time is marked as white dot, the desired current time that I want to show as red dot.
Can I do this somehow without creating completely new custom controlbar?
Middleware could be part of the solution, which allows you to intercept and modify what's sent to / received from the playback tech (video element).
var myMiddleware = function(player) {
return {
currentTime: function(ct) {
// When the video el says the currentTime is 60s, report 160s
return ct + 100;
},
setCurrentTime: function(time) {
// When the user / progress bar wants to seek to 220s, actually seek to 120s
return time - 100;
}
};
};
videojs.use('*', myMiddleware);

Microbit music / scroll output corrupting memory?

My son and I are trying to implement one of the fun-science.org microbit tutorials - building a "tug of war" game. Essentially, 10 presses on button A moves the sprite closer to that button and the same applies on button B. When you hit the edge of the screen, music plays and a message is scrolled on the screen before the game restarts.
We've got it fully working, but once the game has been won, it doesn't seem to reset properly. It works fine on the simulator, but not on the physical device (a microbit 2).
Once the game is won, the behaviour is erratic. It usually puts the sprite back in the middle, sometimes not, but frequently, one button doesn't work in the next game. Occasionally both stop working. In every situation, a restart fixes things.
Is there something wrong with the code? I've wondered whether the music / message is corrupting something and I need to put a wait in for it to complete. I've re-downloaded the hex file and re-flashed the microbit several times, so I think I've eliminated a corrupt code file.
Javascript version of code shown below, but it was actually built in blockly using the Microsoft MakeCode tool.
input.onButtonPressed(Button.A, function () {
sprite.change(LedSpriteProperty.X, -0.1)
})
input.onButtonPressed(Button.B, function () {
sprite.change(LedSpriteProperty.X, 0.1)
})
let sprite: game.LedSprite = null
sprite = game.createSprite(2, 3)
basic.forever(function () {
if (sprite.get(LedSpriteProperty.X) == 0) {
music.startMelody(music.builtInMelody(Melodies.Birthday), MelodyOptions.Once)
basic.showString("liverpool wins")
sprite.set(LedSpriteProperty.X, 2)
} else if (sprite.get(LedSpriteProperty.X) == 4) {
music.startMelody(music.builtInMelody(Melodies.Entertainer), MelodyOptions.Once)
basic.showString("rb leipzig wins")
sprite.set(LedSpriteProperty.X, 2)
}
})
I found it was more reliable if I did game.pause() and game.resume() while scrolling the text and playing the music at the end of the game:
input.onButtonPressed(Button.A, function () {
sprite.change(LedSpriteProperty.X, -0.1)
})
input.onButtonPressed(Button.B, function () {
sprite.change(LedSpriteProperty.X, 0.1)
})
let sprite: game.LedSprite = null
sprite = game.createSprite(2, 3)
basic.forever(function () {
if (sprite.get(LedSpriteProperty.X) == 0) {
game.pause()
music.startMelody(music.builtInMelody(Melodies.Birthday), MelodyOptions.Once)
basic.showString("liverpool wins")
game.resume()
sprite.set(LedSpriteProperty.X, 2)
} else if (sprite.get(LedSpriteProperty.X) == 4) {
game.pause()
music.startMelody(music.builtInMelody(Melodies.Entertainer), MelodyOptions.Once)
basic.showString("rb leipzig wins")
game.resume()
sprite.set(LedSpriteProperty.X, 2)
}
})
You can also take a look at the following version of the game that does not use the game rendering engine https://makecode.microbit.org/projects/tug-of-led to see if that makes a difference.

Titanium geolocation distanceFilter property is not working as expected

Need a help here for geolocation API - even after using Titanium.Geolocation.distanceFilter = 10; 10 in meter callback function is getting triggered randomly without moving here and there. Any idea what is wrong here ?
function Geolocate() {
var self = this;
// The callback function we should call when location is finally determined
this.locationReceivedCallback = function () {};
// Check if location event is already triggered or not
this.isLocationEventRegister = false;
// The function that unregisters the location event
this.unregisterLocationEvent = function () {
this.isLocationEventRegister = false;
Ti.Geolocation.removeEventListener('location', self.locationReceivedCallback);
};
}
Geolocate.prototype.init = function () {
// Setting up some preference values
Titanium.Geolocation.distanceFilter = 10; //The minimum change of position (in meters) before a 'location' event is fired.
if (deviceDetect.isIos()) {
Titanium.Geolocation.accuracy = Titanium.Geolocation.ACCURACY_BEST; // Using this value on Android enables legacy mode operation, which is not recommended.
} else {
Titanium.Geolocation.accuracy = Titanium.Geolocation.ACCURACY_LOW; //Using this value on Android enables simple mode operation.
}
};
Geolocate.prototype.getCurrentPosition = function (callback) {
this.isLocationEventRegister = true;
this.locationReceivedCallback = callback;
Titanium.Geolocation.addEventListener("location", this.locationReceivedCallback);
};
Geolocate.prototype.locationServicesAreAvailable = function () {
if (Titanium.Geolocation.locationServicesEnabled) {
return true;
} else {
return false;
}
};
Geolocate.prototype.cancelLocationRequest = function () {
this.unregisterLocationEvent();
};
module.exports = Geolocate;
Actual scenario is whenever I clicked on get location its gives me current location. then i tried to drag it map or image view for nearest place. Suddenly my view go current location. This is happening because of call back ? How to get rid off this ?
It's not a problem to do with your code, it is simply GPS in-accuracy.
GPS accuracy is (almost) never better than 10 meters, meaning it can be 10 meters off. When it recalculates your position it can be 10 meters down the line, so even if you're perfectly still with perfect GPS accuracy, you still might have differences of about 10 meters.
That said, you probably don't have best GPS accuracy when sitting behind a computer in a building, you probably have closer to 30-45 meters accuracy. Meaning every recalculation can easily be 10 meters differently.
Your solution would actually be rate-limit on top of using this property. The property will make sure it will not trigger multiple times per second, and then you can, in your own code, rate limit what you do with it.

How to debounce a signal in QML?

I have a few properties that have change handlers that request a repaint of a canvas. This works fairly well, but I would like to debounce the signal because a user can only see a limited number of refreshes and some of the data items can change quite frequently. This causes far more refreshes than the user can see and reduces how responsive the UI is.
I have looked at debouncing the signal in javascript (there are examples on the web for how to do that) and tie it in to QML, but I haven't figured out how to get QML and javascript to work together in that way yet.
I would love to see something like the following:
function rateLimitedRefresh(){
// magic to limit to 30 frames per second
canvas.requestPaint()
}
onValueChanged: {
rateLimitedRefresh();
}
With the requestPaint method only being called on the canvas a maximum of 30 times per second.
I used a modification of Mertanian's answer. This modification enforces the frame rate limit at a per frame level as opposed to a per second level.
property var limitStartTime: new Date()
function rateLimitedRefresh(){
// magic to limit to 30 frames per second
var now = new Date();
if (now - limitStartTime >= 32) {
limitStartTime = now
canvas.requestPaint()
}
}
How about something like this:
property var limitStartTime: new Date()
property int refreshesThisSecond: 0
function rateLimitedRefresh(){
// magic to limit to 30 frames per second
if ((new Date()) - limitStartTime >= 33) {
limitStartTime = new Date(limitStartTime.getTime() + 33)
canvas.requestPaint()
}
}
onValueChanged: {
rateLimitedRefresh();
}