4. 들어가기 전에
이 장에서는 C# 스크립트를 작성하기 전에 프로그래밍에 필요한 기본 개념을 알아본다.
이 장에서 다루는 내용
- 프로그래밍 기본 이론
- 변수와 메서드
- 유니티에서 C# 스크립트를 구현하는 방법
- C# 기초 문법과 사용법
레트로의 유니티 게임 프로그래밍 에센스 : 네이버 도서
4.1 변수 개념 잡기
변수는 값을 저장하는 장소이다.
변수에 저장한 값은 게임 도중 언제든지 접근하고 수정할 수 있다. 변수를 사용하는 이유는 원하는 값을 기억하고 다시 사용하기 위해서이다.
게임 상점을 만든다고 생각해본다. 플레이어가 1000 골드를 가지고 있다고 한다. 이것을 기억하는 gold 라는 변수를 만든다. 그리고 1000 을 집어넣는다.
int gold = 1000;
= 는 대입 연산자 혹은 이퀄(equal) 이라고 읽으며, 오른쪽에 있는 값을 왼쪽에 전달한다는 의미이다. 즉, gold 라는 이름에 1000 이라는 값을 저장(할당) 한다.
나중에 변수 gold 에서 1000 이라는 값을 다시 가져올 수 있다. 만약 플레이어가 상점에서 200 골드짜리 아이템을 구매했다면 gold 에서 200 을 빼면 된다.
gold = gold - 200;
* 이 코드의 의미는 gold 의 값(1000) 에서 200을 뺀 값을 다시 gold 변수에 저장하는 것이다. 즉, 이제 gold 는 800 이라는 값을 가지고 있는 것.
4.1.1 변수 선언하기
컴퓨터는 변수가 어떤 종류의 데이터를 다룰지 자동으로 추측하지 못한다. 따라서 변수가 처음 등장할 땐 변수 앞에 사용할 데이터의 종류(타입) 를 표시해야 한다.
새로운 변수를 만들고 타입을 정하는 것을 변수를 선언한다고 표현한다.
int gold;
위 코드는 정수 타입의 값을 다루는 gold 변수를 선언한 것이다. gold 앞의 int 는 소수점이 없는 숫자를 뜻하는 integer(정수) 의 약자이다.
다음과 같이 변수 선언과 동시에 초깃값을 할당할 수도 있다.
int gold = 1000;
변수를 선언한 다음에는 반드시 세미콜론(;) 을 사용해서 코드 한 줄이 끝났음을 알려야 한다. 정리하면 다음과 같은 형식으로 변수를 선언한다.
타입 변수명;
4.1.2 변수의 여러 형태
변수로 다룰 수 있는 타입은 정수(int) 외에도 여러 개 있다. 그중에는 특별한 형태의 값을 사용하는 타입도 있다.
float itemWeight = 1.34f;
bool isStoreOpen = true;
string itemName = "포션";
위에서 알 수 있듯이 int 와 달리 특수한 형태를 요구하는 변수도 있다.
- float 은 실수(소수점을 가질 수 있는 수)를 저장하는 타입이다.
- float 은 부동소수점(floating point) 의 약자이다. '부동' 은 동동 떠다닌다는 의미이다. 그러므로 부동소수점은 소수점이 숫자 사이를 동동 떠다닌다는 의미가 된다. 저장할 값에 따라서 소수점의 위치가 고정되어 있지 않고 변하기 때문에 붙여진 이름(* float 타입의 숫자 끝에는 꼭 f 를 붙여야 한다).
- bool 은 true(참) 또는 false(거짓) 를 저장하는 타입이다.
- bool 은 불리언(boolean) 의 약자이다. bool 은 값으로 true 와 false 만 사용할 수 있다.
- string 은 문자열을 저장하는 타입이다.
- string 은 문자열(문장) 을 저장한다. string 은 저장할 문자열을 반드시 큰따옴표(") 로 묶어야 한다.
4.1.3 정리하기
1. 변수는 값을 저장하는 장소이다.
변수에 값을 저장하면 게임 도중에 언제든지 접근해서 사용할 수 있습니다.
2. 변수를 처음 선언할 때는 반드시 타입을 명시해야 한다.
몇몇 타입의 변수는 특수한 형태의 값을 사용한다. 변수에는 변수 타입에 알맞은 값을 저장해야 한다.
4.2 함수(메서드) 개념 잡기
함수는 미리 정해진 동작을 수행하는 코드의 묶음이다. 어떤 처리를 미리 함수로 만들어두면 다시 반복해서 사용할 수 있다.
4.2.1 함수로 중복 코드 줄이기
프로그래밍에서 함수는 미리 지정된 동작을 수행하는 코드의 묶음이다. 코드를 사용하면 같은 동작을 수행하는 코드를 여러 번 작성할 필요가 없다.
물체를 움직이는 기능을 만들어 이를 나무 상자와 금속 상자에 적용해 움직여본다.
1. 물체를 움직이는 기능 만들기
체력(HP) 을 10 소모하여 물체를 3미터만큼 옮긴다고 한다. 이 기능을 코드 어딘가에 나열하면 다음과 같은 모습이 될 것이다.
물체 움직이기:
체력 10만큼 감소
오브젝트를 3미터 옮기기
2. 나무 상자와 금속 상자 움직이기
앞에서 만든 기능을 나무 상자와 금속 상자를 대상으로 사용한다. 만약 함수가 없다면 물체를 움직이는 코드를 나무 상자와 금속 상자 각각에 대해 반복해서 작성해야 한다. 만약 옮길 물체가 100개라면 동일한 코드를 100번 반복해서 작성해야 한다.
나무 상자 움직이기:
체력 10만큼 감소
오브젝트를 3미터 옮기기
금속 상자 움직이기:
체력 10만큼 감소
오브젝트를 3미터 옮기기
3. 물체를 옮기는 기능에 소리 추가하기
물체를 옮길 때 효과음을 재생하는 처리를 추가하려고 한다. 그렇게 하려면 기존 코드를 다음과 같이 바꿔야 한다.
물체 움직이기:
체력 10만큼 감소
오브젝트를 3미터 옮기기
효과음 재생
이를 적용하려면 물체를 움직이는 모든 부분을 찾아가서 다음과 같이 새로운 코드를 일일이 추가해주어야 한다. 옮길 물체가 100개라면 100군데 모두 수정해야 한다. 이 방법은 귀찮고 비효율적이며 실수하기 쉽다. 새로운 해결책이 필요하다.
나무 상자 움직이기:
체력 10만큼 감소
오브젝트를 3미터 옮기기
효과음 재생
금속 상자 움직이기:
체력 10만큼 감소
오브젝트를 3미터 옮기기
효과음 재생
4. 함수를 사용해서 반복되는 코드 대체
반복되는 코드를 함수로 대체하면 문제를 해결할 수 있다. 물체를 움직이는 기능에 관한 코드를 Move() 라는 함수로 묶는다.
void Move() {
체력 10만큼 감소
오브젝트를 3미터 옮기기
효과음 재생
}
기존에 물체를 움직인 코드를 Move() 뒤에 두고 중괄호로 묶었다. 이렇게 묶인 영역은 Move() 함수의 바디(body) 가 된다. 이렇게 하면 나무 상자와 금속 상자의 중복 코드를 다음과 같이 Move() 함수로 간결하게 대체할 수 있다.
나무 상자 움직이기:
Move()
금속 상자 움직이기:
Move()
이제는 어떤 물체를 움직일 때 Move() 함수만 사용하면 된다. 이처럼 함수를 사용하면 코드가 반복되는 문제를 간결하게 해결할 수 있다.
4.2.2 함수의 입력
나무 상자는 가볍고 금속 상자는 무겁다. 따라서 물체에 따라 필요한 체력과 한 번에 옮기는 거리가 달라야 한다. 앞의 예제에서는 Move() 함수가 사용하는 수치가 체력은 10, 거리는 3으로 고정되어 있다.
나무를 옮길 때는 체력 10을 소모하여 물체를 3미터, 금속을 옮길 때는 체력 30을 소모하여 물체를 1미터만큼 옮기고 싶다.
이럴 때는 함수의 입력을 사용한다. Move() 의 괄호 부분은 바깥에서 값을 줄 수 있는 입구이다. 이 입구를 사용해 값을 전달할 수 있도록 Move() 함수를 변경하겠다.
1. Move() 함수가 입력을 받게 하기
Move() 함수에서 체력과 거리를 다음과 같이 hp 와 distance 변수로 대체한다.
void Move(int hp, int distance) {
체력 hp 만큼 감소
오브젝트를 distance 미터 옮기기
효과음 재생
}
이렇게 하면 Move() 함수 입력을 통해 hp 와 distance 값을 전달할 수 있다(함수의 입력은 변수일수도 있고 상수일 수도 있다).
물체 움직이기:
Move(hp, distance)
* 여기서는 hp, distance 가 변수임. 이 코드에는 적지 않았지만 코드 윗부분에 hp 와 distance 가 선언, 초기화되어 있을 것
2. 서로 다른 값을 사용하여 Move() 함수 실행하기
다음과 같이 나무 상자는 체력 10과 거리 3을 사용하도록, 금속 상자는 체력 30과 거리 1을 사용하도록 Move() 를 실행한다.
나무 상자 움직이기:
Move(10, 3)
금속 상자 움직이기:
Move(30, 1)
3. 코드 의미 다시 한 번 보기
void Move(int hp, int distance) {
체력 hp 만큼 감소
오브젝트를 distance 미터 옮기기
효과음 재생
}
함수 입력 부분 (int hp, int distance) 는 외부로부터 int 타입의 값을 두 개 받겠다는 의미이다. 그리고 입력을 통해 외부에서 받은 값을 각각 hp 와 distance 라는 변수로 함수 내부에서 사용한다.
4.2.3 함수의 출력(반환값)
Move() 함수는 실행 결과를 다른 곳에 전달할 필요가 없었다. 하지만 어떤 함수는 계산이나 처리 결과를 다른 곳에 전달할 필요가 있다.
임의의 숫자를 하나 생성하는 GetRandomNumber() 함수를 살펴보자.
void GetRandomNumber() {
int number = 0; // 선언 및 초기화
number = 임의의_숫자;
}
이 함수는 변수 number 를 만들고 임의의 숫자를 할당한다. 하지만 그 값을 외부에 전달하지는 않는다. 따라서 number 에 저장된 임의의 숫자를 외부에서 사용할 수 없다(4.6.4 스코프에서 더 자세히 설명). 이때 return 키워드를 사용하면 값을 외부로 전달할 수 있다.
int GetRandomNumber() {
int number = 0; // 선언 및 초기화
number = 임의의_숫자;
return number;
}
return number; 는 number 를 반환한다(자신을 호출한 곳에)라는 의미로 해석하면 된다. return 은 함수를 종료하고, 자신을 실행(호출)한 곳으로 되돌아가서 값을 전달(반환) 한다. 여기서는 다음과 같이 동작한다.
- GetRandomNumber() 함수의 모든 처리를 종료한다.
- GetRandomNumber() 함수를 실행한 곳으로 되돌아가서 number 의 값을 전달한다.
컴퓨터는 함수가 return 으로 전달할 데이터 타입을 스스로 추측할 수 없다. 따라서 함수 이름 앞에 반환값(출력값 또는 리턴값이라고도 한다) 의 타입을 표시해주어야 한다.
GetRandomNumber() 는 결과로 정수 int 를 반환하므로 int GetRandomNumber() 이와 같은 형식으로 작성한 것이다
* void 는 반환값이 없음을 의미한다(void 는 공허라는 뜻이다)
이제 GetRandomNumber() 함수의 실행 결과를 다른 곳에 전달할 수 있다. 다음과 같이 함수를 수행한 다음 = 기호를 사용해 함수의 결과(반환값) 을 다른 변수에 저장하면 된다.
여기서 함수를 종료하고 결과를 반환하는 키워드로 return(되돌아가다) 을 사용하는 이유를 알 수 있다. return 은 함수가 끝났을 때 자신을 사용한 곳으로 되돌아가서 값을 전달하기 때문이다.
* return 이 없는 함수
모든 함수는 return 키워드를 가져야 한다. 단, void 를 반환하는 함수는 컴퓨터가 암묵적으로 return 을 만들어 사용하기 때문에 생략해도 된다.
지금까지의 내용을정리하면 다음과 같다.
- 함수는 입력을 받거나 받지 않을 수 있다.
- 함수는 결과를 반환하거나 반환하지 않을 수 있다.
* C# 에서는 (클래스의) 함수를 메서드라고 부른다. 함수와 메서드는 혼용할 수 있지만 통일성을 위해 C# 함수를 모두 메서드로 통일하겠다.
4.3 첫 스크립트 작성하기
C# 프로그래밍을 시작한다. 먼저 연습용 프로젝트를 생성하고 첫 스크립트를 작성한다.
4.3.1 새 프로젝트 생성하기
새 프로젝트를 생성한다. 방법은 1장과 같다. 프로젝트 이름은 'Hello Coding' 으로 한다. 경로는 원하는 곳으로 임의 지정한다.
이 장에서 작성할 스크립트 결과는 콘솔 창에서 확인할 수 있다. 만약 유니티 에디터에 콘솔 창이 보이지 않는다면 유니티 상단 메뉴에서 Windows > General > Console 을 클릭하여 콘솔 창을 띄우고 시작한다.
또한 콘솔 창에서 Collapse 탭이 활성화된 경우 해제할 것을 추천한다. Collapse 가 활성화 되어 있으면 같은 내용의 로그들이 하나로 묶인다. 따라서 로그 출력 순서를 확인하기 불편하다.
4.3.2 첫 C# 스크립트 파일 작성하기
먼저 C# 스크립트를 작성하고 나서 스크립트 구조를 살펴본다.
1. 새로운 C# 스크립트 작성
- 프로젝트 창 왼쪽 상단의 + > C# Script 클릭
- 생성된 스크립트 파일의 이름을 즉시 HelloCode 로 변경
생성한 HelloCode 스크립트 파일을 더블 클릭하면 코드 편집기(비주얼 스튜디오) 에서 코드가 열린다.
열린 HellloCode 스크립트에는 위와 같이 자동으로 생성된 코드가 있다. using, public, class 등의 키워드는 2부가 끝날 때쯤 모두 이해하게 되므로 차근차근 보도록 한다.
4.3.3 스크립트 구성 살펴보기
코딩을 하기 전 자동으로 생성된 스크립트가 어떻게 구성되어 있는지 살펴본다. HelloCode 스크립트에서 지금은 사용하지 않을 부분을 지워서 정리한 뒤 살펴본다.
1. HelloCode 스크립트를 깔끔하게 정리하기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloCode : MonoBehaviour
{
void Start()
{
}
}
2. 스크립트 구성 살펴보기
전체 스크립트가 using, class, Start() 세 부분으로 구성된 것을 알 수 있다. 이 중에서 class 는 5장에서 다룬다. 여기서는 using 과 Start() 를 살펴본다.
- using
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using 키워드를 이용하여 사용할 라이브러리의 경로를 지정하면 해당 라이브러리에 들어 있는 코드를 가져와서 사용할 수 있다. 여기서 using 뒤의 경로를 네임스페이스(namespace) 라고 한다.
C# 과 유니티는 개발에 필요한 여러 라이브러리를 네임스페이스로 제공한다. 예제 코드의 using UnityEngine; 은 UnityEngine 네임스페이스에 존재하는 코드를 가져와서 사용한다는 의미이다.
- Start() 메서드
void Start()
{
}
Start() 메서드는 코드 실행이 시작되는 시발점을 제공한다.
유니티에는 상황에 맞춰 자동으로 실행되는 메서드인 유니티 이벤트 메서드가 있다. Start() 메서드가 대표적인 유니티 이벤트 메서드이다. Start() 메서드는 게임이 시작될 때 자동으로 한 번 실행된다. 따라서 게임 시작과 함께 실행될 코드를 Start() 메서드 안에 넣으면 된다.
4.3.4 Hello World!
Hello World! 라는 문장을 화면에 출력하도록 HelloCode 스크립트를 수정한다.
1. HelloCode 스크립트 수정
Start() 메서드 안에 Debug.Log("Hello World!"); 를 추가했다. 세미콜론(;) 을 실수로 빼먹지 않도록 주의한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloCode : MonoBehaviour
{
void Start()
{
Debug.Log("Hello World!");
}
}
2. HelloCode 스크립트 저장
변경된 코드를 저장하고 유니티 에디터 창으로 돌아간다. 변경된 코드를 저장하지 않으면 유니티 프로젝트에 반영되지 않는다. 따라서 변경 사항은 반드시 저장해야 한다.
- 코드 편집 창에서 스크립트 저장(단축키: 윈도우는 Ctrl + S, 맥은 Command + S)
- 코드 편집 창을 닫거나 최소화 한 후 유니티 에디터 창으로 돌아가기
3. HelloCode 스크립트를 게임 오브젝트에 추가
스크립트를 완성했다고 해서 코드가 동작하는 것은 아니다. 이 상태로는 HelloCode 스크립트가 단순한 텍스트 파일이기 때문이다.
완성된 스크립트를 동작시키고 싶다면 게임 월드에 존재하는 오브젝트로 만들어야 한다. 그러기 위해서는 스크립트를 게임 오브젝트에 컴포넌트로 붙여 넣어야 한다. 빈 게임 오브젝트를 생성하고 작성한 HelloCode 스크립트를 붙인다.
- 하이어라키 창에서 + > Create Empty 클릭
- 프로젝트 창의 HelloCode 스크립트를 하이어라키 창의 GameObject 로 드래그&드롭
- 결과
4. HelloCode 스크립트 실행
- 플레이 버튼을 클릭하여 씬을 시작 -> 콘솔 창에서 "Hello World!" 확인
4.4 코딩 기본 규칙
이 절에서는 앞서 콘솔 창에 Hello World! 메시지를 출력했던 코드를 이용하여 코드 기본 규칙을 살펴본다.
4.4.1 주석
주석은 컴퓨터가 처리하지 않는 영역이다. 그러므로 보통 메모 용도로 사용한다. 코드를 주석으로 바꿔서 잠시 비활성화시키는 용도로 사용할 수도 있다.
한 줄 주석은 슬래시(/) 를 연속 두 번 타이핑하고 입력한다. 여러 줄 주석은 슬래시와 별표(/* 주석 처리할 내용 */) 를 사용하여 시작과 끝을 표시한다.
// 주석은 보통 메모 용도로 사용한다.
/*
주석을 여러 줄에 걸쳐 작성하는 경우
슬래시와 별표를 함께 사용해서
시작과 끝을 표시한다.
*/
다음은 HelloCode 스크립트의 Start() 메서드에 주석을 작성한 예입니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HelloCode : MonoBehaviour
{
void Start()
{
// Hello World! 를 콘솔에 출력
Debug.Log("Hello World!");
}
}
4.4.2 콘솔 출력
콘솔에 메시지를 출력할 때는 Debug.Log() 메서드를 사용한다.
Deubg.Log("출력하고 싶은 값");
Debug.Log() 메서드는 입력한 값을 콘솔로 출력하는 메서드이다. 문장을 출력하는 경우에는 위와 같이 문장을 큰따옴표로 묶어준다.
// 추가적인 예시
int numInt = 1;
Debug.Log(numInt);
Debug.Log(100);
float numFloat = 1.0f;
Debug.Log(numFloat);
Debug.Log(1.0f);
char ch = 'a';
Debug.Log(ch);
Debug.Log('a');
4.4.3 세미콜론
코드 한 문장이 끝나면 반드시 세미콜론(;) 을 끝에 붙인다. 컴퓨터는 코드 한 문장을 세미콜론으로 구분하기 때문이다.
어떤 스크립트에서 변수를 두 개 선언했다고 가정해보자.
1. 정상적으로 실행되는 경우
int a = 1;
int b = 2;
2. 정상적으로 실행되지 않는 경우
컴퓨터가 코드를 읽을 때 줄바꿈은 무시하므로 사실상 밑의 코드는 컴퓨터에게 int a = 1 int b = 2; 와 같이 보인다. 즉, int a = 1 과 int b = 2; 를 구별하지 못하고 한 문장으로 처리하기 때문에 에러가 발생한다.
int a = 1
int b = 2;
4.4.4 using
유니티에서 새로운 스크립트를 생성하면 스크립트 상단에 다음과 같은 코드가 자동으로 추가된다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using 에 네임스페이스를 지정하면 해당 네임 스페이스에 들어 있는 코드를 현재 스크립트로 불러온다. 따라서 유니티가 제공하는 여러 기능을 활용할 수 있게 된다.
대표적으로 Debug 는 UnityEngine 에 포함되어 있다(Debug 에 마우스 커서를 대보면 다음과 같은 안내가 뜨는데 이를 통해 알 수 있다).
즉, Debug.Log() 는 UnityEngine.Debug.Log() 와 같았던 것이다. 여기서 점(.) 연산자는 앞에 있는 대상에서 사용할 기능을 꺼내는 것을 의미한다. 즉, Log() 메서드는 Debug 에서, Debug 는 UnityEngine 에서 사용할 기능을 꺼내 온 것이다.
4.5 변수 연습하기
C# 에서 기본으로 제공하며 가장 자주 사용하는 변수 타입을 사용해본다. 이들은 C# 에 미리 포함되어 있다고 해서 내장 타입(built-int type:기본 제공 타입) 이라고 부른다.
4.5.1 캐릭터 프로필 저장하고 출력하기
다음과 같은 프로필을 가진 캐릭터의 정보를 저장하고 출력하는 예제를 만들어본다. 이를 위해 이전까지 진행한 HelloCode 스크립트의 내용을 수정한다.
- 캐릭터 이름: 라라
- 혈액형: A
- 나이: 17
- 키: 168.3
- 성별: 여성
1. HelloCode 스크립트 수정하기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
// 캐릭터의 프로필을 변수로 만들기
string characterName = "라라";
char bloodType = 'A';
int age = 17;
float height = 168.3f;
bool isFemale = true;
// 생성한 변수를 콘솔에 출력
Debug.Log("캐릭터 이름 : " + characterName);
Debug.Log("혈액형 : " + bloodType);
Debug.Log("나이 : " + age);
Debug.Log("키 : " + height);
Debug.Log("여성인가? : " + isFemale);
}
}
4.5.2 변수 타입
작성한 코드에서 변수를 하나씩 살펴본다.
1. string
string 으로 캐릭터 이름 "라라" 를 저장했다. string 은 문자열을 저장한다. 저장할 문자열은 큰따옴표로 묶는다.
string characterName = "라라";
2. char: 문자 하나
char 로 혈액형 'A' 를 저장했다. char 는 character(문자) 의 약자로서 문자 하나를 저장한다. 저장할 문자는 작은따옴표로 묶는다.
char bloodType = 'A';
3. int: 정수
int 로 나이 17 을 저장했다. int 는 정수(integer: 소수점이 없는 숫자) 를 저장한다.
int age = 17;
4. float: 실수
float 으로 키 168.3 을 저장했다. float 은 소수점을 가진 숫자(실수) 를 저장한다. float 타입의 숫자 뒤에는 f 를 붙여야 한다.
컴퓨터가 기억할 수 있는 범위는 한계가 있다. float 은 32 비트를 사용해 숫자를 표현한다. 따라서 float 은 소수점 아래 7자리까지만 정확하게 표현할 수 있다.
소수점 아래 숫자가 7자리보다 많으면 근삿값으로 처리된다. 따라서 값이 부정확할 수 있다.
float height = 168.3f;
5. bool: 불리언
bool 로 성별을 저장했다. bool 은 true(참) 와 false(거짓) 중 하나를 저장한다. 여기서 isFemale 은 캐릭터가 여성인지 저장한다.
bool isFemale = true;
4.5.3 변수 출력하기
변수에 저장된 값은 Debug.Log() 메서드로 출력할 수 있다. 예를 들어 키를 출력하고 싶다면 Start() 메서드에 다음과 같은 코드를 작성한다.
Debug.Log("키 : ");
Debug.Log(height);
위와 같이 작성하면 두 줄로 나뉘어 출력된다. 메시지를 읽기 쉽도록 문장과 변수에 저장된 값을 하나의 문자열로 합쳐서 출력하려면 다음과 같이 코드를 작성해야 한다.
Debug.Log("키 : " + height);
C# 에서 문자열 뒤에 + 연산자로 값을 더하면 문자여로가 값이 하나의 문자열로 연결된다. 위 코드는 문자열인 "키 : " 와 변수 height 의 값을 하나의 문자열로 합쳐 Debug.Log() 메서드에 입력한 것이다.
4.5.4 테스트하기
1. HelloCode 스크립트 실행하기
- 변경된 스크립트를 저장한다.
- 유니티 에디터로 돌아간다.
- 플레이 버튼 클릭 > 콘솔 창에서 결과 확인
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
// 캐릭터의 프로필을 변수로 만들기
string characterName = "라라";
char bloodType = 'A';
int age = 17;
float height = 168.3f;
bool isFemale = true;
// 생성한 변수를 콘솔에 출력
Debug.Log("캐릭터 이름 : " + characterName);
Debug.Log("혈액형 : " + bloodType);
Debug.Log("나이 : " + age);
Debug.Log("키 : " + height);
Debug.Log("여성인가? : " + isFemale);
}
}
4.5.5 정리하기
이 절에서 확인한 변수 타입은 C# 의 기본 내장 타입 중 일부이다. 대부분의 경우 이 정도의 기본 내장 타입만으로도 충분히 게임을 만들 수 있다.
이외에도 double, long, var 등 몇 가지 기본 내장 타입이 더 있지만 당장은 필요하지 않기 때문에 다루지 않는다.
4.6 메서드 연습하기
HelloCode 스크립트에 두 점 사이의 거리를 계산하는 메서드를 만들어본다.
4.6.1 두 점 사이의 거리
먼저 평면의 두 점 (x1, y1) 과 (x2, y2) 사이의 거리 distance 를 구하는 방법을 알아본다.
이는 피타고라스 정리를 이용하여 빗변의 길이를 계산하면 된다. 계산된 빗변의 길이가 두 점 사이의 거리와 같다.
이를 그대로 코드로 구현하면 된다.
4.6.2 GetDistance() 메서드 만들기
Start() 메서드에서 이전까지의 내용을 지우고, 두 점 사이의 거리를 계산하는 코드를 작성한다.
1. HelloCode 스크립트 수정하기
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
float distance = GetDistance(2, 2, 5, 6);
Debug.Log(distance);
}
float GetDistance(float x1, float y1, float x2, float y2)
{
float width = x2 - x1;
float height = y2 - y1;
float distance = width * width + height * height;
distance = Mathf.Sqrt(distance);
return distance;
}
}
2. HelloCode 스크립트 실행하기
- 변경된 스크립트 저장
- 유니티 에디터로 돌아가기
- 플레이 버튼 클릭 > 콘솔 창에서 결과 확인
점 (2, 2) 에서 점 (5, 6) 까지의 거리가 출력된다.
4.6.3 GetDistance() 메서드 만드는 과정
HelloCode 스크립트의 GetDistance() 메서드를 만드는 과정을 살펴본다.
1. GetDistance() 메서드 선언
GetDistance() 메서드는 입력으로 두 점의 위치를 받고 출력으로 두 점 사이의 거리를 반환한다.
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
}
float GetDistance(float x1, float y1, float x2, float y2)
{
}
}
2. 밑변과 높이 측정
입력받은 두 점으로 만든 직각삼각형의 밑변과 높이를 저장할 변수 width 와 hegiht 가 필요하다.
밑변 width 는 x2-x1, 높이는 y2-y1 로 계산한다. 따라서 GetDistance() 메서드는 다음과 같이 구성된다.
float GetDistance(float x1, float y1, float x2, float y2)
{
float width = x2 - x1;
float height = y2 - y1;
}
3. 밑변 제곱과 높이 제곱 더하기
그다음 계산된 거리를 저장할 변수 distance 를 선언한다. distance 의 값은 밑변의 제곱과 높이의 제곱을 더한 값에 제곱근을 취한 값이다.
float GetDistance(float x1, float y1, float x2, float y2)
{
float width = x2 - x1;
float height = y2 - y1;
float distance = width*width + height*height;
}
4. 제곱근 구하기
이제 width*width + height*height 의 제곱근을 구한다. 근데 제곱근을 구하는 절차는 코드 몇 줄로는 표현하기 힘들다.
그러므로 미리 만들어진 수학 메서드를 사용한다. 유니티는 수학 관련 라이브러리인 Mathf 클래스를 제공한다. Mathf 에서 제공하는 Sqrt() 메서드는 입력값의 제곱근을 계산한다.
다음과 같이 메서드를 이용하여 distance 값에 저장한 값의 제곱근을 구한다.
float GetDistance(float x1, float y1, float x2, float y2)
{
float width = x2 - x1;
float height = y2 - y1;
float distance = width*width + height*height;
distance = Mathf.Sqrt(distance);
}
5. 계산한 거리값 반환하기
GetDistance() 메서드 내부에서 계산된 결과를 return 으로 전달할 수 있게 한다.
float GetDistance(float x1, float y1, float x2, float y2)
{
float width = x2 - x1;
float height = y2 - y1;
float distance = width*width + height*height;
distance = Mathf.Sqrt(distance);
return distance;
}
6. 작성한 GetDistance() 메서드 사용하기
마지막으로 Start() 메서드에서 GetDistance() 메서드를 사용하여 점 (2,2) 와 점 (5,6) 사이의 거리를 계산하고 콘솔에 출력한다.
float distance = GetDistance(2, 2, 5, 6);
Debug.Log("(2,2)에서 (5.6)까지의 거리 : " + distance);
4.6.4 스코프
프로그램 작성 시 다음과 같이 같은 이름의 변수를 두 개 이상 선언하면 에러가 발생한다. 나중에 distance 를 사용할 때 두 distance 중 어떤 것을 사용하려는 건지 컴퓨터가 알 수 없기 때문이다.
void Start() {
float distance = 5;
float distance = 15; // 에러(같은 이름의 변수 중복 선언)
}
하지만 앞선 HelloCode 스크립트 코드에서는 다음과 같이 Start() 메서드와 GetDistance() 메서드에 같은 이름의 변수 distance 를 중복 선언했지만 에러가 나지 않았다.
이는 변수 distance 가 서로 다른 중괄호 안에 선언되어 있기 때문이다. 코드에서 중괄호 {} 는 밖에서 내부가 보이지 않게 감추는 껍데기이다. 그러므로 메서드의 중괄호 밖에서는 메서드 내부의 구현이 보이지 않는다. 메서드 내부에서 선언한 변수는 해당 메서드 내부에서만 유효하다. 이러한 유효 범위를 스코프라고 한다. 스코프는 선언된 변수나 메서드 등이 관측되는 유효 범위이다.
즉, GetDistance() 메서드에서 선언한 distance 변수의 스코프는 GetDistance() 메서드 내부에서 끝난다. 따라서 Start() 메서드는 GetDistance() 메서드 내부에 선언된 distance 를 모른다. 반대의 경우도 마찬가지이다.
서로 스코프가 겹치지 않으므로 Start() 메서드에 선언된 distance 와 GetDistance() 메서드에 선언된 distance 를 구별해서 사용할 수 있다. 따라서 중복 선언 에러가 발생하지 않는다.
4.7 제어문
여기서는 코드 흐름을 통제하는 방법을 배운다.
지금까지는 모든 코드를 위에서 아래로 실행했다. 하지만 제어문을 사용하면 조건에 따라 특정 코드의 실행 여부나 실행 순서를 변경할 수 있다. 제어문에는 분기를 결정하는 조건문(if 문) 과 수행을 여러 번 반복하는 반복문(for 문, while 문) 이 있다.
4.7.1 if 문
연애 게임에는 호감도 시스템이 있다. 호감도가 높으면 히로인과 사귀는 엔딩을 보게 되고 그렇지 않으면 주인공은 혼자 크리스마스를 보낸다. 특정 조건을 만족하면 숨겨진 엔딩이 나오기도 한다.
많은 게임이 특정 조건과 선택에 따라 게임의 분기가 나누어진다.
이때 if 문을 이용한다. if 문은 주어진 조건을 평가한다. 조건(조건식) 은 결과가 true 나 false 중 하나가 되는 변수나 표현식이다.
평가한 조건이 참(true) 이면 if 문 아래에 중괄호로 묶인 영역(if 문 블록) 을 실행한다. 반대로 조건이 거짓(false) 이면 if 문 블록을 무시하고 넘어간다. if 문은 다음과 같은 구조를 갖는다.
if (조건) {
// if 문 블록
// 조건이 참이면 이곳에 있는 코드를 실행
}
if 문을 사용하여 호감도에 따라 서로 다른 엔딩에 도달하는 예제를 만들어본다.
1. if 문으로 엔딩 나누기
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
int love = 100;
if (love > 70)
{
Debug.Log("굿엔딩: 히로인과 사귀게 되었다!");
}
if (love <= 70)
{
Debug.Log("배드엔딩: 히로인에게 차였다.");
}
}
}
4.7.2 비교 연산자
예제의 > 와 <= 는 조건식에 사용하는 비교 연산자이다. 비교 연산자는 두 값을 비교하여 참과 거짓을 결정한다.
연산자 | 읽는 법 | 결과 |
a < b | a가 b보다 작다. | a 값이 b 보다 작으면 참 |
a <= b | a가 b보다 작거나 같다. | a 값이 b 보다 작거나 같으면 참 |
a > b | a가 b보다 크다. | a 값이 b 보다 크면 참 |
a >= b | a가 b보다 크거나 같다. | a 값이 b 보다 크거나 같으면 참 |
a == b | a가 b와 같다. | a 값이 b 값과 같으면 참 |
a != b | a가 b와 다르다. | a 값이 b 값과 다르면 참 |
4.7.3 if .. else 문
if 문 블록 끝에 else 문을 덧붙여 조건이 거짓일 때 실행할 처리를 구성할 수 있다. 예를들어 직전 예제에서 Start() 메서드를 다음과 같이 바꿀 수 있다.
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
int love = 50;
if (love > 70)
{
Debug.Log("굿엔딩: 히로인과 사귀게 되었다!");
}
else
{
Debug.Log("배드엔딩: 히로인에게 차였다.");
}
}
}
love 가 50이면 조건 (love > 70) 은 거짓이 된다. 따라서 코드를 실행하면 if 문 블록이 무시되고 else 문으로 처리가 곧장 이동하여 배드엔딩이 출력된다.
4.7.4 else if 문
else if 문을 사용하면 else 문에 조건을 덧붙일 수 있다. else if 문을 추가해서 호감도에 따른 분기를 더 상세하게 나눠본다.
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
int love = 80;
if (love > 90)
{
Debug.Log("트루엔딩: 히로인과 결혼했다!");
}
else if (love > 70)
{
Debug.Log("굿엔딩: 히로인과 사귀게 되었다!");
}
else {
Debug.Log("배드엔딩: 히로인에게 차였다.");
}
}
}
변경된 love 값은 80이다. 그러므로 처음 조건문 if (love > 90) 을 만족하지 못하고 else 로 처리가 이동한다. 그런데 첫 번째 else 에는 if (love > 70) 이라는 조건이 덧붙여져 있다.
따라서 else if (love > 70) 의 코드 블록은 두 가지 조건을 동시에 만족해야 실행된다.
- love 가 90보다 크지 않다.
- love 가 70보다 크다.
만약 위 조건을 만족하지 못하면 else if 문 아래의 마지막 else 문으로 처리가 이동한다. 위 코드에서 love 값은 90보다 작고 70보다 크기 때문에 else if 문의 조건을 만족하고 굿엔딩이 출력된다.
* 조건문은 무조건 if 로 시작해야 한다. else if 문은 중간에 몇 개가 나오든지 상관 없다. 꼭 else 로 끝날 필요가 없다
4.7.5 논리 연산자
if 문에서 조건을 비교할 때 둘 이상의 조건을 함께 사용할 수 있다. 예를 들면 'a 는 30보다 크면서 동시에 b 는 50보다 커야 한다'는 조건이 필요할 수 있다.
이 외에도 'a 가 30보다 크지 않을 때' 처럼 어떤 조건을 만족하지 못하는 상태를 표현하는 조건이 필요할 수도 있다. 이때 사용하는 연산자가 논리 연산자이다.
연산자 | 읽는 법 | 결과 |
A && B | A 그리고 B | 조건 A 와 B 가 모두 참이어야 결과가 참 |
A || B | A 또는 B | 조건 A 와 B 둘 중 하나라도 참이면 결과가 참 |
!A | A 가 아니다 | A 가 거짓이면 결과는 참, A 가 참이면 결과는 거짓 |
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
// 예시 코드
int age = 11;
// 두 조건 모두 만족해야 결과가 참(if 문 진입)
if (age > 7 && age < 18)
{
Debug.Log("의무 교육을 받고 있다.");
}
// 두 조건 중 하나라도 참이면 결과가 참(if 문 진입)
if (age < 13 || age > 70)
{
Debug.Log("알울 할 수 없는 나이이다.");
}
}
}
4.7.6 for 문
for 문은 조건이 참인 동안 처리를 반복한다. for 문은 다음과 같은 구조를 가진다.
for (초기화; 조건; 갱신) {
// for 문 블록
// 조건이 참인 동안 이곳에 있는 코드를 반복 실행
}
for 문을 사용하기 위한 세 가지 요소는 다음과 같다.
- 초기화: 주로 순번(index) 이 될 변수를 선언하고 초깃값을 정한다.
- 조건: 어떤 조건에서 처리를 계속 반복할지 정한다.
- 갱신: 한 회의 처리가 끝나면 순번을 어떻게 갱신할지 정한다.
1. for 문을 사용하여 순번을 넘겨가며 처리를 반복해본다.
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
for (int i=0; i<10; i++)
{
Debug.Log(i + "번째 순번입니다.");
}
}
}
위 코드는 i 를 0부터 시작해서 9가 될 때까지 i 를 1씩 증가시키며 총 10회 Debug.Log(i + "번째 순번입니다."); 를 수행한다(* i 가 10일 때는 i < 10 의 조건을 만족하지 않으므로 for 문 내부로 진입하지 못하고 반복이 끝나는 것이다).
4.7.7 while 문
while 문은 어떤 조건을 만족하는 동안 while 문 블록을 반복 실행한다. while 문은 다음과 같은 구조를 갖는다.
while (조건) {
// while 문 블록
// 조건이 참인 동안 이곳에 있는 코드를 반복 실행
}
1. while 문 사용하기
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
int i = 0;
while (i < 10)
{
Debug.Log(i + " 번째 루프입니다.");
i++;
}
}
}
예제 코드를 실행하면 0부터 9까지 순번이 순차적으로 증가하면서 콘솔 출력이 반복 실행된다.
* 여기서 주의할 점은 while 문을 사용할 땐 직접 i 의 값을 조정해주어야 한다는 것이다.
for 문은 알아서 다음으로 넘어갈 때 설정된 값만큼 변수의 값이 증가하거나 감소하는데, while 문은 직접 변수 값을 조정해줘야 한다.
4.8 배열
배열은 나열된 여러 값을 하나의 변수로 다룰 수 있는 타입이다. 배열은 일렬로 나열된 방을 가진 건물이라고 생각할 수 있다. 방문마다 호실(순번)이 표시되어 있고, 각 방마다 값 하나가 들어갈 수 있다. 호실 번호를 알면 그 방으로 찾아가 값을 가져오거나 변경할 수 있다.
여기서 호실 번호를 인덱스(index) 라 부르고, 각 방을 배열의 요소 또는 배열의 원소라 부른다. 배열을 사용하면 같은 타입의 변수가 너무 많을 때 간단하게 묶어서 관리할 수 있다.
4.8.1 배열로 점수 관리하기
선생님이 학생의 점수를 저장하고 출력하는 예를 가정해본다. 학생의 점수를 다음과 같은 방식으로 저장하면 학생 수가 많아질수록 점수 관리가 더 힘들어진다.
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
int student1 = 100;
int student2 = 90;
int student3 = 80;
int student4 = 70;
int student5 = 60;
Debug.Log(student1);
Debug.Log(student2);
Debug.Log(student3);
Debug.Log(student4);
Debug.Log(student5);
}
}
만약 학생 수가 5명이 아니라 100명이면 다음과 같이 변수를 100개 만들어 관리해야 한다.
...
int student81;
int student82;
int student83;
int student84;
int student85;
...
이런 경우 배열을 사용해 나열된 값들을 하나의 변수로 다룰 수 있다.
1. 배열을 사용해 점수 관리하기
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
int[] students = new int[5];
students[0] = 100;
students[1] = 90;
students[2] = 80;
students[3] = 70;
students[4] = 60;
Debug.Log(students[0]);
Debug.Log(students[1]);
Debug.Log(students[2]);
Debug.Log(students[3]);
Debug.Log(students[4]);
}
}
4.8.2 코드 설명
int[] students;
int[] 처럼 타입 뒤에 [] 를 붙여 해당 타입에 대한 배열 변수를 선언한다. 여기까지는 아직 배열에 방이 몇 개인지 정하지 않은 상태이다.
int students = new int[5];
이와 같이 선언과 동시에 students 배열에 방을 5개 마련한다.
new 키워드는 어떠한 타입의 오브젝트를 새로 생성한다는 의미이다(5장에서 다룬다). 여기서는 new 를이용해 5개의 방을 가진 int 배열을 생성하여 students 변수에 할당한다.
각 방은 인덱스를 사용하여 접근할 수 있다. 특정 순번의 방(요소) 에 접근할 때는 배열 변수에 대괄호 [] 를 붙여 순번을 명시한다.
students[0] = 100;
이와 같이 첫 번째 요소에 접근하여 100이라는 값을 할당할 수 있다.
첫 번째 요소에 값을 대입하는데 students[1] 이 아닌 students[0] 으로 접근했다는 사실에 주목해야 한다. 배열의 순번은 0부터 시작한다. 따라서 students[1] 은 첫 번째가 아닌 두 번째 학생의 점수이다.
4.8.3 for 문과 함께 사용하기
배열은 for 문과 좋은 조합을 이룬다.
for 문은 순번을 넘기면서 처리를 반복한다. 배열은 순번을 사용해 요소에 접근한다. 따라서 for 문을 사용해 순번을 넘겨가며 배열의 모든 요소에 접근할 수 있다.
앞에 예제에서는 Start() 메서드에 일일이 순번을 명시하여 학생들의 점수를 출력했다.
Debug.Log(students[0]);
Debug.Log(students[1]);
Debug.Log(students[2]);
Debug.Log(students[3]);
Debug.Log(students[4]);
이 부분을 for 문을 사용해 다음과 같이 수정할 수 있다. 실행 결과는 이전과 같다.
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
using UnityEngine;
using UnityEngine.UIElements;
public class HelloCode : MonoBehaviour
{
void Start()
{
int[] students = new int[5];
students[0] = 100;
students[1] = 90;
students[2] = 80;
students[3] = 70;
students[4] = 60;
for (int i=0; i<students.Length; i++)
{
Debug.Log(students[i]);
}
}
}
배열 타입의 변수는 내부에 Length 라는 변수를 가지고 있다. Length 의 값은 배열의 길이이다. 즉, 예제에서 students.Length 의 값은 5이다.
4.9 마치며
이 장에서는 C# 의 기초 문법을 살펴보았다. 다음 장에서는 프로그램을 구성하는 클래스와 오브젝트가 무엇인지, 유니티의 게임 오브젝트를 어떻게 코드로 불러와 사용할 수 있는지 배우게 된다.