Any tutorials on how to make the fan shape and hovers of Slay the Spire hand?

I am trying to do this for couple of days…and the best i got is this


and when hover

I do not like it…looks bad and feels bad.
I want something like this


My attempt

 public void ArrangeCardsHover(int cardIndex)
    {
        List<GameObject> cards = new List<GameObject>();

        foreach (Transform child in handCenter)
        {
            cards.Add(child.gameObject);
        }

        int cardCount = cards.Count;

        // Calculate the total width occupied by spaces between cards
        float totalWidth = (cardCount - 1) * spacing;

        // Calculate the starting position for the first card to be centered horizontally
        float startXPos = handCenter.position.x - (totalWidth / 2f);

        for (int i = 0; i < cardCount; i++)
        {
            float extraSpacing = 0f;

            if (i > cardIndex && cardIndex != -1)
            {
                extraSpacing = extraSpacingAfterIndex;
            }

            // Update x position based on index, spacing, and extraSpacing
            float xPos = startXPos + (i * spacing) + extraSpacing;

            // Keep the y position the same as handCenter
            float yPos = handCenter.position.y;

            // Set the position
            cards[i].transform.position = new Vector3(xPos, yPos, 0f);

            //stop any tween except the index
            if (cardIndex != i)
            {
                LeanTween.cancel(cards[i]);
                //LeanTween.cancel(cards[i].transform.GetChild(0).gameObject);
            }
        
        }
    }

And on the hover i just use this script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class CardEvents : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
    private float hoverScale = 1.2f;
    private float transitionTime = 0.2f;
    private float hoverHeight = 100f;
    private Vector3 originalScale;


    private int index = -1; // Initialize the index to -1

    private GameObject childObjectVisual;

    public LTDescr scaleTween;
    public LTDescr moveTween;

    void Start()
    {
        originalScale = transform.localScale;
        childObjectVisual = gameObject.transform.GetChild(0).gameObject;
        //originalPos = transform.position;
    }

    public void OnPointerEnter(PointerEventData eventData)
    {

        // Cancel any ongoing tweens
        //LeanTween.cancel(childObjectVisual);

        //DeckManager.Instance.ArrangeCardsHover(-1);

        // Scale up the hovered card
        scaleTween = LeanTween.scale(childObjectVisual, originalScale * hoverScale, transitionTime);

        // Move the card slightly up in world space
        float targetY = transform.position.y + hoverHeight;
        moveTween = LeanTween.moveY(gameObject, targetY, transitionTime);

        index = FindCardIndex(childObjectVisual);


        DeckManager.Instance.ArrangeCardsHover(index);
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        index = -1; // Reset the index when the pointer exits the card
                    //DeckManager.Instance.ArrangeCardsHover(-1);

        //LeanTween.cancel(childObjectVisual); // Cancel any ongoing tweens

        // Scale down the card
        scaleTween = LeanTween.scale(childObjectVisual, originalScale, transitionTime);

        ////// Move the card slightly down in world space
        //float targetY = transform.position.y - hoverHeight;
        //LeanTween.moveY(gameObject, targetY, transitionTime);

        DeckManager.Instance.ArrangeCardsHover(-1);
    }

    private int FindCardIndex(GameObject card)
    {

        List<GameObject> cards = new List<GameObject>();

        foreach (Transform child in DeckManager.Instance.handCenter)
        {
            cards.Add(child.GetChild(0).gameObject);
        }

        // Iterate through the list of cards to find the index of the specified card
        for (int i = 0; i < cards.Count; i++)
        {
            if (cards[i] == card)
            {
                return i; // Return the index if the card is found
            }
        }
        return -1; // Return -1 if the card is not found
    }

}

I would get away from this kinda computation and put something in the scene to give you that all in a nice line.

For “something in the scene” you could use the Unity splines package and set up a “here to there” that you then distribute the cards along by evaluating the spline (in code).

if you don’t want Unity’s spline / bezier system, I found this one worked pretty good:

https://github.com/yasirkula/UnityBezierSolution

3 Likes

There was a similar question on the Discussions forum. You can check this reply for how to arrange the cards in an arc. You could probably modify it to increase the radius of the card that is being hovered:

2 Likes

This is really useful!

Thanks for the guidance!
I will try with splines and see.

Thank you!
Looks like an interesting way.

I will try first splines, if that fail i will try this too.

Actually started with your solution first as it was easier for me to understand. This is perfect!

Testing it
9817266--1410789--upload_2024-5-6_16-3-14.png
9817266--1410792--upload_2024-5-6_16-4-18.png
9817266--1410795--upload_2024-5-6_16-4-48.png
9817266--1410798--upload_2024-5-6_16-5-29.png
9817266--1410801--upload_2024-5-6_16-6-6.png

Do you know how I would do the hover that push the left side and the right side of the hand?

Edit : My thoughts on how to do it
Pseudo code

  • When on hover → get the index of the card
  • then we loop for every card
  • we store the original position of the card
  • from 0 until (index -1) we then LeanTween some distance left
  • from (index + 1) until (cards.count - 1) we then LeanTween some distance right

You increase/decrease the angles of the cards before and after the one you want to select, and you increase the radius of the card to slide it a little further out. If you want it to be a little bigger you increase the scale of it too.

2 Likes

So instead of this

  • When on hover → get the index of the card
  • then we loop for every card
  • we store the original position of the card
  • from 0 until (index -1) we then LeanTween some distance left
  • from (index + 1) until (cards.count - 1) we then LeanTween some distance right

I should do this?

  • When on hover → get the index of the card
  • then we loop for every card
  • we store the original position of the card
  • from 0 until (index -1) we then increase the angle
  • from (index + 1) until (cards.count - 1) we then increase the angle

Yes, but decrease the angle for 4. That assumes you want to fan them a little away from the selected card.

1 Like

I kinda wanted to do push instead of angle, as it looks more cleaner in my eyes.

It mostly working correctly but I have an issue when going from left to right…I have no idea how to handle that yet…

What I did so far

Anyone has any ideas?

HandManager Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandManager : MonoBehaviour
{
    public static HandManager Instance;

    public List<GameObject> cards; // Array to hold card GameObjects
    public List<GameObject> points;
    public Vector3[] pathControlPoints;
    public float duration = 3f;
    public float cardSpacing = 0.5f; // Spacing between cards

    public LTSpline spline;

    //
    public float angleDelta = 0;
    public float radius = 0;
    public GameObject centerObject;

    private void Awake()
    {

        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }

    }

    public void Start()
    {
        SetHandCards();
    }


    public void SetHandCards()
    {
        foreach (GameObject card in cards)
        {
            SetCardPosition(card);
        }
    }

    public void PushNeightbourCards(GameObject cardRef)
    {
        int index = FindCardIndex(cardRef);
        int cardPos = 0;


        foreach (GameObject card in cards)
        {

          
            if (cardPos < index)
            {

                //save the original position
                //card.GetComponent<CardEvents>().originalPos = card.transform;

                //push left based on the distance wanted
                float posX = card.transform.position.x + (-200);
                card.GetComponent<CardEvents>().moveTween = LeanTween.moveX(card, posX, 0.1f);
            }
            else if (cardPos > index)
            {
                //save the original position
                //card.GetComponent<CardEvents>().originalPos = card.transform;

                ////push right
                float posX = card.transform.position.x + (200);
                card.GetComponent<CardEvents>().moveTween = LeanTween.moveX(card, posX, 0.1f);
            }
            else
            {
                //do nothing (its the index card)
            }

            //increase the card position
            cardPos++;

        }

    }

    public void ResetNeightbourCards()
    {


        //cancel all previous tween except the previous hover card
        foreach (GameObject card in cards)
        {
            CardEvents cardEvents = card.GetComponent<CardEvents>();
           
            //cancel the move tween if exist
            if (cardEvents.moveTween != null) {
                LeanTween.cancel(card,cardEvents.moveTween.id);
            }
        }

        foreach (GameObject card in cards)
        {
            //move back to the original position
            card.GetComponent<CardEvents>().moveTween = LeanTween.moveX(card, card.GetComponent<CardEvents>().originalPosX, 0.1f);
        }
    }

    public void SetCardPosition(GameObject cardRef)
    {

        int index = FindCardIndex(cardRef);
        //The angle is based on how far the card is from the midpoint of the hand.
        //Note that the midpoint will either be a whole number or x.5
        float midpoint = (cards.Count - 1) / 2f;
        float angle = angleDelta * (midpoint - index);

        //Positive angles rotate counterclockwise, negative angles rotate clockwise
        cards[index].transform.eulerAngles = new Vector3(0, 0, angle);

        //Mathf uses radians
        //A card that is rotated counterclockwise is on the left side of the hand,
        //while a card rotated clockwise should be on the right side of the hand.
        //This means we need to flip either the angle or the x value when calculating the
        //position.
        angle *= -Mathf.Deg2Rad;
        float x = Mathf.Sin(angle) * radius;
        float y = Mathf.Cos(angle) * radius;
        cards[index].transform.position = new Vector3(centerObject.transform.position.x + x, centerObject.transform.position.y + y, 0);

        //save the original position
        cards[index].GetComponent<CardEvents>().originalPosX = cards[index].transform.position.x;
        cards[index].GetComponent<CardEvents>().originalPosY = cards[index].transform.position.y;
    }

    public int FindCardIndex(GameObject card)
    {

        // Iterate through the list of cards to find the index of the specified card
        for (int i = 0; i < cards.Count; i++)
        {
            if (cards[i] == card)
            {
                return i; // Return the index if the card is found
            }
        }
        return -1; // Return -1 if the card is not found
    }


}

CardEvent Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class CardEvents : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
    private float hoverScale = 1.2f;
    private float transitionTime = 0.1f;
    private float hoverHeight = 100f;
    private Vector3 originalScale;


    private int index = -1; // Initialize the index to -1

    private GameObject childObjectVisual;

    public LTDescr scaleTween;
    public LTDescr moveTween;
    public LTDescr localMoveTween;

    //new variables that actually work
    public float originalPosX;
    public float originalPosY;

    public enum stateOfEvent { hover,exithover,clicked }

    public stateOfEvent currentEvent;

    void Start()
    {
        originalScale = transform.localScale;
        childObjectVisual = gameObject.transform.GetChild(0).gameObject;
        //originalPos = transform.position;
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        Debug.Log("Entered : " + HandManager.Instance.FindCardIndex(this.gameObject));
        currentEvent = stateOfEvent.hover;
        // Cancel any ongoing tweens
        //LeanTween.cancel(childObjectVisual);


        // Scale up the hovered card
        scaleTween = LeanTween.scale(childObjectVisual, originalScale * hoverScale, transitionTime);

        //// Move the card slightly up in world space
        float targetY = transform.position.y + hoverHeight;
        localMoveTween = LeanTween.moveY(gameObject, targetY, transitionTime);

        StartCoroutine(WaitForOtherAnimationsToBeDone(0.1f));




    }

    IEnumerator WaitForOtherAnimationsToBeDone(float waitTime)
    {


        //yield on a new YieldInstruction that waits for 5 seconds.
        yield return new WaitForSeconds(waitTime);

        if (currentEvent == stateOfEvent.hover) {
            //push the other cards
            HandManager.Instance.PushNeightbourCards(this.gameObject);
        }
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        Debug.Log("Exited : " + HandManager.Instance.FindCardIndex(this.gameObject));
        currentEvent = stateOfEvent.exithover;
        index = -1; // Reset the index when the pointer exits the card

        //reset the position of all other cards
        HandManager.Instance.ResetNeightbourCards();


        // Scale down the card
        scaleTween = LeanTween.scale(childObjectVisual, originalScale, transitionTime);

        //// Move the card back
        localMoveTween = LeanTween.moveY(gameObject, originalPosY, transitionTime);




    }

}

Is the problem that when you move your mouse cursor to the right while hovering over card n, you end up hovering over card n+2 instead of n+1?

1 Like

Yep.

I guess its because i move them back to the original position…I might need to rethink it

I had to restructure some things and rethink the logic…but it works! I am done with most of it.
There are two things i am missing ( which is not hard to do)

I will provide the functional code here , in case anyone else will require it

https://www.youtube.com/watch?v=ow3Ha-TGE-A

HandManager

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HandManager : MonoBehaviour
{
    public static HandManager Instance;

    public List<GameObject> cards; // Array to hold card GameObjects
    public List<GameObject> points;



    //
    public float angleDelta = 0;
    public float radius = 0;
    public GameObject centerObject;

    public float pushingSpeed = 0.2f;
    public float resetSpeed = 0.2f;
    public float drawSpeed = 0.2f;

    private void Awake()
    {

        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }

    }

    public void Start()
    {
        //SetHandCards();
    }


    public void SetHandCards()
    {
        foreach (GameObject card in cards)
        {
            SetCardPosition(card);
        }
    }

    public void PushNeightbourCards(GameObject cardRef)
    {
        int index;
        int cardPos = 0;

        if (cardRef != null)
        {
            index = FindCardIndex(cardRef);
        }
        else
        {
            index = -1;
        }





        foreach (GameObject card in cards)
        {

            if (index != -1)
            {

                if (cardPos < index)
                {

                    //save the original position
                    //card.GetComponent<CardEvents>().originalPos = card.transform;

                    //push left based on the distance wanted
                    float posX = card.GetComponent<CardEvents>().originalPosX + (-200);
                    card.GetComponent<CardEvents>().moveTween = LeanTween.moveX(card, posX, pushingSpeed);
                }
                else if (cardPos > index)
                {
                    //save the original position
                    //card.GetComponent<CardEvents>().originalPos = card.transform;

                    ////push right
                    float posX = card.GetComponent<CardEvents>().originalPosX + (200);
                    card.GetComponent<CardEvents>().moveTween = LeanTween.moveX(card, posX, pushingSpeed);
                }
                else
                {
                    //do nothing (its the index card)
                }

            }
            else
            {
                float posX = card.GetComponent<CardEvents>().originalPosX;
                card.GetComponent<CardEvents>().moveTween = LeanTween.moveX(card, posX, pushingSpeed);
            }
            //increase the card position
            cardPos++;

        }

    }


    public void SetCardPosition(GameObject cardRef)
    {

        int index = FindCardIndex(cardRef);
        //The angle is based on how far the card is from the midpoint of the hand.
        //Note that the midpoint will either be a whole number or x.5
        float midpoint = (cards.Count - 1) / 2f;
        float angle = angleDelta * (midpoint - index);

        //Positive angles rotate counterclockwise, negative angles rotate clockwise
        cards[index].transform.eulerAngles = new Vector3(0, 0, angle);

        //Mathf uses radians
        //A card that is rotated counterclockwise is on the left side of the hand,
        //while a card rotated clockwise should be on the right side of the hand.
        //This means we need to flip either the angle or the x value when calculating the
        //position.
        angle *= -Mathf.Deg2Rad;
        float x = Mathf.Sin(angle) * radius;
        float y = Mathf.Cos(angle) * radius;
        //cards[index].transform.position = new Vector3(centerObject.transform.position.x + x, centerObject.transform.position.y + y, 0);

        LeanTween.move(cards[index], new Vector3(centerObject.transform.position.x + x, centerObject.transform.position.y + y, 0), drawSpeed);

        //save the original position
        cards[index].GetComponent<CardEvents>().originalPosX = centerObject.transform.position.x + x;
        cards[index].GetComponent<CardEvents>().originalPosY = centerObject.transform.position.y + y;
    }

    public int FindCardIndex(GameObject card)
    {

        // Iterate through the list of cards to find the index of the specified card
        for (int i = 0; i < cards.Count; i++)
        {
            if (cards[i] == card)
            {
                return i; // Return the index if the card is found
            }
        }
        return -1; // Return -1 if the card is not found
    }


}

CardEvents

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class CardEvents : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
    private float hoverScale = 1.2f;
    private float transitionTime = 0.1f;
    private float hoverHeight = 100f;
    private Vector3 originalScale;


    private int index = -1; // Initialize the index to -1

    private GameObject childObjectVisual;

    public LTDescr scaleTween;
    public LTDescr moveTween;
    public LTDescr localMoveTween;

    //new variables that actually work
    public float originalPosX;
    public float originalPosY;

    public enum stateOfEvent { hover,exithover,clicked }

    public stateOfEvent currentEvent;

    void Start()
    {
        originalScale = transform.localScale;
        childObjectVisual = gameObject.transform.GetChild(0).gameObject;
        //originalPos = transform.position;
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        Debug.Log("Entered : " + HandManager.Instance.FindCardIndex(this.gameObject));
        currentEvent = stateOfEvent.hover;
        // Cancel any ongoing tweens
        //LeanTween.cancel(childObjectVisual);


        // Scale up the hovered card
        scaleTween = LeanTween.scale(childObjectVisual, originalScale * hoverScale, transitionTime);

        //// Move the card slightly up in world space
        float targetY = transform.position.y + hoverHeight;
        localMoveTween = LeanTween.moveY(gameObject, targetY, transitionTime);

        HandManager.Instance.PushNeightbourCards(this.gameObject);



    }



    public void OnPointerExit(PointerEventData eventData)
    {
        Debug.Log("Exited : " + HandManager.Instance.FindCardIndex(this.gameObject));
        currentEvent = stateOfEvent.exithover;
        index = -1; // Reset the index when the pointer exits the card

        //reset the position of all other cards
        HandManager.Instance.PushNeightbourCards(null);

        // Scale down the card
        scaleTween = LeanTween.scale(childObjectVisual, originalScale, transitionTime);

        //// Move the card back
        localMoveTween = LeanTween.moveY(gameObject, originalPosY, transitionTime);




    }

}

Thanks everyone for the great help!
Without your input I would have never make it!

1 Like

Regardless of your other questions above, this can be a very useful pattern:

  • given X cards, compute X positions in your “in hand” arc

  • always move the cards smoothly towards those positions

When you deal a fresh card you add it to the in hand collection. This will:

  • increase X by one (X should simply be a property of the collection, such as List.Count)

  • cause the others to smoothly move aside

  • cause the new card to zip to where it needs to be.

Vector3.Lerp() cycled again and again on a position can give a very convincing move-quick-to-dest appearance.

And then for “select these X cards” just transiently set their positions to “local transform up” a bit from the arc and let the cards move themselves up.

This lets you select any number of cards.

1 Like

Basically that’s what I done, I believe you are correct!

My issue was resetting the position instead of smoothly assign them to the new next position