Unity Chapter 6-2. 어메이징 볼링 게임 만들기 : 프롭, 데미지 시스템

Date:     Updated:

카테고리:

태그:

인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!


Chapter 6. 어메이징 볼링 게임 만들기

프롭들 만들기

  • 프롭 : Ball 오브젝트 주변에 분포 되어있는 큐브들!
    • Ball이 떨어지는 곳 주변의 프롭들 또한 같이 파괴된다.
    • 그리고 물리적 영향을 받아 튕겨져 나가기도 할 것.
    • 크기를 다양하게 할 것이다.
  1. Cube 오브젝트 만들기
    • 이름은 Small Prop
    • Material로 색도 입혀주자.
  2. Rigid body 컴포넌트를 붙여준다.
    • Ball이 떨어지는 곳 주변 프롭들은 튕겨져 나가야 하는 등등 물리적 영향을 받아야 하므로!
  3. 📜Prop.cs 스크립트를 만들어 붙여주자

📜 Prop.cs 의 역할

  • 체력을 가지고 있어서 데미지를 받아 체력이 다 하면 파괴되야 함.
  • 프롭의 파티클 오브젝트를 실시간으로 생성한다
  • 나중에 만들 GameManager 에게 자신이 파괴 됐다는 것을 알려 점수를 계산하게끔 해야함.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Prop : MonoBehaviour
{
    public int score = 5; // 자기 자신이 파괴될 때마다 올라갈 점수 = 5점
    public ParticleSystem explosionParticle; // 프롭도 파괴되면 파티클 효과를 재생할 것. Ball과 다르게 파티클 프리팹을 실시간으로 찍어낼 것.
    public float hp = 10f; // 자기 자신(Small Prop)의 체력

    public void TakeDamage(float damage) // 프롭이 스스로 발동하는게 아니라 외부에서 실행시켜 줄 함수. 그래서 public 함수로 만듬.
    {
        hp -= damage;

        if (hp <= 0)
        {
            ParticleSystem instance = Instantiate(explosionParticle, transform.position, transform.rotation); // 파티클 시스템 생성
            Destroy(instance.gameObject, instance.duration); // 해당 파티클이 수명 다한 후 파괴되게끔
            gameObject.SetActive(false); // 프롭은 정말 많기 때문에 렉걸릴 수 있으므로 터진 프롭은 꺼주기. 프롭은 오브젝트 자체를 파괴하지 말고 그냥 꺼주기만.
        }
    }
}
  • 변수
    • score
      • 자기 자신(Small Prop)이 파괴될 때마다 올라갈 점수 = 5점
    • explosionParticle
      • 프롭이 터질 때 재생할 Particle System 컴포넌트를 가진 오브젝트를 이 슬롯에 드래그 앤 드롭 해야한다.
      • 즉 대충 파티클 오브젝트.
      • Ball과 다르게 게임 도중 파티클 오브젝트가 실시간으로 생성되게끔 할 것이다.
    • hp
      • 자기 자신(Small Prop)의 체력
  • 함수
    • public void TakeDamage(float damage)
      • 자기 자신(Small Prop)이 실행하는 함수가 아니라 외부에서 실행시킬 함수.
        • 따라서 public으로 선언해준다.
        • 외부에서 Small Prop에게 데미지를 줄 때 외부에서 실행될 것.
      • 자기 자신(Small Prop)의 체력을 깎고 (hp -= damage;)
      • 체력이 다하면 (if (hp <= 0))
        • ParticleSystem instance = Instantiate(explosionParticle, transform.position, transform.rotation);
          • 게임 도중 실시간으로 explosionParicle 변수가 참조하고 있는 파티클 시스템 오브젝트를 복사하여 생성한다.
            • 프롭의 파티클은 실시간으로 생성하는 것.
          • Ball은 그냥 파티클 오브젝트를 처음부터 자식으로 데리고 있었는데 그 와 다르게 프롭의 파티클 오브젝트는 게임 도중 생성되게 해주자
          • 자기 자신(Small Prop)의 위치, 회전값 에서 생성
        • Destroy(instance.gameObject, instance.duration)
          • instance.gameObject : 즉 Particle System 이 붙어있는 오브젝트를 파괴
          • instance.duration : 파티클 오브젝트의 런타임이 지나고 난 후 파괴
        • gameObject.SetActive(false);
          • Ball과 다르게 Small Prop은 터져도 오브젝트 자체를 Destroy 하지 않는다.
          • 그냥 안보이게 꺼주자.
          • Small Prop 오브젝트는 정말 많아서 다 Destroy 해주다 보면 렉 걸릴 수 있어서

프롭의 파티클 효과 & 효과음

  • Import 해온 파티클 프리팹들 중 SmallExplosion을 Hierarchy 창으로 옮겨주어 오브젝트화 하자.
  • 포탄 파티클 효과 때와 마찬가지로
    • Particle System 컴포넌트 에서
      • looping을 자식들까지 모두 해제해주고
      • Play on Awake 또한 자식들까지 모두 꺼주자
    • Audio Source 컴포넌트도 붙여주자.
      • Audio Clip엔 Import 받은 적당한 효과음을 넣어주자.
      • Play on Awake 는 꺼준다.
  • 수정한 이 SmallExplosion 오브젝트를 📁Prefabs 폴더로 옮겨 나만의 프리팹으로 만들어주자.
  • Small Prop도 프리팹으로 만들어주자
  • Hierarchy 창에 있는 기존 SmallExplosion 오브젝트는 지워 주도록 하자. 게임 도중에 실시간으로 찍어내어 생성할거니까 !
  • ✨✨Small Propexplosion Particle 슬롯에 SmallExplosion 프리팹을 드래그 앤 드롭 해주자.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Prop : MonoBehaviour
{
    public int score = 5; 
    public ParticleSystem explosionParticle; 
    public float hp = 10f;

    public void TakeDamage(float damage) 
    {
        hp -= damage;

        if (hp <= 0)
        {
            ParticleSystem instance = Instantiate(explosionParticle, transform.position, transform.rotation);

            AudioSource explosionAudio = instance.GetComponent<AudioSource>();
            explosionAudio.Play();

            Destroy(instance.gameObject, instance.duration); 
            gameObject.SetActive(false); 
        }
    }
}
  • *AudioSource explosionAudio = instance.GetComponent();*
    • 생성한 오브젝트(explosionParticle이 참조 중인 오브젝트의 사본)의 AudioSource 컴포넌트를 가져온다.


데미지 시스템

  • Ball이 떨어지는 곳 폭발 반경 내에 있는 Prop들에게 데미지를 주어야 한다.(*TakeDamage함수*)
  • Ball폭발 반경을 보이지 않는 가상의 구로 그리고 구에 속하는 모든 `Prop`들을 가져와서 TakeDamage 함수를 발동할 것이다.
    • (구의 중심 + 구의 반지름) 거리 안에 위치한 Prop들은 폭발 반경 안에 있다고 할 수 있다.

Layer를 사용하여 폭발 반경안에 있는 Prop들 구별

  • 유니티에선 TagLayer오브젝트들을 구별할 수 있다.
    • TagLayer의 차이
      • Tag
        • 일반적인 경우에 사용
        • 1:1 비교만 가능
        • 한 오브젝트는 하나의 태그만 가질 수 있다.
      • Layer
        • 주로 물리 처리시 사용
        • 한번에 여러개 필터링 가능
        • 한 오브젝트는 여러개의 레이어를 가질 수 있다. 다중선택 가능.
  1. Layer - Add Layer - “Prop” 추가
  2. Small Prop의 Layer를 “Prop”으로 해준다.
  3. Apply all 해주기.

📜 Ball.cs : 데미지 계산

프롭의 데미지를 깎는건 Ball이 한다.

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

public class Ball : MonoBehaviour
{
    public LayerMask whatIsProp;

    public ParticleSystem explosionParticle;
    public AudioSource explosionAudio;

    public float maxDamage = 100f; 
    public float explosionForce = 1000f; 
    public float lifeTime = 10f; 
    public float explosionRadius = 20f; 

    void Start()
    {
        Destroy(gameObject, lifeTime); 
    }

    private void OnTriggerEnter(Collider other) 
    {
        Collider [] colliders = Physics.OverlapSphere(transform.position, explosionRadius, whatIsProp);

        for (int i = 0; i < colliders.Length; i++)
        {
            Rigidbody targetRigidbody = colliders[i].GetComponent<Rigidbody>();
            targetRigidbody.AddExplosionForce(explosionForce, transform.position, explosionRadius); // 알아서 스스로 튕겨나감

            Prop targetProp = colliders[i].GetComponent<Prop>();

            float damage = CalculateDamage(colliders[i].transform.position);

            targetProp.TakeDamage(damage);
        }

        explosionParticle.transform.parent = null; 

        explosionParticle.Play();  
        explosionAudio.Play(); 

        Destroy(explosionParticle.gameObject, explosionParticle.duration);
        Destroy(gameObject); 
    }
    
    private float CalculateDamage(Vector3 targetPosition)
    {
        Vector3 explosionToTarget = targetPosition - transform.position;

        float distance = explosionToTarget.magnitude;

        float edgeToCenterDistance = explosionRadius - distance;

        float percentage = edgeToCenterDistance/explosionRadius;

        float damage = maxDamage * percentage;

        damage = Mathf.Max(0, damage);

        return damage;
    }
}
  • 🚍 변수
    • public LayerMask whatIsProp
      • public이므로 유니티에서 슬롯이 열리는데 드롭다운으로 열린다..
      • 유니티 슬롯에서 "Prop"을 선택해주자.
      • 이제 whatIsProp 변수는 “Prop” 레이어를 가진 오브젝트를 참조하게 된다.
        • “Prop” 레이어를 가진 오브젝트만 가져올 것이다.
          • 구의 반경에 있는 오브젝트들 중 Prop 이 아닌 오브젝트들까지 다 가져오면 너무 성능 낭비기 때문이다.
  • 🚍 함수
    • OnTriggerEnter
      Collider [] colliders = Physics.OverlapShere(transform.position, explosionRadius, whatIsProp);
      
      • OverlapShere
        • 구의 중심 위치와 구의 반지름을 넘기면 그 가상의 구 반경 안에 있는 모든 Collider들 中 해당 레이어에 해당하는 것들만 colliders 배열에 담아 리턴해준다.
          • 구의 중심
            • transform.position
            • Ball 자기 자신의 위치
          • 구의 반지름
            • explosionRadius
            • 지정해준 폭발 반경
          • 레이어
            • whatIsProp
              • 드롭다운 슬롯에서 지정한 Layer에 해당하는 것들만.
              • 즉 레이어가 “Prop”인 Collider들
      • for문
        • Rigidbody targetRigidbody = colliders[i].GetComponent<Rigidbody>();
          • colliders 배열 원소로부터 Rigidbody 를 갖져온다
            • colliders 배열 원소 👉 Ball 폭발 구 반경 내에 있는 Prop들.
          • Rigidbody를 가져오는 이유는 바로 아래에서 Prop들의 폭발에 대한 물리 처리를 하기 위하여
            • 이제 Prop들이 폭발에 의하여 튕겨져 나가거나 하는 물리적 모션을 취하게 된다.
        • targetRigidbody.AddExplosionForce
          • Rigidbody가 지원 하는 함수로 폭발에 대한 물리 처리를 해준다.
          • 폭발력, 폭발위치, 폭발반경 이 3개의 매개변수를 넘겨주면 알아서 계산하여 물리 처리를 해준다.
        • Prop targetProp = colliders[i].GetComponent<Prop>();
          • 폭발 구 반경에 있는 각각의 프롭들에게 TakeDamage 함수를 사용하여 데미지를 주기 위해 Prop 컴포넌트(스크립트)를 참조한다.
            • TakeDamage는 Prop의 함수기 때문에..
        • targetProp.TakeDamage(damage);
          • 데미지 주기.
    • float CalculateDamage(Vector3 targetPosition)
      • 자기 자신(Ball)의 위치(transform.position)과 매개변수로 받은 targetPosition 사이의 벡터를 구해서 그에 따라 데미지를 얼마나 줄 지 계산하고 그 데미지를 리턴함.
        • 거리에 따라 차등으로 데미지를 부여할 것.
          • 거리 벡터 = 목적지 좌표 - 현재위치 좌표
            • 벡터의 뺄셈
        • explosionToTarget = (targetPosition - transform.position).magnitude
          • (반경 안에 있는 Collider 프롭 위치벡터) - (구의 중심이자 Ball의 위치벡터) 의 길이 와 같다.
          • 구의 중심에 가까울 수록 1.0f (100%)
          • 구의 테두리에 가까울 수록 0.0f (0%)
            • \[{explosionRadius - explosionToTarget}\over{explosionRadius}\]
            • 위 값이 1.0 에 가까울수록 targetPosition이 구의 중심(Ball의 위치)에 가까운 것이고 값이 0.0에 가까울 수록 targetPosition이 구의 중심(Ball의 위치)에서 먼 것이다. (구의 반경에는 있되 구의 테두리에 가까움)
          • distance = 0 이면, 즉 프롭이 Ball의 위치와 일치하면
          • edgeToCenterDistance는 1 이 되고 percentage = 1.0f
          • distance = 1 이면, 즉 프롭이 구의 반경에 위치하되 Ball의 위치와 가장 멀면
          • edgeToCenterDistance는 0 이 되고 percentage = 0.0f
        • damage = Mathf.Max(0, damage);
          • 만약 구의 테두리에 걸치는 바람에 중심은 바깥에 있는데 몸의 한쪽은 구의 반경에 들어와 억울하게 Collider 배열에 들어오게 된 Collider가 있다면 문제가 생긴다. 이런 Collider들은 damage 값이 음수가 되기 때문.
            • 중심이 구의 바깥에 있다는건 explosionRadius < distance 이기 때문에 결과적으로 최종 데미지 dagame 값이 음수가 된다.
            • damage가 음수라는것은 곧 오히려 체력이 차오른다는 의미…
          • 따라서 damage가 음수이면 안되므로 음수인 damage는 0으로 처리될 수 있게 Max 함수를 사용
        • 리턴할 최종 데미지 👉 damage = maxDamage * percentage;

image

그림 정정 : 중심은 반경 外라서 edgoeToCenterDistance가 음수가 나오는 경우를 대비하여



🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

Unity Lesson 1 카테고리 내 다른 글 보러가기

댓글 남기기