Ways to limit Phaser3 update rate? - javascript

In my Phaser3 game there is a global gameTick variable that is incremented every update. I am using this to spawn in enemies in my game every 100th update.
Here is a simplifed example of what is going on in my scene class:
update () {
global.gameTick++;
if (global.gameTick % 100 === 0) {
this.spawnAlien();
}
}
This works fine but as soon as a user plays the game on a monitor with a refresh rate >60hz the update timing breaks and causes the aliens to spawn more frequently.
I have checked this.physics.world.fps and it is 60. I can also modify this.physics.world.timescale but then I would have to do a giant switch statement for every refresh rate.
Either I am missing an obvious solution or my global.gameTick method is not an effective way to accomplish this task.
This is what I have in my config so far
let config = {
type: Phaser.AUTO,
backgroundColor: "#000",
scale: {
parent: "game",
mode: Phaser.Scale.FIT,
width: 1900,
height: 600,
},
physics: {
default: "arcade",
arcade: {
debug: true,
fps: 60 // doesn't fix update frequency
},
fps: { // not sure if this is even doing anything
max: 60,
min: 20,
target: 60,
}
},
pixelArt: true,
};

You can also set the following property in your game's config object:
fps: {
target: 24,
forceSetTimeOut: true
},
Source: https://phaser.discourse.group/t/how-to-limit-fps-with-phaser-3/275/14?u=saricden

To limit the update rate, use the following method.
// The time and delta variables are passed to `update()` by Phaser
update(time, delta) {
this.frameTime += delta
if (this.frameTime > 16.5) {
this.frameTime = 0;
g.gameTick++;
// Code that relies on a consistent 60hz update
}
}
This accumulates the miiliseconds between the last frame and the current frame. It only runs the update() code if there has been 16.5ms of delay.
The example above works for 60fps, but if you want to limit the FPS to a different value use the formula: delay = 1000/fps.

Related

Phaser 3 - Check group collision with world bounds

In my scenario, i create a ship according to player's preferences. The ship consists of the flag and the hull.
An example view
PROBLEM-1
Ship is an Arcade.Group and i want to prevent this group from going outside the borders of the world.
create(){
// Create a group for ship
this.shipGroup = this.physics.add.group()
// Add hull to shipGroup
this.shipGroup.create(400, 500, "1021")
// Add flag to shipGroup
const mainFlag = this.shipGroup.create(400, 500, "FA1")
mainFlag.setOrigin(0.5, 0.8)
// Set collision property to true of every object in shipGroup
this.shipGroup.children.each((item: any) =>
item.setCollideWorldBounds(true)
)
}
update(t: number, dt: number){
if (this.cursor.up.isDown) {
this.shipGroup.setVelocity(...)
}
else {
this.shipGroup.setVelocity(0, 0)
}
}
With this approach every object in group calculated seperately. After hitting the world boundary, the position of the objects is distorted.
PROBLEM-2
To avoid this i tried another approach. I add bounding box to group. Instead of check collision for every object, i will only check collision for bounding box.
create(){
// Create a group for ship
this.shipGroup = this.physics.add.group()
// Add bounding box for shipGroup
this.shipBox = this.shipGroup.create(400, 500, "bbox")
this.shipBox.setCollideWorldBounds(true)
this.shipBox.body.onWorldBounds = true
// Add hull to shipGroup
this.shipGroup.create(400, 500, "1021")
// Add flag to shipGroup
const mainFlag = this.shipGroup.create(400, 500, "FA1")
mainFlag.setOrigin(0.5, 0.8)
/*this.shipGroup.children.each((item: any) =>
item.setCollideWorldBounds(true)
)*/
}
update(t: number, dt: number){
if (this.cursor.up.isDown && !this.shipBox.body.checkWorldBounds()) {
this.shipGroup.setVelocity(...)
}
else {
this.shipGroup.setVelocity(0, 0)
}
}
The problem is checkWorldBounds() returns false even if shipBox hits world boundaries. But collision for shipBox is work.
checkWorldBounds()
Description: Checks for collisions between this Body and the world
boundary and separates them.
Returns: True if this Body is colliding with the world boundary.
How can i implement collision for group and world boundary?
P.S. : phaser version is 3.55.2
There are a few ways to solve/work around this issue, I personally would just use only one image why one physics-body (hull and flag combined) and just move that single image/texture, and switch the image, when needed.
That said, if you need to use the separate images, the easy way is to use a phaser container. (link to the documentation)
create a container
add the images to the container
set the size for the container (default size is width=0 height=0)
create a physics body for the container
done
A short demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
physics: {
default: 'arcade',
arcade: {
gravity:{ y: 0 },
debug: true
}
},
scene: {
create
},
banner: false
};
function create () {
this.add.text(10,10, 'Ship with physics')
.setScale(1.5)
.setOrigin(0)
.setStyle({fontStyle: 'bold', fontFamily: 'Arial'});
let graphics = this.make.graphics();
graphics.fillStyle(0xffffff);
graphics.fillRect(0, 0, 10, 40);
graphics.generateTexture('ship', 10, 40);
graphics.fillStyle(0xff0000);
graphics.fillRect(0, 0, 30, 10);
graphics.generateTexture('flag', 30, 10);
graphics.generateTexture('flag2', 20, 6);
let hull = this.add.image(0, 0, 'ship')
let flag = this.add.image(0, -5, 'flag')
let flag2 = this.add.image(0, 10, 'flag2')
this.ship = this.add.container(100, 80, [ hull, flag, flag2]);
this.ship.setAngle(-90)
this.ship.setSize(40, 30)
this.physics.world.enable(this.ship);
this.ship.body.setVelocity(100, 0).setBounce(1, 1).setCollideWorldBounds(true);
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Info: this demo is based partly from this official example

Math.ceil to increase a number does not update

Does anyone know other solution than below, to be able to increasing speed by value of score?
In my vuex application I have this getter:
getters: {
speed: state => Math.ceil(state.score / 5) || 15
}
}
The speed should increase every 5th score
This is not the case, as {{ speed }} doesn't change from 15. Imported it like so:
computed: {
...mapGetters('playerData', {
speed: 'speed'
})
},

Surrounding 3d sound effects using howler.js or another library?

I'm working on a project and I need to add 3d sounds effects, like the sound is continually moving around the listener effects. Is it possible to achieve that with howlerjs i see that with howler i'm able to play a sound from specific coordinates/orientation but how to achieve surrounding/ambisonics sounds ?
Or another library in JavaScript to achieve that?
Thanks for your help.
Half a year late, but yeah that's entirely possible in howler.js, haven't used it myself but judging from the docs you can just update the position. there's some more libraries that do it that I've found, check here how 3dage does exactly what you want:
https://codepen.io/naugtur/pen/QgmvOB?editors=1010
var world = IIIdage.World({
tickInterval: 200
})
var annoyingFly = IIIdage.Thing({
is: ['fly'],
sounds: {
'buzzing constantly': {
sound: 'buzz',
times: Infinity
}
},
reacts: [
{
// to: world.random.veryOften(),
to: world.time.once(),
with: 'buzzing constantly'
}
]
})
// scene should create and expose a default world or accept one
var scene = IIIdage.Scene({
title: 'Annoying fly',
library: {
sounds: {
'buzz': {
src: ['https://webaudiogaming.github.io/3dage/fly.mp3']
}
}
},
world: world,
things: [ // scene iterates all things and spawns them into the world. same can be done manually later on.
annoyingFly({
pos: [-1, -15, 0],
dir: [1, 0, 0],
v: 1
})
]
}).load().run()
setTimeout(function () {
scene.dev.trace(IIIdage.dev.preview.dom())
}, 500)
setInterval(function rotateVector() {
var angleRad = 0.15
var d=scene.things[0].attributes.dir
var x=d[0], y=d[1]
var cosAngle = Math.cos(angleRad), sinAngle = Math.sin(angleRad)
scene.things[0].attributes.dir = [x * cosAngle - y * sinAngle, y * cosAngle + x * sinAngle, 0]
}, 500)
window.scene = scene
There's still some others that do similar stuff:
https://www.npmjs.com/package/songbird-audio
https://www.npmjs.com/package/ambisonics
Hope this pushes you in the right direction if you still want help with it.

React JS animations based on JSON data

I am using React/Redux and am storing animation data in JSON and trying to get it to display on a React page.
I am using setTimeout (for pauses) and setInterval (for animation movement). However, I seem to be having trouble understanding how to implement the animations correctly and think I'm going about things totally the wrong way.
JSON data:
"objects": [
{
"title": "puppy",
"image_set": [
{
"image": "images/puppy_sitting.png",
"startx": 520,
"starty": 28,
"pause": 1000
},
{
"image": "images/puppy_walking.png",
"startx": 520,
"starty": 28,
"endx": 1,
"endy": 1,
"time": 1000
},
{
"image": "images/puppy_crouching.png",
"startx": 1,
"starty": 1,
"endx": 500,
"endy": 400,
"time": 2000
}
]
},
{
"title": "scorpion",
"image_set": [
{
"image": "images/scorping_sleeping.png",
"startx": 100,
"starty": 400,
"pause": 5000
},
{
"image": "images/scorpion_walking.png",
"startx": 100,
"starty": 400,
"endx": 500,
"endy": 400,
"time": 7000
},
{
"image": "images/scorpion_walking.png",
"startx": 500,
"starty": 400,
"endx": 100,
"endy": 400,
"time": 2000
},
{
"image": "images/scorpion_walking.png",
"startx": 100,
"starty": 400,
"endx": 200,
"endy": 400,
"time": 7000
},
{
"image": "images/scorpion_walking.png",
"startx": 200,
"starty": 400,
"endx": 100,
"endy": 400,
"time": 1000
}
]
}
]
Each object can have several images related to them. The animations will continue to repeat non-stop. Each object should move concurrently with each of the other objects so that I can create a scene of various animals and objects moving around it.
Animation code:
I'm pretty sure I'm barking up the wrong tree here, but my code looks something like this:
// image_set is the list of images for a specific object
// object_num is the array index corresponding to the JSON objects array
// selected is the array index corresponding to which image in the image_set will be displayed
runAnimation(image_set, object_num, selected){
// Uses prevState so that we keep state immutable
this.setState(prevState => {
let images = [...prevState.images];
if (!images[object_num]){
images.push({image: null, x: 0, y: 0})
}
images[object_num].image = image_set[selected].image;
images[object_num].x = this.getFactoredX(image_set[selected].startx);
images[object_num].y = this.getFactoredY(image_set[selected].starty);
return {images: images};
})
if (image_set[selected].endx && image_set[selected].endy && image_set[selected].time){
let x = this.getFactoredX(image_set[selected].startx)
let y = this.getFactoredY(image_set[selected].starty)
let startx = x
let starty = y
let endx = this.getFactoredX(image_set[selected].endx)
let endy = this.getFactoredY(image_set[selected].endy)
let time = image_set[selected].time
let x_increment = (endx - x) / (time / 50)
let y_increment = (endy - y) / (time / 50)
let int = setInterval(function(){
x += x_increment
y += y_increment
if (x > endx || y > endy){
clearInterval(int)
}
this.setState(prevState => {
let images = [...prevState.images]
if (images[object_num]){
images[object_num].x = x
images[object_num].y = y
}
return {images: images};
})
}.bind(this),
50
)
}
if (image_set[selected].pause && image_set[selected].pause > 0){
selected++
if (selected == image_set.length){
selected = 0
}
setTimeout(function() {
this.runAnimation(image_set, object_num, selected)
}.bind(this),
image_set[selected].pause
)
}
else {
selected++
if (selected == image_set.length){
selected = 0
}
setTimeout(function() {
this.runAnimation(image_set, object_num, selected)
}.bind(this),
50
)
}
}
Redux and this.props.data
Redux brings in the data as props. So, I have a function called from my componentDidMount and componentWillReceiveProps functions that passes the original image set into the loadAnimationFunction.
My render()
In my render() function I have something like this:
if (this.state.images.length > 1){
animated = this.state.images.map((image, i) => {
let x_coord = image.x
let y_coord = image.y
return (
<div key={i} style={{transform: "scale(" + this.state.x_factor + ")", transformOrigin: "top left", position: "absolute", left: x_coord, top: y_coord}}>
<img src={`/api/get_image.php?image=${image.image}`} />
</div>
)
})
}
x_factor / y_factor
Throughout my code there is also reference to x and y factor. This is because the background that the animations appear in may be scaled smaller or larger. Therefore I also scale the position of the starting and ending x/y coordinates for each animation as well as scale the animated images themselves.
time and pause time
Time indicates the time in ms that the animation should take. Pause time indicates how long in ms to pause before moving to the next animation.
The problem
The code does not move the animations smoothly and they seem to jump around sporadically.
Also, when I click the mouse anywhere on the page it causes the animations to jump to another position. Why would clicking the mouse affect the animation?
One thing I've noticed is that if I have the console open for debugging purposes, this really slows down the animations.
What can I do to my code so that the animations work as expected?
You are trying to animate your element using a setInterval doing a setState of the coordinates and with an absolute position. All of these cannot achieve great performance.
First, setInterval should never be used for animations, and you should prefer requestAnimationFrame as it will allow 60fps animations since the animation will be run before the next repaint of the browser.
Second, doing a setState would re-render your whole component which could potentially have an impact on the rendering timing as I assume your component doesn't render only your images. You should try to avoid at maximum to re-render things that haven't changed, so try to isolate your images for the animations.
Lastly, when you position your element with left and top properties, but you should stick to that, positioning, and not animating as the browser would do the animation pixel by pixel and would not be able to create good performances. Instead, you should use CSS translate(), as it can do sub-pixel calculation and will work on the GPU instead, allowing you to achieve 60fps animations. There is a good article on that by Paul Irish.
That being said, you should probably use react-motion which would allow you a smooth animation:
import { Motion, spring } from 'react-motion'
<Motion defaultStyle={{ x: 0 }} style={{ x: spring(image.x), y: spring(image.y) }}>
{({ x, y }) => (
<div style={{
transform: `translate(${x}px, ${y}px)`
}}>
<img src={`/api/get_image.php?image=${image.image}`} />
</div>
)}
</Motion>
There is also the React Transition Group, Transition could move your elements using a translate animation like explained above. You should also go take a look at the react animations docs here.
Worth a try too, is React Pose, which is pretty easy to use and also performs quite well with a clean API. Here is the get started page for React.
Here is a quick demo using your concept with a sit/walking/running cycle. Notice how react-motion is the only one to handle the transition in between the frames without hardcoding the duration of the transition, which would go against a fluid UI, the state only handles going through the different steps.
Quoting the react-motion Readme:
For 95% of use-cases of animating components, we don't have to resort to using hard-coded easing curves and duration. Set up a stiffness and damping for your UI element, and let the magic of physics take care of the rest. This way, you don't have to worry about petty situations such as interrupted animation behavior. It also greatly simplifies the API.
If you are not satisfied with the default spring, you can change the dampling and stiffness parameters. There's an app which could help you get the one which satisfy you the most.
Source
React is not exactly meant to be used for animations. I'm not saying you can't animate react components, but it's not part of the problem domain react tries to solve. What it does do is provide you with a nice framework to have the several UI pieces interact with each other. I.e. when creating a game for instance, you'll use react and redux to create and manage the screens, buttons etc. however the game itself, would be separately contained and not use react.
Just a long-winded way to say that if you want to use animations react will not suffice, it's better to use something like greensock's animation library: https://greensock.com/
They provide a tutorial on how to use it in conjunction with react: https://greensock.com/react
Let css do the transitions. Use transform: translate instead of top and left.
The animations you have in your sample are very easy to express with css transition, transition-delay, and transform.
I would put my effort in converting the JSON to css (using a cssInJs solution that allows you to generate the classes on the fly) and apply those classes to the images.
something like this(working example with your JSON sample): https://stackblitz.com/edit/react-animate-json
const App = () =>
<div>
{objects.map(object =>
<Item item={object} />)
}
</div>
Item.js:
class Item extends React.Component {
state = { selected: 0, classNames: {} }
componentDidMount() {
this.nextImage();
this.generateClassNames();
}
generateClassNames = () => {
const stylesArray = this.props.item.image_set.flatMap((image, index) => {
const { startx, starty, endx = startx, endy = starty, time } = image;
return [{
[`image${index}_start`]: {
transform: `translate(${startx}px,${starty}px)`,
transition: `all ${time || 0}ms linear`
}
}, {
[`image${index}_end`]: { transform: `translate(${endx}px,${endy}px)` }
}]
});
const styles = stylesArray.reduce((res, style) => ({ ...res, ...style }), {})
const { classes: classNames } = jss.createStyleSheet(styles).attach();
this.setState({ classNames });
}
nextImage = async () => {
const { image_set } = this.props.item;
let currentImage = image_set[this.state.selected];
await wait(currentImage.pause);
await wait(currentImage.time);
this.setState(({ selected }) =>
({ selected: (selected + 1) % image_set.length }), this.nextImage)
}
render() {
const { selected, classNames } = this.state;
const startClassName = classNames[`image${selected}_start`];
const endClassName = classNames[`image${selected}_end`];
return <img
className={`${startClassName} ${endClassName}`}
src={this.props.item.image_set[selected].image}
/>
}
}
const wait = (ms) => new Promise(res => setTimeout(res, ms));
I believe that your fundamental problem lies in the way React/Redux handle state. React may batch multiple update requests together to make rendering more efficient. Without further diagnostic measures, my guess is that the state handling after setState will just respond too rigidly.
The solution would to update your animation outside the state system, either using a ready-made framework or simply by taking care of the animation yourself; get a reference to the element and update it instead of re-rendering the element every time the state is updated.
without going deep about animations in JS (there are already plently of valid answers here) you should consider how you render your images:
<div key={i} style={{transform: "scale(" + this.state.x_factor + ")", transformOrigin: "top left", position: "absolute", left: x_coord, top: y_coord}}>
<img src={`/api/get_image.php?image=${image.image}`} />
</div>
You should actually see a warning when compiling this (or was it in the docs?) because you use the loop index as the key. This should lead to an image object being rendered in different divs as more images are added/removed. This is especially critically if you have a css-transition effect on the div.
TLDR: use some identifier as the key instead of the variable i (may be generate one when you create the animation?)
Also if you have a css transition on the div, you should remove it, because together with the changes from setInterval the transition calculation won't be able to keep up with the changes.

Cache SpriteSheets in EaselJS

How can I cache SpriteSheets in EaselJS? I have a Sprite object and when I use user.hero.cache(0, 0, 30, 40); it stops playing animation (probably because I'm just caching the current frame, not the entire SpriteSheet image). So how can I cache it?
Here's my relevant EaselJS code:
data = {
images: ["Graphics/hero.png"],
frames: {
width: 30,
height: 40
},
animations: {
stand: 0,
run: [1, 2, "runLoop", 0.15],
runLoop: [3, 7, true, 0.15],
jump: [8, 10, "happy", 0.5],
happy: 11,
fall: 12,
stopFalling: [13, 14, "stand", 0.2],
almostFalling: [16, 19, true, 0.1]
}
};
user.hero.spriteSheet = new createjs.SpriteSheet(data);
user.hero = new createjs.Sprite(user.hero.spriteSheet, "stand");
user.hero.name = "hero";
user.hero.x = user.hero.safeX = 40 * 3;
user.hero.y = user.hero.safeY = 0;
user.hero.offset = 4;
user.hero.regX = user.hero.offset + 2;
user.hero.regY = user.hero.offset;
user.hero.width = 30 - (user.hero.offset * 2) - 10;
user.hero.height = 40 - (user.hero.offset * 2);
user.hero.xvel = user.hero.yvel = 0;
user.hero.cache(0, 0, 30, 40); // <--- This is the problem.
movableObjContainer.addChild(user.hero);
movableObj.push(user.hero);
Without cache:
With cache:
I've tried caching the data.image or user.hero.spriteSheet too, without success.
Is there any way to cache the SpriteSheet without compromising its animations?
When you cache the sprite, you are saving off how it looks at that instant.
Can you explain why you want to cache it? The only reason I can think of to cache it would be to apply filters. Each time you cache it, the contents are drawn to an off-screen canvas, which is then drawn in place of it. This makes a lot of sense if you have complex content, like a container or graphics, which do not change, but with the spritesheet, it means you are creating a new bitmap to draw a bitmap. The spritesheet itself is a great way to be filesize, network, and GPU-optimzed, so re-caching it is basically negating all those benefits.
If you want to cache anyways, you will need to re-cache it, or call updateCache() every time it changes. Here is an example using the ticker.
createjs.Ticker.on("tick", function() {
user.hero.updateCache();
// or
user.hero.cache(0,0,30,40)
}, this);
Here is a quick demo I did a while back using a few approaches for filters. It provides an example of constant re-caching, as well as an example where the entire spritesheet is cached to apply the filter once, and then that cached version is used for the sprite.
http://jsfiddle.net/lannymcnie/NRH5X/

Categories