안녕하세요. 😊
이번 글에서는 Unity에서 스킬을 구현하는 스크립트인 "SkillController"에 추가된 기능에 대해 다뤄 보겠습니다.
먼저 전체 코드를 보겠습니다.
using TMPro ;
using UnityEngine ;
using UnityEngine . UI ;
using System ;
using System . Collections . Generic ;
using UnityEngine . AI ;
public class SkillController : MonoBehaviour
{
[ Serializable ]
public class Skill
{
public Image skillImage ;
public TextMeshProUGUI skillText ;
public KeyCode skillKeyCode ;
public float cooldown ;
[ HideInInspector ] public bool isCoolingDown = false ;
[ HideInInspector ] public float currentCooldown = 0f ;
public GameObject skillRangeQuadParent ;
public GameObject skill1Object ;
[ HideInInspector ] public Vector3 skillStartPos ;
[ HideInInspector ] public bool skillIsMoving = false ;
public float skillMoveDistance = 5f ;
public float skillMoveSpeed = 2f ;
[ HideInInspector ] public Vector3 skillMoveDirection = Vector3.zero;
}
[ SerializeField ] private Transform playerTransform ;
[ SerializeField ] private NavMeshAgent playerNav ;
[ SerializeField ] private List < Skill > skills ;
private Vector3 ? skill2TargetPos = null ;
void Start ()
{
foreach ( var skill in skills)
{
if (skill.skillImage != null )
skill.skillImage.fillAmount = 0f ;
if (skill.skillText != null )
skill.skillText.text = "" ;
if (skill.skillRangeQuadParent != null )
skill.skillRangeQuadParent.gameObject. SetActive ( false );
if (skill.skill1Object != null )
{
// skill.skillStartPos = skill.skill1Object.transform.position;
skill.skill1Object. SetActive ( false );
}
}
}
void Update ()
{
foreach ( var skill in skills)
{
HandleSkillInput (skill);
HandleSkillCooldown (skill);
HandleSkill1Movement (skill);
}
}
private void HandleSkillInput ( Skill skill)
{
if (Input. GetKey (skill.skillKeyCode) && !skill.isCoolingDown)
{
if (!skill.skillRangeQuadParent.activeSelf)
skill.skillRangeQuadParent.gameObject. SetActive ( true );
if (skill == skills[ 2 ])
{
Plane groundPlane = new Plane (Vector3.up, playerTransform.position);
Ray ray = Camera.main. ScreenPointToRay (Input.mousePosition);
float enter;
if (groundPlane. Raycast (ray, out enter))
{
Vector3 hitPoint = ray. GetPoint (enter);
Vector3 playerPos = playerTransform.position;
playerPos.y = 0 ;
Vector3 targetPos = hitPoint;
targetPos.y = 0 ;
Vector3 dir = targetPos - playerPos;
// 최대 거리 제한
if (dir.magnitude > skill.skillMoveDistance)
{
dir = dir.normalized * skill.skillMoveDistance;
targetPos = playerPos + dir;
}
if (!skill.skillRangeQuadParent.activeSelf)
skill.skillRangeQuadParent. SetActive ( true );
if (skill.skill1Object != null )
{
skill.skill1Object. SetActive ( true );
skill.skill1Object.transform.position = new Vector3 (targetPos.x, playerTransform.position.y + 0.01f , targetPos.z);
skill2TargetPos = skill.skill1Object.transform.position;
}
}
}
else if (skill == skills[ 0 ] || skill == skills[ 1 ] || skill == skills[ 3 ])
{
Ray ray = Camera.main. ScreenPointToRay (Input.mousePosition);
RaycastHit hit;
if (Physics. Raycast (ray, out hit, Mathf.Infinity))
{
Vector3 dir = hit.point - skill.skillRangeQuadParent.transform.position;
dir.y = 0 ;
skill.skillRangeQuadParent.transform.rotation = Quaternion. LookRotation (dir);
}
}
}
if (Input. GetKeyUp (skill.skillKeyCode) && !skill.isCoolingDown)
{
skill.isCoolingDown = true ;
skill.currentCooldown = skill.cooldown;
skill.skillRangeQuadParent.gameObject. SetActive ( false );
if (skill == skills[ 2 ])
{
if (skill2TargetPos.HasValue)
{
Vector3 teleportPos = skill2TargetPos.Value;
teleportPos.y = playerTransform.position.y;
Vector3 lookDir = (teleportPos - playerTransform.position);
lookDir.y = 0 ;
if (lookDir.sqrMagnitude > 0.01f )
{
playerTransform.rotation = Quaternion. LookRotation (lookDir.normalized);
}
if (playerNav != null )
{
playerNav. Warp (teleportPos);
}
else
{
playerTransform.position = teleportPos;
}
skill2TargetPos = null ;
}
if (skill.skill1Object != null )
skill.skill1Object. SetActive ( false );
}
else if (skill == skills[ 0 ] || skill == skills[ 1 ] || skill == skills[ 3 ])
{
if (playerTransform != null )
{
Vector3 lookDir = skill.skillRangeQuadParent.transform.forward;
lookDir.y = 0 ;
if (lookDir.sqrMagnitude > 0.01f )
{
playerTransform.rotation = Quaternion. LookRotation (lookDir);
skill.skillMoveDirection = lookDir.normalized;
}
}
if (skill.skill1Object != null )
{
skill.skill1Object. SetActive ( true );
skill.skillIsMoving = true ;
skill.skill1Object.transform.position = playerTransform.position + new Vector3 ( 0f , 1f , 0.4f );
skill.skillMoveDirection = playerTransform.forward.normalized;
skill.skillStartPos = skill.skill1Object.transform.position;
skill.skill1Object.transform.rotation = Quaternion. LookRotation (skill.skillMoveDirection);
}
}
}
}
private void HandleSkillCooldown ( Skill skill)
{
if (skill.isCoolingDown)
{
skill.currentCooldown -= Time.deltaTime;
if (skill.currentCooldown <= 0f )
{
skill.isCoolingDown = false ;
skill.currentCooldown = 0f ;
if (skill.skillImage != null )
skill.skillImage.fillAmount = 0f ;
if (skill.skillText != null )
skill.skillText.text = "" ;
}
else
{
if (skill.skillImage != null )
skill.skillImage.fillAmount = skill.currentCooldown / skill.cooldown;
if (skill.skillText != null )
skill.skillText.text = Mathf. Ceil (skill.currentCooldown). ToString ();
}
}
}
private void HandleSkill1Movement ( Skill skill)
{
if (!skill.skillIsMoving || skill.skill1Object == null )
return ;
skill.skill1Object.transform.position += skill.skillMoveDirection * skill.skillMoveSpeed * Time.deltaTime;
float traveledDist = Vector3. Distance (skill.skill1Object.transform.position, skill.skillStartPos);
if (traveledDist >= skill.skillMoveDistance)
{
ResetSkill1Object (skill);
}
}
public void ResetSkill1Object ( Skill skill)
{
if (skill.skill1Object == null )
return ;
// skill.skill1Object.transform.position = skill.skillStartPos;
skill.skill1Object. SetActive ( false );
skill.skillIsMoving = false ;
}
}
1. 가장 먼저 추가된 NavMeshAgent컴포넌트를 가져올 playerNav 변수입니다.
이는 현재 플레이어의 이동을 NavMeshAgent을 사용해하고 있는데, 이동을 한 후
'비전이동 = 스킬 3 = KeyCode(W)"를 하고 나면 다시 이동하기 전 위치로 돌아가는 현상이 있어서 transform의 position 이동이
아닌 NavMeshAgent의 Warp()로 이동하기 위함입니다.
[ SerializeField ] private NavMeshAgent playerNav ;
2. 해당 키가 눌리고 있을 때와 떼어졌을 때 추가 로직을 작성해 주었습니다.
private void HandleSkillInput ( Skill skill)
{
if (Input. GetKey (skill.skillKeyCode) && !skill.isCoolingDown)
{
if (!skill.skillRangeQuadParent.activeSelf)
skill.skillRangeQuadParent.gameObject. SetActive ( true );
if (skill == skills[ 2 ])
{
Plane groundPlane = new Plane (Vector3.up, playerTransform.position);
Ray ray = Camera.main. ScreenPointToRay (Input.mousePosition);
float enter;
if (groundPlane. Raycast (ray, out enter))
{
Vector3 hitPoint = ray. GetPoint (enter);
Vector3 playerPos = playerTransform.position;
playerPos.y = 0 ;
Vector3 targetPos = hitPoint;
targetPos.y = 0 ;
Vector3 dir = targetPos - playerPos;
// 최대 거리 제한
if (dir.magnitude > skill.skillMoveDistance)
{
dir = dir.normalized * skill.skillMoveDistance;
targetPos = playerPos + dir;
}
if (!skill.skillRangeQuadParent.activeSelf)
skill.skillRangeQuadParent. SetActive ( true );
if (skill.skill1Object != null )
{
skill.skill1Object. SetActive ( true );
skill.skill1Object.transform.position = new Vector3 (targetPos.x, playerTransform.position.y + 0.01f , targetPos.z);
skill2TargetPos = skill.skill1Object.transform.position;
}
}
}
else if (skill == skills[ 0 ] || skill == skills[ 1 ] || skill == skills[ 3 ])
{
Ray ray = Camera.main. ScreenPointToRay (Input.mousePosition);
RaycastHit hit;
if (Physics. Raycast (ray, out hit, Mathf.Infinity))
{
Vector3 dir = hit.point - skill.skillRangeQuadParent.transform.position;
dir.y = 0 ;
skill.skillRangeQuadParent.transform.rotation = Quaternion. LookRotation (dir);
}
}
}
if (Input. GetKeyUp (skill.skillKeyCode) && !skill.isCoolingDown)
{
skill.isCoolingDown = true ;
skill.currentCooldown = skill.cooldown;
skill.skillRangeQuadParent.gameObject. SetActive ( false );
if (skill == skills[ 2 ])
{
if (skill2TargetPos.HasValue)
{
Vector3 teleportPos = skill2TargetPos.Value;
teleportPos.y = playerTransform.position.y;
Vector3 lookDir = (teleportPos - playerTransform.position);
lookDir.y = 0 ;
if (lookDir.sqrMagnitude > 0.01f )
{
playerTransform.rotation = Quaternion. LookRotation (lookDir.normalized);
}
if (playerNav != null )
{
playerNav. Warp (teleportPos);
}
else
{
playerTransform.position = teleportPos;
}
skill2TargetPos = null ;
}
if (skill.skill1Object != null )
skill.skill1Object. SetActive ( false );
}
else if (skill == skills[ 0 ] || skill == skills[ 1 ] || skill == skills[ 3 ])
{
if (playerTransform != null )
{
Vector3 lookDir = skill.skillRangeQuadParent.transform.forward;
lookDir.y = 0 ;
if (lookDir.sqrMagnitude > 0.01f )
{
playerTransform.rotation = Quaternion. LookRotation (lookDir);
skill.skillMoveDirection = lookDir.normalized;
}
}
if (skill.skill1Object != null )
{
skill.skill1Object. SetActive ( true );
skill.skillIsMoving = true ;
skill.skill1Object.transform.position = playerTransform.position + new Vector3 ( 0f , 1f , 0.4f );
skill.skillMoveDirection = playerTransform.forward.normalized;
skill.skillStartPos = skill.skill1Object.transform.position;
skill.skill1Object.transform.rotation = Quaternion. LookRotation (skill.skillMoveDirection);
}
}
}
}
먼저 스킬이 눌리고 있을 때입니다.
Skill이 Skills [2] = KeyCode(E) 일 때 Plane과 Ray를 이용해 바닥과 마우스 위치를 정확히 계산해 줬으며,
Skill2 TargetPos에 이동할 위치를 따로 저장해 주었습니다.
스킬 1, 2, 4 = Q, W, R 은 같은 발사 로직을 갖고 있기에 따로 작업하지 않고 같이 구현해 주었습니다.
또한 최대 거리는 Skill 클래스의 skillMoveDistance 이상 벗어나면 플레이어의 위치를 기준으로 skillObject가 이동할 Vector3 값을 정해주었으며, 최대 거리가 아닐 시에는 x와 y를 hitPoint에 위치 + 0.01f (그라운드와 겹치지 않게 하기 위함)에 구현했습니다.
if (!skill.skillRangeQuadParent.activeSelf)
skill.skillRangeQuadParent.gameObject. SetActive ( true );
if (skill == skills[ 2 ])
{
Plane groundPlane = new Plane (Vector3.up, playerTransform.position);
Ray ray = Camera.main. ScreenPointToRay (Input.mousePosition);
float enter;
if (groundPlane. Raycast (ray, out enter))
{
Vector3 hitPoint = ray. GetPoint (enter);
Vector3 playerPos = playerTransform.position;
playerPos.y = 0 ;
Vector3 targetPos = hitPoint;
targetPos.y = 0 ;
Vector3 dir = targetPos - playerPos;
// 최대 거리 제한
if (dir.magnitude > skill.skillMoveDistance)
{
dir = dir.normalized * skill.skillMoveDistance;
targetPos = playerPos + dir;
}
if (!skill.skillRangeQuadParent.activeSelf)
skill.skillRangeQuadParent. SetActive ( true );
if (skill.skill1Object != null )
{
skill.skill1Object. SetActive ( true );
skill.skill1Object.transform.position = new Vector3 (targetPos.x, playerTransform.position.y + 0.01f , targetPos.z);
skill2TargetPos = skill.skill1Object.transform.position;
}
}
}
else if (skill == skills[ 0 ] || skill == skills[ 1 ] || skill == skills[ 3 ])
{
Ray ray = Camera.main. ScreenPointToRay (Input.mousePosition);
RaycastHit hit;
if (Physics. Raycast (ray, out hit, Mathf.Infinity))
{
Vector3 dir = hit.point - skill.skillRangeQuadParent.transform.position;
dir.y = 0 ;
skill.skillRangeQuadParent.transform.rotation = Quaternion. LookRotation (dir);
}
}
}
3. 스킬 키 코드를 뗐을때 실행될 조건문입니다.
가장 먼저 teleportPos 변수에 skill2 TargetPos의 저장된 값을 복사해 주었습니다.
플레이어의 순간이동 구현은 NaviMeshAgent의 Warp() 함수를 이용해 이동시켜 주었으며
이동된 방향 쪽으로 플레이어를 바라보게 하기 위하여 LookRotation 함수를 사용하였습니다.
if (Input. GetKeyUp (skill.skillKeyCode) && !skill.isCoolingDown)
{
skill.isCoolingDown = true ;
skill.currentCooldown = skill.cooldown;
skill.skillRangeQuadParent.gameObject. SetActive ( false );
if (skill == skills[ 2 ])
{
if (skill2TargetPos.HasValue)
{
Vector3 teleportPos = skill2TargetPos.Value;
teleportPos.y = playerTransform.position.y;
Vector3 lookDir = (teleportPos - playerTransform.position);
lookDir.y = 0 ;
if (lookDir.sqrMagnitude > 0.01f )
{
playerTransform.rotation = Quaternion. LookRotation (lookDir.normalized);
}
if (playerNav != null )
{
playerNav. Warp (teleportPos);
}
else
{
playerTransform.position = teleportPos;
}
skill2TargetPos = null ;
}
if (skill.skill1Object != null )
skill.skill1Object. SetActive ( false );
}
/ ..생략 .. /
Unity에서 SkillController의 Inspector창입니다.
스킬 클래스의 각 필드에 맞게 연결을 해줬습니다.
감사합니다. 😊