Lately, I've been working on porting my game, World of Turtle, from Steam to mobile. It's a comment I kept hearing: "This would be perfect as a mobile game!".
Well, I heard the y'all, so I decided to go for it.
That game is entirely written in JavaScript. I know, it's not very adequate for making a game, and I should be using Unity or something like that. Instead, I port the game to mobile (Android and IOS) using Cordova. For now, that's the best I can do, ok? And it works!... good enough.
Everything seemed to be going fine, I managed to get decent frameRate with requestAnimationFrame, and even reached to 60fps on my phone by reducing the pixel resolution.
But there was one stupid bug that kept reoccurring. After a few days, the game would constantly freeze to a halt. I thought it was a performance issue and my game was using too much CPU, but then I noticed something peculiar.
When I touch the screen, the game unfreezes for a few seconds. If I keep dragging my finger, the game seems to actually run at 60fps. What's going there? I also noticed that the loading screen doesn't freezes. The spinning wheel keeps going and going, until the game starts and everything freezes.
Still, I couldn't figure out any way to fix it. I kept searching on stack overflow, even asked ChatGPT. Nothing that relates to the issue I was facing.
I was thinking there must be something wrong with the performance of requestAnimationFrame so I did some research about it.
From what I read, one of the advantage of requestAnimationFrame is that it won't be doing wasteful rendering. "Guarantee when it’ll paint; only that it’ll paint when needed.". That made me wonder... Perhaps requestAnimationFrame thinks it doesn't need to paint because nothing is moving. Perhaps it doesn't know that there's a whole game inside the WebGL canvas that needs refresh.
So I tried something new: Just put a CSS animation that moves constantly, forcing the screen to refresh. I didn't want to just put a spinning wheel, but something useful for the game, so I thought perhaps a analog clock would be nice. That way the player doesn't lose track of time when they try to solve a level.
Here's the code in React:
Clock.module.css
:
@keyframes clockspin {
0% {
transform: rotate(var(--rotate-start));
}
100% {
transform: rotate(var(--rotate-end));
}
}
.clock {
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-name: clockspin;
}
Clock.tsx
:
import React, { CSSProperties, useCallback, useEffect, useState } from "react";
import styles from "./Clock.module.css";
interface Props {
style?: CSSProperties;
}
interface StyleWithClock extends CSSProperties {
"--rotate-start"?: string;
"--rotate-end"?: string;
}
export default function Clock({ style }: Props) {
const styleRotated = useCallback(
({
deg,
durationSeconds,
length,
thickness,
color = "#333333",
}: {
deg: number;
durationSeconds: number;
length?: number;
thickness: number;
color?: string;
}): StyleWithClock => ({
position: "absolute",
"--rotate-start": `${deg - 90}deg`,
"--rotate-end": `${deg - 90 + 360}deg`,
animationDuration: `${durationSeconds}s`,
transformOrigin: `${thickness / 2}px ${thickness / 2}px`,
borderRadius: `${thickness / 2}px ${thickness / 2}px`,
width: length,
height: thickness,
backgroundColor: color,
top: 20 - thickness / 2,
left: 20 - thickness / 2,
}),
[],
);
const time = new Date();
const hoursAngle = (360 * (time.getHours() % 12)) / 12;
const minutesAngle = 360 * (time.getMinutes() / 60);
const secondsAngle = 360 * (time.getSeconds() / 60);
return (
<div
style={{
left: "50%",
top: 20,
...style,
width: 40,
height: 40,
position: "fixed",
border: "gray 2px solid",
borderRadius: "50%",
backgroundColor: "#dddddd",
zIndex: 3000,
}}
>
<div
className={styles.clock}
style={styleRotated({ deg: hoursAngle, durationSeconds: 43200, thickness: 2, length: 10, color: "black" })}
></div>
<div
className={styles.clock}
style={styleRotated({
deg: minutesAngle,
durationSeconds: 3600,
thickness: 1,
length: 15,
color: "#333333",
})}
></div>
<div
className={styles.clock}
style={styleRotated({ deg: secondsAngle, durationSeconds: 60, thickness: 1, length: 14, color: "red" })}
/>
</div>
);
}
Turns out, I was right. During the game, the clock forced the screen to refresh, running the game at 60fps. Meanwhile, in the menu screen, I noticed the water stopped animating because there was no clock there!
Anyway, I'm sure there would have been a more proper way to fix this issue, like configure the Android app to keep refreshing, but for now, this is the only solution I can come up with. (please let me know if there is another way, but no matter what, I'm keeping the clock!).
I hope this post was helpful to you, especially if you ran into the same issue I was having.
Have a good day!
PS: My game is currently in pre-release while I'm working on it, feel free to check it out: https://play.google.com/store/apps/details?id=net.dobuki.worldofturtle.lite
Top comments (0)