
오늘 있었던 일
장애물이 자연스럽게 회전하면서 날아가게 하고 싶었다.
이렇게 세팅하고 실행한 결과
void SpawnNextObstacle()
{
// 풀에서 비활성 장애물 꺼내기
GameObject obstacle = obstaclePool.Dequeue();
// ================================================== 추가 부분
obstacle.transform.rotation = Quaternion.identity;
Rigidbody rb = obstacle.GetComponent<Rigidbody>();
if (rb != null)
{
rb. velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
ObstacleBehavior obstacleBehavior = obstacle.GetComponent<ObstacleBehavior>();
if (obstacleBehavior != null)
{
obstacleBehavior.InitObstacle(); // 커스텀 초기화 함수 호출
}
// +============================================
... 아래 생략
private bool isKnockback = false; // 넉백 중인가
void FixedUpdate()
{
if (!isKnockback) // 넉백 중이 아닐 때
{
_rigidbody.velocity = Vector3.back * obstacleSpeed; // 장애물 이동방향과 속도
}
}
private void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Player")) // 플레이어와 충돌하면
{
// 충돌한 상대방 오브젝트에서 PlayerCondition이라는 스크립트 컴포넌트를 가져와라
PlayerCondition playerCondition = other.gameObject.GetComponent<PlayerCondition>();
if (playerCondition.IsInvincible == false) // 무적이 아닐 때
{
StartCoroutine(nameof(GameOver)); // 게임 오버
}
else // 무적일 때
{
isKnockback = true; // 넉백 중이다
int direction = Random.Range(0, 2); // 장애물 날아가는 방향 정하기
Vector3 selectedDirection;
if (direction == 0)
{
selectedDirection = Vector3.right; // 오른쪽 방향으로 날림
}
else
{
selectedDirection = Vector3.left; // 왼쪽 방향으로 날림
}
// 플레이어와 충돌하면 장애물이 대각선 방향으로 날아감
Vector3 knockbackDirection = (selectedDirection + Vector3.up * 5f).normalized;
// Impulse 모드는 즉각적인 힘을 가함
_rigidbody.AddForce(knockbackDirection * knockbackForce, ForceMode.Impulse);
// -- 이 코드가 문제 / 장애물이 회전하면서 날아가도록 하는 로직-----
Vector3 randomTorqueDirection = Random.insideUnitSphere;
float torqueForce = 100f; // 힘이 너무 강했던 것 같다
_rigidbody.AddTorque(randomTorqueDirection * torqueForce, ForceMode.Impulse);
// ---------------------------------------------------
}
}
}

장애물이 빠른 속도로 하늘로 솟구치고 재생성되지 않는 문제가 발생했다.
장애물이 플레이어 뒤로 갔을 때 비활성화되어야 하는데
장애물이 앞과 위의 방향으로 날아가다보니
재생성되지 않는 것으로 파악하고 있다.
이라기 보다는 타협
일단 기존대로하고
Vector3 knockbackDirection = (selectedDirection 5f + Vector3.up 5f + Vector3.back).normalized;
이 부분의 수치를 조절하는 것으로 함
public void InitObstacle()
{
transform.rotation = Quaternion.Euler(0, 90f, 0);
_rigidbody.velocity = Vector3.back * obstacleSpeed; // 장애물 이동방향과 속도
_rigidbody.angularVelocity = Vector3.zero; // 회전각도 초기화
// 장애물 모델링이 기본적으로 90도 틀어져있기 때문에 90도를 더 돌려 180도(정면)로 맞춰준다.
}
// 플레이어와 장애물이 충돌하면 게임 정지
private void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Player")) // 플레이어와 충돌하면
{
// 충돌한 상대방 오브젝트에서 PlayerCondition이라는 스크립트 컴포넌트를 가져와라
PlayerCondition playerCondition = other.gameObject.GetComponent<PlayerCondition>();
if (playerCondition.IsInvincible == false) // 무적이 아닐 때
{
StartCoroutine(nameof(GameOver)); // 게임 오버
}
else // 무적일 때
{
int direction = Random.Range(0, 2); // 장애물 날아가는 방향 정하기
// 왼쪽 방향과 오른쪽 방향중 하나 랜덤으로 선택됨
Vector3 selectedDirection = (direction == 0 ? Vector3.right : Vector3.left);
// 플레이어와 충돌하면 장애물이 대각선 방향으로 날아감
Vector3 knockbackDirection = (selectedDirection * 5f + Vector3.up * 5f + Vector3.back).normalized;
// Impulse 모드는 즉각적인 힘을 가함
_rigidbody.AddForce(knockbackDirection * knockbackForce, ForceMode.Impulse);
}
}
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 플레이어 모델링의 색상을 바꾸는 스크립트 (무적 상태일 때)
public class InvincibleEffect : MonoBehaviour
{
[Header("무적 시각 효과 설정")]
[SerializeField] private Renderer characterRenderer; // 캐릭터 모델의 Renderer 컴포넌트를 연결
public float intensity = 1f; // 발광 강도 (인스펙터에서 조절 가능)
public float changeDuration = 1f; // 색상 / 강도가 변하는 시간
private bool _isInvincible = false; // 무적 상태 인지 확인
private Material[] _materials; // 여러 개의 material로 모델링이 구성되어 있어서 배열로 함
// 각 material별 원래 Emission 색상 및 키워드 상태를 저장할 Dictionary
private Dictionary<Material, Color> _initEmsissions;
// 원래의 발광 색상 저장 및 초기화
void Start()
{
// 이 스크립트에서 캐릭터에 적용된 모든 material을 가져와 materials배열에 저장
_materials = characterRenderer.materials;
_initEmsissions = new Dictionary<Material, Color>(); // Dictionary 초기화
// material을 하나씩 가져옴
for (int i = 0; i < _materials.Length; i++)
{
Material mat = _materials[i]; // 현재 [i]번째의 material을 mat에 할당
// 현재 material이 "_EmissionColor"속성을 가지고 있는다면
if (mat.HasProperty("_EmissionColor"))
{
// 원래 Emission 색상을 저장
_initEmsissions[mat] = mat.GetColor("_EmissionColor");
mat.SetColor("_EmissionColor", Color.black); // Emission 색상을 검은색(기본값)으로 설정
mat.DisableKeyword("_EMISSION"); // Emission 키워드(발광 기능) 비활성화
// _EMISSION: 유니티 Standard 쉐이더에서 발광 기능을 활성화/비활성화하는 데 사용되는 쉐이더 키워드
// 인스펙터에서 Emission 체크박스를 켜고 끄는 것과 동일한 역할을 코드로 수행
}
}
}
// 캐릭터가 현재 무적 상태가 아니라면, 무적 상태로 전환하고
// material에 발광 효과를 부드럽게 페이드 인(서서히 밝아지는)시키는 코루틴을 시작
public void StartInvincible() // 무적 상태 시작
{
// 현재 무적 상태인지 확인 (중복 실행 방지)
// 무적이면 메서드 종료
if (_isInvincible) return;
_isInvincible = true; // 무적으로 전환
// materials 배열이 유효한지 확인 (null이거나 비어있는 경우를 대비한 안전 장치)
if (_materials == null || _materials.Length == 0) return;
// materials 배열에 있는 모든 재질을 하나씩 순회
foreach (Material mat in _materials)
{
// 현재 material이 쉐이더가 발광(Emission) 속성(_EmissionColor)을 지원하는지 다시 확인
if (mat.HasProperty("_EmissionColor"))
{
Color selectedEmissionColor = Color.black; // 기본값은 검은색
// 각 material의 원래(인스펙터에서 설정된) Emission 색상을 가져옴
if (_initEmsissions.ContainsKey(mat))
{
selectedEmissionColor = _initEmsissions[mat];
}
// 발광 효과를 부드럽게 페이드 인(서서히 밝아지는) 시키는 코루틴 시작
StartCoroutine(FadeEmission(mat, mat.GetColor("_EmissionColor"), selectedEmissionColor * intensity,
changeDuration, true));
}
}
}
// 캐릭터가 현재 무적 상태라면, 무적 상태를 해제하고
// material의 발광 효과를 원래 색상으로 부드럽게 페이드 아웃(서서히 어두워지는)시키는 코루틴을 시작
public void EndInvincible() // 무적 상태 종료
{
// 현재 무적 상태인지 확인 (중복 실행 방지)
// 무적이 아니면 메서드 종료
if (!_isInvincible) return;
_isInvincible = false; // 무적 해제
// materials 배열이 유효한지 확인 (null이거나 비어있는 경우를 대비한 안전 장치)
if (_materials == null || _materials.Length == 0) return;
// 'materials' 배열에 있는 모든 재질을 하나씩 순회
foreach (Material mat in _materials)
{
// 현재 material이 쉐이더가 발광(Emission) 속성(_EmissionColor)을 지원하는지 다시 확인
if (mat.HasProperty("_EmissionColor"))
{
Color originalColor = Color.black; // 기본값
// 각 material의 원래 Emission 색상 (Start에서 저장된 것)을 가져옴
if (_initEmsissions.ContainsKey(mat))
{
originalColor = _initEmsissions[mat];
}
// 발광 효과를 부드럽게 페이드 아웃(서서히 어두워지는) 시키는 코루틴 시작
StartCoroutine(FadeEmission(mat, mat.GetColor("_EmissionColor"), Color.black, changeDuration, false,originalColor));
}
}
}
// 발광이 부드럽게 켜지고 꺼지는(페이드 인/아웃) 시각적 변화
IEnumerator FadeEmission(Material mat, Color startColor, Color endColor, float duration,
bool finalKeywordState = true, Color? restoreColor = null)
{
float timer = 0f; // 페이드(서서히 색이 바뀌는) 타이머
while (timer < duration)
{
// 현재 시간에 따른 페이드 진행률을 계산하여 색상 보간
Color currentColor = Color.Lerp(startColor, endColor, timer / duration);
// material의 Emission 색상을 currentColor으로 설정
mat.SetColor("_EmissionColor", currentColor);
// material의 Emission 키워드 활성화 (계속 발광 상태)
mat.EnableKeyword("_EMISSION");
timer += Time.deltaTime; // 프레임 시간만큼 타이머 증가
yield return null; // 끝나면 다음 프레임까지 대기
}
// 무적 활성화 루프가 끝나면 endColor 색상으로 설정
mat.SetColor("_EmissionColor", endColor);
// finalKeywordState 적용 (페이드가 끝난 후에만 적용)
// StartInvincible 호출 시 (true)
if (finalKeywordState)
{
mat.EnableKeyword("_EMISSION"); // 키워드 활성화
}
else
{
mat.DisableKeyword("_EMISSION"); // 키워드 비활성화
if (restoreColor.HasValue)
{
mat.SetColor("_EmissionColor", restoreColor.Value);
}
// 만약 원래 Emission이 없었거나, 효과를 끄는 것이라면 검은색으로 설정
else if (endColor.r + endColor.g + endColor.b == 0) // 색상값이 0이면 (검은색이면)
{
mat.SetColor("_EmissionColor", Color.black);
}
}
}
}
====
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObstacleBehavior : MonoBehaviour
{
Rigidbody _rigidbody;
public float obstacleSpeed = 25f; // 장애물 이동속도
public float knockbackForce = 500f; // 넉백 강도
void Awake()
{
_rigidbody = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
_rigidbody.velocity = Vector3.back * obstacleSpeed; // 장애물 이동방향과 속도
}
/// <summary>
/// 무적으로 상태에서 충돌되어 날아가는 장애물의 transform이 기본값과 다름.
/// 재소환 시에 기본값으로 초기화
/// </summary>
public void InitObstacle()
{
transform.rotation = Quaternion.Euler(0, 90f, 0);
_rigidbody.velocity = Vector3.back * obstacleSpeed; // 장애물 이동방향과 속도
_rigidbody.angularVelocity = Vector3.zero; // 회전각도 초기화
// 장애물 모델링이 기본적으로 90도 틀어져있기 때문에 90도를 더 돌려 180도(정면)로 맞춰준다.
}
// 플레이어와 장애물이 충돌하면 게임 정지
private void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Player")) // 플레이어와 충돌하면
{
// 충돌한 상대방 오브젝트에서 PlayerCondition이라는 스크립트 컴포넌트를 가져와라
PlayerCondition playerCondition = other.gameObject.GetComponent<PlayerCondition>();
if (playerCondition.IsInvincible == false) // 무적이 아닐 때
{
StartCoroutine(nameof(GameOver)); // 게임 오버
}
else // 무적일 때
{
int direction = Random.Range(0, 2); // 장애물 날아가는 방향 정하기
// 왼쪽 방향과 오른쪽 방향중 하나 랜덤으로 선택됨
Vector3 selectedDirection = (direction == 0 ? Vector3.right : Vector3.left);
// 플레이어와 충돌하면 장애물이 대각선 방향으로 날아감
Vector3 knockbackDirection = (selectedDirection * 5f + Vector3.up * 5f + Vector3.back).normalized;
// Impulse 모드는 즉각적인 힘을 가함
_rigidbody.AddForce(knockbackDirection * knockbackForce, ForceMode.Impulse);
}
}
}
IEnumerator GameOver()
{
CharacterManager.Instance.Player.controller.Dead();
Time.timeScale = 0;
yield return new WaitForSecondsRealtime(2.28f);
Debug.Log("장애물과 충돌로 인한 게임정지");
UIController.Instance.ShowGameOverUI();
}
}
===
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerCondition : MonoBehaviour
{
[SerializeField] private InvincibleEffect _invincibleEffect; // InvincibleEffect스크립트 인스펙터에서 할당
private bool isInvincible = false; // 무적상태 확인
private Item _item;
public bool IsInvincible // 외부에서 무적 상태인지 확인할 수 있도록 함
{
get { return isInvincible; }
private set { isInvincible = value; }
}
// 무적 메서드
public void ActivateInvincibility(float duration)
{
if (!isInvincible) // 무적일 때
{
IsInvincible = true; // 무적상태
// 무적 코루틴 시작
StartCoroutine(InvincibilityCoroutine(duration));
}
}
/// <summary>
/// 무적활성화 코루틴, durataion에 따라 효과가 지속됨
/// </summary>
/// <param name="duration"></param>
/// <returns></returns>
IEnumerator InvincibilityCoroutine(float duration)
{
if (_invincibleEffect != null)
{
_invincibleEffect.StartInvincible(); // 무적 효과 시작
}
// 실제 무적 시간보다 2초 먼저 종료 시키고 무적 이펙트를 먼저 끔
// 무적이 끝나고 2초 후에 이펙트가 꺼지기 때문에 무적 해제를 알아보기가 힘들음
yield return new WaitForSeconds(duration - 2);
if (_invincibleEffect != null)
{
_invincibleEffect.EndInvincible(); // 무적 효과 종료
}
// 2초 후 무적 종료
yield return new WaitForSeconds(2);
IsInvincible = false; // 무적 해제
}
}