유니티 심화

[주말과제] - 궁수의 전설[완]

다모아 2023. 8. 19. 22:55

 

일단 배경 생성


캐릭터
조이스틱
포탈
벽돌
화살
쥐[몬스터]

사용한 에셋


 

1. 조이스틱 움직이면 움직이게 설정

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField]
    private float moveSpeed = 2f;
    [SerializeField]
    private VariableJoystick joystick;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        //조이스틱 x, y좌표
        Vector2 moveDir = (Vector2.right * joystick.Direction.x) + (Vector2.up * joystick.Direction.y);

        this.transform.Translate(moveDir.normalized * this.moveSpeed * Time.deltaTime);

        Debug.Log(moveDir);
        if (moveDir == Vector2.zero)
        {
            //공격
        }
        else if (moveDir.x > 0)
        {
            //오른쪽
            
        }
        else if (moveDir.x < 0)
        {
            //왼쪽
        }
        else if (moveDir.y > 0)
        {
            //위
        }
        else if (moveDir.y < 0)
        {
            //아래
        }
    }
}

 

조이스틱 움직이면 캐릭터가 움직인다.

 

 


벽돌을 통과안하게 tag로 벽을 Wall로 설정해서 하고싶었는데

왜 그런건지

Trigger로 해도 , Collision으로 해도 태그를 알아보지못한다.

그래서 어떻게 해야할지 잘 모르겠다.

 

-----------------> 알았다. 지금 나는 2D Unity를 사용중인데 OnTrigger나 Collision이나 2D용으로 쓰지 않고 3D? 용으로 사용해서 반응이 없었던 것이었다.


화살을 발사하면 캐릭터에서 몬스터까지 가게했는데 갑자기 실행하면 이미지가 안보인다..

이건 무슨 오류 때문에 발생한건지 잘 모르겠다. 

----------> 3D로 보니까 화살이 LookAt으로 쥐를 보면서 가는건데 화살 자체가 3D로 LookAt되서 2D로는 보이지 않았던 것 같다.

찾아보니까 transform.Lookat 함수는 회전값도 변경시켜서 2D에서는 사용할 수 없는 것 같다.

 

ArrowController

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using System;
public class ArrowController : MonoBehaviour
{
    [SerializeField]
    private float attackSpeed = 1f;

    private GameObject monsterGo;
    private GameObject playerGo;

    // Start is called before the first frame update
    void Start()
    {
        this.monsterGo = GameObject.Find("Rat");
        this.playerGo = GameObject.Find("Player");
        //화살 생성 위치
        this.transform.position = this.playerGo.transform.position + this.playerGo.transform.up * 0.5f;
    }

    // Update is called once per frame
    void Update()
    {
        //바라보기
        this.transform.LookAt(this.monsterGo.transform.position);
        //이동
        this.transform.position = Vector2.Lerp(this.transform.position, this.monsterGo.transform.position, Time.deltaTime * 1f);
    }

    public void Fire()
    {
        //Instantiate(this.gameObject);
    }
}

 

PlayerController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class PlayerController : MonoBehaviour
{
    private enum eAnimState
    {
        Idle, Run, Attack
    }

    [SerializeField]
    private float moveSpeed = 2f;
    [SerializeField]
    private VariableJoystick joystick;
    [SerializeField]
    private ArrowController arrowController;

    private Animator anim;
    // Start is called before the first frame update
    void Start()
    {
        this.anim = this.GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        //바깥으로 안나가는 x, y좌표
        float clampX = Mathf.Clamp(this.transform.position.x, -2.35f, 2.70f);
        float clampY = Mathf.Clamp(this.transform.position.y, -4.00f, 4.50f);

        this.transform.position = new Vector2(clampX, clampY);

        //몬스터와 플레이어 사이의 방향벡터 계산

        //조이스틱 x, y좌표
        Vector2 moveDir = (Vector2.right * joystick.Direction.x) + (Vector2.up * joystick.Direction.y);

        this.transform.Translate(moveDir.normalized * this.moveSpeed * Time.deltaTime);

        if (moveDir == Vector2.zero)
        {
            //공격
            this.PlayAnimation(eAnimState.Attack);
            this.arrowController.Fire();
        }
        else if (moveDir.x > 0)
        {
            //오른쪽
            this.PlayAnimation(eAnimState.Run);
        }
        else if (moveDir.x < 0)
        {
            //왼쪽
            this.PlayAnimation(eAnimState.Run);
        }
        else if (moveDir.y > 0)
        {
            //위
            this.PlayAnimation(eAnimState.Run);
        }
        else if (moveDir.y < 0)
        {
            //아래
            this.PlayAnimation(eAnimState.Run);
        }
    }
    private void PlayAnimation(eAnimState state)
    {
        this.anim.SetInteger("State", (int)state);
    }
}

https://saens.tistory.com/26

 

[Unity] 2D LookAt Lerp

Transform에는 특정 target을 바라보도록 하는 함수인 LookAt이 기본적으로 내장되어 있습니다. 그런데 이 함수는 한 프레임 만에 회전값을 변경시킵니다. 2D에서는 다음과 같이 간단하게 수식 하나로,

saens.tistory.com

2D는 LookAt이 없나.. 찾아보다가 찾게되었다.

LookAt2DLerp 사용 후


ArrowController

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using System;

public class ArrowController : MonoBehaviour
{
    [SerializeField]
    private float attackSpeed = 1f;

    private GameObject monsterGo;
    private GameObject playerGo;

    private float elapsedTime = 0f;

    // Start is called before the first frame update
    void Start()
    {
        this.monsterGo = GameObject.Find("Rat");
        this.playerGo = GameObject.Find("Player");
        //화살 생성 위치
        this.transform.position = this.playerGo.transform.position + this.playerGo.transform.right * 1f;
    }

    // Update is called once per frame
    void Update()
    {
        //바라보기
        this.transform.LookAt2DLerp(this.monsterGo.transform.position);
        //이동
        this.transform.position = Vector2.Lerp(this.transform.position, this.monsterGo.transform.position, Time.deltaTime * 1f);
        if (Input.GetMouseButtonDown(0))
        {
            this.elapsedTime = 0f;
        }
    }

    public void Fire()
    {

        this.elapsedTime += Time.deltaTime;
        if (this.elapsedTime >= 0.95f)
        {
            Instantiate(this.gameObject);
            this.elapsedTime = 0f;
        }
    }
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Monster")
        {
            Destroy(this.gameObject);
        }
    }
}

public static class LookAtExtension
{
    public static void LookAt2DLerp(this Transform transform, Vector2 dir, float lerpPercent = 0.05f)
    {
        float rotationZ = Mathf.Acos(dir.x / dir.magnitude)
            * 180 / Mathf.PI
            * Mathf.Sign(dir.y);

        transform.rotation = Quaternion.Lerp(
            transform.rotation,
            Quaternion.Euler(0, 0, rotationZ),
            lerpPercent
        );
    }
}

 

PlayerController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class PlayerController : MonoBehaviour
{
    private enum eAnimState
    {
        Idle, Run, Attack
    }

    [SerializeField]
    private float moveSpeed = 2f;
    [SerializeField]
    private VariableJoystick joystick;
    [SerializeField]
    private ArrowController arrowController;

    private Animator anim;
    // Start is called before the first frame update
    void Start()
    {
        this.anim = this.GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        //바깥으로 안나가는 x, y좌표
        float clampX = Mathf.Clamp(this.transform.position.x, -2.35f, 2.70f);
        float clampY = Mathf.Clamp(this.transform.position.y, -4.00f, 4.50f);

        this.transform.position = new Vector2(clampX, clampY);

        //몬스터와 플레이어 사이의 방향벡터 계산

        //조이스틱 x, y좌표
        Vector2 moveDir = (Vector2.right * joystick.Direction.x) + (Vector2.up * joystick.Direction.y);

        this.transform.Translate(moveDir.normalized * this.moveSpeed * Time.deltaTime);

        if (moveDir == Vector2.zero)
        {
            //공격
            this.PlayAnimation(eAnimState.Attack);
            this.arrowController.Fire();
        }
        else if (moveDir.x > 0)
        {
            //오른쪽
            this.PlayAnimation(eAnimState.Run);
        }
        else if (moveDir.x < 0)
        {
            //왼쪽
            this.PlayAnimation(eAnimState.Run);
        }
        else if (moveDir.y > 0)
        {
            //위
            this.PlayAnimation(eAnimState.Run);
        }
        else if (moveDir.y < 0)
        {
            //아래
            this.PlayAnimation(eAnimState.Run);
        }
    }
    private void PlayAnimation(eAnimState state)
    {
        this.anim.SetInteger("State", (int)state);
    }
}

 


3초후 삭제되고 벽에 닿으면 화살스피드 0으로 만들기
오류 발생 : 캐릭터가 위에로 가서 때리면 화살이 이상하게 간다..


뭔가.. 벽에 붙으면 멈추게 하거나 벽으로 못가게 하는걸 어떻게 해야할지 몰라서 기존 위치로 초기화 시키는 방식으로 택했다.

 


거의 완성된 거 같다..?

 

구현내용

조이스틱[움직이기] 

몬스터가 있으면 공격하기

몬스터가 없으면 공격 안하기

벽에 닿으면 원래 위치로 초기화[벽에 부딪히면 멈추는걸 어떻게 해야할지 모르겠음]

몬스터가 죽으면 포탈 소환하기


PortalController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PortalController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    public void CreatePortal()
    {
        this.gameObject.SetActive(true);
    }
}

 

MonsterController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MonsterController : MonoBehaviour
{
    [SerializeField]
    private PortalController portalController;

    private int hp = 3;
    public int Hp
    {
        get
        {
            return this.hp;
        }
        set
        {
            if(value == 0)
            {
                Debug.Log("몬스터 사망!");
                this.Die();
            }
            else
            {
                this.hp = value;
                Debug.LogFormat("남은 체력: ({0}/{1})", this.hp, this.maxHp);
            }
        }
    }
    private int maxHp;

    // Start is called before the first frame update
    void Start()
    {
        this.maxHp = this.hp;
    }

    private void Die()
    {
        Destroy(this.gameObject);
        portalController.CreatePortal();
    }
}

 

ArrowController

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using System;

public class ArrowController : MonoBehaviour
{
    [SerializeField]
    private float attackSpeed = 1f;
    private int arrowDamage = 1;

    private GameObject monsterGo;
    private GameObject playerGo;
    private MonsterController monsterController;

    private float elapsedTime = 0f;

    
    // Start is called before the first frame update
    void Start()
    {
        this.monsterGo = GameObject.Find("Rat");
        this.playerGo = GameObject.Find("Player");
        this.monsterController = this.monsterGo.GetComponent<MonsterController>();

        //화살 생성 위치
        this.transform.position = this.playerGo.transform.position + this.playerGo.transform.right * 1f;
    }

    // Update is called once per frame
    void Update()
    {
        //바라보기
        this.transform.LookAt2DLerp(this.monsterGo.transform.position);
        //이동
        this.transform.position = Vector2.Lerp(this.transform.position, this.monsterGo.transform.position, Time.deltaTime * this.attackSpeed);
        if (Input.GetMouseButtonDown(0))
        {
            this.elapsedTime = 0f;
        }
    }

    public void Fire()
    {
        this.elapsedTime += Time.deltaTime;
        if (this.elapsedTime >= 0.95f)
        {
            Instantiate(this.gameObject);
            this.elapsedTime = 0f;
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        
        if (collision.tag == "Monster")
        {
            Destroy(this.gameObject);
            this.monsterController.Hp -= this.arrowDamage;
        }
        else if(collision.tag == "Wall")
        {
            this.attackSpeed = 0f;
            Destroy(this.gameObject, 3f);
        }
    }
}

public static class LookAtExtension
{
    public static void LookAt2DLerp(this Transform transform, Vector2 dir, float lerpPercent = 0.05f)
    {
        float rotationZ = Mathf.Acos(dir.x / dir.magnitude)
            * 180 / Mathf.PI
            * Mathf.Sign(dir.y);

        transform.rotation = Quaternion.Lerp(
            transform.rotation,
            Quaternion.Euler(0, 0, rotationZ),
            lerpPercent
        );
    }
}

 

PlayerController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

public class PlayerController : MonoBehaviour
{
    private enum eAnimState
    {
        Idle, Run, Attack
    }

    [SerializeField]
    private float moveSpeed = 2f;
    [SerializeField]
    private VariableJoystick joystick;
    [SerializeField]
    private ArrowController arrowController;
    [SerializeField]
    private GameObject monsterGo;

    private Animator anim;
    // Start is called before the first frame update
    void Start()
    {
        this.anim = this.GetComponent<Animator>();
        this.monsterGo = GameObject.Find("Rat");
    }

    // Update is called once per frame
    void Update()
    {
        //바깥으로 안나가는 x, y좌표
        float clampX = Mathf.Clamp(this.transform.position.x, -2.35f, 2.70f);
        float clampY = Mathf.Clamp(this.transform.position.y, -4.00f, 4.50f);

        this.transform.position = new Vector2(clampX, clampY);

        //몬스터와 플레이어 사이의 방향벡터 계산

        //조이스틱 x, y좌표
        Vector2 moveDir = (Vector2.right * joystick.Direction.x) + (Vector2.up * joystick.Direction.y);

        this.transform.Translate(moveDir.normalized * this.moveSpeed * Time.deltaTime);

        if (moveDir == Vector2.zero)
        {
            //공격
            if (this.monsterGo != null)
            {
                this.PlayAnimation(eAnimState.Attack);
                this.arrowController.Fire();
            }
            else
                this.PlayAnimation(eAnimState.Idle);
        }
        else if (moveDir.x > 0)
        {
            //오른쪽
            this.PlayAnimation(eAnimState.Run);
        }
        else if (moveDir.x < 0)
        {
            //왼쪽
            this.PlayAnimation(eAnimState.Run);
        }
        else if (moveDir.y > 0)
        {
            //위
            this.PlayAnimation(eAnimState.Run);
        }
        else if (moveDir.y < 0)
        {
            //아래
            this.PlayAnimation(eAnimState.Run);
        }
    }
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag == "Wall")
        {
            Debug.Log("벽을 통과할 수 없습니다.");
            this.transform.position = new Vector2(-2.02f, -3.73f);
        }
    }
    private void PlayAnimation(eAnimState state)
    {
        this.anim.SetInteger("State", (int)state);
    }
}