
칼끼리의 충돌 관리를 해주려고 했는데 문제점을 하나 발견했다.

칼이 Target에 박힌 이후에 칼들끼리 충돌이 발생한다. 물론 예외처리로 이미 꽂힌 칼들끼리는 충돌을 감지해도 로직이 실행되지 않도록 할 수는 있는데 그러면 결국 충돌 이벤트를 계속해서 실행한다는 뜻이라 성능적으로 비효율적이다. 그래서 Collider의 모양을 아래와 같이 바꿔주었다.

Unity에서 Circle이나 Box처럼 삼각형 Collider를 바로 지원해주지는 않는다.
Polygon Colldier 2D를 써서 자체적으로 Edit Collider로 원하는 Collider 모양을 만들 수 있다. 웬만하면 변의 개수를 최소화 시킨 다각형으로 만드는 게 좋다. 아래처럼 변이 많아질 수록 Polygon 개수가 늘어나서 처리량이 늘어난다.
이렇게 삼각형으로 바꾼 다음에 확인해보니 꽂힌 칼들끼리의 충돌이 일어나지 않는다.

칼 스프라이트를 임포트 하면서 쓸만한 다른 이미지도 몇개 임포트 해줬다.

좀 괜찮아 보이는 것 같다!
먼저 남은 칼의 개수 변수를 만들어서 OnHit마다 감소되도록 해서 0이 되면 파괴되도록 하자.

파괴는 잘 된다. 남은 칼 개수를 표시할 UI를 제작해보자.

그럴싸 해 보인다. 이제 스크립트로 Target의 체력만큼 Icon을 만들도록 하고 화면을 터치할 때 마다 Icon의 Color를 바꾸도록 스크립트를 작성해보자. Target 프리팹이 자체적으로 자신이 파괴되는데 필요한 knife 개수를 변수로 가지고 있고 GameManager에서 Target을 스폰할 때 해당 변수 값을 읽어서 UI Manager로 넘겨준다.
[Button("SpawnTarget")]
public void SpawnTarget() {
GameObject currentTarget = Instantiate(target);
RemainKnives = currentTarget.GetComponent<Target>().knivesToDestroy;
UIManager.Instance.SpawnKnivesIcon(RemainKnives);
}
knife 개수를 매개벼수로 넘겨받아 아이콘을 생성하도록 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIManager : MonoBehaviour
{
public static UIManager Instance;
public GameObject knifeIconsContainer;
public GameObject KnifeIconPrefab;
public List<GameObject> KnifeIcons;
private void OnEnable() {
if (Instance == null) {
Instance = this;
}
else {
Destroy(gameObject);
}
}
public void SpawnKnivesIcon(int cntKnives) {
for (int i = 0; i < cntKnives; i++) {
GameObject knifeIcon = Instantiate(KnifeIconPrefab);
knifeIcon.transform.SetParent(knifeIconsContainer.transform);
KnifeIcons.Add(knifeIcon);
}
}
}
이제 OnTouchScreen 마다 knife 개수를 줄이고 아이콘에도 적용시켜준다. 이 때 RemainKnives가 0이면 Target을 파괴시켜주도록 한다.
public void OnTouchScreen() {
RemainKnives--;
UIManager.Instance.DecreaseKnifeCount(RemainKnives);
if (RemainKnives == 0) {
Events.OnAllKnivesOnHit.Invoke();
}
}
public void DecreaseKnifeCount(int RemainKnives) {
if (RemainKnives < 0) {
return;
}
KnifeIcons[(KnifeIcons.Count - 1)- RemainKnives].GetComponent<Image>().color = Color.black;
}
[Button("DestroyTarget")]
public void DestroyTarget() {
transform.DOKill();
//TODO - Destroy(gameObject);
}
일단 잘 생성되서 icon이 배치되고

정상적으로 터치 할 때마다 칼 icon도 잘 변경된다.

문제가 좀 있었는데 이게 OnHit일때 RemainKnives를 체크하도록 수정해서 고쳤다.
[Button("OnHit")]
public void OnHit() {
transform.DOPunchPosition(Vector3.right * shakeStrength, shakeDuration, vibrato, randomness);
StartCoroutine(FlashCoroutine());
if (GameManager.Instance.RemainKnives == 0) {
Events.OnAllKnivesOnHit.Invoke();
}
}
이제 칼 간의 충돌을 구현하고 게임 오버만 구현하면 일단 MainScene에서의 작업은 마무리 할 수 있다. 일단 GameOver를 구현해보자. 실제 인 게임 영상을 보면 화면이 반짝이면서 칼이 튕겨나가고 게임 오버가 된다.
칼이 튕겨 나가서 떨어지도록 하려면 다시 중력의 영향을 받아야하고, 좀 회전하듯이 튕겨나가야 해서 힘도 랜덤하게 주어야한다.
else if(collision.gameObject.CompareTag("Knife") && canMove) {
canMove = false;
Events.OnTouchScreen -= FireKnife;
Events.OnCollisionBetweenKnives.Invoke();
rb.velocity = Vector3.zero;
rb.isKinematic = false;
Vector2 collisionDirection = (transform.position - collision.transform.position).normalized;
float bounceForce = Random.Range(5f, 7.5f);
float randomAngle = Random.Range(-60f, 60f);
Vector2 forceDirection = Quaternion.Euler(0, 0, randomAngle) * collisionDirection;
rb.AddForce(forceDirection * bounceForce, ForceMode2D.Impulse);
float randomTorque = Random.Range(-200f, 200f);
rb.angularVelocity = randomTorque;
}
좀 복잡해보이지만 한줄씩 따라가면 쉽다.
AddForce로 힘을 받아서 튕겨나도록 한다.auglarVelocity에 랜덤한 값을 적용시켜준다.
잘 튕겨난다. 화면 반짝임은 화면 전체를 덮는 오브젝트를 하나 만들어서 그냥 DOColor로 반짝이는 효과를 줘보자.
화면을 모두 덮는 Square 오브젝트를 흰색으로 하나 만들어주고 아래와 같이 작성해줬다.
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using Sirenix.OdinInspector;
using UnityEngine;
public class BackgroundEffect : MonoBehaviour
{
public GameObject Background_White;
private SpriteRenderer spriteRenderer;
public float flashDuration = 0.5f;
public float flashStrength = 0.25f;
private void Start() {
spriteRenderer = Background_White.GetComponent<SpriteRenderer>();
}
private void OnEnable() {
Events.OnCollisionBetweenKnives += BackgroundFlash;
}
private void OnDisable() {
Events.OnCollisionBetweenKnives -= BackgroundFlash;
}
[Button("Flash")]
public void BackgroundFlash() {
spriteRenderer.DOFade(flashStrength, flashDuration / 3)
.OnComplete(() => spriteRenderer.DOFade(0f, flashDuration / 3 * 2));
}
}
이래서 Events로 구현하는게 정말 좋다. 이렇게 구독만 해주면 바로바로 이벤트 발생때마다 실행되도록 구현할 수 있다.

화면이 잘 깜빡인다. 그리고 실제 인게임처럼 원형 이펙트가 생기도록 하고 싶은데, 그냥 이펙트 이미지를 하나 찾아서 투명도, 위치 조절로 해도 되지만 스크립트가 그러면 괜히 복잡해질 거 같아서 Particle System으로도 쉽게 구현했다. (Particle System은 위치만 잘 잡아주고 Play만 시켜주면 된다.)

충돌 위치를 잡아주려 했으나..
OnTriggerEnter2D는OnCollisionEnter2D와 달리 충돌 지점을 바로 얻어올 수가 없다..
OnCollision은 아래와 같이 매우 쉽게 충돌 지점을 찾을 수 있다..
private void OnCollisionEnter2D(Collision2D collision)
{
// 충돌한 모든 접촉점(contact point)을 가져옴
foreach (ContactPoint2D contact in collision.contacts)
{
Vector2 collisionPoint = contact.point;
Debug.Log("Collision Point: " + collisionPoint);
}
}
결국 충돌 지점을 추정해야하는데, 두 Collider의 중심을 찾아서 평균 값을 내거나
Vector3 Position1 = this.GetComponent<Collider2D>().bounds.center;
Vector3 Position2 = collision.bound.center;
Vector3 hitPosition = ( Position1 + Position2 ) / 2
아니면 아래와 같이 작성해서 구할 수 있다.
Vector2 collisionPoint = collision.bounds.ClosestPoint(transform.position);
collision.bounds.ClosestPoint는 현재 Collider2D의 경계(bound)에서 주어진 위치(transform.position)에 가장 가까운 지점을 반환한다.
후자의 방법이 더 간단하고 코드도 직관적이라서 후자의 방법으로 구현했다.
else if(collision.gameObject.CompareTag("Knife") && canMove) {
/* 기존 코드 */
Vector2 collisionPoint = collision.bounds.ClosestPoint(transform.position);
SFXManager.Instance.playImpact(collisionPoint);
}
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using Sirenix.OdinInspector;
using UnityEngine;
public class BackgroundEffect : MonoBehaviour
{
public GameObject Background_White;
private SpriteRenderer spriteRenderer;
public float flashDuration = 0.15f;
public float flashStrength = 0.05f;
private void Start() {
spriteRenderer = Background_White.GetComponent<SpriteRenderer>();
}
private void OnEnable() {
Events.OnCollisionBetweenKnives += BackgroundFlash;
}
private void OnDisable() {
Events.OnCollisionBetweenKnives -= BackgroundFlash;
}
[Button("Flash")]
public void BackgroundFlash() {
spriteRenderer.DOFade(flashStrength, flashDuration / 2)
.OnComplete(() => spriteRenderer.DOFade(0f, flashDuration / 2));
}
}
최종적으로 아래와 같이 구현됐다.

아래 정도만 구현하면 그래도 대부분의 기능은 거의 구현한 듯?