Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
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
Tags
more
Archives
Today
Total
관리 메뉴

개임 게발

[Unity] [빗물받는 르탄이] 캐릭터를 움직이고 속도 조절하기, 랜덤한 빗방울 만들기 본문

강의 정리

[Unity] [빗물받는 르탄이] 캐릭터를 움직이고 속도 조절하기, 랜덤한 빗방울 만들기

lhgenol 2025. 2. 2. 07:12

애니메이션 맛보기(1-3)

이미지를 애니메이션화 하기

  • 'Project' -> Images 폴더+ -> 이미지+
  • 'Hierarchy' -> 2D Object -> Sprites -> Square+ -> 'Inspector' -> Sprite Renderer -> Sprite -> 이미지+
  • 'Project' -> Assets -> Animations 폴더+ -> Animation Clip+ -> 'InsPector' -> Loop Time 체크
  • 'Hierarchy' -> Square 클릭 -> 'Project' -> Animations Clip -> 'InsPector'
  • 'Project' -> Animations Clip -> 이미지+

캐릭터 움직이기(1-4)

스크립트의 정의

  • 스크립트는 class라고 하는 단위에서 움직이게 된다. 하나의 스크립트 - 하나의 class. 나중엔 하나에 여러 class를 함께 둘 수도 있긴 하다.
  • ; (세미콜론) = '코드 한 줄이 끝났다'고 하는 범위를 구분 지어 주는 역할.
  • 빨간 줄이 남았다 = 아직 더 작성할 코드가 남았다
  • Debug.Log = 일종의 프린트
  • 연산 = 계속해서 어떤 값을 변화시켜 주는 것.
  • Start는 딱 한 번만 실행되기 때문에 반복은 Update
  • // Update is called once per frame <- 프레임은 1초에 연산되는 횟수.
    PC의 성능이 좋으면 프레임 수 많아짐 -> 1초에 연산하는 횟수가 많아짐

Transform의 Position값 설정

  • 르탄이를 움직여 보자.
  • 가로 값을 더해주려면 Update에다 작성한다.
void Update()
    {
        transform.position.x +=
    }
  • +=은 더해서 넣어준다는 뜻
  • 코딩에서 =(이퀄)은 수학적 기호와 다름
  • 수학 -> 같다 / 코딩 -> 넣어준다
    • 그럼 같다는 어떻게 표현? == (두 번)
void Update()
    {
        transform.position.x += 1
    }
  • 이러면 에러가 뜬다. (에러코드 CS1612)
  • 포지션은 X, Y, Z 하나하나에 직접 접근할 수는 없다. 직접 값 넣기 불가
  • 가지고 올 수는 있지만, 넣어줄 수는 없음
void Update()
    {
        transform.position.x += new Vector3(1f, 0, 0);
    }
  • Vector3 = Position 값
  • InsPector 창 Transform의 X, Y, Z값을 각각 벡터에서 담당
  • 다만 매번 new Vector3(1f, 0, 0); 값을 넣어주기는 번거로움. 그래서 C# 스크립트에서 좀 더 편한 기능을 제공한다.
 void Update()
    {
        transform.position += Vector3.right;
    }
  • 이렇게 할 수 있다.
  • 그런데 또 이렇게 하면 PC의 성능에 따라 속도 차이가 발생.
    (1초마다 x에 1을 더해주는 연산 속도가 저마다 다르면 -> aPC는 1초에 60번 계산, bPC는 1초에 120번 계산..)
  • 모든 기기들이 똑같은 속도를 낼 수 있게끔 맞춰줘야 함 -> 프레임 설정
  • 결국 프레임을 똑같이 맞춰주는 코드 작성 필요
void Start()
    {
        Application.targetFrameRate = 60;
    }
  • 이렇게 해주면, 이 코드가 실행되는 모든 기기들은 이 프레임의 타겟이 60이 된다.
  • 모든 기기들이 1초에 60번만 계산할 수 있게끔 맞춰주는 것

Player의 속도 조절

  • 르탄이의 움직임 속도를 조절해 보자.
void Update()
    {
        transform.position += Vector3.right;
    }
  • 여기서 Vector3.right가 1이 된다. 이 숫자를 수정하려면 곱셈을 활용하자.
void Update()
    {
        transform.position += Vector3.right * 0.05f;
    }
  • 0.05 뒤의 f는 소수점 단위의 수를 표현한다는 의미
  • 현재 x, y, z의 값이 1, 0, 0 이니까 그 값에 각 0.05를 곱해주는 것
  • 결국 x값만 0.05가 된다.

캐릭터 움직이기(1-4)

direction

  • 르탄이가 벽 끝에 부딪히면 반대로 이동하게 하자.
void Update()
    {
        transform.position += Vector3.right * 0.05f;
    }
  • 조건문 문법을 활용해 로직 구현
  • float direction = 0.05f; 변수 입력하자.
    float direction = 0.05f;

       void Update()
          {
              transform.position += Vector3.right * direction;
          }
  • 0.05f를 direction;으로 변경
  • 변수: 일종의 상자. 누군가가 설정해 놓은 데이터를 그대로 받아올 수 있게 해주는 기능
  • direction이라는 상자 안에 0.05라는 데이터를 넣어 놓은 것.
  • 반대로 Start에 direction = -0.05f를 넣어줬다면, 연산하는 곳의 direction값도 -0.05가 됨
  • 그러면 Vector.right에 1에다가 -0.05가 곱해지니, -0.05가 Position x값에 계속 더해짐 -> 르탄 왼쪽 이동
  • 소수점(0.05f)은 float라고 하는 자료형을 가진 변수에다 넣어줘야 함
  • 자료형: 일종의 타입. (등기우편, 일반우편...)

자료형의 종류

    float direction = 0.05f;
    int number = 1;
    string str = "안녕";
  • int = 정수형. 소수점 사용 불가
  • string = 문자열
  • str = 문자를 넣을 수 있는 자료형. 숫자 불가
  • 파란색으로 표시되는 자료형, 초록색으로 표시되는 값, 데이터의 자료형을 맞춰줘야 함

  • 이제 르탄이의 x값이 2.6(오른쪽 벽)이 되었을 때 direction값을 음수로 바꿔주고, x값이 -2.6(왼쪽 벽)보다 작아지려고 할 때 direction값을 양수로 바꿔주면 된다.
  • 이를 위해 조건문 if 작성 필요
           if(transform.position.x > 2.6f)
        {


        }
  • = 만약, 르탄 게임 오브젝트에 붙어 있는 컴포넌트 transform에 있는 position의 x값이 2.6보다 커진다면
           if(transform.position.x < -2.6f)
        {


        }
  • 만약, 르탄 게임 오브젝트에 붙어 있는 컴포넌트 transform에 있는 position의 x값이 -2.6보다 작아진다면
  • 이렇게 조건을 붙일 수 있다. 조건 내용은 중괄호 안에 넣자.
    void Update()
    {
        if(transform.position.x > 2.6f)
        {
            direction = -0.05f;
        }

        if(transform.position.x < -2.6f)
        {
            direction = 0.05f;
        }

        transform.position += Vector3.right * direction;
    }
  • 이렇게 해주면 오른쪽 왼쪽으로 움직이는 르탄이 완성. 이제 못나간다.

GetComponent

  • 르탄이가 벽에 부딪힐 때 이미지를 반대로 만들어 주자.
  • 'Inspector' -> Sprite Renderer -> Flip의 x, y 체크해주면 뒤집히는 르탄이를 볼 수 있다.
  • 이 flip이라는 옵션을 스크립트로 가져와야 함
if(transform.position.x > 2.6f)
        {
            SpriteRenderer.flip;
            direction = -0.05f;
        }
  • 근데 이렇게 하면 안됨ㅠ
  • 검색을 해보자. >SpriteRenderer 스크립트<
    • 'renderer = GetComponent(); // 게임오브젝트의 스프라이트 렌더러 컴포넌트 가져오기'
    • 변수는 일종의 박스니까 SpriteRenderer라고 하는 컴포넌트 데이터를 담아줘야 함. 그래서 GetComponent를 활용한다.
    SpriteRenderer renderer;

    void Start()
    {
        Application.targetFrameRate = 60;
        renderer = GetComponent<SpriteRenderer>();
    }

    void Update()
    {
        if(transform.position.x > 2.6f)
        {
            renderer.flipX = true;
            direction = -0.05f;
        }

        if(transform.position.x < -2.6f)
        {
            renderer.flipX = false;
            direction = 0.05f;
        }
  • 이렇게 하면, 정상적으로 뒤집히는 르탄이를 볼 수 있다.
  • GetComponent는 말 그대로 컴포넌트를 가지고 올 수 있는 기능. 메서드 또는 함수라고도 부를 수 있다.
  • GetComponent 사용 시 주의할 점
    • 스크립트가 붙어 있는 곳과 같은 위치에 있어야 함(같은 인스펙터 상의 위치)
    • 르탄 스크립트가 붙어있는 인스펙터 창에 같이 있어야 함(같은 줄)
    • 그럼 우리가 갖고 올 수 있는 컴포넌트는? Transform, Sprite Renderer, Animator 등.
    • 하지만 Main Camera에 있는 Camera 컴포넌트는 불가능하다는 뜻.만약 쓰려면 카메라 인스펙터 창에 르탄 스크립트를 넣어주면 ok

클릭 시 반응 구현(direction, renderer.flipX)

  • 클릭했을 때 르탄이의 방향(이미지, x값)을 바꿔주자.
    void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {

        }
    }
  • 키보드, 마우스, 카메라 등 외부 입력 장치들의 정보는 Input이라고 하는 키워드에 담겨져 있음
  • 왼쪽 버튼이 0, 오른쪽 버튼이 1
  • 0을 눌렀을 때 우리는 방향 전환, 실제 위치를 반대로 이동해줘야 함 -> 조건문
void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
              direction *= -1;
        }
    }
  • *(별표): 곱센연산. 곱해서 넣는다
  • direction(0.05)에 -1을 곱해서 넣는다 -> -0.05
    void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
              direction *= -1;
              renderer.flipX = !renderer.flipX;
        }

        if(transform.position.x > 2.6f)
        {
            renderer.flipX = true;
            direction = -0.05f;
        }

        if(transform.position.x < -2.6f)
        {
            renderer.flipX = false;
            direction = 0.05f;
        }

        transform.position += Vector3.right * direction;
    }
  • renderer.flipX도 현재 값과 반대로 넣어줘야 함.
  • true와 false, 참과 거짓 이렇게 두 가지 값만 있는 것을 불(bool)값이라고 한다.
  • 현재의 불값과 반대되게 바꿔주는 키워드는 느낌표(!) 사용
  • 이렇게 해주면 클릭할 때마다 방향과 x값이 바뀌는 르탄이 완성

빗방울 구현하기 - 빗방울 코딩하기(1-5)

Rigidbody 2D

  • 빗방울을 구현해 보자.
  • 빗방울(2D Object)에 Rigidbody 2D 컴포넌트 추가
  • Rigidbody 2D: 물리에 필요한 다양한 힘을 적용시킬 수 있음 (중력, 물체의 무게값, 마찰력 등)
  • Rigidbody 2D를 추가하고 실행해 보면 빗방울이 떨어지는 걸 볼 수 있다. 충돌 구현은 아직 불가능
  • Ground와 충돌하게 하려면 Circle Collider 2D 컴포넌트 추가
  • 빗방울과 충돌할 Ground에도 Box Collider 2D 컴포넌트 추가
  • 이러면 땅과 충돌하는 빗방울 완성

Destroy

  • Ground에 닿았을 때 파괴되는 빗방울을 만들어 보자.
private void OnCollisionEnter2D(Collision2D collision)
    {

    }
  • 충돌 현상은 OnCollisionEnter2D 안에 적어주면 됨
  • On은 어떤 이벤트, 어떤 현상이 발생했을 때 쓰게 됨
  • OnCollisionEnter(2D): (2D 환경에서) 충돌이 시작되는 순간
    private void OnCollisionEnter2D(Collision2D collision)
    {
        Debug.Log("충돌");
    }
  • Rain 컴포넌트에 Rain 스크립트를 추가해 주고 디버그 해보면 충돌을 확인할 수 있다.
    이제 여기에 조건문을 넣자.
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.name == "Ground")
        {

        }
    }
  • =(이퀄) 기호가 두 개 있으면 같다는 뜻.
  • 게임 오브젝트의 이름이 Ground와 같다면 -> true값이 나오면서 중괄호 로직이 실행됨
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.name == "Ground")
        {
            Destroy(collision.gameObject);
        }

        게임 오브젝트의 이름이 Ground와 같다면(if) 파괴시켜라(Destroy),
        충돌한 물체의 게임 오브젝트를.
  • 이러면 대지가 갈라지는 모습을 볼 수 있다.
  • Destroy(collision.gameObject);를 Destroy(this.gameObject);로 바꿔주자.
  • this는 해당 Rain 스크립트가 붙어있는 자기 자신을 의미
    private void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.name == "Ground")
        {
            Destroy(this.gameObject);
        }
    }
  • 이렇게 하면 잘 작동한다.

CompareTag

  • 그런데 게임 오브젝트의 이름이 바뀌면 이 로직이 제대로 동작을 하지 않게 됨.
    그래서 방법을 살짝 바꿔줘야 한다.
  • Ground 인스펙터 창 맨 위 Tag에 Ground 태그를 추가해 주고 태그에 넣으면 된다.
  • 이제 gameObject.name의 name 대신 태그를 넣어주자.
     private void OnCollisionEnter2D(Collision2D collision)
    {
        if(collision.gameObject.CompareTag("Ground"))
        {
            Destroy(this.gameObject);
        }
    }
  • Ground라는 Tag값이 있을 때 파괴시켜 준다.
  • 프로젝트 내에 Ground라는 이름이 여러 개일 수도 있기 때문에, 필요한 Ground에만 Tag를 달아준 것
  • 이러면 똑같지만 보다 안전하게(?) 파괴가 된다.

빗방울 구현하기 - 랜덤한 빗방울(1-6)

Random.Range

  • 이제 빗방울을 랜덤한 위치에 생성해 보자.
    void Start()
    {
        float x = Random.Range(-2.4f, 2.4f);
        float y = Random.Range(3.0f, 5.0f);
    }
  • Range는 범위. 최소값과 최대값이 들어가게 됨
  • 빗방울 랜덤 위치 x, y값이 소수점이니 float 사용
  • 다음엔 이 추출된 값을 Position 값에 넣어줘야 함
    void Start()
    {
        float x = Random.Range(-2.4f, 2.4f);
        float y = Random.Range(3.0f, 5.0f);

        transform.position = new Vector3(x, y, 0);
    }
  • 이렇게 하면 랜덤하게 위치하는 빗방울을 확인할 수 있다.

int type

  • 이제 랜덤한 크기의 빗방울을 만들어 보자.
  • 빗방울S, 빗방울M, 빗방울L -> 타입1 빗방울, 타입2 빗방울, 타입3 빗방울
  • 먼저 랜덤한 타입 값을 뽑아 보자.
  • 타입 1, 2 이렇게 정수이기 때문에 int를 사용하자.
  • 변수 이름은 type
    void Start()
    {
       int type = Random.Range(1, 4);
    }
  • 1에서 4까지 넣은 이유는 마지막 4는 포함되지 않기 때문. 이건 실수도 마찬가지(2.4f라면 2.39f)
    • 실수는 큰 차이가 없기에 일단은 정수만 조심하자.
    void Start()
    {
        int type = Random.Range(1, 4);

        if(type == 1)
        {

        }

        else if(type == 2)
        {

        }        

        else if(type == 3)
        {

        }       
    }
  • 연관된 if문이 연속되면, if문으로 다 나눠주는 게 아니라 else if라고 적어주면 됨
  • 묶여져 있기 때문에, 해당되는 조건문이 있다면 해당 중괄호 안의 코드만 실행되고 뒷부분은 생략되어 다음 코드로 넘어감
  • if if if 이렇게 있으면 맨 위가 true여도 다 비교를 하게 됨. 굳이 그럴 필요 없다.
  • 이제 사이즈 세팅, 색깔 세팅, 점수 세팅 필요. 사이즈랑 점수는 따로 변수로 빼주도록 하자.
  • size는 float으로 지정하고, 맨 마지막에 size는 localScale에다가 적용시켜 주자.
    float size = 1.0f;
    int score = 1;

    void Start()
    {
        int type = Random.Range(1, 4);

        if(type == 1)
        {
            size = 0.8f;
            score = 1;
        }

        else if(type == 2)
        {
            size = 1.0f;
            score = 2;
        }        

        else if(type == 3)
        {
            size = 1.2f;
            score = 3;
        }       

        transform.localScale = new Vector3(size, size, 0);
    }
  • 만약 0.8이라는 값이 사이즈에 세팅이 되면, 나머지 조건문들은 걸리지 않게 됨(타입1에 true가 되기 때문에). 그럼 바로 transform.localScale 세팅으로 넘어가게 되는 것
  • 그러면 사이즈는 0.8이 들어가 있으니까 0.8로 맞춰지게 되는 것
  • 이제 색깔 세팅이 필요하다.
    SpriteRenderer renderer;

    void Start()
    {    
        int type = Random.Range(1, 4);

        if(type == 1)
        {
            size = 0.8f;
            score = 1;
            renderer.color = new Color(100 / 255f, 100/255f, 1f, 1f);
        }

        else if(type == 2)
        {
            size = 1.0f;
            score = 2;
            renderer.color = new Color(130 / 255f, 130/255f, 1f, 1f);
        }        

        else if(type == 3)
        {
            size = 1.2f;
            score = 3;
            renderer.color = new Color(150 / 255f, 150/255f, 1f, 1f);
        }       

        transform.localScale = new Vector3(size, size, 0);
    }
  • color 값은 float 값이 들어감. 그래서 최대값으로 나눠줘야 함
  • 예를 들어 150이라고 하는 값을 컬러에 세팅해 줬다면, 150을 최대값인 255로 나눠서 new color 안에다가 넣어줘야 함
  • 이렇게 하면 최종적으로 랜덤한 위치에서, 랜덤한 크기의, 랜덤한 색깔의 빗방울이 떨어지게 된다.