본문 바로가기

프로젝트/미니게임파티

두 번째 미니게임 블록 깨기 개발 일지 3

728x90
반응형
  • 공 반사 로직 수정
  • 아이템 블록이 깨지면 아이템 떨어트리는 기능 구현
  • 폭탄, +, -, 안전망 아이템 기능 구현

 

공 반사 로직 수정

기존의 공 반사 로직에서 제대로된 반사가 이루어지지 않는 경우가 있어 로직을 조금 수정했다.

공이 블록이나 벽에 반사 될 때 크게 두 가지 (수평, 수직) 경우로만 나누어서 처리해주었다.

충돌 지점의 방향이 수직인 경우는 수평 반사, 충돌 지점의 방향이 수평인 경우는 수직 반사로 처리해주었다.

충졸 지점의 방향은 상,하,좌,우 벡터와 내적을 계산하여 0.9 초과인 경우로 판단하였다.

var normal = collision.contacts[0].normal;

if (Vector2.Dot(normal, Vector2.down) > 0.9f || (Vector2.Dot(normal, Vector2.up) > 0.9f))
{
    direction = Vector2.Reflect(lastVelocity.normalized, Vector2.right.normalized);
}
else if (Vector2.Dot(normal, Vector2.right) > 0.9f || Vector2.Dot(normal, Vector2.left) > 0.9f)
{
    direction = Vector2.Reflect(lastVelocity.normalized, Vector2.up.normalized);
}

아이템 블록이 깨지면 떨어트리는 기능 구현

우선 아이템 블록을 기존 블록과 분리하기 위해 Brick을 상속 받는 ItemBrick을 만들었다.

그리고 Brick의 함수들은 virtual로 변경해주고 ItemBrick에서는 override하여 함수를 작성하였다.

public class Brick : MonoBehaviour
{
    public SpriteRenderer mainSprite;
    public SpriteRenderer subSprite;

    public virtual void SetColors(Color32 mainColor, Color32 subColor)
    {
        mainSprite.color = mainColor;

        if(subSprite != null)
        {
            subSprite.color = subColor;
        }
    }

    public virtual void OnBrickDestroyed() {}
}

public class ItemBrick : Brick
{
    public GameObject Item;
    private GameObject item;

    // 아이템을 미리 블록 위치에 생성해둔다.
    private void Awake()
    {
        item = Instantiate(Item, transform.position, Item.transform.rotation);
        item.SetActive(false);
    }

    // 아이템 색상도 같이 변경될 수 있도록 override하여 함수 작성
    public override void SetColors(Color32 mainColor, Color32 subColor)
    {
        base.SetColors(mainColor, subColor);
        item.GetComponent<Item>().SetColor(subColor);
    }

    // 블록이 부셔질 때 아이템 떨어트리기
    public override void OnBrickDestroyed() 
    {
        DropItem();
    }

    private void DropItem()
    {
        item.SetActive(true);
    }
}

아이템은 구분하기 위해 ItemType enum을 추가하였고, 스스로 아래로 떨어지며 패들과 부딪히거나 화면 밖으로 벗어나면 사라지도록 만들었다.

public class Item : MonoBehaviour
{
    public ItemType ItemType;
    public SpriteRenderer Sprite;
    private float dropSpeed = 8;

    private void Update()
    {
        transform.Translate(dropSpeed * Time.deltaTime * Vector3.down);

        if(transform.position.y < -12)
        {
            Destroy(transform.gameObject);
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.CompareTag("Paddle"))
        {
            Destroy(transform.gameObject);
        }
    }

    public void SetColor(Color32 color)
    {
        Sprite.color = color;
    }
}

아이템이 기능이 실행되는 부분은 PaddleController에 추가하였다.

GameManager에서 아이템 기능이 실행되는 경우도 염두에 두어 PaddleController에 추가했다.

private void OnTriggerEnter2D(Collider2D collision)
    {
        var Item = collision.gameObject.GetComponent<Item>();

        if (Item != null)
        {
            switch (Item.ItemType)
            {
                case ItemType.Ball:
                    break;
                case ItemType.Bomb:
                    break;
                case ItemType.Heart:
                    break;
                case ItemType.Minus:
                    break;
                case ItemType.Plus:
                    break;
                case ItemType.Safetynet:
                    break;
            }
        }
    }

폭탄 기능

폭탄은 먹는 경우 패들의 속도가 느려지는 디버프 아이템이다.

속도가 느려지는 동안을 알 수 있도록 시각적인 색상 애니메이션도 추가했다.

private void ApplySlowEffect(float slowMultiplier, float duration)
{
    StopAllCoroutines();
    StartCoroutine(SlowRoutine(slowMultiplier, duration));
    StartCoroutine(SmoothColorChange(duration));
}

private IEnumerator SlowRoutine(float slowMultiplier, float duration)
{
    currentSpeed = defaultSpeed * slowMultiplier;
    yield return new WaitForSeconds(duration);
    currentSpeed = defaultSpeed;
}

private IEnumerator SmoothColorChange(float duration)
{
    float halfDuration = duration / 2f;
    float t = 0f;

    // 지속 시간 반 동안은 어두워졌다가
    while (t < halfDuration)
    {
        t += Time.deltaTime;
        float lerpT = t / halfDuration;
        spriteRenderer.color = Color.Lerp(Color.white, new Color32(50, 50, 50, 250), lerpT);
        yield return null;
    }

    t = 0f;

    // 남은 지속 시간 반 동안은 다시 원래 색상으로
    while (t < halfDuration)
    {
        t += Time.deltaTime;
        float lerpT = t / halfDuration;
        spriteRenderer.color = Color.Lerp(new Color32(50, 50, 50, 250), Color.white, lerpT);
        yield return null;
    }

    spriteRenderer.color = Color.white;
}

+ & - 기능

+와 -는 패들의 길이가 늘어나는 버프와 줄어드는 디버프이다.

늘어나고 줄어들 때 자연스러울 수 있도록 애니메이션을 추가했다.

private void ApplyScaleEffect(float scaleMultiplier, float scaleDuration, float holdDuration)
{
    StopCoroutine(nameof(ScaleRoutine));
    StartCoroutine(ScaleRoutine(scaleMultiplier, scaleDuration, holdDuration));
}

private IEnumerator ScaleRoutine(float scaleMultiplier, float scaleDuration, float holdDuration)
{
    Vector3 targetScale = new Vector3(defaultScale.x * scaleMultiplier, defaultScale.y, defaultScale.z);
    Vector3 originalScale = defaultScale;

    float t = 0f;

    // 자연스러운 스케일 변경 애니메이션
    while (t < scaleDuration)
    {
        t += Time.deltaTime;
        float lerpT = t / scaleDuration;
        transform.localScale = Vector3.Lerp(originalScale, targetScale, lerpT);
        yield return null;
    }

    transform.localScale = targetScale;

    // 지속 시간까지 변경된 스케일 유지
    yield return new WaitForSeconds(holdDuration);

    // 지속 시간이 끝나면 기존 크기 변경 애니메이션
    t = 0f;
    while (t < scaleDuration)
    {
        t += Time.deltaTime;
        float lerpT = t / scaleDuration;
        transform.localScale = Vector3.Lerp(targetScale, originalScale, lerpT);
        yield return null;
    }

    transform.localScale = originalScale;
}

그리고 패들의 길이가 변경되면서 기존에 화면 밖으로 벗어나지 않도록 보간했던 부분도 수정이 필요했다.

private void Update()
{
    float currentX = transform.position.x;
    float newX = currentX;

    if (Input.GetKey(KeyCode.LeftArrow))
    {
        newX -= currentSpeed;
    } else if (Input.GetKey(KeyCode.RightArrow))
    {
        newX += currentSpeed;
    }

    // 기존 scale 1을 기준으로 보간값 새롭게 계산
    float currentScale = transform.localScale.x;
    float clamX = 14.8f + (1f - currentScale) * 3f;

    newX = Mathf.Clamp(newX, -clamX, clamX);

    transform.position = new Vector3(newX, transform.position.y, transform.position.z);
}

안전망 기능

사실 이름을 뭐라고 할지 몰라서 내맘대로 안전망이라고 붙였는데 아이템을 먹으면 일회용 패들이 아래에 깔리는 기능이다.

해상도가 가로로 길기 때문에 5개로 배치하였고 안전망 아이템을 먹었을 때 비어있는 안전망만 올라오도록 구현했다.

각 안전망마다 Safetynet 컴포넌트를 추가하여 개별적으로 비활성화 상태인 경우만 활성화 함수를 실행하도록 했다.

public class Safetynet : MonoBehaviour
{
    // 안전망이 활성화 되어 있는지 상태
    public bool isActive;

    // 안정망 활성화 함수
    public void ActiveSafetynet()
    {
        gameObject.SetActive(true);
        isActive = true;

        StopAllCoroutines();
        StartCoroutine(FloatSafetynet());
    }

    // 안전망에 공이 부딪히면 사라짐
    private void OnCollisionEnter2D(Collision2D collision)
    {
        StartCoroutine(DropSafetynet());
    }

    // 안전망이 위로 올라오는 애니메이션
    IEnumerator FloatSafetynet()
    {
        Vector3 startPosition = transform.position;
        Vector3 targetPosition = startPosition + new Vector3(0, 2, 0);

        float duration = 0.5f;
        float elapsed = 0f;

        while (elapsed < duration)
        {
            transform.position = Vector3.Lerp(startPosition, targetPosition, elapsed / duration);
            elapsed += Time.deltaTime;
            yield return null;
        }
    }

    // 안전망이 아래로 가라앉고 사라지는 애니메이션
    IEnumerator DropSafetynet()
    {
        Vector3 startPosition = transform.position;
        Vector3 targetPosition = startPosition - new Vector3(0, 2, 0);

        float duration = 0.5f;
        float elapsed = 0f;

        while (elapsed < duration)
        {
            transform.position = Vector3.Lerp(startPosition, targetPosition, elapsed / duration);
            elapsed += Time.deltaTime;
            yield return null;
        }

        gameObject.SetActive(false);
        isActive = false;
    }
}

 

728x90
반응형