프로젝트 도전 기능을 구현하였다.
[CreateAssetMenu(menuName ="Items/Actions/Stamina")]
public class StaminaAction : ScriptableObject, IItemAction
{
public int healStaminaAmount;
public void Execute(Player player)
{
player.condition.AddStamona(healStaminaAmount);
}
}

PlayerController에 아래 코드 추가
private bool isFirstPerson = false;
// 시점 위치
[SerializeField] private Vector3 thirdPersonOffset = new Vector3(0, 2, -4);
[SerializeField] private Vector3 firstPersonOffset = new Vector3(0, 1.6f, 0.2f);
카메라 전환 함수 추가
private void SwitchView()
{
isFirstPerson = !isFirstPerson;
Vector3 targetOffset = isFirstPerson ? firstPersonOffset : thirdPersonOffset;
playerCamera.transform.localPosition = targetOffset;
}
InputSystem에 액션 추가 후 리스너 연결 및 핸들러 함수 구현
playerInput.actions["ViewToggle"].started += OnViewToggle;
private void OnViewToggle(InputAction.CallbackContext ctx)
{
SwitchView();
}
3D 오브젝트 - Cube 생성 후 알맞게 설정
MovePlatform 스크립트 작성
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
public class MovePlatform : MonoBehaviour
{
[SerializeField]
private float moveSpeed;
[SerializeField]
private float moveDistance;
[SerializeField]
private float waitTime;
[SerializeField]
private LayerMask playerLayer;
private bool isMovingRight;
private Rigidbody rb;
private Vector3 startPos;
private Vector3 endPos;
private HashSet<Transform> playerOnPlatform = new HashSet<Transform>();
[SerializeField]
private TextMeshPro Right;
[SerializeField]
private TextMeshPro Left;
private void Start()
{
rb = GetComponent<Rigidbody>();
startPos = transform.position;
endPos = transform.position;
isMovingRight = true;
Right.enabled = true;
Left.enabled = false;
StartCoroutine(MovingPlatform());
}
void FixedUpdate()
{
Vector3 delta = transform.position - endPos;
endPos = transform.position;
foreach(Transform t in playerOnPlatform)
{
t.position += delta;
}
}
private IEnumerator MovingPlatform()
{
while(true)
{
//2초 대기
yield return new WaitForSeconds(waitTime);
//도착지 계산
Vector3 targetPos = CalculateDistance(startPos, moveDistance);
//이동
while (Vector3.Distance(transform.position, targetPos) > 0.01f)
{
Vector3 newPos = Vector3.MoveTowards(transform.position, targetPos, moveSpeed *Time.fixedDeltaTime);
rb.MovePosition(newPos);
yield return new WaitForFixedUpdate();
}
//도착지 위치 고정
rb.MovePosition(targetPos);
//방향전환
isMovingRight = !isMovingRight;
DirectionTextConversion();
startPos = targetPos;
}
}
private Vector3 CalculateDistance(Vector3 startPos, float moveDistance)
{
return startPos + (isMovingRight ? Vector3.right : Vector3.left) * moveDistance;
}
private void DirectionTextConversion()
{
Right.enabled = !Right.enabled;
Left.enabled = !Left.enabled;
}
private void OnTriggerEnter(Collider other)
{
if(((1<<other.gameObject.layer) & playerLayer) != 0)
{
playerOnPlatform.Add(other.transform);
}
}
private void OnTriggerExit(Collider other)
{
if (((1 << other.gameObject.layer) & playerLayer) != 0)
{
playerOnPlatform.Remove(other.transform);
}
}
}
인스펙터 설정

벽을 탈 수 있는 오브젝트 생성
해당 오브젝트에 추가할 ClimbingPlatform 스크립트 작성
public class ClimbingPlatform : MonoBehaviour, IInteractable
{
public string GetInteractPrompt()
{
string str = $"벽타기 : F";
return str;
}
public void OnInteract()
{
GameObject.FindWithTag("Player").GetComponent<PlayerController>().EnterClimbMode();
}
}
PlayerController, PlayrerInteraction 스크립트 수정
public class PlayerInteraction : MonoBehaviour
{
//생략..
public LayerMask layerMask1; //추가
//생략..
private void Awake()
{
playerInput = GetComponent<PlayerInput>();
cam = Camera.main;
}
private void OnEnable()
{
playerInput.actions["Interaction"].started += OnInteractInput;
}
private void OnDisable()
{
playerInput.actions["Interaction"].started -= OnInteractInput;
}
// Update is called once per frame
void Update()
{
//상호작용 레이캐스트
InteractRay();
}
private void InteractRay()
{
PlayerController c = PlayerManager.Instance.Player.controller;
if (Time.time - lastCheckTime > checkRate)
{
lastCheckTime = Time.time;
Vector3 originBottom = transform.position + Vector3.down * 0.5f;
Ray ray = cam.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2));
Ray ray1 = new Ray(originBottom,transform.forward);
RaycastHit hit;
RaycastHit hit1;
if (Physics.Raycast(ray, out hit, maxCheckDistance, layerMask))
{
if (hit.collider.gameObject != curInteractGameObject)
{
curInteractGameObject = hit.collider.gameObject;
curInteractable = hit.collider.GetComponent<IInteractable>();
SetPromptText();
}
}
else if (Physics.Raycast(ray1, out hit1, 0.8f, layerMask1) && !c.isClimbing)
{
ClimbingPlatform climbTarget = hit1.collider.GetComponent<ClimbingPlatform>();
if (climbTarget != null)
{
curInteractGameObject = hit1.collider.gameObject;
curInteractable = hit1.collider.GetComponent<IInteractable>();
SetPromptText();
}
}
else
{
curInteractGameObject = null;
curInteractable = null;
promptText.gameObject.SetActive(false);
}
}
}
private void SetPromptText()
{
promptText.gameObject.SetActive(true);
promptText.text = curInteractable.GetInteractPrompt();
}
public void OnInteractInput(InputAction.CallbackContext context)
{
PlayerController c = PlayerManager.Instance.Player.controller;
if (curInteractable != null)
{
curInteractable.OnInteract();
curInteractGameObject = null;
curInteractable = null;
promptText.gameObject.SetActive(false);
}
if (curInteractable != null && c.isClimbing)
{
c.ToggleClimbMode();
curInteractGameObject = null;
curInteractable = null;
promptText.gameObject.SetActive(false);
return;
}
}
}
public class PlayerController : MonoBehaviour
{
//생략...
[Header("벽타기")]
public bool isClimbing = false;
[SerializeField] private float climbingSpeed;
[SerializeField] private LayerMask climbable;
//생략...
private void FixedUpdate()
{
//추가된 코드
if (isClimbing)
ClimbMove();
else
Move();
}
private void LateUpdate()
{
if (canLook)
CameraLook();
}
// Move 입력 처리
private void OnMove(InputAction.CallbackContext ctx)
{
if (ctx.phase == InputActionPhase.Performed)
curMovementInput = ctx.ReadValue<Vector2>();
else if (ctx.phase == InputActionPhase.Canceled)
curMovementInput = Vector2.zero;
}
//생략...
private void Move()
{
Vector3 dir = (transform.forward * curMovementInput.y + transform.right * curMovementInput.x) * movSpeed;
dir.y = rb.velocity.y;
rb.velocity = dir;
}
private void ClimbMove()
{
Vector3 dir = new Vector3(curMovementInput.x, curMovementInput.y, 0);
rb.velocity = dir * climbingSpeed;
// 바닥 근처에서 벽 감지 안 되면 자동 해제
Vector3 bottom = transform.position + Vector3.down * 0.9f;
if (!Physics.Raycast(bottom, transform.forward, 1f, climbable))
{
ExitClimbMode();
}
}
public void EnterClimbMode()
{
isClimbing = true;
rb.useGravity = false;
}
public void ExitClimbMode()
{
isClimbing = false;
rb.useGravity = true;
}
//생략...
public void ToggleClimbMode()
{
if (isClimbing)
ExitClimbMode();
else
EnterClimbMode();
}
}