A downloadable game

ENGLISH VERSION (french version below)

We will see how to create an animated mechanical counter on Unity 3D.

In this example, it is a depth gauge, which calculates the depth of a submarine moving underwater.


How does it works?!

Depending on the depth of the descent of our submarine, we will make the images corresponding to a numerical value visible on the screen.

When we go beyond the 9, we have to move the image of the tens. And when the tens and units reach 9 together, we move the hundreds.

But the animation of the images requires adjustments in order to keep a perfect fluidity.
The images of the units, tens and hundreds contain a sequence of numbers.
If we had made an image from 0 to 9, the passage from 9 to 0 would have been impossible because the animation from 9 to 0 requires displaying both numbers at the same time!

The duplicated digit 0 allows the animation from 9 to 0 and then from 0 to 1 without jerking. We could just as well have started at 5 and continued on the numbers 6, 7, 8, 9, 0, 1, ..., 5, 6, which would have been the same. But for the sake of logical calculations, we start at 0.

This animation system allows for fluid and perpetual animation.




An Image GameObject

I created the figures with the UI -> Text component, but there were problems with shifting depending on the resolution used.
In order to have a rendering that doesn't move, it's finally better to use an image.

The image is therefore integrated into a UI GameObject -> Image.
You need to duplicate this GameObject three times, for the units, tens and hundreds.



Calibrate each GameObject Image to the bottom right (or however you like, depending on how you create your mechanical counter).

The structure in the hierarchy is as follows:
Counter : GameObject UI -> Image
Hundreds, Tens, Units : GameObject child UI -> Image



The C# code

First of all, we need to calculate precisely the step between each digit on the image in order to make the depth level visible graphically.
So we measure the starting value Y: -161.98. Then we graphically move the image to the next 0, which gives Y = -1.4.
This gives a total delta of 161.98 + 1.4 = 163.38


We will therefore store the position of each number in an array.

private float[] _step = new float[11];
private void Start() {
  for (ushort i = 0; i < 10; i++) {
  {
    // pas = pos Y + (delta * i / 10)
    _step[i] = -161.98f + (163.38f * i / 10);
  }
}


It's better to calculate the steps beforehand than to calculate them in real time in the middle of the animation. Don't forget that we are in a video game, so we need to consume as little CPU as possible!

Then, in the FixedUpdate method, we will add the engine code of the mechanical counter:

void FixedUpdate()
{
    // If you are underwater, the counter starts its animation
    if (GameManager.isUnderwater)
    {
        // The modulo will return the remainder of division
        // by 10, so a digit between 0 and 9
        _unitsFloat = playerDepth % 10;
        _units = (int)_unitsFloat;
        // The tens are divided by 10 thanks to the multiplication
        // by 0.1
        // Division takes 4 times more CPU cycles than
        // multiplication!
        _tensFloat = playerDepth * 0.1f % 10;
        _tens = playerDepth >= 10 ? (int)_tensFloat : 0;
        _hundredsFloat = playerDepth * 0.01f % 100;
        _hundreds = playerDepth >= 100 ? (int)_hundredsFloat : 0;
        // Lerp allows to perform a linear interpolation,
        // see the Unity documentation ;-)
        _unitsConvert = Mathf.Lerp(_step[0], _step[_step.Length -1], _unitsFloat * 0.1f);
        if (_units == 9)
        {
            // Moving the tens
            _tensConvert = Mathf.Lerp(_step[_tens], _step[_tens + 1], _unitsFloat - 9);
            if (_tens == 9)
            {
                // Moving the hundreds
                _hundredsConvert = Mathf.Lerp(_step[_hundreds], _step[_hundreds + 1], _unitsFloat - 9);
            }
        }
        else
        {
            // Counter animation on all counter readings
            _tensConvert = Mathf.Lerp(_step[0], _step[_step.Length -1], _tens * 0.1f);
            _hundredsConvert = Mathf.Lerp(_step[0], _step[_step.Length -1], _hundreds * 0.1f);
        }
        // Apply the new Y position to the IU image at each 
        // counter: units, tens, hundreds.
        _ImageUnitsPos.anchoredPosition = new Vector3(-2.39f, _unitsConvert, 0f);
        _ImageTensPos.anchoredPosition = new Vector3(-15.34f, _tensConvert, 0f);
        _ImageHundredsPos.anchoredPosition = new Vector3(-27.48f, _hundredsConvert, 0f);
    }
}
Integrate the C# code to the Counter GameObject, and drag&drop the units, tens and hundreds GameObjects so that the script has the references.



Don't forget to check the Rect Mask 2D component at the Counter GameObject so that the numbers are hidden and that we only see the values we are interested in!



To conclude

Of course, you can always do a more optimised and cleaner C# code, and not bother with floating values writing in the source code. It is better to calculate the graphical values via a script rather than by hand.
The purpose of this tutorial is to explain the logic behind the operation of an old-fashioned mechanical meter.

I hope you enjoyed it!
Come and see its integration in my game STEAMPUNK GRAVITY on Tweeter.




VERSION FRANCAISE

Compteur mécanique à l'ancienne en C# sur Unity

Nous allons voir comment créer un compteur mécanique animé sur Unity 3D.

Dans cet exemple, il s'agit d'un profondimètre, qui calcule donc la profondeur d'un engin sous-marin se déplaçant sous l'eau.


La logique de fonctionnement

En fonction de la descente en profondeur dans l'eau par notre engin, on va faire interagir les images correspondantes à une valeur chiffrée et visible à l'écran.

Quand on dépasse l'unité 9, il faut déplacer l'image des dizaines. Et quand les dizaines et unités arrivent  à 9 ensemble, on déplace les centaines.

Mais l'animation des images nécessite des réglages afin de garder une fluidité parfaite.
Les images des unités, dizaines et centaines contiennent une suite de chiffres.
Si on avait fait une image de 0 à 9, le passage de 9 à 0 aurait été impossible car l'animation du 9 au 0 nécessite d'afficher les deux chiffres en même temps !

Le chiffre 0 dupliqué permet d'avoir l'animation du 9 jusqu'au 0 puis du 0 au 1 sans saccade. On aurait pu tout aussi bien commencer à 5 puis continuer sur les chiffres 6, 7, 8, 9, 0, 1, ..., 5, 6, ce qui aurait été pareil. Mais dans un soucis de logique de calculs, on commence à 0.

Ce système d'animation permet d'avoir une animation fluide et perpétuelle.




Un GameObject Image

J'ai créé les chiffres avec le composant UI -> Text mais il y avait des problèmes de décalage selon la résolution utilisée.
Afin d'avoir un rendu qui ne bouge pas, il est finalement préférable d'utiliser une image.

L'image est donc intégrée dans un GameObject UI -> Image.
Il faut dupliquer ce GameObject trois fois, pour les unités, les dizaines et les centaines.



Calibrer chaque GameObject Image en bas à droite (ou comme vous voulez, cela dépendra de la manière dont vous créez votre compteur mécanique).

La structure dans la hiérarchie est ainsi :
Counter : GameObject UI -> Image
Hundreds, Tens, Units : GameObject enfant  UI -> Image



Le code C#

Il faut dans un premier temps calculer précisément le pas entre chaque chiffre sur l'image afin de rendre le niveau de profondeur visible graphiquement.
On mesure donc la valeur de départ Y : -161.98. Puis on déplace graphiquement l'image jusqu'au prochain 0, ce qui donne Y = -1.4.
Ce qui donne un delta total de 161.98 + 1.4 = 163.38


On va donc stocker la position de chaque chiffre dans un tableau.

private float[] _step = new float[11];
private void Start() {
  for (ushort i = 0; i < 10; i++) {
  {
    // pas = pos Y + (delta * i / 10)
    _step[i] = -161.98f + (163.38f * i / 10);
  }
}


C'est mieux de faire un calcul préalable des pas plutôt que de les calculer en temps réel au milieu de l'animation. Il ne faut pas oublier que nous sommes dans un jeu vidéo, alors il faut consommer le minimum de CPU !

Ensuite, dans la méthode FixedUpdate, on va ajouter le code moteur du compteur mécanique :

void FixedUpdate()
{
    // Si on est sous l'eau, le compteur démarre son animation
    if (GameManager.isUnderwater)
    {
        // Le modulo retournera le restant de division par 10,
        // soit un chiffre en 0 et 9
        _unitsFloat = playerDepth % 10;
        _units = (int)_unitsFloat;
        // Les dizaines sont divisées par 10 grâce à la 
        // multiplication par 0.1
        // La division prends 4 fois plus de cycles CPU que
        // la multiplication !
        _tensFloat = playerDepth * 0.1f % 10;
        _tens = playerDepth >= 10 ? (int)_tensFloat : 0;     
        _hundredsFloat = playerDepth * 0.01f % 100;
        _hundreds = playerDepth >= 100 ? (int)_hundredsFloat : 0;
        // Lerp permet d'effectuer une interpolation linéaire,
        // voir la doc de Unity ;-)
        _unitsConvert = Mathf.Lerp(_step[0], _step[_step.Length -1], _unitsFloat * 0.1f);
        if (_units == 9)
        {
            // On bouge les dizaines
            _tensConvert = Mathf.Lerp(_step[_tens], _step[_tens + 1], _unitsFloat - 9);
            if (_tens == 9)
            {
                // On bouge les centaines
                _hundredsConvert = Mathf.Lerp(_step[_hundreds], _step[_hundreds + 1], _unitsFloat - 9);
            }
        }
        else
        {
            // animation du compteur sur l'ensemble des valeurs
            // du compteur
            _tensConvert = Mathf.Lerp(_step[0], _step[_step.Length -1], _tens * 0.1f);
            _hundredsConvert = Mathf.Lerp(_step[0], _step[_step.Length -1], _hundreds * 0.1f);
        }
        // Applique la nouvelle position Y à l'image de l'UI
        // à chaque compteur : unités, dizaines, centaines.
        _ImageUnitsPos.anchoredPosition = new Vector3(-2.39f, _unitsConvert, 0f);
        _ImageTensPos.anchoredPosition = new Vector3(-15.34f, _tensConvert, 0f);
        _ImageHundredsPos.anchoredPosition = new Vector3(-27.48f, _hundredsConvert, 0f);
    }
}
Intégrer le code C# au GameObject Counter, et faire un drag&drop des GameObjects des unités, dizaines et centaines afin que le script ait les références.



Ne pas oublier de cocher le Rect Mask 2D au GameObject Counter afin que les chiffres soient cachés et que l'on ne voit que les valeurs qui nous intéressent !



Pour conclure

Evidemment, on peut toujours faire un code C# plus optimisé et plus propre, et ne pas s'embêter avec des chiffres en durs. Il est préférable de calculer les valeurs graphiques via un script plutôt qu'à la main.
Ce tutoriel a pour but de dégrossir la logique de fonctionnement d'un compteur mécanique à l'ancienne.

J'espère que ça vous a plu !
Venez voir son intégration dans mon jeu STEAMPUNK GRAVITY, visible sur Tweeter.



Leave a comment

Log in with itch.io to leave a comment.