Unity Chapter 3. C# 프로그래밍 [기본]
카테고리: Unity Lesson 1
태그: C Sharp Unity Game Engine
인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!
Chapter 3. C# 프로그래밍 : 기본
- Microsoft C# 공식문서 보러가기
- 스크립트를 실행시키려면 스크립트를 적용할 hierarchy창의 오브젝트에 스크립트를 드래그 앤 드롭 해준 후 플레이 버튼을 누른다
변수와 함수의 이해
- 🚓 변수(variable) 🚓
- 값이 할당되는 이름
- 그 값을 나중에 다시 쓰기 위해 이름을 붙임.
- 그 자체로 값이 아닌 값을 가리키는 이름표
int gold = 1000
일 때 gold 변수 이름만 기억하면 1000 값을 찾아올 수 있다.
- 데이터 타입을 써주어야 한다. ex) int, float, bool, string
- 변수에 할당된 데이터는 게임 도중(런타임)에 얼마든지 접근하여 수정이 가능하다.
- 값이 할당되는 이름
- 🚓 함수(Funcion) 🚓
- 미리 정해진 동작을 수행하는 코드 묶음
- 공격 기능을 수행하는 코드를 미리 만들어두면 이 기능을 필요로 하는 여러 곳에서 재사용할 수 있다. 이 코드가 바로 함수가 되는 것.
// 몬스터를 공격할 때 수행할 기능 void Attack(Monster target, int damage, int point) { playAnimation(); playSound(); target.hp = target.hp - damage; // 몬스터의 체력 exp = exp + point; // 경험치 } Monster ork; // 몬스터 종류 > 오크 Attack(ork, 5, 30); // 오크는 공격할 때마다 체력이 5밖에 안줄어 들지만 경험치를 많이 준다. Monster goblin; // 몬스터 종류 > 고블린 Attack(goblin, 20, 5); // 고블린은 공격할 때마다 체력이 20이나 줄어 쉽지만 경험치를 5로 적게 준다.
- 함수의
Return 반환값
- 함수 내부에서 일어나는 일은 밖에서 알 수가 없다.
- 함수를 사용하는 쪽에다가 결과값을 꺼내주는 것이 바로
리턴 값
int num = getNumber();
- 함수는 리턴한 직후 종료 됨.
- 반환하는 값의 데이터 타입을 앞에 명시해주어야 한다.
int getNumber() {...}
- 리턴 값이 없을땐
void
. (굳이 결과를 꺼내주지 않아도 된다.)
- 리턴 값이 없을땐
- 미리 정해진 동작을 수행하는 코드 묶음
콘솔 출력 + C# 기본 변수
C# 스크립트 만들기
- Project 창에서 Create - C# Script
- 새 스크립트를 만든 후 열어준다.
-
스크립트 또한 컴포넌트다. 사용자 지정 컴포넌트나 마찬가지.
-
스크립트 생성하면 있는 디폴트 코드
using System.Collections; using System.Collections.Generic; using UnityEngine; public class NewBehaviourScript : MonoBehaviour { void Start() { } void Update() { } }
using UnityEngine;
- 유니티 엔진에서 제공하는 코드를 사용할 것.
public class NewBehaviourScript : MonoBehaviour
- 모든 컴포넌트들은 MonoBehaviour을 상속 받아야 유니티로부터 메세지를 받을 수 있다.
void Start()
- 게임이 실행될 때 유니티는 가장 먼저 Start 메세지부터 브로드캐스팅 한다.
- 게임이 실행되자마자 이 컴포넌트가 실행할 내용.
- 게임 시작될 때 딱 한번 실행 됨.
- 따라서 컴포넌트를 초기화하는 내용이 주로 들어간다.
void Update()
- 매 프레임마다 계속해서 실행되는 부분
- 게임 내내 계~속 실행되야 하는 컴포넌트라면 이 부분을 작성해야 함.
콘솔 출력
Debug.Log
를 사용한다. using한 UnityEngine에 들어있다. 유니티 인터페이스의 Console창에 문자열이 출력된다.- Unity
Console창
에서Clear on Play
하면 이전에 쌓여있던 과거의 로그들이 정리된다. - Unity 에서 플레이 버튼을 누르면
Debug.Log
로 입력한 문자열들이 콘솔에 출력될 것이다!
Debug.Log("유니티의 Console 창에 출력할 내용");
Debug.Log("Hello, World!");
스크립트를 오브젝트에 붙이기
스크립트를 드래그 해서 Hierarchy창의 해당 오브젝트에, 혹은 오브젝트 클릭 후 Inspector 창의 오브젝트에 대고 드롭 해주면 된다. 스크립트도 컴포넌트기 때문에 이렇게 오브젝트에 붙여주면 스크립트 내용대로 오브젝트가 기능하게 된다.
C# 기본 변수
void Start()
{
int age = 23;
int money = -1000;
Debug.Log(age);
Debug.Log(money);
float height = 169.1234567f; // float 4byte. 소수저
double pi = 3.14159265359;
bool isBoy = true;
bool isGril = false;
char grade = 'A'; // 한개의 문자
string movieTitle = "Love Letter"; // 여러개의 문자
Debug.Log("내 나이는! " + age); // "내 나이는! 23"
Debug.Log("내가 가진 돈은!" + money); // "내가 가진 돈은! -1000"
Debug.Log("원주율은! " + pi); // "원주율은! 3.14159265359"
}
-
Start() 함수 안에 구현했으니 이 스크립트(컴포넌트)가 붙어진 오브젝트들은 플레이 버튼을 눌러 시작하자마자 1회 실행되어 콘솔창에 Debug.Log 내용을 출력할 것
-
소숫점 가진 실수
float
- 값의 끝에
f
를 붙여주어 double과 구분한다. - 4byte
- 소숫점 아래 7자리까지만 정확하다. 그 이상은 근삿값 처리.
- 값의 끝에
double
- 8byte
- 소숫점 아래 15자리까지 정확하다. 그 이상은 근삿값 처리.
- 8byte
- 게임은 정확성보다 성능이 더 중요하므로 용량을 더 적게 차지하는 float을 주로 사용한다.
var 키워드
var myName = "So Hyun";
// string var myName = "So Hyun"; 와 같다.
var
는 마치 auto
같은 것. 대입된 값의 자료형으로 데이터 타입을 추론한다. 추론하여 자동으로 타입을 결정해준다.
사칙연산 + 복합연산자
+
,-
,*
,/
,%
++num
,num++
,--num
,num--
a += 2
는a = a + 2
와 같다.
함수 + 스코프
함수
: 미리 만들어 놓고 필요할 때마다 재사용할 수 있는 코드 묶음
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
void Start()
{
float sizeOfCircle = 30f;
float radius = GetRadius(sizeOfCircle); // 함수 사용하여 계산
Debug.Log("원의 사이즈: " + sizeOfCircle + " 원의 반지름: " + radius);
}
float GetRadius(float size)
{
float pi = 3.14f;
float tmp = size/pi;
float radius = Mathf.Sqrt(tmp);
return radius; // float 리턴
}
}
Mathf
UnityEngine
에서 제공하는- 수학과 관련된 함수들이 미리 들어있는 함수 집합
- Mathf.Sqrt(tmp);
- tmp의 제곱근을 구해주는 Mathf 내부의 함수
- GetRadius 함수 내부의
float radius
와 Start 함수 내부의float radius
는 이름만 같을 뿐 별개의 함수다. - 둘이 서로 scope가 다르기 때문
형변환 + 조건문
형변환
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
void Start()
{
int height = 170;
float heightDetail = 170.3f;
heightDetail = height; // 에러 x
height = heightDetail; // 에러 o
height = (int)heightDetail; // 잃어버리는 정보 감수하고 직접 명시해 형변환
}
}
- 암묵적 형변환
heightDetail = height;
- 에러 X
- 잃어버리는 정보가 없으면 컴파일러가 자동 형변환 해준다
- 170을 float에 넣는건 정보 손실될게 딱히 없음.
height = heightDetail;
- 에러 O
- heightDetail은 소수점이 있기 때문에 이를 int인 height에 넣으려고 하면 소수점을 버려야 하는 정보 손실이 일어나기 떄문
- 따라서 이와 같이 정보손실이 일어나는 경우에는 자동 형변환을 해주지 않는다.
- 프로그래머가 직접 명시적 형변환을 해주어야 함
- 명시적 형변환
height = (int)heightDetail;
- 소수점 잃어버리는 것을 감수하더라도 int에 넣겠다는 명시적 형변환
- 170.3f 에서 0.3 소수점이 잘리고 170 값만 height에 대입된다.
조건문
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
void Start()
{
bool isGirl = true;
if(isGirl != false)
{
Debug.Log("나는 여자야"); // 얘 출력
}
else
{
Debug.Log("나는 남자야");
}
isGirl = false;
if (isGirl == true)
{
Debug.Log("나는 여자야");
}
else
{
Debug.Log("나는 남자야");
Debug.Log("나는 남자야"); // 얘 출력
}
int age = 17;
}
}
- if 조건문에는 확실하게
ture
,false
bool 값이 나올 수 있는 조건식이 들어가야 한다. ==
,!=
,>
,<
,>=
,<=
- and :
&&
(이항. 두 조건식을 엮음) - or :
||
(이항) - not :
!
(단항) else if
:if
조건문이 false임과 동시에 또 새로운 조건문을 따짐else
:if
문도,else if
문도 다 false일 때 해당됨
bool타입을 정수 1, 0 으로 처리했던 C/C++
과는 다르게 C#에서는 bool값을 `True`, `False`로 출력한다.
분기문 + 반복문
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
void Start()
{
int year = 2017;
// 분기문 switch -> if-else문과 일맥상통 한다
switch(year) // 기준이 될 변수
{
case 2012: // year = 2012인 경우
Debug.Log("레미제라블");
break;
case 2016: // year = 2016인 경우
Debug.Log("곡성");
break;
case 2017: // year = 2017인 경우
Debug.Log("트랜스포머5");
break;
default: // 어떠한 케이스에도 해당되지 않을 때.
Debug.Log("년도가 해당사항 없음");
break;
}
// 반복문
for (int i = 0; i < 10; i = i + 2) // 1. for i = 0, 2, 4, 6, 8 이렇게 5번 반복
{
Debug.Log("현재 순번: " + i);
}
Debug.Log("루프 끝");
bool isShot = false;
int index = 0;
int luckyNumber = 4;
while (isShot == false) // 2. while
{
index = index + 1;
if (index == luckyNumber)
{
Debug.Log("총알에 맞았다!");
isShot = true;
}
else
{
Debug.Log("총알에 맞지 않았다.");
}
}
do // 3. do-while. 처음 한번은 무조건 실행한다. while문은 조건문에 처음부터 맞지 않으면 아예 실행되지 않을 수도 있다.
{
Debug.Log("Do-while");
} while (isShot == false);
}
}
배열
new
키워드를 이용하여 배열을 생성한다.- C++와는 다르게 배열 이름의 값을 바꿀 수 있나보다.
int[] scores = new int[10]; scores = new int[20]; // 이렇게 ! C++에서는 안되는데..
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
void Start()
{
int[] scores = new int[10];
scores[0] = 90;
scores[1] = 45;
scores[2] = 60;
scores[3] = 70;
scores[4] = 56;
scores[5] = 80;
scores[6] = 90;
scores[7] = 100;
scores[8] = 45;
scores[9] = 14;
// scores[10] = 30; 이건 out of index
for(int i=0; i<10; i++)
{
Debug.Log("학생 " + i + "번째의 점수 "+ scores[i]);
}
scores = new int[20]; // 이렇게 하면 기존 원소들 싹~~~ 날라가고 새로운 20개 크기의 빈 배열이 할당됨.
}
}
클래스와 오브젝트 ⭐
개념
사과의 이상적이고 추상적인 모습(= 클래스
)이 있다. 살짝 하트 모양이고 빨갛고 초록 꼭다리를 가지고 있는 등등 이런 모습. 그러나 현실 세계의 사과들(오브젝트
)은 이 이상적인 모습에서 조금씩 다 다르다. 좀 멍든 사과라던가 초록색이라던가.. 그러나 대략적으로 사과의 추상적인 모습과 비교하여 이것들이 모두 사과임을 알 수 있다.
- 프로그래밍에서의 추상화(= `클래스`)와 구체화(= `오브젝트`)
- 클래스를 만들어 추상화를 해둔 후 오브젝트로 찍어내어 구체화 시키는 방식.
- 클래스는 붕어빵틀, 실제로 현실 세계에서 만들어진 붕어빵들은 오브젝트라고 생각하면 된다.
클래스
- 이상적인 세계에서 존재하는 원형, 단 하나의 기준
- 실제로는 존재하지 않는다.
- 가장 중요한 특성만 개략적으로 알려준다.
- 구체적인 수치가 없다.
- ex)
Dog
라는 클래스- 개의 추상적인 특징들을 묶어 담고 있음
오브젝트
- 실제 존재하는 것들
- 자기 자신을 스스로 챙길 수 있다.
- 하나의 온전한 단위
- 하나의 원본에서 파생 되어도, 서로 구분 가능하다.
- ex)
Dog
라는 클래스로뽀삐
,몽자
,식빵이
라는 이름을 가진 각각의 실제 강아지들을 만든다. 이 세 마리의 강아지들은 공통적으로 Dog에서 파생됐지만 서로 연관이 없는 독립적인 개체이며 서로 구분된다..
코드
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Animal // Animal 클래스
{
public string name; // 동물은 이름을 가지고 있다. public을 꼭 붙어주어야 외부에서도 보고 사용할 수 있다. (디폴트 private)
public string sound; // 동물은 소리를 가지고 있다. public을 꼭 붙어주어야 외부에서도 보고 사용할 수 있다. (디폴트 private)
public float weight; // 동물은 무게를 가지고 있다. public을 꼭 붙어주어야 외부에서도 보고 사용할 수 있다. (디폴트 private)
}
public class HelloClass : MonoBehaviour
{
void Start()
{
/* 각각의 동물 객체마다 name, sound, weight 속성을 가지고 있지만 값은 전부 다 다름.*/
/*객체들은 서로 구분되고 독립적인 존재들*/
Animal jack = new Animal(); // Animal 객체 "jack"을 찍어내어 탄생시킴
jack.name = "JACK"; // name이 public이 아니였다면 에러
jack.sound = "Bark"; // sound가 public이 아니였다면 에러
jack.weight = 4.5f; // weight이 public이 아니였다면 에러
Animal annie = new Animal(); // Animal 객체 "annie"을 찍어내어 탄생시킴
annie.name = "ANNIE";
annie.sound = "Wee";
annie.weight = 0.8f;
Animal nate = new Animal(); // Animal 객체 "nate"을 찍어내어 탄생시킴
nate.name = "NATE";
nate.sound = "NYaa";
nate.weight = 1.2f;
nate = jack; // jack이 가지고 있는 정보를 nate에 덮어 씌운다.
nate.name = "JIMMY"; // ⭐ jack의 이름까지도 바뀐다! ⭐
Debug.Log(jack.name);
Debug.Log(nate.name);
}
}
변수는 값을 가리키는 이름표임을 기억하자.
nate = jack; // jack이 가지고 있는 정보를 nate에 덮어 씌운다.
nate.name = "JIMMY"; // ⭐ jack의 이름까지도 바뀐다! ⭐
- 객체들은 이름이 없는데 우리가 변수로 이름표를 붙여준 것 뿐이다.
nate = jack
- jack이 가지고 있는 정보를 nate에 덮어 씌운다.
- 이제 두 변수 nate와 jack가 기존의 jack이 혼자 가리키던 그 동일한 인스턴스를 가리키게 된다.
- 기존에 jack이 가리키던 인스턴스는 이름표(변수)가 2개가 됨. jack, nate 두개로 접근 가능해짐
- 즉 이제 하나의 인스턴스를 두 변수가 가리키게 됨.
- 기존에 nate가 가리키던 인스턴스는 이름표(변수)가 없어져 접근할 방법이 없는 미아(가비지)가 되버린다.
C++
에서는 이 같은 가비지들이 메모리 누수 문제를 일으키지 않도록 프로그래머가 직접 관리해야 했지만Java와 C#
에서는가비지 콜렉터
가 알아서 작동하여 이런 가비지들을 정리하고 메모리 해제시켜 주므로 프로그래머가 신경 쓸 필요가 없다.
- 변수는 3개지만 오브젝트는 2개인 현상이 되어버림
nate.name = "JIMMY"
- 따라서 jack.name 또한 “JIMMY”가 된다.
Call by Reference
기존에 미리 만들어져 존재하는 것을 가져와서 쓴다는 개념.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class jump : MonoBehaviour
{
public Rigidbody rb;
void Start()
{
rb.AddForce(0, 1000, 0);
}
void Update()
{
}
}
public Rigidbody rb;
변수
는 오브젝트 그 자체가 아니라 오브젝트를 가리키는 이름표라는 것을 기억하자.public
으로 해놓으면 유니티 인터페이스에서 Rigidbody 컴포넌트 객체를 드래그 앤 드롭할 수 있는 슬롯이 열린다.- 이 슬롯에 오브젝트에 실제로 현재 붙어있는 Rigidbody 컴포넌트를 드래그 앤 드롭하면 이제 rb라는 이름표가 가리키는 이 컴포넌트에 접근할 수 있게 된다.
rb.AddForce(0, 1000, 0);
- 이제 rb 변수(이름표)로 실존하는 이 컴포넌트를 접근할 수 있다.
rb
라는 이름으로 슬롯에 드래그 앤 드롭한 실존하는 이 컴포넌트에 접근하여 AddForce 기능을 하게끔 함.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기