카테고리 없음

[Unity] C# 스크립트 기초 (2)

jaeheon0520 2024. 2. 21. 18:33

배열 사용하기

스크립트를 사용하다 보면 여러 가지 값(게임 순위와 점수 등)을 한꺼번에 다루어야 할 때가 있다. 이때 변수를 하나하나 마드는 것은 나무나 수고스럽다. 

 

배열 선언과 규칙

이때는 배열을 쓰면 해결된다. 배열은 변수 상자를 가로로 이어 붙인 길고 얇은 상자와 비슷하다.

 

다음과 같은 형태로 사용한다.

 

int[] points = new int[5]

 

오른쪽에 new 키워드가 나왔다. 영어로는 '새로운'이라는 뜻이지만 스크립트에서는 '만든다'라는 뜻에 가깝다. 여기서는 new int[5]를 써서 int형 상자를 다섯 개 만든다. 즉, 배열을 만들려면 배열을 선언하고 바로 이어서 new를 사용해 필요한 상자 수를 지정해야 한다.

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

public class Test : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        int[] array = new int[5];

        array[0] = 2;
        array[1] = 10;
        array[2] = 5;
        array[3] = 15;
        array[4] = 3;

        for (int i = 0; i < 5; i++) {
            Debug.Log(array[i]);
        }
    }
}

 

 

배열에 넣는 요소의 개수가 분명하다면 new를 써서 배열의 요소 수를 지정하지 않아도 된다. 자주 쓰는 방법이므로 꼭 기억해두자.

 

int[] points = {83, 99, 52, 93, 15};

 

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

public class Test : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        int[] points = {83, 99, 52, 93, 15};

        for (int i = 0; i < points.Length; i++) {
            if (points[i] >= 90) {
                Debug.Log(points[i]);
            }
        }
    }
}

 

 

for 문의 반복 조건은 0부터 points.Length까지이다. points.Length란 글자 그대로 points 배열의 길이(요소 수)이다. 배열형의 변수명.Length를 써서 배열의 길이를 얻을 수 있다.

 

다음은 배열에 포함되는 값의 평균을 구하는 스크립트이다.

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

public class Test : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        int[] points = {83, 99, 52, 93, 15};

        int sum = 0;

        for (int i = 0; i < points.Length; i++) {
                sum += points[i];
            }

            float average = sum / points.Length;
            Debug.Log(average);
    }
}

 

평균값은 68.4가 정답이지만 68로 출력된다. 이것은 C#에서는 정수끼리 나눗셈을 하면 소수점 이하가 버려져 결과는 정수가 되기 때문이다. 

 

소수점까지 출력하고 싶다면 정수끼리 나눗셈을 하는 것이므로 처음에 1.0f를 넣어준다. 꼼수처럼 보이지만 이 내용을 모르면 계산 결과를 소수점으로 구할 때 오류가 발생할 수 있으므로 꼭 기억해 두어야 한다.

 

float average = 1.0f * sum / points.Length;

 

메서드 만들기

메서드

지금까지는 Start 메서드 안에 모든 처리를 작성했다. 하지만 처리가 길어지면 읽기도 힘들고 디버그도 어려워 문제가 발생할 수 있다. 이때는 모아 둔 처리마다 이름을 붙여 각각을 블록으로 사용할 수 있는 메서드를 쓰면 편리하다. 길어진 처리를 의미가 있는 처리 블록으로 분해하고 이름을 붙이는 구조를 만드는데, 이렇게 분해한 각 처리를 메서드(또는 함수)라고 한다.

 

메서드로 건네는 값을 인수, 메서드에서 돌려받는 값을 반환값이라고 한다. 인수는 여러 개를 건넬 수 있지만 반환값은 한 개로 정해져 있다.

 

클래스 만들기

매서드는 처리를 모아 둔 것이고, 클래스는 메서드와 변수를 모아 둔 것이다. 여러 변수와 메서드를 하나로 합치지 않고 따로따로 구현하면 어느 변수와 메서드가 연결되어 있는지 알기 어렵다. 클래스를 사용하면 관계가 있는 변수와 메서드를 하나로 합칠 수 있으므로 스크립트를 관리하기 쉽다.

 

클래스 서식을 단순화하면 다음과 같다. class 키워드 다음에 클래스명을 쓰고, 그 안에 클래스에서 사용하는 변수와 메서드를 쓴다. 클래스에서 사용한 변수를 멤버 변수, 클래스에서 사용한 메서드를 멤버 메서드라고 한다.

 

class 클래스명
{
    멤버 변수 선언;
    멤버 메서드 구현;
}

 

작성한 클래스는 int나 string 등 데이터형으로 사용할 수 있다. 즉, Player 클래스를 만들면 Player형을 사용할 수 있게 된다.

 

지금까지 써 온 Test 스크립트(Test.cs)에 Player 클래스를 추가해보자. Player 클래스는 Test 클래스 외부에 추가하는 것에 주의하자.

 

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

public class Player
{
    private int hp = 100;
    private int power = 50;

    public void Attack() {
        Debug.Log(this.power + "대미지를 입혔다");
    }

    public void Damage(int damage) {
        this.hp -= damage;
        Debug.Log(damage+ " 대미지를 입었다");
    }
}

public class Test : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Player mPlayer = new Player();
        mPlayer.Attack();
        mPlayer.Damage(30);
    }
}

 

 

클래스를 사용하는 방법

Player mPlayer를 써서 Player형의 mPlayer 변수를 선언한다. 이 단계에서는 Player형의 상자를 만들었을 뿐이므로 Player형의 실체인 인스턴스를 작성해 대입해야 한다. 인스턴스를 만들려면 new 키워드 다음에 클래스명()을 쓴다. 이로써 Player 클래스의 인스턴스가 만들어지고 이것을 mPlayer 변수 안에 대입한다.

 

 

접근 수식자

Player 클래스의 멤버 변수와 멤버 메서드 앞에 public이나 private 키워드가 붙어 있다. 이것을 접근 수식자라고 하는데, OO.xx라고 썼을때 다른 클래스에서 멤버로 접근 가능한지 여부를 나타낸다. 접근 수식자를 생략하면 private으로 간주되므로 공개하고 싶은 변수와 메서드가 있다면 public 수식자를 붙이는 것이 좋다.

 

접근 수식자 접근 가능 클래스
public 모든 클래스에서 접근 가능
protected 같은 클래스와 해당 클래스의 서브클래스에서 접근 가능
private 같은 클래스에서만 접근 가능

 

this 키워드

this는 자신의 인스턴스를 가리키는 키워드이다. 즉 this.power는 자신의 인스턴스가 가진 power 변수를 나타낸다. 사실 this를 붙이지 않아도 자기 클래스의 멤버 변수를 활용할 수 있다. 다만 다음과 같이 멤버 변수와 동일한 이름으로 로컬 변수(power)를 선언했다고 했을 때, power라고만 쓰면 로컬 변수의 값이 우선해서 사용된다. 따라서 멤버 변수를 쓸 때는 명시적으로 this를 붙여야 오류를 막을 수 있다.

 

public void Attack() {
    int power = 9999;
    Debug.Log(power + " 대미지를 입혔다");
    // 로컬 변수가 우선 쓰이고, 999 대미지를 주게 된다.
}

 

이 절에서 설명한 내용을 프로그래밍에서는 객체 지향이라고 한다. 객체 지향의 3대 요소는 상속, 접근 수식자에 의한 캡슐화, 다형성이다.

 

MonoBehaviour?

Test 클래스의 선언부분 뒤에 붙은 : MonoBehaviour는 상속이라고 한다. 이 부분은 유니티가 미리 준비한 MonoBehaviour 클래스의 기능을 Test 클래스에 집어넣겠다고 선언하는 과정이다.

MonoBehaviour 클래스는 게임 오브젝트를 구성하는 기본 기능을 멤버 변수와 멤버 메소드로 준비한 클래스이다. 따라서 게임 오브젝트에 붙여 실행하는 스크립트는 MonoBehaviour 클래스를 상속해야 한다.

 

Vector 클래스 사용하기

Vector

3D 게임을 만들려면 공간에서 오브젝트를 어디에 둘지, 어느 쪽으로 옮길지, 어디로 힘을 보낼지 등을 정해야 하므로 float형의 x, y, z 값 세 개를 쓴다. C#에는 이러한 값을 하나로 합쳐 다룰 수 있는 Vector3 클래스(정확히는 구조체라고 함)가 준비되어 있다. 반면 2D 게임용에는 float형의 x, y 값을 갖는 Vector2 클래스가 있다. Vector3 클래스의 구조는 다음과 같다.

 

class Vector3
{
    public float x;
    public float y;
    public float z;
    
    // Vector용 멤버 메서드가 아래에 이어진다.
}

 

이처럼 Vector3 클래스에는 x, y, z 멤버 변수가 있고, Vector2 클래스에는 x, y 멤버 변수가 있다. 둘 다 좌표나 벡터로 쓸 수 있다.

 

x = 3, y = 5를 좌표로 쓰면 오브젝트가 (3, 5) 위치에 배치되었다는 것을 의미한다. 한편 벡터로 쓰면 현재 위치에서 'X축 방향으로 3', 'Y축 방향으로 5' 움직였다는 것을 의미한다.

 

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


public class Test : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Vector2 playerPos = new Vector2(3.0f, 4.0f);
        playerPos.x += 8.0f;
        playerPos.y += 5.0f;
        Debug.Log(playerPos);
    }
}

 

 

Vector2 클래스의 멤버 변수(x, y)에 덧셈을 할 수 있는지는 이전 코드에서 확인했다. 다음으로 Vector2 클래스끼리 뺼셈을 해보자.

 

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


public class Test : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Vector2 startPos = new Vector2(2.0f, 1.0f);
        Vector2 endPos = new Vector2(8.0f, 5.0f);
        Vector2 dir = endPos - startPos;
        Debug.Log(dir);
       
        float len = dir.magnitude;
        Debug.Log(len);
    }
}

 

 

이 예제에서는 startPos에서 endPos로 향하는 dir 벡터를 구한다. 두 점의 좌표에서 벡터를 구하기 때문에 11번 줄의 endPos에서 startPos를 뺀다. 

 

14번 줄에서는 startPos부터 endPos까지 거리를 구한다. 이 거리는 dir 벡터 길이와 같으므로 Vector2 클래스가 갖는 magnitude 멤버 변수를 사용해서 dir 벡터의 길이를 구한다.

 

오늘 하루도 쌓였다.