Insider Look: Tweening UI Elements in Crystal Companies

Note: This is a bit more technical than our normal posts! If you’re a Unity developer and know a little code this could help with your own projects. At the end of the post, I’ve attached a sample project with some very basic implementations that can be experimented with.

Hey guys! This is Will from Boreal Games and I thought I might write up something a little different than we would normally do and give an “insider look” at some of the more technical stuff I’ve worked on in Crystal Companies.

The cards positioned correctly in game. Looks simpler than it is!

One of the more surprising challenges I had to deal with was moving the cards around the screen. There were a number of considerations I needed to take into account:

  1. The movement needed to respect resolution, across devices/screen sizes
  2. It needed to reflect how someone would expect cards to move across the screen
  3. It had to deal with dynamic start and end points
  4. Preferably it wouldn’t cause unnecessary overhead

My first thought was I could leverage the built-in animation system to move the UI elements around, but because of requirements 3 & 4, this idea was thrown out pretty quickly. The animation system doesn’t support “dynamic” changes and if I needed to have a card return from an arbitrary dragged position this wasn’t going to work. In addition, the animation system caused unnecessary overhead because it would update the position values every frame, causing the canvas to update even if there was no change in position.

Requirement 1 was solved by doing everything by using empty rectTransform inside a scaled canvas panel anchored to the corners of the screen. That way any specific points would scale with the rest of the UI, such as where a card moves to when played or where they leave the screen.

The second requirement is perhaps the most subjective. There were some things I wanted to reflect in the system that mirrored how you would handle physical cards. For example, the cards are held in a curve in the hand and they accelerate and decelerate as they move in smooth paths across the screen. They don’t just move in straight lines from position to position, there is a little “naturalism” in the pathing.

Yugi helpfully demonstrates.

This lead to employing some well established bezier curve math to construct the paths. To move along these paths I also rolled out a basic tweening system with some specific considerations. Considerations like the ability to cancel a tween instantly and smoothly correct out of them, or having lots of objects move along the same curve while respecting each other’s positions. If you want a really detailed primer on curves and splines of all kinds, Catlikecoding has a great tutorial, here.

With all this figured out let’s get down the nitty gritty and look at the setup:

The objects that are going to be moved should all be inside the parent tween container. While this isn’t technically necessary (as you can convert between the child transformations with some built in unity utilities) it does make it much easier to understand whats going on. Unless your specific project requires you separate them out (perhaps for performance reasons you have multiple canvases) I recommend keeping them together. The tween container should be anchored as shown to each corner with stretch. This makes any rectTransforms we put inside scale appropriately with our canvas.

Once we have this setup we can use it to draw some screen space bezier curves. If we take a look at the BezierCurve class, we can see that it takes a bunch of parameters. The version I’ve attached to the end of this post has some other rectTransforms it uses to mark out the curve, which you wouldn’t use in a final implementation but are there to help us visualize what is going on.

Note that the positions are Vector3’s but we are only doing 2 dimensions. The code is the same either way.

The most important thing we might mess with is the start and end points, which the curve uses to calculate its path. To better understand what we are working with, you can imagine that it looks like this:

There are some other things going on under the hood that calculate specific distance look up tables, but you can check the code comments if you’re interested in what’s happening there.

Now that we have our resolution independent path we need to actually move along it. To do this I created a simple class, QuickTweener. At its most basic the QuickTweener class is just a timer that evaluates where the object should be along the bezier curve at any given time. You can see it has some other additional settings, the most interesting being the AnimCurve. Evaluating this curve and then using that to evaluate the position along the assigned curve gives us control over the rate of change of the object along the path. This particular feature was a nod to the second requirement of this problem which was that people wouldn’t expect things to move linearly from place to place. The animCurve can be used to do some interesting things, particularly if you temporarily push it above 1, so it bounces past its location before rebounding back.

That all we really needed to cover the basic idea of moving things across the screen smoothly, but if you dig inside the code you can see some inklings of other features that we ended up creating to better facilitate a hand system, but those were more specific to problems posed by Crystal Companies.

Here is the example package:

I hope you enjoyed this “technical” look inside Crystal Companies!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.