본문 바로가기
프로그래밍/Unity

내가 유니티에선 왕초보? - 4주차 - 레이어와 Hp

by 리나그(ReenAG) 2021. 5. 8.
728x90
반응형

 

passingprogram.tistory.com/29

 

내가 유니티에선 왕초보? - 3주차 - 애니메이션

passingprogram.tistory.com/27 코딩에선 초보가 아닌 내가 유니티에선 왕초보? - 2주차 - 움직여보기 휴... 지난번에 달랑 한번하고 포스팅을 하지 않았더니 완전 감각이 다 죽어버렸을지도 모르겠다. 결

passingprogram.tistory.com

이 포스트는 지난번 포스트와 이어진다. 이전까지 모든 애니메이션을 만들어 두었으니, 이젠 정말 공격할 수 있는 기능만 있으면 될 따름이다.

이번 포스팅에서 배울 것은

->Custom Layer 설정

->RayCast를 이용한 GameObject파괴

->간단한 Hp바 만들기

 

의외로 지난번 보다 해야할 것이나 코드는 그다지 없다. 우선, 지난번 영상에서 보았을지 모르겠지만, Slime이 상대할 가소로운 유닛 하나를 생성해두도록 하자.

오브젝트 이름은 아무거나... 나는 Man으로 설정했다.

이렇게 슬라임이 상대한 적을 만들어 주었다. 이 녀석에게도 Rigidbody2D와 BoxCollider2D를 넣어두었다. 일부러 슬라임이 이 녀석을 밀지 못하도록 Mass와 Linear Drag를 크게 설정해두자.

 

이젠 Custom Layer를 한 개 만들어보자.

Edit Layers...를 클릭!

 

이렇게 간단하게 Layer를 한개 만들 수 있다. Hierarchy와는 또 다른 방법으로 GameObject들을 관리, 감지, 렌더링할 수 있는 방법이 바로 이것이다. 그럼 아까 만든 Man에 Layer를 적용하자.

Enemy로 설정!

벌써 끝났다. 이제 Man은 Enemy라는 속성이 생긴 것과 같다. 그렇다면 속성에 걸맞는 스크립트가 필요한 법!

Enemy.cs를 새로 작성해 주자 :

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

public class Enemy : MonoBehaviour
{
    // Start is called before the first frame update

    [SerializeField] public int max_hp;
    public int hp;

    public void getAttacted(int amonut){
        hp -= amonut;
        Debug.Log("Damage :" + amonut);
        if(hp <= 0)
            Destroy(gameObject);
    }

    public float getHpBarNormalized(){
        return (float)hp / (float)max_hp;
    }

    void Start()
    {
        hp = max_hp;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

시리얼라이즈 필드를 이용했으니까 최대 체력은 Inspector에서 직접 지정해주자. getAttacked는 데미지 계산을 해서 hp를 받고, 만약 hp가 0보다 작거나 같게되면 게임오브젝트를 파괴해서 죽는 것처럼 해놓았다.(별다른 죽는 애니메이션은 없지만...) 이걸 방금 전의 Man에게 넣어주자.

Slime.cs도 조금 바꾸어 주었다.

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

public class Silme : MonoBehaviour
{

    [SerializeField] public float speed;
    [SerializeField] public float jumpforce;
    public Vector2 presistAddVelocity;
    private Rigidbody2D rigidBody;
    private Animator animator;
    public bool is_jumping;
    public Enemy enemy;

    // Start is called before the first frame update
    void Start()
    {
        rigidBody = this.GetComponent<Rigidbody2D>();
        animator = this.GetComponent<Animator>();
        presistAddVelocity = new Vector2(0, 0);
        is_jumping = false;
    }

    // FixedUpdate is called when absolute delta elisped
    void FixedUpdate(){
        //rigidBody.velocity = presistAddVelocity * speed + rigidBody.velocity;
        rigidBody.AddForce(presistAddVelocity * speed, ForceMode2D.Impulse);
        animator.SetBool("Moving", presistAddVelocity != Vector2.zero);
        animator.SetBool("Jumping", is_jumping);
    }

    // Update is called once per frame
    void Update()
    {
        RaycastHit2D rayHit = Physics2D.Raycast(rigidBody.position, Vector2.left, 5f, LayerMask.GetMask("Enemy"));
        Debug.DrawRay(rigidBody.position, Vector2.left * 5f, new Color(0, 1, 0));

        if(rayHit.collider != null)
            enemy = rayHit.collider.gameObject.GetComponent<Enemy>();
        else
            enemy = null;
    }

    public void Jump(){
        Vector2 jumpVec = is_jumping ? Vector2.zero : new Vector2(0, jumpforce);
        rigidBody.AddForce(jumpVec, ForceMode2D.Impulse);
    }

    public void Attack_Start(){
        animator.SetTrigger("Attack");
        if(enemy != null){
            enemy.getAttacted(10);
        }
    }

    public void Attack_Stop(){

    }

    private void OnCollisionStay2D(Collision2D collision2D){
        if(collision2D.gameObject.name == "Floor (6)")
            is_jumping = false;
    }

    private void OnCollisionExit2D(Collision2D collision2D){
        if(collision2D.gameObject.name == "Floor (6)")
            is_jumping = true;
    }

}

FixedUpdate라는 새로운 Update가 보인다. 이 업데이트는 기존의 프레임 별로 불리는 업데이트와 달리, 정확시 절대 시간에 따라서 불린다. 무슨 소린가 하면, 게임이 23FPS로 돌아갈 때 Update는 23번 불리겠지만, FixedUpdate는 그것과 상관없이 Unity 내부 설정에 따라서(예를 들어, 60번?) 계속해서 불린다. 그렇게 되면... 적어도 이 녀석은 싱크가 나가있는 녀석이라는 뜻이기도 한데... 아무튼 여기에 기존의 Update 구문을 넣는 이유는 속도등이 게임의 렉과 관련없이 업데이트 되어야 하기 때문이다. 왠만하면 그런 민감한 요소들은 이곳에다 넣어놓자.

 

사실 중요한 건 새로워진 Update()에 들어간 RayCast라는 녀석이다.

...

단적으로 이야기해서, 이게 여러분이 직접 게임 엔진을 만들면 고생하는 이유이다. 3D에서는 진짜 매우~ 기초적인 RayCast조차도 만들기 어렵다. 물론 내가 수학 공부를 덜해서 그런 것도 있겠지만, 만들기 쉽지 않다. 3D에서 직선 하나 띡 놔준 다음, 그 선을 따라갔을 때 나온 제일 앞의 오브젝트를 가져오는 일은... 너무 어렵다. 내가 Unity 제작자들을 이렇게 찬양하게 만든 RayCast의 기능은 직선에 닿는 오브젝트를 가져오는 것이다.

 

현재 슬라임의 왼쪽으로 5 유닛 길이의 직선이 발사되고 있다. RayCast는 이 직선에 닿는 모든 오브젝트들을 검사한다. 근데 그 오브젝트의 Layer를 검사해서 우리가 만든 "Enemy"에 속한다면? 그 오브젝트를 rayHit.collider.gameObject로 가져올 수 있다. 우리는 그 gameObject안의 Enemy스크립트를 가져오는 것이다. 조금 복잡할 수도 있지만, 중요한 것은 "슬라임 앞에 있는 영문 모를 오브젝트를 지정해서 가져올 수 있다"는 점이다.

 

우리는 이제 AttackButton을 누르면서 신나게 Ememy의 체력을 깎아 없애버릴 수 있다. 근데 지금 상태에서는 Enemy의 체력을 확인하기 어렵다. Hp바를 한 번 만들어 보도록하자!

UI가 필요한 것인데, 우선 Image를 선택해서 생성한다. 이후 Scene 창을 이용하거나 RectTransform을 이용해서 이 이미지를 적절히 배치하자.

Scene창을 이용해서도 배치할 수 있다.

그렇게 배치를 했으면 Inspector에서 Image를 원하는 직사각형 이미지로 바꾸고, 속성을 다음과 같이 세팅한다.

Fill Method를 Horizontal로 설정하는 것을 잊지 말자!

이제 이 이미지는 원할 때 왼쪽부터 부분적으로 렌더링할 수 있다. 재미 삼이서 아래의 Fill Amount를 조절해 보자. 진짜 체력바 처럼 왔다 갔다하는 것을 볼 수 있다. 이제 이걸 조절하게끔만 하면 된다.

HPBar.cs를 새로 만들어 주자 :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//이거 추가하는 것 잊지 말기!
using UnityEngine.UI;

public class HPBar : MonoBehaviour
{
    [SerializeField] Silme silme;
    private Image image;

    // Start is called before the first frame update
    void Start()
    {
        image = gameObject.GetComponent<Image>();
    }

    // Update is called once per frame
    void Update()
    {
        if(silme.enemy == null){
            image.fillAmount = 0;
        } else {
            image.fillAmount = silme.enemy.getHpBarNormalized();
        }
    }
}
 

using UnityEngine.UI를 추가로 해야지 동작하는 코드이다. 유념하길! 이 스크립트를 방금 만든 이미지 오브젝트 HPBar에 Add Component를 이용해서 넣어주자.

그렇게 넣어준 다음에 시리얼라이즈 필드로 지정해둔 슬라임만 제대로 넣어주면 이젠 정말로 다 만든 것이다.

결과는 이렇다 :

 

이렇게 Hp바를 포함한 공격기능을 전부 구현해 보았다. 다음 포스팅은 뭘 할지는 잘 모르겠지만 2차 학프가 시작되었으니 아마도 거기서 배운 내용들을 포스팅하지 않을까 싶다.

728x90
반응형