using UnityEngine;
using System.Collections;
using Unity.Cinemachine;
public class Area1_Library : MonoBehaviour
{
[Header("퍼즐 상태")]
public int collectedBooks = 0; // 수집한 책 수량
public int placedBooks = 0; // 설치한 책 수량
[Header("보상")]
public GameObject baseball; // 클리어 보상 야구공 오브젝트
[Header("연출 설정")]
public DoorController nextRoomDoor;
public PlayerController playerController; // 조작 제어용
public CinemachineCamera doorCam; // 문 비출 카메라 (Door1_Cam)
// 책 수집 시 호출되는 함수
public void CollectBook()
{
collectedBooks++;
Debug.Log("책 수집 현재 수량: " + collectedBooks);
}
// 책 설치 시도 함수
public bool TryPlaceBook()
{
// 소지 중인 책 확인
if (collectedBooks > 0)
{
collectedBooks--; // 소지 수량 감소
placedBooks++; // 설치 수량 증가
Debug.Log("책 설치 현재 설치량: " + placedBooks);
// 목표 수량 도달 확인
if (placedBooks >= 3)
{
ClearPuzzle();
}
return true; // 설치 성공 반환
}
Debug.Log("소지 중인 책 없음");
return false; // 설치 실패 반환
}
// 퍼즐 완료 처리 로직
void ClearPuzzle()
{
Debug.Log("1구역 퍼즐 완료 보상 생성");
if (baseball != null) baseball.SetActive(true);
}
public void StartDoorCutscene()
{
StartCoroutine(DoorOpenCutscene());
}
IEnumerator DoorOpenCutscene()
{
if (playerController != null) playerController.StopMovementForCutscene();
if (doorCam != null) doorCam.Priority = 20;
// 카메라가 문으로 이동할 시간
yield return new WaitForSeconds(2.0f);
// 문 개방
if (nextRoomDoor != null) nextRoomDoor.OpenDoor();
// 열리는거 잠시 대기
yield return new WaitForSeconds(2.5f);
// 시점 복구
if (doorCam != null) doorCam.Priority = 0;
yield return new WaitForSeconds(2.0f);
if (playerController != null) playerController.enabled = true;
}
}
using UnityEngine;
public class Book : MonoBehaviour, IInteractable
{
public Area1_Library manager; // 도서관 매니저 참조
[Header("사운드 설정")]
public AudioClip takSound; // 책 주울 때 탁 소리
private Material bookMaterial;
private Color defaultEmission = new Color(0f, 0.3f, 0.8f) * 1f; // 기본 푸른빛 에미션
private Color highlightEmission = new Color(1f, 0.9f, 0.2f) * 3f; // 주시 시 노란빛 에미션
void Start()
{
// 렌더러 컴포넌트 추출 및 마테리얼 에미션 활성화
Renderer rend = GetComponentInChildren<Renderer>();
if (rend != null)
{
bookMaterial = rend.material;
bookMaterial.EnableKeyword("_EMISSION");
// 초기 상태 에미션 색상 설정
bookMaterial.SetColor("_EmissionColor", defaultEmission);
}
}
// 시선이 닿았을 때 호출
public void OnFocus()
{
if (bookMaterial != null)
{
bookMaterial.SetColor("_EmissionColor", highlightEmission);
}
}
// 시선이 벗어났을 때 호출
public void OnLoseFocus()
{
if (bookMaterial != null)
{
bookMaterial.SetColor("_EmissionColor", defaultEmission);
}
}
// 클릭 상호작용 시 호출
public void OnInteract()
{
if (manager != null)
{
manager.CollectBook();
}
// 주울 때 탁 소리
if (takSound != null)
{
AudioSource.PlayClipAtPoint(takSound, transform.position);
}
// 피드백 초기화 및 오브젝트 비활성화
OnLoseFocus();
gameObject.SetActive(false);
}
}
using UnityEngine;
public class BookSocket : MonoBehaviour, IInteractable
{
public Area1_Library manager; // 도서관 매니저 참조
public GameObject visualBook; // 설치 시 활성화할 책 모델
private bool isFilled = false; // 책 설치 여부 확인
[Header("사운드 설정")]
public AudioClip takSound; // 꽂을 때 탁 소리
private Material socketMaterial;
private Color defaultColor = Color.black; // 기본 에미션 색상
private Color focusColor = Color.yellow * 1.5f; // 주시 시 에미션 색상
void Start()
{
// 자식 오브젝트의 렌더러 참조 추출
Renderer childRenderer = GetComponentInChildren<Renderer>();
if (childRenderer != null)
{
socketMaterial = childRenderer.material;
socketMaterial.EnableKeyword("_EMISSION");
socketMaterial.SetColor("_EmissionColor", defaultColor);
}
else
{
Debug.LogWarning("소켓 자식 오브젝트 내 렌더러 부재");
}
}
public void OnInteract()
{
// 이미 설치된 상태거나 매니저 참조 부재 시 종료
if (isFilled || manager == null) return;
// 매니저를 통한 설치 로직 실행 및 결과 확인
bool success = manager.TryPlaceBook();
if (success)
{
isFilled = true;
// 꽂을 때 탁 소리
if (takSound != null)
{
AudioSource.PlayClipAtPoint(takSound, transform.position);
}
// 시각적 모델 활성화
if (visualBook != null)
{
visualBook.SetActive(true);
}
// 설치 완료 후 피드백 초기화
OnLoseFocus();
}
}
public void OnFocus()
{
// 미설치 상태에서 주시할 경우 색상 변경
if (!isFilled && socketMaterial != null)
{
socketMaterial.SetColor("_EmissionColor", focusColor);
}
}
public void OnLoseFocus()
{
// 에미션 색상 초기화
if (socketMaterial != null)
{
socketMaterial.SetColor("_EmissionColor", defaultColor);
}
}
}
using UnityEngine;
using System.Collections;
using Unity.Cinemachine;
public class Area2_MachineRoom : MonoBehaviour
{
[Header("조작 대상")]
public Transform indicatorLight; // 위치 표시용 라이트
public GameObject rustyBat; // 퍼즐 보상 아이템
[Header("기준점 및 정답 위치")]
public Transform startPos; // 라이트 시작 위치
public Transform centerPos; // 퍼즐 정답 위치
[Header("제한 구역")]
public Collider strikeZoneCollider; // 라이트 이동 제한 영역
[Header("레버별 이동 방향 및 거리")]
public Vector3 lever1_Offset = new Vector3(0, 0.5f, 0); // 북쪽 이동
public Vector3 lever2_Offset = new Vector3(0, -0.5f, 0); // 남쪽 이동
public Vector3 lever3_Offset = new Vector3(0.5f, 0, 0); // 동쪽 이동
public Vector3 lever4_Offset = new Vector3(-0.5f, 0, 0); // 서쪽 이동
[Header("연출 설정")]
public DoorController nextRoomDoor; // 2구역 -> 3구역 문
public PlayerController playerController;
public CinemachineCamera doorCam; // Door2_Cam (2구역 문 비추는 카메라)
private Vector3 currentTargetPos;
private bool isCleared = false;
void Start()
{
// 라이트 초기 위치 설정
if (startPos != null)
{
currentTargetPos = startPos.position;
indicatorLight.position = currentTargetPos;
}
}
// 각 레버 클릭 시 호출되어 이동 방향 결정
public void MoveLightDirection(int leverID)
{
if (isCleared) return;
Vector3 step = Vector3.zero;
// 레버 ID에 따른 이동 보정치 할당
switch (leverID)
{
case 1: step = lever1_Offset; break;
case 2: step = lever2_Offset; break;
case 3: step = lever3_Offset; break;
case 4: step = lever4_Offset; break;
}
currentTargetPos += step;
// 콜라이더 영역 기반 이동 범위 제한
if (strikeZoneCollider != null)
{
Bounds bounds = strikeZoneCollider.bounds;
currentTargetPos.x = Mathf.Clamp(currentTargetPos.x, bounds.min.x, bounds.max.x);
currentTargetPos.y = Mathf.Clamp(currentTargetPos.y, bounds.min.y, bounds.max.y);
}
}
void Update()
{
if (isCleared || indicatorLight == null || centerPos == null) return;
// 목표 위치로 부드러운 이동 처리
indicatorLight.position = Vector3.Lerp(indicatorLight.position, currentTargetPos, Time.deltaTime * 5f);
// 정답 위치 도달 여부 판정
float distanceToCenter = Vector3.Distance(indicatorLight.position, centerPos.position);
if (distanceToCenter < 0.1f)
{
ClearPuzzle();
}
}
// 퍼즐 완료 로직 실행
void ClearPuzzle()
{
if (isCleared) return;
isCleared = true;
Debug.Log("2구역 퍼즐 클리어");
// 보상 활성화 및 다음 문 개방
if (rustyBat != null)
{
rustyBat.SetActive(true);
}
}
public void StartBatCutscene()
{
StartCoroutine(BatOpenCutscene());
}
IEnumerator BatOpenCutscene()
{
// 조작 잠금 및 카메라 전환
if (playerController != null) playerController.StopMovementForCutscene();
if (doorCam != null) doorCam.Priority = 20;
yield return new WaitForSeconds(2.0f);
// 문 개방
if (nextRoomDoor != null) nextRoomDoor.OpenDoor();
yield return new WaitForSeconds(2.5f);
// 카메라 복귀 및 조작 해제
if (doorCam != null) doorCam.Priority = 0;
yield return new WaitForSeconds(2.0f);
if (playerController != null) playerController.enabled = true;
}
}
using UnityEngine;
public class Lever : MonoBehaviour, IInteractable
{
public Area2_MachineRoom manager; // 기계실 매니저 참조
public int leverID; // 레버 식별 번호 (1:북, 2:남, 3:동, 4:서)
[Header("사운드 설정")]
public AudioClip leverSound; // 레버 철컥 소리
[Header("시각적 피드백")]
private Material leverMat;
private Color defaultColor = Color.blue * 2.0f; // 기본 파란색 에미션
private Color focusColor = Color.yellow * 2.5f; // 주시 시 노란색 에미션
void Start()
{
// 자식 오브젝트의 렌더러 컴포넌트 추출
Renderer rend = GetComponentInChildren<Renderer>();
if (rend != null)
{
leverMat = rend.material;
leverMat.EnableKeyword("_EMISSION");
// 초기 에미션 색상 설정
leverMat.SetColor("_EmissionColor", defaultColor);
}
}
public void OnInteract()
{
// 매니저 참조 확인 후 이동 명령 전달
if (manager != null)
{
manager.MoveLightDirection(leverID);
}
// 레버 소리 재생
if (leverSound != null) AudioSource.PlayClipAtPoint(leverSound, transform.position);
}
public void OnFocus()
{
// 주시 시 노란색 피드백 적용
if (leverMat != null)
{
leverMat.SetColor("_EmissionColor", focusColor);
}
}
public void OnLoseFocus()
{
// 주시 해제 시 원래 색상 복구
if (leverMat != null)
{
leverMat.SetColor("_EmissionColor", defaultColor);
}
}
}
using System.Collections;
using UnityEngine;
using UnityEngine.AI;
using Unity.Cinemachine;
public class Area3_Manager : MonoBehaviour
{
[Header("진행 상태")]
public int totalTargets = 4; // 목표 파괴 개수
private int destroyedTargets = 0; // 파괴된 타겟 수량
[Header("4구역 진입문")]
public DoorController nextRoomDoor; // 다음 구역 연결 문
[Header("플레이어 제어")]
public MonoBehaviour playerController; // 플레이어 조작 스크립트 참조
[Header("연출 설정")]
public CinemachineCamera doorCam;
private HelperAI helperAI; // 조력자 AI 참조
void Start()
{
helperAI = FindAnyObjectByType<HelperAI>();
// 조작 스크립트 자동 할당 시도
if (playerController == null)
{
GameObject player = GameObject.FindGameObjectWithTag("Player");
if (player != null) playerController = player.GetComponent<PlayerController>();
}
}
private void OnTriggerEnter(Collider other)
{
// 플레이어 진입 시 컷씬 실행 및 트리거 비활성화
if (other.CompareTag("Player"))
{
StartCoroutine(Area3EntranceCutscene(other.transform));
Collider col = GetComponent<Collider>();
if (col != null) col.enabled = false;
}
}
// 3구역 진입 연출 코루틴
IEnumerator Area3EntranceCutscene(Transform playerTransform)
{
Debug.Log("3구역 컷씬 시작");
if (playerController != null)
{
// MonoBehaviour를 PlayerController로 변환해서 브레이크를 밟음
PlayerController pc = playerController.GetComponent<PlayerController>();
if (pc != null) pc.StopMovementForCutscene();
}
// 플레이어 조작 및 충돌 일시 정지
Collider playerCollider = playerTransform.GetComponent<Collider>();
if (playerCollider != null) playerCollider.enabled = false;
// 조력자 이동 설정
if (helperAI != null)
{
helperAI.isCutsceneMode = true;
Vector3 targetPos = playerTransform.position + playerTransform.forward * 2f;
NavMeshAgent helperAgent = helperAI.GetComponent<NavMeshAgent>();
if (helperAgent != null)
{
helperAgent.SetDestination(targetPos);
float timeout = 0f;
// 목표 지점 도착 대기
while ((helperAgent.pathPending || helperAgent.remainingDistance > 0.5f) && timeout < 3f)
{
timeout += Time.deltaTime;
yield return null;
}
}
// 플레이어와 완벽하게 같은 방향을 보게 하여 등 내밈
helperAI.transform.rotation = playerTransform.rotation;
helperAI.OfferBox();
}
// 가방 아이템 설정
HelperBox helperBox = FindAnyObjectByType<HelperBox>();
if (helperBox != null)
{
helperBox.allowedItem = "Baseball";
helperBox.isArea3UnlockBox = true;
helperBox.ShowItemInBox("Baseball");
}
yield return new WaitForSeconds(1.5f);
// 플레이어 조작 및 충돌 복구
if (playerCollider != null) playerCollider.enabled = true;
if (playerController != null) playerController.enabled = true;
Debug.Log("컷씬 종료 조작 권한 복구");
}
// 타겟 파괴 시 호출되는 진행 관리 함수
public void TargetDestroyed()
{
destroyedTargets++;
// 모든 타겟 제거 확인
if (destroyedTargets >= totalTargets)
{
Debug.Log("3구역 클리어 진행로 개방 및 아이템 회수");
// 투척 기능 봉인 및 장착 해제
PlayerThrow pt = FindAnyObjectByType<PlayerThrow>();
if (pt != null) pt.SealBallForever();
// 맵에 잔류하는 야구공 오브젝트 제거
PickupItem[] droppedBalls = FindObjectsByType<PickupItem>(FindObjectsSortMode.None);
foreach (PickupItem item in droppedBalls)
{
if (item.itemName == "Baseball") Destroy(item.gameObject);
}
// 가방 상태 리셋 및 잠금
HelperBox box = FindAnyObjectByType<HelperBox>();
if (box != null)
{
box.AddItem("Baseball");
box.isLooted = false;
box.isLocked = true;
box.isArea3UnlockBox = false;
}
// 클리어 연출 코루틴 실행
StartCoroutine(ClearCutscene());
}
}
// 3구역 클리어 연출 코루틴
IEnumerator ClearCutscene()
{
// 조작 잠금
if (playerController != null)
{
PlayerController pc = playerController.GetComponent<PlayerController>();
if (pc != null) pc.StopMovementForCutscene();
}
// 카메라 문으로 이동
if (doorCam != null) doorCam.Priority = 20;
yield return new WaitForSeconds(2.5f);
// 4구역으로 가는 문 개방
if (nextRoomDoor != null) nextRoomDoor.OpenDoor();
yield return new WaitForSeconds(2.5f);
// 카메라 원상 복구
if (doorCam != null) doorCam.Priority = 0;
yield return new WaitForSeconds(2.0f);
// 조작 권한 복구
if (playerController != null) playerController.enabled = true;
}
}
using UnityEngine;
public class TargetPlate : MonoBehaviour
{
public Area3_Manager manager; // 3구역 매니저 참조
private bool isDestroyed = false; // 파괴 여부 확인
private Renderer targetRenderer; // 렌더러 캐싱용
[Header("사운드 설정")]
public AudioClip breakSound; // 터지는 소리
void Start()
{
// 렌더러 참조 저장
targetRenderer = GetComponent<Renderer>();
// 매니저 미할당 시 자동 검색
if (manager == null)
{
manager = FindAnyObjectByType<Area3_Manager>();
}
}
private void OnCollisionEnter(Collision collision)
{
if (isDestroyed) return;
// 충돌 오브젝트의 아이템 정보 확인
PickupItem item = collision.gameObject.GetComponent<PickupItem>();
if (item != null && item.itemName == "Baseball")
{
BreakPlate();
}
}
void BreakPlate()
{
isDestroyed = true;
// 매니저에 파괴 신호 전달
if (manager != null)
{
manager.TargetDestroyed();
}
// 파괴되는 사운드
if (breakSound != null) AudioSource.PlayClipAtPoint(breakSound, transform.position);
// 파편 효과 생성 및 원본 비활성화
SpawnDebrisEffect();
gameObject.SetActive(false);
}
void SpawnDebrisEffect()
{
if (targetRenderer == null) return;
Material myMaterial = targetRenderer.material;
// 파편 조각 생성 루프
for (int i = 0; i < 15; i++)
{
// 기본 큐브 생성 및 설정
GameObject debris = GameObject.CreatePrimitive(PrimitiveType.Cube);
debris.GetComponent<Renderer>().material = myMaterial;
// 위치 무작위 분산 및 크기 조절
debris.transform.position = transform.position + Random.insideUnitSphere * 0.5f;
debris.transform.localScale = Vector3.one * 0.2f;
// 물리 컴포넌트 추가 및 폭발력 적용
Rigidbody rb = debris.AddComponent<Rigidbody>();
if (rb != null)
{
rb.AddExplosionForce(800f, transform.position + Vector3.down * 0.5f, 3f);
}
// 일정 시간 후 제거
Destroy(debris, 3f);
}
}
}
using UnityEngine;
public class PlayerThrow : MonoBehaviour
{
[Header("연결")]
public Animator anim;
public GameObject realBallPrefab;
public Transform throwPoint;
public GameObject heldBallModel;
[Header("상태")]
public bool isHoldingBall = false; // 공 보유 여부
public bool canThrowBall = false; // 3구역 가방 클릭 전까지 투척 제한
public bool isArea3Cleared = false; // 3구역 클리어 여부
[Header("설정")]
public float throwForce = 25f;
private bool isThrowing = false;
[Header("사운드 설정")]
public AudioClip windSound; // 바람 소리
void Start()
{
// 게임 시작 시 모든 권한 초기화
isHoldingBall = false;
canThrowBall = false;
isArea3Cleared = false;
if (heldBallModel != null) heldBallModel.SetActive(false);
}
void Update()
{
// 3구역 클리어 시 로직 중단
if (isArea3Cleared) return;
// 공 보유 및 가방 허락 상태에서 투척 가능
if (Input.GetMouseButtonDown(0) && isHoldingBall && canThrowBall && !isThrowing)
{
isThrowing = true;
anim.SetTrigger("Throw");
}
}
// 아이템 획득 시 호출
public void EquipBall()
{
isHoldingBall = true;
//if (heldBallModel != null) heldBallModel.SetActive(true);
}
// 3구역 가방 클릭 시 투척 제한 해제
public void UnlockThrow()
{
canThrowBall = true;
Debug.Log("공 투척 제한 해제");
}
// 애니메이션 이벤트에서 투척 실행
public void ShootBall()
{
// 투척 시 손에서 공 제거하여 무한 투척 방지
isHoldingBall = false;
if (heldBallModel != null) heldBallModel.SetActive(false);
GameObject ball = Instantiate(realBallPrefab, throwPoint.position, throwPoint.rotation);
Rigidbody rb = ball.GetComponent<Rigidbody>();
if (rb != null)
{
rb.AddForce(Camera.main.transform.forward * throwForce, ForceMode.Impulse);
}
// 바람 소리 휙~
if (windSound != null) AudioSource.PlayClipAtPoint(windSound, throwPoint.position);
// 동작 종료 처리만 수행
Invoke("FinishAnimation", 1.0f);
}
// 투척 동작 종료 및 상태 초기화
void FinishAnimation()
{
isThrowing = false;
}
// 3구역 클리어 시 모든 투척 관련 기능 봉인
public void SealBallForever()
{
isArea3Cleared = true;
isHoldingBall = false;
canThrowBall = false;
if (heldBallModel != null) heldBallModel.SetActive(false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using Unity.Cinemachine;
public class Area4_Manager : MonoBehaviour
{
[Header("플레이어 제어")]
public MonoBehaviour playerController;
[Header("적군 리스트")]
public List<EnemyAI> minions = new List<EnemyAI>();
public EnemyAI boss;
[Header("보상 및 클리어")]
public GameObject dosimeterPrefab;
public DoorController nextRoomDoor;
[Header("연출 카메라")]
public CinemachineCamera spawnCam; // 적 스폰 장소 비출 카메라
public CinemachineCamera doorCam; // 다음 구역 문 비출 카메라
private int livingMinions;
private HelperAI helperAI;
private PlayerMelee playerMelee;
void Start()
{
helperAI = FindAnyObjectByType<HelperAI>();
livingMinions = minions.Count;
// 플레이어 근접 공격 컴포넌트 참조 저장
if (playerController != null)
{
playerMelee = playerController.GetComponent<PlayerMelee>();
}
// 적 유닛 초기 비활성화
if (boss != null) boss.gameObject.SetActive(false);
foreach (var m in minions)
{
if (m != null) m.gameObject.SetActive(false);
}
}
public void StartBattle()
{
HelperBox box = FindAnyObjectByType<HelperBox>();
if (box != null) box.isLocked = true;
StartCoroutine(SpawnMinionsRoutine());
}
// 쫄몹 스폰 연출 코루틴
IEnumerator SpawnMinionsRoutine()
{
Debug.Log("미니언 스폰 연출 시작");
// 조작 잠금
if (playerController != null)
{
PlayerController pc = playerController.GetComponent<PlayerController>();
if (pc != null) pc.StopMovementForCutscene();
}
// 카메라가 적 스폰 장소로
if (spawnCam != null) spawnCam.Priority = 20;
yield return new WaitForSeconds(2.0f); // 카메라 이동 시간
yield return new WaitForSeconds(0.5f); // 긴장감 정적
foreach (var m in minions)
{
if (m != null)
{
m.gameObject.SetActive(true);
m.isActive = false;
}
}
// 등장하는 모습 1.5초 대기
yield return new WaitForSeconds(1.5f);
// 카메라 플레이어에게 복귀
if (spawnCam != null) spawnCam.Priority = 0;
yield return new WaitForSeconds(2.0f);
// 카메라가 돌아왔으니 켜고 전투 시작
foreach (var m in minions)
{
if (m != null) m.isActive = true;
}
// 전투 시작, 조작 권한 복구
if (playerController != null) playerController.enabled = true;
}
// 적 처치 시 호출되는 상태 관리 함수
public void OnEnemyDown(EnemyAI enemy)
{
if (minions.Contains(enemy))
{
livingMinions--;
minions.Remove(enemy);
Debug.Log("미니언 처치 남은 수량: " + livingMinions);
if (livingMinions <= 0)
{
StartCoroutine(BossWakeUpRoutine());
}
}
else if (enemy == boss)
{
Debug.Log("보스 처치 보상 생성");
if (dosimeterPrefab != null)
{
Vector3 dropPos = boss.transform.position + Vector3.up * 1f;
Instantiate(dosimeterPrefab, dropPos, Quaternion.identity);
}
}
}
// 보스 등장 연출 코루틴
IEnumerator BossWakeUpRoutine()
{
Debug.Log("보스 등장 연출 시작");
// 플레이어 조작 잠금 (전투 일시 정지)
if (playerController != null)
{
PlayerController pc = playerController.GetComponent<PlayerController>();
if (pc != null) pc.StopMovementForCutscene();
}
// 쫄몹 전멸 후 잠깐 멈춤
yield return new WaitForSeconds(1.0f);
// 카메라를 다시 스폰 장소(보스 등장 위치)로 이동
if (spawnCam != null) spawnCam.Priority = 20;
// 카메라 이동 시간 2초 + 등장 직전 정적 0.5초
yield return new WaitForSeconds(2.0f);
yield return new WaitForSeconds(0.5f);
// 보스 스폰
if (boss != null)
{
boss.gameObject.SetActive(true);
boss.isActive = false;
}
yield return new WaitForSeconds(2.0f);
// 카메라 플레이어에게 원상 복구
if (spawnCam != null) spawnCam.Priority = 0;
// 시점 돌아오는 시간 대기
yield return new WaitForSeconds(2.0f);
// 카메라 복귀 보스 AI 가동
if (boss != null) boss.isActive = true;
// 조작 권한 복구 및 보스전
if (playerController != null) playerController.enabled = true;
}
// 방사능 측정기 획득 시 호출
public void OnDosimeterPickedUp()
{
Debug.Log("측정기 획득 진행로 개방");
// 가방 상태 영구 봉인
HelperBox box = FindAnyObjectByType<HelperBox>();
if (box != null)
{
box.allowedItem = "";
box.isLocked = true;
box.isLooted = true;
}
// 플레이어 무기 장착 해제
if (playerMelee != null)
{
playerMelee.isHoldingBat = false;
if (playerMelee.heldBatModel != null)
{
playerMelee.heldBatModel.SetActive(false);
}
}
// 클리어 연출 코루틴 시작
StartCoroutine(DosimeterClearCutscene());
}
// 방사능 측정기 획득 연출 코루틴
IEnumerator DosimeterClearCutscene()
{
// 조작 잠금
if (playerController != null)
{
PlayerController pc = playerController.GetComponent<PlayerController>();
if (pc != null) pc.StopMovementForCutscene();
}
// 카메라 문으로 이동
if (doorCam != null) doorCam.Priority = 20;
yield return new WaitForSeconds(2.0f);
yield return new WaitForSeconds(0.5f);
// 문 개방
if (nextRoomDoor != null) nextRoomDoor.OpenDoor();
yield return new WaitForSeconds(2.5f);
// 카메라 원상 복구
if (doorCam != null) doorCam.Priority = 0;
yield return new WaitForSeconds(2.0f);
// 조작 권한 복구
if (playerController != null) playerController.enabled = true;
}
private void OnTriggerEnter(Collider other)
{
// 구역 진입 시 컷씬 실행
if (other.CompareTag("Player"))
{
StartCoroutine(Area4EntranceCutscene(other.transform));
Collider col = GetComponent<Collider>();
if (col != null) col.enabled = false;
}
}
// 4구역 진입 및 배트 배달 연출
IEnumerator Area4EntranceCutscene(Transform playerTransform)
{
Debug.Log("4구역 진입 컷씬 시작");
if (playerController != null)
{
// MonoBehaviour를 PlayerController로 변환해서 브레이크를 밟음
PlayerController pc = playerController.GetComponent<PlayerController>();
if (pc != null) pc.StopMovementForCutscene();
}
Collider playerCollider = playerTransform.GetComponent<Collider>();
if (playerCollider != null) playerCollider.enabled = false;
if (helperAI != null)
{
helperAI.isCutsceneMode = true;
Vector3 targetPos = playerTransform.position + playerTransform.forward * 2f;
NavMeshAgent helperAgent = helperAI.GetComponent<NavMeshAgent>();
// 조력자 이동 처리
if (helperAgent != null && helperAgent.isOnNavMesh)
{
helperAgent.isStopped = false;
helperAgent.SetDestination(targetPos);
float timeout = 0f;
while ((helperAgent.pathPending || helperAgent.remainingDistance > 0.5f) && timeout < 3f)
{
timeout += Time.deltaTime;
yield return null;
}
}
else
{
helperAI.transform.position = targetPos;
}
// 플레이어와 완벽하게 같은 방향을 보게 하여 등을 내밈
helperAI.transform.rotation = playerTransform.rotation;
helperAI.OfferBox();
}
// 가방 아이템 설정
HelperBox helperBox = FindAnyObjectByType<HelperBox>();
if (helperBox != null)
{
helperBox.allowedItem = "Bat";
helperBox.isArea4UnlockBox = true;
helperBox.ShowItemInBox("Bat");
}
yield return new WaitForSeconds(1.5f);
// 조작 복구
if (playerCollider != null) playerCollider.enabled = true;
if (playerController != null) playerController.enabled = true;
Debug.Log("컷씬 종료 조작 권한 복구");
}
}
using UnityEngine;
public class DosimeterItem : MonoBehaviour, IInteractable
{
private Material[] itemMaterials;
[Header("발광 컬러 설정")]
private Color baseColor = Color.blue * 2.0f; // 기본 에미션
private Color focusColor = Color.yellow * 2.5f; // 주시 시 에미션
[Header("사운드 설정")]
public AudioClip zipSound; // 가방 지퍼 소리
void Start()
{
// 자식 오브젝트의 모든 렌더러 컴포넌트 추출
Renderer[] renderers = GetComponentsInChildren<Renderer>();
if (renderers.Length > 0)
{
itemMaterials = new Material[renderers.Length];
for (int i = 0; i < renderers.Length; i++)
{
itemMaterials[i] = renderers[i].material;
// 마테리얼 에미션 기능 활성화
itemMaterials[i].EnableKeyword("_EMISSION");
// 초기 기본 색상 적용
if (itemMaterials[i].HasProperty("_EmissionColor"))
{
itemMaterials[i].SetColor("_EmissionColor", baseColor);
}
}
}
}
// 시선이 닿았을 때 호출
public void OnFocus()
{
if (itemMaterials == null) return;
foreach (Material mat in itemMaterials)
{
if (mat != null)
{
mat.SetColor("_EmissionColor", focusColor);
}
}
}
// 시선이 벗어났을 때 호출
public void OnLoseFocus()
{
if (itemMaterials == null) return;
foreach (Material mat in itemMaterials)
{
if (mat != null)
{
mat.SetColor("_EmissionColor", baseColor);
}
}
}
// 클릭 상호작용 시 호출
public void OnInteract()
{
// 4구역 매니저에 획득 신호 전달
Area4_Manager manager = FindAnyObjectByType<Area4_Manager>();
if (manager != null)
{
manager.OnDosimeterPickedUp();
}
// 지퍼 소리 재생
if (zipSound != null)
{
AudioSource.PlayClipAtPoint(zipSound, transform.position);
}
// 오브젝트 제거
Destroy(gameObject);
}
}
using UnityEngine;
using System.Collections;
public class PlayerHealth : MonoBehaviour
{
[Header("체력 설정")]
public float maxHealth = 100f; // 최대 체력임
private float currentHealth; // 현재 체력임
[Header("부활 위치")]
public Transform area4RespawnPoint; // 4구역 입구 부활 위치임
private PlayerController playerController; // 플레이어 조작 스크립트임
private CharacterController characterController; // 캐릭터 컨트롤러 컴포넌트임
private Animator anim;
private PlayerMelee playerMelee;
void Start()
{
currentHealth = maxHealth;
playerController = GetComponent<PlayerController>();
characterController = GetComponent<CharacterController>();
anim = GetComponentInChildren<Animator>();
playerMelee = GetComponent<PlayerMelee>();
}
// 데미지 처리 함수임
public void TakeDamage(float damageAmount)
{
// 이미 죽은 상태라면 데미지 무시함
if (currentHealth <= 0) return;
currentHealth -= damageAmount;
Debug.Log("데미지 발생함 남은 체력임: " + currentHealth);
// 체력 상태에 따라 애니메이션을 다르게
if (currentHealth > 0)
{
// 체력이 남았으면 움찔 (Hit)
if (anim != null) anim.SetTrigger("Hit");
}
else
{
// 체력이 0 이하가 되면 픽 쓰러짐 (Dead)
if (anim != null) anim.SetTrigger("Dead");
// 사망 시 빠따 휘두르기 금지 & 배트 모델 숨기기
if (playerMelee != null)
{
playerMelee.isDead = true;
if (playerMelee.heldBatModel != null) playerMelee.heldBatModel.SetActive(false);
}
StartCoroutine(DieAndRespawnRoutine());
}
}
// 사망 및 부활 처리 코루틴임
IEnumerator DieAndRespawnRoutine()
{
Debug.Log("사망함 조작을 일시 중단함");
// 플레이어 조작 잠금 처리함
if (playerController != null) playerController.enabled = false;
// 2초 동안 정지 상태 유지함
yield return new WaitForSeconds(2.5f);
// 부활 위치로 순간이동함
if (area4RespawnPoint != null)
{
// 컨트롤러가 켜져 있으면 위치 이동이 안 되므로 잠시 끔
if (characterController != null) characterController.enabled = false;
transform.position = area4RespawnPoint.position;
if (characterController != null) characterController.enabled = true;
}
// 상태 초기화 및 조작 복구함
currentHealth = maxHealth;
if (playerController != null) playerController.enabled = true;
// 부활 시 다시 휘두를 수 있게 해주고, 배트를 들고 있었다면 다시 보여주기
if (playerMelee != null)
{
playerMelee.isDead = false;
if (playerMelee.isHoldingBat && playerMelee.heldBatModel != null)
playerMelee.heldBatModel.SetActive(true);
}
if (anim != null)
{
anim.ResetTrigger("Dead"); // 혹시라도 찌꺼기로 남아있을 사망 신호를 깔끔하게 지움
anim.Rebind(); // 애니메이터의 기억을 아예 포맷해서 게임 처음 켰을 때(대기 상태)로 강제 리셋
}
Debug.Log("부활 완료함 4구역 입구에서 다시 시작함");
}
}
using UnityEngine;
using System.Collections;
public class PlayerMelee : MonoBehaviour
{
[Header("연결")]
public Animator anim;
public Collider batCollider; // 진짜 데미지를 줄 빠따의 껍데기
public GameObject heldBatModel; // 손에 쥐고 있는 빠따 모델
[Header("상태")]
public bool isHoldingBat = false; // 매니저와 상자가 확인할 상태 변수
private bool isSwinging = false;
public bool isDead = false;
void Start()
{
if (batCollider != null) batCollider.enabled = false;
}
void Update()
{
// 좌클릭 했고 빠따를 쥐고 있고 안 휘두르고 있을 때만 공격
if (Input.GetMouseButtonDown(0) && isHoldingBat && !isSwinging && !isDead)
{
if (anim != null) anim.SetTrigger("Swing");
StartCoroutine(FPSBatSwing());
}
}
// 조력자나 4구역 입구에서 빠따를 얻었을 때 호출되는 함수
public void EquipBat()
{
isHoldingBat = true;
if (heldBatModel != null) heldBatModel.SetActive(true);
// 야구공 강제 해제
PlayerThrow pt = GetComponent<PlayerThrow>();
if (pt != null)
{
pt.isHoldingBall = false; // 야구공 던지기 권한 박탈
if (pt.heldBallModel != null) pt.heldBallModel.SetActive(false); // 손에 든 야구공 숨기기
}
Debug.Log("빠따 장착 완료! 이제 야구공은 안 던집니다!");
}
// FPS 배트 궤적 & 판정 연출 코루틴
IEnumerator FPSBatSwing()
{
isSwinging = true;
// 휘두르기 전 원래 들고 있던 각도 저장
Quaternion startRot = heldBatModel.transform.localRotation;
// 대각선 앞으로 내려찍는 타격 각도 계산 (X축으로 숙이고, Y축으로 비틂)
Quaternion strikeRot = startRot * Quaternion.Euler(60f, -30f, 0f);
// 공격 판정 즉시 킴
//if (batCollider != null) batCollider.enabled = true;
// 엄청 빠른 속도로 내려찍기 (0.1초 컷)
float t = 0;
bool isHitColliderActive = false; // 판정이 켜졌는지 체크하는 변수
while (t < 1f)
{
t += Time.deltaTime * 10f; // 속도 조절
heldBatModel.transform.localRotation = Quaternion.Slerp(startRot, strikeRot, t);
// 배트가 시각적으로 70%쯤 내려찍혔을 때 공격 판정을 킴
// 배트가 기울어질 때 적과 부딪히며 타격음 발생 하도록
if (t >= 0.7f && !isHitColliderActive)
{
if (batCollider != null) batCollider.enabled = true;
isHitColliderActive = true;
}
yield return null;
}
// 공격 판정 즉시 끔 (다단 히트 방지)
if (batCollider != null) batCollider.enabled = false;
// 내려찍은 상태로 아주 잠깐 멈춤 (타격감 극대화)
yield return new WaitForSeconds(0.1f);
// 다시 부드럽게 원래 자세로 복귀
t = 0;
while (t < 1f)
{
t += Time.deltaTime * 4f; // 복귀 속도
heldBatModel.transform.localRotation = Quaternion.Slerp(strikeRot, startRot, t);
yield return null;
}
// 혹시 모를 오차 방지를 위해 각도 완전 초기화
heldBatModel.transform.localRotation = startRot;
// 다음 공격까지 약간의 쿨타임
yield return new WaitForSeconds(0.2f);
isSwinging = false;
}
public void EnableBatCollider() { }
public void DisableBatCollider() { }
using UnityEngine;
public class MeleeWeapon : MonoBehaviour
{
[Header("타격 설정")]
public float damage = 35f; // 타격 데미지
public float knockbackForce = 15f; // 물리적 밀쳐내기 강도
[Header("사운드 설정")]
public AudioClip hitSound; // 배트 타격음
private Collider hitCollider;
void Start()
{
// 콜라이더 참조 저장 및 초기 설정
hitCollider = GetComponent<Collider>();
if (hitCollider != null)
{
hitCollider.isTrigger = true;
hitCollider.enabled = false;
}
}
// 애니메이션 이벤트에서 공격 판정 활성화
public void EnableCollider()
{
if (hitCollider != null) hitCollider.enabled = true;
}
// 공격 판정 비활성화
public void DisableCollider()
{
if (hitCollider != null) hitCollider.enabled = false;
}
// 적 유닛과의 충돌 판정 처리
private void OnTriggerEnter(Collider other)
{
// 부모 오브젝트에서 적 AI 컴포넌트 추출
EnemyAI enemy = other.GetComponentInParent<EnemyAI>();
if (enemy != null)
{
// 타격 방향 계산 및 수평 보정
Vector3 hitDirection = (enemy.transform.position - transform.position).normalized;
hitDirection.y = 0;
// 데미지 및 넉백 전달
enemy.TakeDamage(damage, hitDirection, knockbackForce);
// 타격음
if (hitSound != null) AudioSource.PlayClipAtPoint(hitSound, transform.position);
// 다단 히트 방지를 위한 판정 즉시 종료
DisableCollider();
}
}
}
using UnityEngine;
using UnityEngine.SceneManagement;
public class NextScene : MonoBehaviour
{
[Header("전환 설정")]
public string nextSceneName; // 이동할 다음 씬의 이름
private bool hasTriggered = false; // 중복 실행 방지용
private void OnTriggerEnter(Collider other)
{
// 플레이어가 닿았고, 아직 전환된 적이 없다면
if (other.CompareTag("Player") && !hasTriggered)
{
hasTriggered = true; // 두 번 부딪히는 것 방지
// 혹시 모를 버그를 막기 위해 조작을 잠깐 멈춤
PlayerController pc = other.GetComponent<PlayerController>();
if (pc != null) pc.StopMovementForCutscene();
// 다음 씬으로
Debug.Log($"{nextSceneName} 씬으로 바로 이동합니다.");
SceneManager.LoadScene(nextSceneName);
}
}
}
using UnityEngine;
using UnityEngine.SceneManagement;
using Unity.Cinemachine;
public class PlayerTransfer : MonoBehaviour
{
// 씬이 여러 번 바뀌어도 플레이어가 중복 생성되지 않도록 막아주는 장치
private static PlayerTransfer instance;
void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject); // 씬이 넘어가도 이 오브젝트(플레이어)를 파괴하지 않음
// 씬이 로드될 때마다 'OnSceneLoaded' 함수를 실행하도록 예약
SceneManager.sceneLoaded += OnSceneLoaded;
}
else
{
// 이미 플레이어가 존재한다면 새로 넘어온 플레이어는 파괴 (중복 방지)
Destroy(gameObject);
}
}
// 새로운 씬이 화면에 완전히 로드된 직후에 자동으로 실행되는 함수
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// 새로운 씬에서 "SpawnPoint" 스트링을 찾음
GameObject spawnPoint = GameObject.Find("SpawnPoint");
// 만약 찾았다면, 플레이어의 위치와 바라보는 방향을 그곳으로 맞춤
if (spawnPoint != null)
{
CharacterController cc = GetComponent<CharacterController>();
if (cc != null) cc.enabled = false;
transform.position = spawnPoint.transform.position;
transform.rotation = spawnPoint.transform.rotation;
if (cc != null) cc.enabled = true;
// 묶어뒀던 조작 다시 풀어주기 (스크립트 다시 켜기)
PlayerController pc = GetComponent<PlayerController>();
if (pc != null)
{
pc.enabled = true;
}
// 새로운 씬의 시네머신 카메라가 나(Player)를 찍도록 강제 연결하기
CinemachineCamera cam = FindAnyObjectByType<CinemachineCamera>();
if (cam != null)
{
cam.Follow = transform;
cam.LookAt = transform;
}
Debug.Log("플레이어가 새로운 씬의 스폰 지점으로 무사히 이동했습니다.");
}
}
void OnDestroy()
{
// 오브젝트가 파괴될 때는 예약해둔 이벤트를 취소 (메모리 누수 방지)
SceneManager.sceneLoaded -= OnSceneLoaded;
}
}
https://drive.google.com/file/d/1PMCAyOAwok6eTE-U3gZuEAA6ToRAwhE4/view?usp=sharing