post-cover
Shapes With Abigail!
I Created A Kids Game
Written Fri Dec 29 2023
By Michael Freno
112 Hits
Shapes With Abigail!
I Created A Kids Game

A Quick Side Project

I have been working on a mobile game for the past 6 or so weeks and took a quick break to create another game mainly for my girlfriends nephew(a 3 year-old). I had previously intended to so a few months back but got caught up with other things.

The original imputus for creating the app was a night out with my girlfriends sister's family, my girlfriends nephew was being a fuss, he had previously played a game on her phone, and kept asking to play it by repeatedly asking, "Shapes with Abigail?". We tried a few other games and they were all litered with either ads or walls asking for microtransactions, so I wanted to make something simple that didn't have any of that.

After getting some experience with react native on the aformentioned mobile game I felt I could throw together something quickly. The total project took ~2 weeks, most of which was spent in getting it ready and on the app stores(still only on the Apple app store), but the apk(for android) is available here.

The game it self is rather simple either matching shapes, simple math problems, or spelling words given an image.

There aren't a ton of each type of problem around 50 shape sets, 30 math and words. The making of the problems however is super easy and will add more later. They are stored in a JSON file and look like this for shape sets:

...  
  [
    {
      "svg": "triangle",
      "size": 65,
      "color": "#a3e635",
      "shifts": "y",
      "targetX": 12,
      "targetY": 20
    },
    {
      "svg": "triangle",
      "shifts": "x",
      "size": 85,
      "color": "#a3e635",
      "targetX": 0,
      "targetY": 40
    },
    {
      "svg": "triangle",
      "shifts": "y",
      "size": 120,
      "color": "#a3e635",
      "targetX": -20,
      "targetY": 60
    }
  ],
...

Or like this for numbers:

  ...
  {
    "operation": "addition",
    "operands": ["nine", "nine"],
    "result": "eighteen",
    "otherOptions": ["seventeen", "nineteen", "sixteen"]
  },
  {
    "operation": "subtraction",
    "operands": ["nine", "one"],
    "result": "eight",
    "otherOptions": ["seven", "six", "nine"]
  },
...

The words are a little more time consuming as I need an image to use, so that probably not be expanded as much in future.

The project was immedietly useful for my other game, as I learned about making draggable assets and hit detection. It's a tad more refined in my other game, but here is how its done in Shapes with Abigail in the simplest case.

   const positionChecker = (movableX: number, movableY: number) => {
    targetRef.current?.measureInWindow(
      (targetX, targetY, targetWidth, targetHeight) => {
        const overLapRequired = targetWidth * 0.3;

        const isWidthAligned =
          movableX + componentSizing / 2 - overLapRequired >= targetX &&
          movableX - componentSizing / 2 + overLapRequired <=
            targetX + targetWidth;
        const isHeightAligned =
          movableY +
            componentSizing / 2 -
            overLapRequired -
            (Platform.OS == "android" ? 40 : 0) >=
            targetY &&
          movableY -
            componentSizing / 2 +
            overLapRequired -
            (Platform.OS == "android" ? 60 : 0) <=
            targetY + targetHeight;

        if (isWidthAligned && isHeightAligned && notAligned) {
          vibrate({ style: "medium", onAndroid: true });
          setNotAligned(false);
          Animated.parallel([
            Animated.timing(fadeAnim, {
              toValue: 0,
              duration: 1500,
              useNativeDriver: true,
            }),
            Animated.timing(scaleAnim, {
              toValue: 1.5,
              duration: 1500,
              useNativeDriver: true,
            }),
          ]).start(() => {
            //this fires at animation completion
            gameRestart();
          });
        }
      },
    );
  };

For whatever reason Android devices had a weird issue with the collision detection that needed a correction that was quite ugly to have.

As for how this is triggered:

  return(
...
      <View className="flex h-24 flex-row justify-center">
          {movableNumbers.map((movableNumber, idx) => {
            return (
              <View
                className={`ml-16 ${
                  !notAligned && movableNumber.correctResult ? "opacity-0" : ""
                } ${
                  movableNumber.Component && movableNumber.Component.length > 1
                    ? "mr-28"
                    : "mr-16"
                }`}
                key={movableNumber.id + movableNumber.color + idx}
              >
                <Draggable
                  onDrag={
                    movableNumber.correctResult
                      ? (_, g) => positionChecker(g.moveX, g.moveY)
                      : undefined
                  }
                  shouldReverse
                >
                  <View className="flex flex-row">
                    {movableNumber.Component &&
                      movableNumber.Component.map((Comp, key) => (
                        <Comp
                          key={key}
                          width={componentSizing - 30}
                          height={componentSizing}
                          color={movableNumber.color}
                        />
                      ))}
                  </View>
                </Draggable>
              </View>
            );
          })}
        </View>
...
)

An array of Draggables(https://www.npmjs.com/package/react-native-draggable) is rendered, only the correct answer actually fires the function when being dragged (fired every frame), providing the function with the top left point(x,y) of the Draggable at that frame.

Anyway this project was super fun quick thing to make that turned out to be super useful in another project, and as with all things, the same logic was implemented much more cleanly and effectively the second time. You may think the above wasnt bad, which it wasn't terrible, the other modes position checker is a biiit uglier.

Comments
No Comments Yet