DEV Community

Cover image for How I optimized my game by adding a stupid clock
Jack Le Hamster
Jack Le Hamster

Posted on

How I optimized my game by adding a stupid clock

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;
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

World of Turtle on Android with a stupid clock

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)