Mini Project - 4

규수·2026년 4월 16일

개발 공부 일지

목록 보기
28/44

오늘은 Balatro의 핵심 재미 요소.
JokerCard에 대한 구현과 Jokercard를 통한 카드계산 기능 구현에 대한 내용을 정리해보겠습니다.

구현 흐름

조커 카드를 생성하고 카드 계산을 위해 다음과 같은 흐름으로 기획하였습니다.

JokerCard - JokerCard에 대한 기본 변수, 이미지 정의, 조커 effectType에 따른 계산함수 호출
JokerCalculator - 조커카드별 점수 계산식 정의
JokerSlot - 상점에서 조커 구매시 배치될 공간. 카드계산시 해당 조커카드가 슬롯에 배치되어있을 경우 조커계산대상.

1. 조커카드의 등급, 기본 Chip,Mult, 조커효과타입, 카드 가격으로 카드를 생성한다.
2. 생성된 카드는 추후 상점에 랜덤하게 등장한다.
3. 상점에 등장된 조커카드를 구매하면 조커슬롯에 구매한 카드가 배치된다.
4. 배치된 조커카드가 존재한다면 카드 계산시 족보판정에 기여한 카드 칩 계산 다음 순서에 조커 카드 계산을 진행한다.
5. 조커 카드계산은 구매한 순서대로 계산시 Queue에 저장 후, 순차적으로 계산 함수를 호출한다.(JokerCard)
6. JokerCard에서 전달받은 효과를 기반으로 매칭되는 계산함수를 호출한다.
7. 최종 호출받은 JokerCalculator에서 조건에 충족하는지 확인 후, 최종 Chip, Mult를 추가한다.

구현 코드

JokerCard, JokerSlot, JokerCalculator의 핵심 구현 코드는 다음과 같이 구현해보았습니다.

JokerCard.cs
<style>
public void Initalize(int i, string rarity,  float addmult, int addChip, int sellPrice, JokerEffectType effectType)
{
    this.rarity = rarity;
    this.addMult = addmult;
    this.addChip = addChip;
    this.sellPrice = sellPrice;
    this.effectType = effectType;

    string generateId = $"JokerCard_{i}";

    string name = (i < jokerNames.Length) ? jokerNames[i] : "" ;

    string descrition = (i < jokerDescription.Length) ? jokerDescription[i] : "";

    base.Initalize(generateId, name, descrition);


    //Debug.Log($"[JokerCard] Rarity: {this.rarity}, AddMult: {this.addMult}, AddChip: {addChip}, SellPrice: {this.sellPrice}, EffectType: {effectType}");
}

public void ProcessJokerEffect(Hand hand, HandRanking ranking, List<PlayCard> card, JokerScoreRecord score)
{
    switch (effectType) {
        case JokerEffectType.AddTwicePictureRank:
            JokerCalculator.AddTwicePictureRank(this, card); break;
        case JokerEffectType.AddMultCaseRanking:
            JokerCalculator.AddMultCaseRanking(this, card, ranking); break;
        case JokerEffectType.UseUnderThree:
            JokerCalculator.UseUnderThree(this, card, hand); break;
        case JokerEffectType.SpadeBonus:
            JokerCalculator.AddSpecificSuit(this, card, Suit.Spade); break;
        case JokerEffectType.DiamondBonus:
            JokerCalculator.AddSpecificSuit(this, card, Suit.Diamond); break;
        case JokerEffectType.HeartBonus:
            JokerCalculator.AddSpecificSuit(this, card, Suit.Heart); break;
        case JokerEffectType.ClubBonus:
            JokerCalculator.AddSpecificSuit(this, card, Suit.Club); break;
        case JokerEffectType.BuildMult:
            JokerCalculator.BuildMult(this, card, hand, executeCount); break;
    }
    score.totalChip += addChip;
    score.totalMult += addMult;
}
</style>
JokerSlot.cs
<style>
public void CalculateJokerEffect(JokerScoreRecord score)
{
    //Debug.Log($"CalculateJokerEffect, 호출 확인");
    if (hasJokerCount == 0) return;
    jokerEffect.Clear();
    
    FillJokerEffect();
    //Debug.Log($"CalculateJokerEffect, jokerEffect.Count: {jokerEffect.Count}");
    if (jokerEffect.Count <= 0)
    {
        //Debug.Log($"ProcessJokerEffect, jockerEffect Queue Count 0: {jokerEffect.Count}");
        return;
    }

    UseJokerEffect(score);

}

private void FillJokerEffect()
{
    for (int i = 0; i < hasJokerCards.Length; i++)
    {
        if (hasJokerCards[i] != null)
        {
            jokerEffect.Enqueue(hasJokerCards[i]);
        }
    }
}

private void UseJokerEffect(JokerScoreRecord score)
{
    Hand hand = FindFirstObjectByType<Hand>();
    if (hand == null) return;

    HandRanking ranking = hand.handEvaluator.handRanking;
    List<PlayCard> cards = hand.handEvaluator.scoreCards;


    while (jokerEffect.Count > 0)
    {
        JokerCard jokerCard = jokerEffect.Dequeue();
        jokerCard.ProcessJokerEffect(hand, ranking, cards, score);
        //Debug.Log($"UseJokerEffect, ProcessJokerEffect 호출 확인");
        if (jokerCard.effectType != JokerEffectType.BuildMult)
        {
            jokerCard.addChip = 0;
            jokerCard.addMult = 0;
        }
    }
}
</style>
JokerCalculator.cs
<style>
public static class JokerCalculator
{
    public static void AddTwicePictureRank(JokerCard joker, List<PlayCard> selectCard)
    {
        if (selectCard == null) return;
        //Debug.Log($"그림카드 두번 동작확인");
        foreach (var card in selectCard)
        {
            if (card.rank > 10)
            {
                joker.addChip += card.rank;
            }        
        }
        //Debug.Log($"그림카드 두번 동작, 얻게된 총 Chip : {joker.addChip}");
    }

    public static void UseUnderThree(JokerCard joker, List<PlayCard> selectCard, Hand hand)
    {
        if (selectCard == null) return;

        if (selectCard.Count > 3 || hand.selectCardList.Count > 3) return;

        joker.addMult += 10;

        //Debug.Log($"3장이하 사용 동작 확인, 얻게된 총 Mult : {joker.addMult}");
    }
}
</style>

오늘은 조커카드에 대한 구현을 진행하였습니다.
처음에는 Static으로 선언하지 않았지만 여러 곳에서 new로 호출하다보니 계산로직이 불필요하게 생성되는 문제도 있고 전역에서 접근하는 편의성을 가지는 것이 옳다고 판단하여 싱글톤으로 전환하려 했습니다. 그러다 싱글톤은 계산만 담당하는 스크립트에서는 적합하지 않다는 것을 배워 Static으로 전환하게 되었습니다.

0개의 댓글