티스토리 뷰

1월 20일

Player & ItemBox Interaction 구현

작업 내용 요약

Test_Scene_Nam 씬 만들고 작업.

플레이어와 아이템상자 간 상호작용 시스템 구현.

플레이어가 아이템상자에 접근하면 상호작용 UI (줍기(E)) 텍스트가 표시되며,

E키 입력 시 해당 아이템상자가 Photon 서버 기준으로 Destroy 되도록 처리.

1. Player 프리팹에 상호작용 추가

  • Player 프리팹에 PlayerInteraction 스크립트 부착
  • 이를 프리팹 자체에 적용하기 위해 overrides-apply all

  • 아이템 탐지 위해 Interactable 전용 layer 만들고, 이를 Item에 부착
  • 플레이어가 Interactable 오브젝트 근처에 접근 시:
    • 아이템 상자 윗부분에 줍기(E) UI 표시
  • E키 입력 시:
    • 상호작용 대상 아이템을 PhotonNetwork.Destroy() 로 제거
    • 방 안에 있는 클라이언트에게서 동일하게 삭제됨
  • Inspector Field
    • 상호작용 거리 : 1.2
    • Interactable mask에만 반응하도록
    • interact key : E키

  • PlayerInteraction.cs 코드
using UnityEngine;
using Photon.Pun;

//플레이어가 앞의 상호작용 대상을 Raycast로 감지, E키로 상호작용
public class PlayerInteraction : MonoBehaviour
{
    [Header("Raycast")]
    //레이캐스트 쏘는 최대 거리. 이 거리 안에 있는 물체만 상호작용
    public float interactDistance = 1.2f;
    //Raycast가 맞출 레이어 필터(Interactable 레이어만 감지)
    public LayerMask interactableMask;

    [Header("Input")]
    //상호작용 키를 E로 설정.
    public KeyCode interactKey = KeyCode.E;

    //내부 상태 변수
    //현재 Raycast로 감지된 대상
    private ItemBox currentTarget;
    //플레이어가 바라보는 방향 벡터
    private Vector2 lookDir = Vector2.right; //기본은 일단 오른쪽

    //매 프레임마다 자동 호출됨
    private void Update()
    {
        //현재 입력 기반으로 바라보는 방향(lookDir) 갱신
        UpdateLookDirection();
        //lookdir 방향으로 Raycast 쏴서 상호작용
        DetectInteractable();
        //대상이 있을 때 E키 입력 받으면 Interact() 실행
        HandleInput();
    }

    //바라보는 방향 결정
    void UpdateLookDirection()
    {
        //현재 입력 방향을 그대로 바라보는 방향으로 사용
        //좌/우 입력 값 가져오기
        float x = Input.GetAxisRaw("Horizontal");
        //상/하 입력 값 가져오기
        float y = Input.GetAxisRaw("Vertical");

        //2d 방향벡터로 묶어서 input 변수 선언
        Vector2 input = new Vector2(x, y);

        //방향키를 하나라도 누르고 있으면
        if (input.sqrMagnitude > 0.01f)
            // 입력 방향을 길이 1로 정규화해서(크기는 상관없으니까) lookDir로 저장
            lookDir = input.normalized;
    }

    //레이캐스트로 상호작용 가능한 물체 찾음
    void DetectInteractable()
    {
        //플레이어 위치(transform.position)에서 lookDir 방향으로 interactDistance만큼 Raycast 쏜다.
        //interactableMask로 interactable레이어만 맞도록 필터링
        RaycastHit2D hit = Physics2D.Raycast((Vector2)transform.position, lookDir, interactDistance, interactableMask);

        //이번 프레임에 새로 감지된 타겟 변수
        ItemBox newTarget = null;

        //레이캐스트가 어떤 콜라이더에 맞았으면
        if(hit.collider != null)
            //맞은 오브젝트의 ItemBox2D 스크립트 확인
            newTarget = hit.collider.GetComponent<ItemBox>();
        
        //타겟이 바뀌었으면(플레이어가 이동하면서 ui 옮겨가야 함)
        if(newTarget != currentTarget)
        {
            //이전 타겟 ui 끔
            if(currentTarget != null)
                currentTarget.ShowUI(false);
            //현재 타겟을 새 타겟으로 교체
            currentTarget = newTarget;
            //새 타겟 ui 켬
            if(currentTarget!=null)
                currentTarget.ShowUI(true);
        }

        //디버깅: Scene뷰에서 레이캐스트 어디로 쏴지는지 표시
        //currentTarget 있으면 초록, 없으면 빨강 표시
        Debug.DrawRay(transform.position, lookDir * interactDistance, currentTarget ? Color.green : Color.red);
    }

    //입력(E키) 처리
    void HandleInput()
    {
        if(currentTarget == null) return;
        //이번 프레임에 상호작용 /ㅋㅋㅋㅋㅌ키(E) 눌렀으면 true
        if (Input.GetKeyDown(interactKey))
        {
            //E키 누르자마자 끔
            currentTarget.ShowUI(false);
            //타겟의 Interact() 호출, 누른 사람이 누구인지 photon에 전달
            currentTarget.Interact(PhotonNetwork.LocalPlayer);
        }
    }

}

 

2. Item 상자 오브젝트 관련

  • interactable layer 적용
  • collider 설정 → isTrigger 체크
  • Interact UI : 이 상자 아래 Canvas 끌어와서 붙임

  • ItemBox 스크립트 부착
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

//아이템에 붙이는 스크립트
//플레이어가 E로 상호작용하면 Interact()가 호출됨
public class ItemBox : MonoBehaviourPun
{
    //디버그용 콘솔에 띄우는 아이템 이름
    public string debugItemName = "Test Item";

    [Header("UI")]
    [SerializeField]
    //ItemBOX 아래 Canvas 넣기
    private GameObject interactUI;

    //중복 상호작용 방지 플래그(이미 먹힌 상자인지)
    private bool used;

    private void Awake()
    {
        //interactUI null 아닌 거 확인하고
        if(interactUI != null)
            //기본으로 줍기(E) 안보이도록
            interactUI.SetActive(false);
    }

    //PlayerInteraction이 보면 켜고, 안 보면 끔
    public void ShowUI(bool show)
    {
        Debug.Log($"[ItemBox] ShowUI({show}) on {name}");
        //interactUI 존재 && 아직 사용 안한 상자일 때
        if(interactUI != null && !used)
            interactUI.SetActive(show);
    }

    //플레이어가 E키 상호작용 성공했을 때 호출될 함수
    public void Interact(Player interactor)
    {
        //이미 누가 먹었으면(used=true) 추가 실행 금지
        if (used) return;
        //이제부터 사용됨 상태
        used = true;
        //먹는 순간 "줍기(E)" UI 끔
        ShowUI(false);
        //콘솔에 누가, 뭘 눌렀는지 확인
        Debug.Log($"[ItemBox] Opened by {interactor?.NickName} : {debugItemName}");

        //TODO(Phase2): 나중에 실제 아이템 데이터(ItemData)와 인벤토리 연결 코드를 여기에 추가할 예정
        //예: playerInventory.Add(itemData); 같은 코드가 들어갈 자리

        //보통 방장만 Destroy 하는 게 안전하다네유..
        if (PhotonNetwork.IsMasterClient)
        {
            PhotonNetwork.Destroy(gameObject);
        }
        else
        {
            //이 클라이언트가 방장이 아니면 방장에게 요청 (RPC)
            photonView.RPC(nameof(RequestDestroy), RpcTarget.MasterClient);
        }
    }

    [PunRPC]
    private void RequestDestroy()
    {
        //방장에서 실행됨
        if (PhotonNetwork.IsMasterClient)
            PhotonNetwork.Destroy(gameObject);
    }

}

 

⚠️ 참고 사항 — Input System 설정

  • 본 작업은 New Input System을 사용하지 않고 Old Input 방식(Input.GetKey, Input.GetKeyDown)을 사용함
  • 따라서 프로젝트 설정에서 Input System을 BOTH로 설정해야 정상 동작함

필수 설정 경로

Edit →ProjectSettings →Player →OtherSettings
→ActiveInputHandling:BOTH
  • 해당 설정이 New Input System Only로 되어 있을 경우:
    • E 키 입력이 인식되지 않음
    • 상호작용(UI 표시 / 아이템 줍기) 기능이 정상 동작하지 않음

1월 21일

작업 요약

1. 오브젝트 동작 변경

  • ItemBox는 가구이므로 사라지지 않도록 변경.
  • 시체는 신고되면 방 전체에서 사라지도록 만듦.
  • 모두 플레이어와 상호작용하는 오브젝트들이므로 IInteractable 인터페이스 코드 새롭게 짰음.
using UnityEngine;
using Photon.Realtime;

//상호작용 가능한 대상들이 갖는 기능,약속 적어둔 인터페이스
public interface IInteractable
{
    //가까이 있을 때 UI(줍기/신고) 켜거나 끄는 함수
    void ShowUI(bool show);

    //플레이어가 E를 눌렀을 때 실행되는 상호작용 함수
    void Interact(Player interactor);
}
  • 이에 따라 PlayerInteraction.cs 코드에서 IInteractable 사용하도록 변경
//맞은 오브젝트의 IInteractable 스크립트 확인
newTarget = hit.collider.GetComponent<IInteractable>();
  • 변경된 ItemBox 코드
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

//아이템에 붙이는 스크립트
//플레이어가 E로 상호작용하면 Interact()가 호출됨
//인터페이스 implements
public class ItemBox : MonoBehaviourPun, IInteractable
{
    //디버그용 콘솔에 띄우는 아이템 이름
    public string debugItemName = "Test Item";

    [Header("UI")]
    [SerializeField]
    //ItemBOX 아래 Canvas 넣기
    private GameObject interactUI;

    //중복 상호작용 방지 플래그(이미 먹힌 상자인지)
    private bool used;

    private void Awake()
    {
        //interactUI null 아닌 거 확인하고
        if(interactUI != null)
            //기본으로 줍기(E) 안보이도록
            interactUI.SetActive(false);
    }

    //PlayerInteraction이 보면 켜고, 안 보면 끔
    public void ShowUI(bool show)
    {
        Debug.Log($"[ItemBox] ShowUI({show}) on {name}");
        //interactUI 존재 && 아직 사용 안한 상자일 때
        if(interactUI != null && !used)
            interactUI.SetActive(show);
    }

    //플레이어가 E키 상호작용 성공했을 때 호출될 함수
    public void Interact(Player interactor)
    {
        //이미 누가 먹었으면(used=true) 추가 실행 금지
        if (used) return;

        //방장이 열림 확정->모두에게 전파->사용됨 상태로 만들어야 함

        //오픈한사람의 넘버 초기화
        int openerActorNumber = -1;

        if(interactor != null)
        {
            //연 사람의 고유 넘버 저장
            openerActorNumber = interactor.ActorNumber;
        }

        //내가 방장일 경우, 전체에게 '열림' 적용
        if (PhotonNetwork.IsMasterClient)
        {
            //이 박스에 붙은 PhotonView 이용,
            //방 안 모두에게 RpcOpen 함수 원격 호출(누가 열었는지도 전달)
            photonView.RPC("RpcOpen", RpcTarget.All, openerActorNumber);
        }
        else
        {
            //방장 아닐 경우, 방장에게 open request
            photonView.RPC("RequestOpen", RpcTarget.MasterClient, openerActorNumber);
        }
    }
    
    [PunRPC]
    //방장에게 열기 요청
    private void RequestOpen(int openerActorNumber)
    {
        if(!PhotonNetwork.IsMasterClient) return;
        //방장이 전체에게 open 적용
        photonView.RPC(nameof(RpcOpen), RpcTarget.All, openerActorNumber);
    }

    //모두에게 열림 적용하는 함수
    [PunRPC]
    private void RpcOpen(int openerActorNumber)
    {
        if(used) return;
        //열린 상태 확정
        used = true;
        //안내 ui 끄기
        ShowUI(false);

        //누가 열었는지 ActorNumber로 찾아서 로그 찍기
        Player openerPlayer = null;
        
        if(PhotonNetwork.CurrentRoom != null)
        {
            openerPlayer = PhotonNetwork.CurrentRoom.GetPlayer(openerActorNumber);
        }

        //"누가 뭘 열었습니다." 공지 필요할 경우
        string openerName = "Unknown";
        if(openerPlayer != null)
        {
            openerName = openerPlayer.NickName;
        }

        Debug.Log("[ItemBox] Opened by " + openerName + " : " + debugItemName);

        //!!!!여기부터가 '연 사람만 받는 개인 처리' 자리
        //예) 팝업 띄우기, 인벤토리에 넣기, 개인 효과 등
        if(PhotonNetwork.LocalPlayer != null)
        {
            if(PhotonNetwork.LocalPlayer.ActorNumber == openerActorNumber)
            {
                // 여기 코드는 "연 사람"에게만 실행됨
                Debug.Log("[ItemBox] This client opened the box -> show personal popup / give item");
                // TODO: UIManager.ShowItemPopup(...)
                // TODO: Inventory.AddRandomItem(...)
            }
        }

        //가구 열림 스프라이트 관련해서 추가..?
        
    }
}

 

2. 시체 오브젝트 및 신고 UI 구현

  • 캡슐 모양 오브젝트로 시체(DeadBody) 프리팹 생성
  • 플레이어가 시체 근처로 다가가면 “신고(E)” 텍스트 UI 표시
  • E키 상호작용 시 시체 제거 동기화, 중복 신고 방지도 포함.
  • 캡슐 오브젝트에 Capsule Collider 2D, Photon View, Dead Body 스크립트 컴포넌트로 붙임.
  • 캔버스 render mode : world space

  • DeadBody.cs 코드
using UnityEngine;
using Photon.Realtime;
using Photon.Pun;

//시체 오브젝트
//가까이 가면 신고(E) UI 표시
//E 눌러서 한번만 신고
//신고되면 방 전체에서 시체 사라짐
public class DeadBody : MonoBehaviourPun, IInteractable
{
    [Header("UI")]
    [SerializeField] private GameObject reportUI; //신고(E)(report) UI

    // 이미 신고됐는지(중복 신고 방지)
    private bool reported;

    private void Awake()
    {
        // 시작할 때 UI는 꺼두기
        if (reportUI != null)
        {
            reportUI.SetActive(false);
        }
    }

    //가까이 있을 때 UI 키기
    public void ShowUI(bool show)
    {
        if(reported) return;

        if(reportUI!=null)
            reportUI.SetActive(show);
    }

    //E키 눌렀을 때 신고 처리(PlayerInteraction에서 호출)
    public void Interact(Player interactor)
    {
        //이미 신고됐으면 무시
        if(reported) return;

        //신고한 사람 ActorNumber 저장
        int reporterActorNumber = -1;
        if(interactor != null)
        {
            reporterActorNumber = interactor.ActorNumber;
        }

        //방장이면 바로 전체에 신고 처리 적용
        if (PhotonNetwork.IsMasterClient)
        {
            photonView.RPC(nameof(RPCReport), RpcTarget.All, reporterActorNumber);
        }
        else
        {
            //방장이 아니면 방장에게 신고 요청
            photonView.RPC(nameof(RequestReport), RpcTarget.MasterClient, reporterActorNumber);
        }
    }

    //방장 실행: 신고 요청 받고 -> 확정 -> 전체 적용 + destroy
    [PunRPC]
    private void RequestReport(int reporterActorNumber)
    {
        if(!PhotonNetwork.IsMasterClient) return;
        if (reported) return;

        //전체에게 신고 처리 적용
        photonView.RPC(nameof(RPCReport), RpcTarget.All, reporterActorNumber);
    }

    //신고 요청
    [PunRPC]
    private void RPCReport(int reporterActorNumber)
    {
        //중복 호출 방지
        if(reported) return;

        //신고 확정
        reported = true;

        //ui 끄기
        ShowUI(false);

        //디버그 로그 : 누가 신고했는지
        Debug.Log("[DeadBody] Reported! reporterActorNumber = " + reporterActorNumber);

        //Destroy는 여기서 방장만
        if (PhotonNetwork.IsMasterClient)
            PhotonNetwork.Destroy(gameObject);

        //TODO: 여기서 "회의/투표 시작"을 연결하면 됩니다..!
        //투표는 보통 방장만 시작시키고, 다른 클라는 UI만 열게 만드는 구조가 안정적이라네유..
        //예) VoteManager.Instance.StartVote();

    }
}

테스트 해봄.

 

3. 늦게 들어온 플레이어 프리팹이 생성되지 않는 문제 해결

  • 테스트 씬에 PlayerSpawner 붙여 멀티플레이어 입장 테스트 진행
  • 문제: 늦게 들어온 플레이어의 캐릭터 프리팹이 생성되지 않음
  • 해결: NetworkManager의 씬 이동을 LoadScene → PhotonNetwork.LoadLevel로 변경
  • 이유: Photon의 방 관련 콜백들을 처리하고 있는 도중에 SceneManager의 LoadScene이 강제로 씬 전환해버리니 문제가 생겼던 것. 반면 PhotonNetwork.LoadLevel은 Photon이 씬 로딩과 룸 상태를 네트워크 흐름에 맞춰 처리해줘서 이런 타이밍 꼬임이 줄어듦.

4. 늦게 들어온 플레이어 닉네임이 안 뜨는 문제 해결

  • 문제: 늦게 들어온 유저/새로 로드된 씬에서 닉네임 UI가 비어있음

  • 해결: 닉네임을 단순히 로컬에만 들고 있지 않고,→ 닉네임이 정해지는 순간 플레이어 프로퍼티(포톤이 관리하는 플레이어 상태 테이블)로 전파되며, 다른 클라이언트도 해당 값을 읽어 UI에 표시 가능
  • PhotonNetwork.NickName + CustomProperties("nick")에 저장해서 룸 참가자들에게 공유되도록 처리
  • ConnectSceneManager.cs 에 ApplyNicknameToPhoton(string nick) 함수 추가 → 닉네임 입력 끝났을 때(OnEndEdit), RoomItemUI.cs에서 참여하기 버튼 눌렀을 때(OnJoinButtonClicked) 에서 이 함수 호출하도록 변경
//닉네임 photon에 저장, 갱신 함수
    private void ApplyNicknameToPhoton(string nick)
    {
        if(string.IsNullOrWhiteSpace(nick)) return;
        
        PhotonNetwork.NickName = nick;

        //Photon 서버 메모리 상의 플레이어 상태 테이블 담을 해시테이블 생성
        var props = new ExitGames.Client.Photon.Hashtable();
        //상태 테이블에 닉네임 저장
        props["nick"] = nick;
        //저장된 로컬 플레이어의 테이블을 photon 서버에 업로드/동기화
        PhotonNetwork.LocalPlayer.SetCustomProperties(props);
    }
  • 결과

깃에 올릴 땐 PlayerSpawner 제외하기.


1월 22일

Git 작업 로그 - 커밋 상세

### 1. feat: 플레이어 상호작용 시스템 코드 구현

1) 시체 상호작용 스크립트

- `Assets/Scripts/DeadBody.cs`
- `Assets/Scripts/DeadBody.cs.meta`

2) 상호작용 인터페이스

- `Assets/Scripts/IInteractable.cs`
- `Assets/Scripts/IInteractable.cs.meta`

3) 아이템 박스 상호작용 스크립트

- `Assets/Scripts/ItemBox.cs`
- `Assets/Scripts/ItemBox.cs.meta`

4) 플레이어 상호작용 입력/감지 스크립트

- `Assets/Scripts/PlayerInteraction.cs`
- `Assets/Scripts/PlayerInteraction.cs.meta`

### 2. feat: 시체 및 아이템 박스 상호작용 프리팹 추가

1) 시체 상호작용 프리팹

- `Assets/Prefabs/DeadBody.prefab`
- `Assets/Prefabs/DeadBody.prefab.meta`

2) 아이템 박스 상호작용 프리팹

- `Assets/Prefabs/ItemBox.prefab`
- `Assets/Prefabs/ItemBox.prefab.meta`

### 3. feat: 플레이어 프리팹에 상호작용 컴포넌트 추가

- `Assets/Prefabs/Player.prefab`
- `Assets/Prefabs/Player.prefab.meta`

### 4. fix: 상호작용 시스템 연동을 위한 네트워크 로직 수정

- `Assets/Scripts/NetworkManager.cs`
- `Assets/Scripts/NetworkManager.cs.meta`
- `Assets/Scripts/ConnectSceneManager.cs`
- `Assets/Scripts/ConnectSceneManager.cs.meta`

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함