Building Reigns: How to strengthen reign over mobile game development using Reanimated 2.

Wojciech Stanisz
Software Mansion
Published in
7 min readMay 27, 2021

--

This is the second post in a series dedicated to building a clone of Reigns game in React Native using Reanimated 2 and Gesture Handler. If you haven’t seen the first part where we focused on the gesture handling implementation you can find it here. This time around we are going to focus more on the visuals. We’ll create dynamic text and flip animations, imitate shadows, and put graphics to make our card look superb!

Like in the previous article you can find the code in the Github repository that you can visit by clicking the iteration link next to the title of each chapter.

Animated text — iteration-2

Result of iteration-2 code.

As the very first step of this part of the tutorial, we are going to add text to our card that reacts to its transition in the following manner: it will show the right option only when the card is moved a little to the right and the left option only when the card is swiped to the left.

The new render looks something like this:

As you can see there are two new separate text wrappers (highlighted blue and green) instead of one conditional text render like:

<Animated.Text>
{x.value > 0 ? rightText : leftText}
</Animated.Text>

We have to remember that it’s not that simple to use shared value in code. We can’t use it simply in the code even if the component itself is Animated.View or Animated.Text. If we really wanted to make only one text wrapper we should use the useAnimatedProps hook (you can read about it here), but for simplicity in this example we always render two separate components and animate their opacity based on the card’s movement.

The second thing that you may notice is that each animated text wrapper got some separate animatedSideTextWrapper style assigned to it. Those style objects look almost the same with the small exception that the interpolator got opposite input values. An example for the right wrapper looks like this:

Right text wrapper’s style.

The last thing worth noting in this step is topTextWrapper style object because the values assigned are a little odd at a first glance.

Interesting values here are width , left and right . The simple explanation for them would be that we want to rotate one component inside a different component and that’s really troublesome. I think there’s no use in explaining in words something that you can understand with only one picture:

Broken card styles.

To solve the problem we can just create a component a little too big so the rotation won’t expose the boundaries, just like we did in the styles above.

Card after fixing the styles.

Flip animation — iteration-3

Result of iteration-3 code.

A very important part of Reigns is showing the next cardin a smooth deck-like manner. To make animation possible we will add a new wrapper that contains the whole card and rotate it accordingly.

Theoretically, we could use Animated.View located directly below to get only one Animated.View responsible for both animation but since it’s used to animate finger movement I wanted to keep it as simple and as clear as possible. I did this by keeping the single responsibility for each Animated.View,so we get a separate View to turn the card around and to move it around using gestures.

To rotate our card we will need a new shared value. Just to keep learning something new our new value will start with initial value 1.

New shared value initialization.

Now’s the time to use it. Let’s use the useAnimatedStyle. Inside we can see three props changed based on openAnimation shared value: scale, perspective, and rotateY.

The only necessary change is rotateY moved by 180 degrees but scale and perspective are making the animation so much prettier! You can try to tweak all those values to see how it changes the look!

Style logic controlling our card’s flip animation.

Movement shared values like x and y are controlled by gesture handler. This time we would rather flip the card by using a trigger inside the code. That’s why I added a new button that controls the openAnimation shared value. It should be animated from initial 1 to 2 and back again. This time around we can use a new function: instead of withSpring — that we are already familiar with — we can use new withTiming.

It works almost the same as withSpring but this time around it’s not calculated based on physics values, but it’s based on some predefined bezier curve the animation will follow. All animation adjustments can be set in the config object, but in my case, I only adjusted the duration of the animation to make it a little longer than the default value.

Pressing this button will flip the card.

Creating card’s front and back — iteration-4

Result of iteration-4 code.

This step’s gonna be easy!

This time around the only thing we add is just some graphics for the card front and reverse. For that reason, in our render method, we can find new CardPerson.js and CardReverse.js.

But…we can’t just simply add them. We also need to write a logic that will determine when to show the card and which side of the card should be presented. Since we know the card flip animation is based on openAnimation shared value that travels between values 1 and 2, we can safely assume we should stop rendering the reverse just in the middle when the value exceeds 1.5.

For that reason, we can add a new opacity prop to previously created animatedFront style, that will switch whenever the value exceeds 1.5 .

And very similar animatedBack style but with reversed values:

Animated shadows — iteration-5

Result of iteration-5 code.

Adding shadows will bring some additional depth to our animation. It’s a simple, yet effective trick to add some depth to any 3D animation. I’m totally aware this trick won’t work in all cases, but adding it on a simple 2D plane can be a breeze using the new Reanimated.

First things first, we need to update our render. To keep the single responsibility rule introduced earlier I decided to create two new Animated.View — one for each side of the card.

New shadow components added to our render method.

The trick here is to use position: absolute combined with relatively high zIndex and size spread over the whole component so the shadow would cover the component but won’t infringe the internal flow while covering it.

Shadow basic styles.

Finally, we can connect it with our openAnimation shared value. If you have a really sharp eye you can spot I used Extrapolate.CLAMP instead Extrapolate.EXTEND in the interpolation function. That’s because I didn’t want the opacity value to wander around out of control. CLAMP will keep the last given value and won’t extrapolate it further. It makes sure that the opacity will never go below 0 or above 1. If that ever happened, it would have a disastrous effect: the react-native component would be confused, and every value not in range between 0 and 1 would return just 1. That means strange visual glitches out of our control.

NOT EPIC.

Shadow animated styles.

We’ve done a lot!

We are almost done with work around our card. It looks amazing isn’t it? During our journey we used a lot of different tools around reanimated 2 and I hope you are getting familiar with it!

Can you see the schema already?

First, we create shared we can use:

const mySharedValue = useSharedValue(0);

Then we start creating Animated.View with useAnimatedStyle object inside:

<Animated.View style={animatedStyle} />

Created useAnimatedStyle always use some sharedValue to calculate new style:

const animatedFront = useAnimatedStyle(() => {
return {
height: mySharedValue.value,
};
});

And finally, we use some reanimated mechanism like withTiming or withSpring to animate it:

mySharedValue.value = withTiming(100);

You are a quick learner! 👏

Next time we will make the application alive by creating basic game logic to control the whole experience, start menu and loading animation! It’s also gonna be the last part of our journey so stay tuned and see you soon!

--

--