일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 개발
- 가상현실
- HAPTIC
- 팀프로젝트
- 유니티 GUI
- meta
- CGV
- 포트폴리오
- 오브젝트 풀링
- 연습
- 개발일지
- 앱 배포
- VR
- 유니티 Json 데이터 연동
- Oculus
- 멀티플레이
- OVR
- 드래곤 플라이트 모작
- 모작
- XR
- input system
- meta xr
- Photon Fusion
- 드래곤 플라이트
- 팀 프로젝트
- 길건너 친구들
- ChatGPT
- 오큘러스
- 유니티
- 유니티 UI
- Today
- Total
EasyCastleUNITY
[과제]궁수의 전설 이동 및 공격 만들기(2023.08.23 00:01분 최종완성) 본문
궁수의 전설의 이동 및 공격 기능을 만들기 위해서 직접 다운을 받아서 플레이 해보았다.
그 결과, 알아낸 특징들은 다음과 같다.
1. 이동은 화면 중앙 하단에 있는 조이스틱을 이용하여 이동한다.
ㄴ 이 조이스틱은 중앙 하단이 아닌 곳에서 터치를 하여 이동하려고 하면, 해당 위치로 움직여서 이동기능을 한다.
2. 공격 기능이 실행되는 것에는 여러 조건이 있었다.
ㄴ 몬스터가 있을 경우, 이동을 멈추면, 그 자리에서 궁수는 공격을 한다. (몬스터를 바라보고 공격)
ㄴ 몬스터가 죽어서 사라지거나, 몬스터가 없을 경우, 이동을 멈추어도 궁수는 공격하지 않는다.
(죽어도 움직이는 게 아닌 이상, 해당 몬스터가 죽은 자리를 계속 바라보고 있다.)
ㄴ 플레이어는 몬스터와 플레이어 사이에 장애물이 있어도 공격을 하며,
장애물이 있을 경우, 해당 공격은 장애물에 가로막힌다.
ㄴ 날아가는 화살은 몬스터와 부딫치면 바로 사라진다.
하지만 장애물에 부딫치는 경우, 1초 정도 남아있다 사라진다. (지연삭제)
ㄴ 기본적인 공격력은 200, 몬스터 체력 400정도
이 모든 기능을 바로 만들려고 시도 하지 말고 단계별로 만들기 위해, 개발단계를 설정해보았다.
1. 조이스틱을 통한 이동 기능
1-1. 터치한 위치에 조이스틱이 이동하여 해당 위치에서 이동 기능 시행 (가능하면 하기로)
2. 이동을 하는 경우, 몬스터가 있으면, 해당 몬스터를 바라보고 이동하도록 함
3. 이동을 하는 경우, 몬스터가 없으면, 앞을 보도록 설정 (정확히는 움직이는 방향에 앞을 보도록 설정)
(2&3) -> 테스트를 위해 버튼을 클릭하면, 삭제하도록 하는 기능 임시로 만듬
4. 공격 기능 개발
4-1. 이동을 멈추고, 몬스터를 바라보며 공격 (정확히는 이동중에도 몬스터를 바라보고 있다.)
4-2. 몬스터가 없는 경우, 이동을 멈추어도 공격 기능은 실행되지 않는다.
4-3. 몬스터를 향해 공격을 하고, 장애물이 있으면, 장애물에 공격이 막힌다.
ㄴ 이 경우, 바로 화살이 사라지는 것이 아닌, 1초 정도 존재하고 있다가 사라진다.
4-4. 화살이 빗나갔을 경우, 계속 날아가는 것이 아닌, 바닥에 박힌다. (중력에 영향을 받는다)
궁수의 전설은 2.5D 이지만, 2D 플랫폼을 사용하여 만들어 보기로 하였다.
테스트
1. 플레이어 조이스틱으로 이동
조이스틱은 이 asset의 Variable Joystick을 이용했다.
PlayerControl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Test
{
public class PlayerControl : MonoBehaviour
{
public enum eControlType
{
KeyBoard, Joystick
}
[SerializeField] private VariableJoystick joystick;
[SerializeField] eControlType controlType;
[SerializeField] private float moveSpeed = 1.0f;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
float h = 0;
float v = 0;
//키보드로 조종, 간편한 테스트를 위해 포함
if (this.controlType == eControlType.KeyBoard)
{
h = Input.GetAxisRaw("Horizontal");
v = Input.GetAxisRaw("Vertical");
}
// 조이스틱으로 조종
else if (this.controlType == eControlType.Joystick)
{
h = this.joystick.Direction.x;
v = this.joystick.Direction.y;
}
Vector2 dir = (Vector2.up * v) + (Vector2.right * h);
this.transform.Translate(dir.normalized * this.moveSpeed * Time.deltaTime);
}
}
}
2. 몬스터가 있으면 플레이어가 그 몬스터를 보며 이동한다.
처음에는 LookAt으로 구현했었지만, 플레이어의 각도가 회전하게 되어, 화면상에서 안보이는 현상이 생기게 되었다.
이 문제를 해결하기 위해 searching을 했고 몬스터와 플레이어 사이 간에 바라보기 위해 필요한 각도를 계산하는 방법을 적용했다.
https://brainfreeee.tistory.com/66
이 블로그를 응용했다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Test
{
public class PlayerControl : MonoBehaviour
{
public enum eControlType
{
KeyBoard, Joystick
}
[SerializeField] private VariableJoystick joystick;
[SerializeField] eControlType controlType;
[SerializeField] private float moveSpeed = 1.0f;
private MonsterControl monster;
private float rotateSpeed;
// Start is called before the first frame update
void Start()
{
this.monster = GameObject.FindAnyObjectByType<MonsterControl>();
}
// Update is called once per frame
void Update()
{
float h = 0;
float v = 0;
//키보드로 조종, 간편한 테스트를 위해 포함
if (this.controlType == eControlType.KeyBoard)
{
h = Input.GetAxisRaw("Horizontal");
v = Input.GetAxisRaw("Vertical");
}
// 조이스틱으로 조종
else if (this.controlType == eControlType.Joystick)
{
h = this.joystick.Direction.x;
v = this.joystick.Direction.y;
}
if (monster != null) { //몬스터가 죽으면 바라보지 않도록 하기 위함
//나의 좌표와, 몬스터의 좌표를 빼서 방향벡터를 생성
Vector2 direction = new Vector2(this.transform.position.x - monster.transform.position.x,
this.transform.position.y - monster.transform.position.y);
//나온 방향벡터를 x,y 성분으로 분리하여
//그 사이의 각도를 계산한다.
//즉 몬스터를 바라보는데 필요한 각도를 계산한다.
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
this.transform.rotation = Quaternion.Euler(0, 0, angle + 90);
}
Vector2 dir = (Vector2.up * v) + (Vector2.right * h);
this.transform.Translate(dir.normalized * this.moveSpeed * Time.deltaTime);
}
}
}
3. 몬스터가 사라지면 원래 앞을 보는 각도로 돌아와 이동
이 기능을 테스트 하기 위해 버튼을 만들어, 몬스터를 삭제하는 기능을 만들었다.
이 테스트에서는 키보드로 플레이어를 조종한다.
위에 영상에서 볼 수 있듯, 대상을 바라보며 이동하다,
대상이 사라지자 그 회전각도를 유지한 상태로 이동을 하는 플레이어가 보인다.
이러한 문제는 회전각도를 마음대로 조종할 수 없는 실제 플레이어의 입장에서 많이 불편하다.
그래서 해결하기 위해 이 회전 각도를 (0,0,0)으로 초기화 하기로 하였다.
이렇게 몬스터를 바라보며 회전하는 기능은 테스트가 완료되었다.
완성된 PlayerControl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Test
{
public class PlayerControl : MonoBehaviour
{
public enum eControlType
{
KeyBoard, Joystick
}
[SerializeField] private VariableJoystick joystick;
[SerializeField] eControlType controlType;
[SerializeField] private float moveSpeed = 1.0f;
private MonsterControl monster;
// Start is called before the first frame update
void Start()
{
this.monster = GameObject.FindAnyObjectByType<MonsterControl>();
}
// Update is called once per frame
void Update()
{
float h = 0;
float v = 0;
//키보드로 조종, 간편한 테스트를 위해 포함
if (this.controlType == eControlType.KeyBoard)
{
h = Input.GetAxisRaw("Horizontal");
v = Input.GetAxisRaw("Vertical");
}
// 조이스틱으로 조종
else if (this.controlType == eControlType.Joystick)
{
h = this.joystick.Direction.x;
v = this.joystick.Direction.y;
}
if (monster != null) { //몬스터가 죽으면 바라보지 않도록 하기 위함
//나의 좌표와, 몬스터의 좌표를 빼서 방향벡터를 생성
Vector2 direction = new Vector2(this.transform.position.x - monster.transform.position.x,
this.transform.position.y - monster.transform.position.y);
//나온 방향벡터를 x,y 성분으로 분리하여
//그 사이의 각도를 계산한다.
//즉 몬스터를 바라보는데 필요한 각도를 계산한다.
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
this.transform.rotation = Quaternion.Euler(0, 0, angle + 90);
}
else
{
this.transform.rotation = Quaternion.identity; //초기화
}
Vector2 dir = (Vector2.up * v) + (Vector2.right * h);
this.transform.Translate(dir.normalized * this.moveSpeed * Time.deltaTime);
}
}
}
4. 공격 기능 개발
공격 기능의 특징은 다음과 같다.
1. 이동을 할 때는 공격을 하면 안된다.
2. 이동을 멈추고, 공격 대상(몬스터)가 있으면 공격한다.
3. 공격 대상(몬스터)가 사라지면, 이동을 멈추고 있어도 공격을 멈춘다.
4. 공격은 장애물이 있다면, 장애물에 막힌다.
이러한 특징을 구현하기 전에 기본적인 공격 기능을 만들기로 했다.
플레이어의 자식으로서, 빈 오브젝트인 firePos를 만들고 거기서 화살을 생성하여 공격하게 했다.
화살의 생성은 ArrowGenerator가, 화살의 이동은 ArrowControl이 담당한다.
충돌처리
몬스터와 부딫치면, 화살이 사라지고 몬스터의 hp가 감소하도록 만든다.
4-1. 이동을 할 때는 공격 금지
앞에서 만든 이동 기능의 h,v는 이동하지 않을 때는 둘다 0이라는 것을 응용
Main에서 player의 h,v를 받아 둘다 0일때만 화살을 생성하도록 변경
4-2. 몬스터가 사라지는 경우, 이동을 안 해도, 다시 공격을 하지 않는다.
4-3. 장애물이 있다면 장애물에 막힌다.
이 기능은 몬스터에는 Monster 라는 태그를 주고, 장애물에는 Obstacle이라는 태그를 주어서
충돌처리에서 처리하도록 하였다.
이렇게 위에서 설정한 개발 순서를 지켜가며 개발을 끝냈다.
아래는 테스트가 끝나고 완전히 정리한 스크립트와 시연영상이다.
ArrowControl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowControl : MonoBehaviour
{
private float moveSpeed = 5.0f;
// Update is called once per frame
void Update()
{
this.transform.Translate(Vector2.up * this.moveSpeed * Time.deltaTime);
this.SelfDestory();
}
private void SelfDestory()
{
if (this.transform.position.y > 6.5f)
{
Destroy(this.gameObject);
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Monster"))
{
Debug.Log("<color=yellow>화살 삭제</color>");
Destroy(this.gameObject);
}
else if (collision.CompareTag("Obstacle")) //장애물
{
this.moveSpeed = 0.0f;
Destroy(this.gameObject, 2.0f); //2초 있다가 삭제
}
}
}
ArrowGenerator
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowGenerator : MonoBehaviour
{
[SerializeField] private GameObject arrowPrefab;
[SerializeField] private Transform firePos;
[SerializeField] private float intervelTime = 1.0f;
private float elaspedTime;
public void ArrowGenerate()
{
this.elaspedTime += Time.deltaTime;
if (this.elaspedTime > this.intervelTime)
{
//firePos에 화살을 생성
GameObject go = Instantiate(arrowPrefab, firePos.position, firePos.rotation);
this.elaspedTime = 0.0f;
}
}
}
MonsterControl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterControl : MonoBehaviour
{
[SerializeField] private int hp = 5;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("<color=yellow>몬스터 피격</color>");
this.hp--;
if (this.hp <= 0)
{
this.Die();
}
}
private void Die()
{
Debug.Log("<color=red>몬스터 사망!!</color>");
Destroy(this.gameObject);
}
}
PlayerControl
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
public enum eControlType
{
KeyBoard, Joystick
}
[SerializeField] private VariableJoystick joystick;
[SerializeField] eControlType controlType;
[SerializeField] private float moveSpeed = 1.0f;
private MonsterControl monster;
private float horizontal = 0;
private float vertical = 0;
public float Horizontal
{
get { return this.horizontal; }
}
public float Vertical
{
get { return this.vertical; }
}
// Start is called before the first frame update
void Start()
{
this.monster = GameObject.FindAnyObjectByType<MonsterControl>();
}
// Update is called once per frame
void Update()
{
this.PlayerMove();
}
private void PlayerMove()
{
//키보드로 조종, 간편한 테스트를 위해 포함
if (this.controlType == eControlType.KeyBoard)
{
horizontal = Input.GetAxisRaw("Horizontal");
vertical = Input.GetAxisRaw("Vertical");
}
// 조이스틱으로 조종
else if (this.controlType == eControlType.Joystick)
{
horizontal = this.joystick.Direction.x;
vertical = this.joystick.Direction.y;
}
if (monster != null)
{ //몬스터가 죽으면 바라보지 않도록 하기 위함
//나의 좌표와, 몬스터의 좌표를 빼서 방향벡터를 생성
Vector2 direction = new Vector2(this.transform.position.x - monster.transform.position.x,
this.transform.position.y - monster.transform.position.y);
//나온 방향벡터를 x,y 성분으로 분리하여
//그 사이의 각도를 계산한다.
//즉 몬스터를 바라보는데 필요한 각도를 계산한다.
float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
this.transform.rotation = Quaternion.Euler(0, 0, angle + 90);
}
else
{
this.transform.rotation = Quaternion.identity; //초기화
}
Vector2 dir = (Vector2.up * vertical) + (Vector2.right * horizontal);
this.transform.Translate(dir.normalized * this.moveSpeed * Time.deltaTime);
}
}
GameMain
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameMain : MonoBehaviour
{
[SerializeField] private MonsterControl monster;
[SerializeField] private PlayerControl player;
[SerializeField] private ArrowGenerator arrowGenerator;
// Update is called once per frame
void Update()
{
if (player.Horizontal == 0 && player.Vertical == 0)
{
if (monster != null)
arrowGenerator.ArrowGenerate();
}
}
}
시연영상
하면서 유의 했던점
여태까지 배운 것에 집합이라 어려운 것은 없었다.
하나 유의했던 것은 2d 플랫폼에서의 타겟 추적이었는데
이번에 하는 방법을 알게 되어, 많은 곳에 응용을 할 수 있게 되었다고 생각하였다.
'유니티 심화' 카테고리의 다른 글
총알반사 (0) | 2023.08.21 |
---|---|
충돌감지를 통한, 플레이어 회전 변경 (0) | 2023.08.21 |
3D Space Shooter Test1 (0) | 2023.08.18 |
Space Shooter 카메라 Player Follow (0) | 2023.08.18 |
2D 탄막 게임 테스트 (적 피격 애니메이션 및 사망 애니메이션) (0) | 2023.08.18 |