본문 바로가기

Unity 공부/Unity_2D

[Day 6] 자동 원거리 공격 구현

[자동 원거리 공격 구현] --> 주변의 몬스터를 자동으로 타게팅하여 공격하는 무기 구현

  • Add Layer --> User Layer 6을 Enemy로 설정
  • Prefabs안의 Enemy의 Layer를 Enemy로 설정
  • C# 스크립트 생성(Scanner)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Scanner : MonoBehaviour
{
    public float scanRange; // 범위
    public LayerMask targetLayer; // 레이어
    public RaycastHit2D[] targets; // 스캔 결과 배열
    public Transform nearestTarget; // 가장 가까운 목표

    void FixedUpdate(){
        targets = Physics2D.CircleCastAll(transform.position, scanRange, Vector2.zero, 0, targetLayer); // 원형 범위로 오브젝트를 검색
        nearestTarget = GetNearest();
    }

    Transform GetNearest(){ // 가장 가까운 오브젝트를 찾는 함수
        Transform result = null;
        float diff = 100;

        foreach(RaycastHit2D target in targets){ // 범위 안에 인식 된 타겟 중, 가장 가까운 적을 찾아내는 함수
            Vector3 myPos = transform.position;
            Vector3 targetPos = target.transform.position;
            float curDiff = Vector3.Distance(myPos, targetPos);

            if(curDiff < diff) { // 작은 수를 점점 대입해 가며 
                diff = curDiff;
                result = target.transform;
            }
        }
        return result;
    }
}
  • Scanner 스크립트를 Player 오브젝트에 드래그&드랍
  • 근접무기 프리펩 하나를 Scene에 드래그&드랍
  • Sprite Renderer에 Bullet 3 sprite로 바꿔주기
  • Box Collider 2D를 Remove Component --> Capsule Collider 2D 추가
  • Hierachy의 새로운 Bullet 이름 변경 후 Prefabs 폴더에 드래그&드랍
  • Capsule Collider 2D에 Is Trigger 체크
  • Hierachy 안의 Weapon을 Ctrl + D로 복사
        - Id | 1
        - Prefab Id | 2
        - Damage | 3
        - Count | 0
        - Speed | 0
  • 기존의 Player 스크립트에 코드 추가
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    public Vector2 inputVec;
    public float speed;
    public Scanner scanner; // 스캐너 변수 추가

    Rigidbody2D rigid;
    SpriteRenderer spriter;
    Animator anim;
    
    void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
        spriter = GetComponent<SpriteRenderer>();
        anim = GetComponent<Animator>();
        scanner = GetComponent<Scanner>(); // 변수 초기화
    }

    void Update()
    {
        inputVec.x = Input.GetAxisRaw("Horizontal");
        inputVec.y = Input.GetAxisRaw("Vertical");
    }

    private void FixedUpdate()
    {
        Vector2 nextVec = inputVec.normalized*speed* Time.fixedDeltaTime;
        rigid.MovePosition(rigid.position + nextVec);
    }

    private void LateUpdate()
    {
        anim.SetFloat("Speed", inputVec.magnitude); 

        if (inputVec.x != 0) 
        {
            spriter.flipX = inputVec.x < 0;
        }
    }
}
  • 기존의 Weapon 스크립트에 코드 추가
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using System.Runtime.ConstrainedExecution;
using UnityEngine;

public class Weapon : MonoBehaviour
{
    public int id;
    public int prefabId;
    public float damage;
    public int count;
    public float speed; // 발사 속도

    float timer; // 타이머
    Player player;

    void Awake(){ // 부모의 컴포넌트 가져오기
        player = GetComponentInParent<Player>();
    }

    void Start(){
        Init();
    }

    void Update(){
        switch (id){
            case 0:
                transform.Rotate(Vector3.back * speed * Time.deltaTime);
                break;

            default:
                timer += Time.deltaTime; // 프레임이 소비한 시간

                if(timer > speed){
                    timer = 0f;
                    Fire();
                }
                break;

        }

        // .. Test Code ..
        if (Input.GetButtonDown("Jump")){
            LevelUp(10, 1);
        }
    }

    public void LevelUp(float damage, int count)
    {
        this.damage += damage;
        this.count += count;

        if(id == 0){
            Batch();
        }
    }

    public void Init(){
        switch(id){
            case 0:
                speed = 150; // (+) --> 반시계방향
                Batch();
                break;

            default:
                speed = 0.3f; // 발사속도 0.3f
                break;
        }
    }

    void Batch(){
        for(int index=0; index < count; index++) {
            Transform bullet;
           
            if(index < transform.childCount){
                bullet = transform.GetChild(index);
            }
            else{
                bullet = GameManager.instance.pool.Get(prefabId).transform;
                bullet.parent = transform;

            }

            bullet.localPosition = Vector3.zero;
            bullet.localRotation = Quaternion.identity;

            Vector3 rotVec = Vector3.forward * 350 * index / count;
            bullet.Rotate(rotVec);
            bullet.Translate(bullet.up * 1.5f, Space.World);
            bullet.GetComponent<Bullet>().Init(damage, -1); // -1 is Infinity Per.

        }
    }

    void Fire()
    {
        if (!player.scanner.nearestTarget){ // 지정된 목표가 없으면 호출되지 않음
            return;
        }
        Transform bullet = GameManager.instance.pool.Get(prefabId).transform;
        bullet.position = transform.position;

    }
}
  • Prefabs의 총탄에 Add Component
        - RigidBody 2D --> gravity Scale | 0
  • 기존의 Bullet 스크립트에 코드 추가
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float damage;
    public int per;

    Rigidbody2D rigid;

    void Awake(){
        rigid = GetComponent<Rigidbody2D>();
    }

    public void Init(float damage, int per, Vector3 dir) // Vector3 방향 추가
    {
        this.damage = damage;
        this.per = per;

        if(per > -1){
            rigid.velocity = dir;
        }
    }

    void OnTriggerEnter2D(Collider2D collision)
    {
        if(!collision.CompareTag("Enemy") || per == -1){
            return;
        }

        per--;

        if (per == -1){ // 관통력이 -1이 됨
            rigid.velocity = Vector2.zero; // 비활성화 이전 물리 속도 초기화
            gameObject.SetActive(false); // 스스로 사라짐
        }
    }
}
  • 기존의 Weapon 스크립트에 코드 추가
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using System.Runtime.ConstrainedExecution;
using UnityEngine;

public class Weapon : MonoBehaviour
{
    public int id;
    public int prefabId;
    public float damage;
    public int count;
    public float speed;

    float timer;
    Player player;

    void Awake(){
        player = GetComponentInParent<Player>();
    }

    void Start(){
        Init();
    }

    void Update(){
        switch (id){
            case 0:
                transform.Rotate(Vector3.back * speed * Time.deltaTime);
                break;

            default:
                timer += Time.deltaTime;

                if(timer > speed){
                    timer = 0f;
                    Fire();
                }
                break;

        }

        // .. Test Code ..
        if (Input.GetButtonDown("Jump")){
            LevelUp(10, 1);
        }
    }

    public void LevelUp(float damage, int count)
    {
        this.damage += damage;
        this.count += count;

        if(id == 0){
            Batch();
        }
    }

    public void Init(){
        switch(id){
            case 0:
                speed = 150; // (+) --> 반시계방향
                Batch();
                break;

            default:
                speed = 0.3f;
                break;
        }
    }

    void Batch(){
        for(int index=0; index < count; index++) {
            Transform bullet;
           
            if(index < transform.childCount){
                bullet = transform.GetChild(index);
            }
            else{
                bullet = GameManager.instance.pool.Get(prefabId).transform;
                bullet.parent = transform;

            }

            bullet.localPosition = Vector3.zero;
            bullet.localRotation = Quaternion.identity;

            Vector3 rotVec = Vector3.forward * 350 * index / count;
            bullet.Rotate(rotVec);
            bullet.Translate(bullet.up * 1.5f, Space.World);
            bullet.GetComponent<Bullet>().Init(damage, -1, Vector3.zero); // 인자값 추가

        }
    }

    void Fire()
    {
        if (!player.scanner.nearestTarget){
            return;
        }

        Vector3 targetPos = player.scanner.nearestTarget.position; // 타겟의 위치
        Vector3 dir = targetPos - transform.position; // 타겟의 방향
        dir = dir.normalized; // 현재 벡터의 방향은 유지하고 크기를 1로 변환

        Transform bullet = GameManager.instance.pool.Get(prefabId).transform;
        bullet.position = transform.position;
        bullet.rotation = Quaternion.FromToRotation(Vector3.up, dir); // 지정된 축을 중심으로 목표를 향해 회전
        bullet.GetComponent<Bullet>().Init(damage, count, dir); // 초기화 함수 호출

    }
}
  • 기존의 Bullet 스크립트에 코드 추가
    ㅡ아래는 완성된 코드ㅡ
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    public float damage;
    public int per;

    Rigidbody2D rigid;

    void Awake(){
        rigid = GetComponent<Rigidbody2D>();
    }

    public void Init(float damage, int per, Vector3 dir)
    {
        this.damage = damage;
        this.per = per;

        if(per > -1){
            rigid.velocity = dir * 15f; // 속력을 곱해주어 총알이 날아가는 속도 증가
        }
    }

    void OnTriggerEnter2D(Collider2D collision)
    {
        if(!collision.CompareTag("Enemy") || per == -1){
            return;
        }

        per--;

        if (per == -1){
            rigid.velocity = Vector2.zero;
            gameObject.SetActive(false);
        }
    }
}

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

★Slow and Steady★