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

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay