유데미 스타터스 취업부트캠프 1기

[유데미 스타터스 취업 부트캠프 1기 유니티] 3주차 학습일지

REDSHA 2022. 7. 10. 19:52

🐥 C#

본격적인 개발강의가 시작되어 앞으로 학습기록을 남길예정이다!

khwang

유니티 개요

유니티는 엔진자체는 c++로 이루어져 있으나 .NET의 FrameWork를 사용하여, C#으로 스크립트를 작성해도 컨텍스트 스위칭을 통해 제어가 가능해진다.

 

유니티에서 사용되는 파일은 크게 cs파일과 , meta파일을 주로 보게될것인데 cs는 CSharp의 줄임말, 유니티 스크립트의 개념으로 포팅이 되어있다.  실제 VisualStudio에서 C#을 써보면 main이라는 클래스가 별도로 있다.

 

유니티는 unity.dll에서 main을 사용하기 때문에 main개념이없고 이대신 monobehavior라는 개념을 사용한다.

ex) day1ex1 : MonoBehavior

이는 유니티에서 가장 기본이 되는 클래스로 유니티내에서 오브젝트에서 존재하기 위해 필요한 클래스이다.

따라서 monobehavior를 상속하지 않는 cs문서는 스크립트의 역할만하고 오브젝트에서는 역할을 수행하지 못한다.

 

monobehavior 에서 사용되는 우선순위 개념

awake() - 제일 처음에 한번 실행

 <- onEnable() 유니티에서 체크로 킬 때 이게 실행

start() 무조건 스타트에 불림

update() 프레임당 한번씩 불림

 <- TriggerEnter

<- CollisionEnter

<- onDisable 유니티에서 체크로 끌 때 이게 실행

 

프로그래밍에는 자료형, 변수, 리터럴이라는 큰 개념이 있다.

리터럴은 선언을 안하고 바로쓰는 것을 말한다.

변수는 메모리에 적재하는것, 값을 담아 두는 용도를 말한다.

자료형은 변수를 어떤 메모리로(int, char, string, sbyte, ...)로 존재하는지를 말한다.

int(자료형) _number(변수명) = 123(값);

변수선언이 이루어진 이후 코드에서 사용이 가능해지나 Debug.log(_number - 0.1)에서 0.1은 선언을 안하고 바로 사용한것처럼

별도의 선언 없이 사용가능한것이 리터럴이다.

 

프로그래밍 : 변수 => 명사 /  함수 => 동사 라고 생각가능하다.

게임을 하나 가정해보자 *FPS게임인데 총을 쏴서 적군을 맞춘다.

명사: 총, 적군 / 동사: 쏴서, 맞춘다

총, 적군을 명사, 쏴서, 맞춘다는 동사를 만들면되는 것

 

자료형에 대한 설명

변수 = 명사, 할당 (data적으로 숫자, 문자, 문자열등 여러개가 있다.) 클래스 기준으로는 Texture, GameObject class등도 있다

여기서 사용되는 unsigned는 0부터 시작하는, 양의정수를 뜻하며 sigend는 음의정수도 갖는다.(C#에서는 usigned)

signed, 자료형의 종류를 통해 다루는 비트의 범위를 세분화 해주는 이유는 최적화등의 이유가 있는데 만약 자료형을 적절히 사용하지 못하면 아래와 같은 문제가 생길 수 있다.

 

게임을 하다보면은 표현할 수 있는 수치를 넘어가는 경우가 있을 수 도 있다!

이 경우 오버플로우라고하며 다시 0부터 시작하게된다.

이게 적용되는 사례는 문명이라는 게임의 간디, 턴이 지날수록 평화도가 증가하는 로직이었는데 최대값을 넘어서 평화도가 음수로 전환되었고 따라서 평화적으로 설계된 기획의도와는 달리 매우 공격적으로 변한 사례가 있다.

 

자료형중 숫자는 정수형, 실수형이 있다.

실수 float / 정수 int long 을 통해 진행이된다.

이를 통해 수학적 계산을 할 수 있다.

이때 나오는 개념이 연산자, 실생활에서도 사용되는 사칙연산

+(더하기) -(빼기) /(나누기) *(곱하기) %(나머지연산)

 

이항연산자 예:

arithmetic = arithmetic + 9;

arithmetic = arithmetic - 4;

arithmetic = arithmetic * 2;

arithmetic = arithmetic / 4;

arithmetic = arithmetic % 3;

복합대입연산자 예:

arithmetic += 9;

arithmetic -= 4;

arithmetic *= 2;

arithmetic /= 4;

arithmetic %= 3;

arithmetic = 2

증감연산자 예;

전위 -> 다음 문이 실행전에 연산후 실행

후위 -> 문실행이후 연산

Debug.Log(++arithmetic); // 3

Debug.Log(arithmetic++); // 3

Debug.Log(arithmetic); // 4

Debug.Log(--arithmetic); // 3

Debug.Log(arithmetic--); // 3

Debug.Log(arithmetic); // 2

비교연산자 예:

== : 같냐

= , > : 크거나 같냐, 크냐

<=, <  :작거나 같냐, 작냐

output : boolean 참/거짓

 

문자형/ 문자열

c언어에서는 문자형 char을 배열로해서 포인터를 가져가서 string처럼 쓰는데

c#에서는 char의 배열을 string이라고 따로 쓴다. (C#내부클래스로 존재한다.)

char형은 선언시 ‘ ’

string형은 선언시 “ ”

string strsample = "hello world"

char chsample = 'h'

debug.log(strsample[0] == chsample);

bool형은 자료의 참 거짓을 확인하기 위한 자료형

c#에서는 true, false아니면 값을 안 받는다.

c 는 1이면 true, c#에서는 1이어도 true임을 용납 안한다

즉, if(1)이라고 하면 에러가 난다, boolean 자체에 bool b = 1; 이게 안된다는 뜻이다

 

비트비교연산

&& / &                                   || / |

if (true && true) -> 둘다 true면 true

if (false && true) -> false

if (false && false) -> 둘다 false면 true

&& : 앞조건 연산후 불일치시 종료

& : 앞조건 연산후 뒤조건 연산후 종료

 

if(false && (instantiate ("Enemy") == null))

조건문을 진행하며 Enemy를 생성하고 싶었으나 false에서 이미 불일치 판단하고 instantiate ("Enemy") == null 연산 실행안함

즉 , && || 이면 앞선 하나가 맞거나 틀리면 아예 실행조차안한다는 것

(&&, || 모두 같은 방식으로 동작)

형변환 

캐스팅(casting)

int a = 3; float b = 4f

만약 b = a;를 하고싶다면 형 변환이 일어난다.

int _number = 123; // 캐스팅(형변환)

float afterFloat = _number;

값이 인트인데 float에 들어가진다 (실수에 넣는 경우 상관없다)

double afterDouble = _number;

값이 인트인데 double에 들어가진다 (실수에 넣는 경우 상관없다)

//int afterInt = _numberWithFloat1;

문제는 float을 int에 넣게되면 오류가 발생한다.

int afterInt = (int)_numberWithFloat1;

이럴 때 명시적 형변환 (int)를 사용하면 정상적으로 원하는 바를 이룰수가있다.

(대신 뒤에 소수점 부분은 제외됨)

 

암시적 형변환과 명시적 형변환이 언제 일어나냐

int -> float은 상관이 없다 int는 정수 -> float 가수부 대입

문제는 반대 float -> int 가수부 -> 정수 없어지는 부분과 손실되는 부분이 발생

이를 명시적 형변환이라고 함

afterDouble = afterFloat;

float => double은 상관이없으나

//afterFloat = afterDouble;

double => float은 문제가 발생

이유는 double의 비트가 더많기 때문 즉, 버리는게 생기는 순간 명시적 형변환이 필요한것

afterFloat = (float)afterDouble;

객체지향에서도 캐스팅이 문제가 생김

이때는 as, is라는 c#의 구문을 이용해서 해결할 예정이다.

 

예약어!

int, sbyte, short ...

c#에서 예약어란 내가 이단어를 사용할것이라고 예약해놓은 것 즉 프로그래밍시

변수명을 int sbyte를 사용할 수 없게끔 선점한 개념이라고 생각하면된다.

 

 

배열

5명의 hp를 관리한다면?

private int _nHp1 = 10;

private int _nHp2 = 10;

private int _nHp3 = 10;

private int _nHp4 = 10;

private int _nHp5 = 10;

 

그럼 500명은?

 

private int _nHp1 = 10;

....

배열을 사용한다면 더욱 간편하게 관리가능하다

 

같은 자료형 + 이름 + 메모리상에 순차저장 (포인터개념)

배열 생성

int[5] arrayHp = new int[]

직접 코딩을 하면서 보면

private int[] _nArrayHp = new int[5] {1, 2, 3 ,4 ,5 };

중괄호를 사용하면 배열의 크기도 자동으로 정해서 생성해줌

반대로 빈상태로 선언해놓고 new int로 크기를 선언해줘도된다.

Array라는 개념은 c#닷넷프레임워크 라이브러리에서 제공하는 클래스가있는데 그중 array라는 클래스를 상속을 받는다

따라서 array 클래스안의 함수들을 사용할 수 있다.(length 등의 함수)

 

 

🐥 UNITY 간단소개

카메라

여러개를 만들시 maincamera는 하나만 존재한다, preview가보인다고 GameScene에서 보일거라고 착각하면 안됨 (clipingplanes far의 값밖에 있으면 감지하지 못한다.)

카메라의 빛을 피사체가 만들어준다.(쉐이더가 필요한 이유)

Object는 회전, 위치 등을 담은 trasform component를 가지고 있다. Inspecter 좌 상단 체크박스가 있는데 이게 active라는 개념이다. 이 Object에 있는 Component들은 active 일 때만 실행한다. 만약에 기능을 써야 하기 때문에 active는 끌수 없다면 meshRenderer같은 컴포넌트만 끄거나, 쉐이더를 투명으로 조정 해주는 식으로 조정할 수 있다.

 

Hierachy 창 : Scene에 어떤 게 있는지 알 수 있는 창

Hierachy -> Root <= cube2 - cube 유니티에서는 Root라는 개념이랑 World Position, Local Postion이라는 개념이 있다.

World Postion은 실제 Hierachy 최상단에서 가지고 있는 포지션 Scene에 무조건 상속, Scene에 있는 위치를 가져온다

Local Postion은 상대적인 위치를 다룬다, Cube2에 대해 Cube가 어디에 있는지를 다룬다

즉, Cube2는 World Postion 자식인 Cube는 LocalPostion 인것이다.

LocalPostion을 World Postion으로 보기 위해서는 코드에서 보거나 디버그 창등에서 조회가 가능하다.

 

Locking 버튼을 누르면 Inspecter창이 안 바뀌는데 이걸 이용해서 한번에 드래그로 Object나 다량의 데이터를 가져갈 수 있다(여러개의 Object를 다룰 때 유용한 방법이다).

 

Inspecor창이 아닌 코드상에서 Component에 대해 접근을 하고싶다면 GetComponent라는 기능을 쓴다.

예를 들어 Inspecter창에 MeshRenderer가 있다면 코드창에서 this.GetComponent<MeshRenderer>()를 사용해서 가져오게 된다.

이를 setactive등을 활용해서 제어, 관리 할 수 있다. getcomponentchilderen은 상위 발견된 한 개체만 가져오고, getcomponentchilderens은 여러 개를 가져올 수 있다. 이를 foreach등을 활용해서 원하는 값으로 조절이 가능하다.

 

 

🐥 객체지향

“클래스”

클래스 : 어떤 문제를 해결하기 위한 데이터를 만들기 위해 추상화

추상화 : 쓰고자 하는 내용만 뽑아내는 것

ex)

LOL 챔피언이 있다면, HP MP 각종 STATUS가 있을 수있는데 지금 게임하는데 필요없는애들은 배제하고 쓸놈들만 추려내는 것

 

메소드

public void Move() // 접근 한정자 , 반환형(리턴형), 메서드이름 매개변수
{
// Move 행위에 대한 내용
}

메소드의 형식

int a = 1;
int b = 3;
int result = a + b;
public int Add(int a, int b) // 접근한정자 반환형(리턴형) 메서드 이름 매개변수
{
	return a + b;
}

Champion 클래스내에 이렇게 코드를 작성하고

Champion champ = new Champion();
var result = champ.Add(1, 3);

public void MyLog(string logMsg)
{
int a = 1;

int b = 3;

int result = a + b;

Debug.log(result);

Debug.log(Add(a,b));
}

다른 클래스에서 실행해보면, 둘 다 4가 나옴

즉, 함수화 로 연산자를 만든 것이다 (Add라는)

함수는 input값, 실행값, 출력값이 있다.

 

그러나 아래와 같이 입력값과 출력값이 없을 수 도 있다.

public void Move()
{
GetComponent<RectTransform>().aanchoredpostion.x += 5f
}
  • 아웃풋이없고 안에서 실행만해라 라는 함수도 있다
  • input, Add함수처럼 인풋(매개변수), 아웃풋(return)값이 있는 함수도 있다.

형식

형식이없는 경우 void : 즉, return값을 생략해도 된다는 것

반대로 int 나 string 인 경우 return값이 존재해야함 (형이 일치해야함)

  • public string GetName();
  • public int GetHp();

 

위 두 경우 return값이 없으면 에러가 난다.

그리고 이 자료형이 입력된 함수의 경우 대입이 가능하다

  • string playerName = GetName();
  • int playerHp = GetHp();

이를 활용하면 메시지만 나오는 Debug.log를 현재시간이 나올 수 있게끔 커스텀이 가능하다.

public void MyLog(string LogMsg)

{

Debug,log("Time : " + DateTime.now + " / Msg:" + logMsg);

}

 

여기서 return은 void 형식이니까 생략가능!

그러나 아래와 같이 입력값과 출력값이 없을 수 도 있다.

public void Move()
{
GetComponent<RectTransform>().aanchoredpostion.x += 5f
}
  • 아웃풋이없고 안에서 실행만해라 라는 함수도 있다
  • input, Add함수처럼 인풋(매개변수), 아웃풋(return)값이 있는 함수도 있다.

형식

형식이없는 경우 void : 즉, return값을 생략해도 된다는 것

반대로 int 나 string 인 경우 return값이 존재해야함 (형이 일치해야함)

  • public string GetName();
  • public int GetHp();

위 두 경우 return값이 없으면 에러가 난다.

그리고 이 자료형이 입력된 함수의 경우 대입이 가능하다

  • string playerName = GetName();
  • int playerHp = GetHp();

이를 활용하면 메시지만 나오는 Debug.log를 현재시간이 나올 수 있게끔 커스텀이 가능하다.

public void MyLog(string LogMsg)

{

Debug,log("Time : " + DateTime.now + " / Msg:" + logMsg);

}

여기서 return은 void 형식이니까 생략가능!

 

클래스를 만들자

Class Champion
{
int Hp;
int Mp;
// 원래는 명사만 넣었는데 이제는 함수도 넣을 수 있다!
int Add(int a, int b);
string Getname();
}

그럼 실제로 어떻게 사용할 것인가

클래스도 사용자가 정의한 자료형이라고 보면 된다.(c# 자료형이랑 약간 다르기도 함)

변수선언 할때랑 똑같이 사용한다

  1. int선언할 때 어떻게 했나? int nAdd = 0;
  2. int 배열선언할때는? int[] arrAdd = new int[4];

"new"

이 new는 클래스에 대한 new 한정자라고 불림

이 new들은 기본자료형과는 다르게 참조형 자료형이다!

  • new가 안붙으면 값형 자료형
  • new가 붙으면 참조형 자료형

(callbyback, callbyref이라는 개념이 사용됨 차후 설명 예정)

그래서 class 도 new가 붙는 것이다.

Champion champ = new Champion;

여기서 Champion = Champion은 일치시켜줘야함 (int a = int b이런것처럼 자료형 일치)

champ = 변수명(인스턴스명) / Champion = 생성자

int result = champ.add(1, 3);

Debug.log(result);

이렇게 코드를 진행하면 4까지 나오는것에 대한 이해가 될 것이다.

오버로딩

아까 Add라는 함수를 만들었다

public int Add(int a, int b)();

자료형에 대한 이야기를 했었다 int는 add했을 때 add(1,3)을 쓸 수있다

그러면 add(1.3f, 2.5f)가 되나? 안된다. 따라서 추가구현을 해줘야한다

그러나 add라는 것은 내용이 겹친다.

 

굳이 int Addint(int a, int b), float Addfloat(float a, float b)처럼 여러개를 구현해도 되지만

개발자 입장에서 내용이 똑같으면 보기쉽게 합치자라는 내용이 오버로딩에 대한 내용이다.

오버로딩은 함수이름은 같고 매개변수의 타입이 다르면된다. 그걸 우리는 오버로딩이라고 한다.

 

public int Add (int type);

public float Add (float type);

그러니

public int Add (int type);

public float Add (int type);

매개변수가 둘다 int라서 오류가난다

 

따라서

public int Add (int type);

public float Add (float type);

처럼 매개변수가 다르게 설정해야한다.

이 방법은 개발편의성과 효율성에서 매우 좋은 방법이다.

한정자

public private protected

public int SetHp()
{
return _hp;
}

private void SetLvUp()
{
_v++;
}

이걸 다른 클래스 내에서 사용한다면

Champion champ = new Champion();
champ.SetHp();
champ.SetLvUp(); <=  에러발생

오류 화면을 그대로 적으면

Cannot Access Private Method "SetLvUp" Here

Private, Here가 중요한데 여기서는 사용할 수 없다는뜻

즉, Champion 클래스 내에서는 아무렇지도 않게 불러올 수 있다.

private는 본인클래스내에서만! 사용가능하다.

 

그렇다면 왜 귀찮게 한정자를 사용하냐!

게임기획적 측면에서 GetHp() HP를 보여주거나 내가 보거나 상관이 없는 경우가 많다.

그런 경우에는 외부에서 접근해도 상관이없다.

 

그러나 SetLvUp()에 대해 프로그래밍적으로 외부에서 건드릴 일 이 없는 함수는 private로 처리를 한다.

외부에서 건드릴 일이 있는것들은 public으로 한다.

 

생성자

Champion champ = new Champion();
어떤 캐릭터든 hp가 있을텐데
만약에 챔피언이 여러 마리가 있을 때

Champion champ0 = new Champion();
champ0.hp = 10;

Champion champ1 = new Champion();
champ1.hp = 10;

Champion champ2 = new Champion();
champ2.hp = 10;

이러면 코드가 길어진다 따라서 생긴게 생성자! 중요한건 public 한정자를 사용해야함!

public Champion(int hp, int mp)
{
_hp = hp;
_mp = mp;

Debug.Log("챔피언이 생성되었습니다");
}

이런식으로 새롭게 생성될 때 (instantiate)될 때 이값을 가진채로 생성됨

그럼 이걸 어떻게 사용하나?

Champion champ0 = new Champion(200, 300);
Champion champ2 = new Champion(700, 600);
Champion champ1 = new Champion(500, 600);

이것과 관련된 개념 소멸자 (없어질 때 사용)

c#에서는 GC (GarbageCollector)가 있어서 변수를 사용하지 않으면 사라지는 도구

메모리 해재될 때 불린다

~Champion()
{
Debug.log("Free");

_hp = 0;

_mp = 0;
}

다음은 Property(속성)

public Type Type(get; set;);

prop 치고 tabtab하면 자동 생성되는 생성자 폼

Champion champ0 = new Champion(-200, 300);

처럼 잘못 쳤을때를 막아줄 수 있는방법 prop!

어떻게 보면 자료형 선언이랑 똑같다

public int ~~~~;

대신 {get; set;}이라는 것을 넣는다

public int Hp
{

	get
	{ return _hp; }

	set
	{
		if(value < 0)
		{
			value = 0;
		}
		_hp = value;
	}

}

champ._hp => get에 접근한다 (get은 그값을 꼭가져와야 하므로 return을 넣는다)

champ.Hp = -100 => Set에 접근한다 (Hp는 Set이므로 꼭 value라는 변수명으로 인식시켜서 그것을 원하는 값에 대입한다.)

또다른 경우가 하나있다.

public int Hp {get; set;}

public int MP {get; private set;}

public + get, set이 되어있다면 Hp값을 가져오고 설정을 할 수 있다.

public + get, set이 되어있다면 가져오는건 자유, 설정은 불가, 이런식으로도 사용이가능하다.

여기까지가 프로퍼티에 대한 설명!

값형이랑 참조형에대한 설명

int main()
{
	int a = 20;
	TestValue(a);
	Debug.Log(a);
}

void TesatValue(int a)
{
	a = 10;
}

new라는 한정자가 붙는 경우와 안 붙는 경우가 있다.

int는 값자료형 이 a를 20이라는 값을 대입하고 testvakue()에 넘겨주었다.

a에 10을 넣고 디버그로그로 a를 출력해보면 20이 나온다!

처음에 20으로 시작해서 TestValue로 10을 넣어줬는데 왜 20이 출력이된걸까?

Testvalue에 a가 매개변수로 들어왔을 때 본체가 들어온게 아닌 값만 복사해서 들고온 것이다.

int[] a = new int[1] { 20 };
TestValue(a);
Debug.log(a[0]);
void TesatValue(int a) {a[0] = 10;}

아까는 TestValue에 넘겨줘도 20이 나왔는데 이번에는 10이 나왔다!

배열 같은경우도 참조형자료형이기 때문이다. 직접 주소를 받고(int a) 찾아가[a[0]] 본체를 바꾼 것이다(=10).

TestValuesClass Class1 = new TestValuesClass();
tvClasss.value = 20;
TestValue(tvClass1);
Debug.Log(tvClasss.value);
void TestValue(tvClasss t) {t.value = 10;}

이번에도 10이 나왔다! 이것역시 주소를 받고(tvClass) 집에 찾아가(t.value) 본체를 바꾼 것이다.(=10)


상속

상속이 무엇일까?

레거시, 부모로부터 물려받는 것 프로그램에 있어서 parent, child가 있고 유니티 내에서도 있는 개념이라 잘 알아둬야한다.

 

왜 상속을 하느냐! Champion이라는 클래스가 있는데 문제는 이 챔피언의 속성을 가진 친구가 많다.

클래스상속 선언 -> calss Ari : Champion 어디서 본 것? class temp : MonoBehavior

우리가 corounitne, debug.log등을 사용가능한것도 상위개체에 있는 것을 다 가져온 것이다.

public class Champion
{
	public virtual void Move() {Debug.log("hi")};
}

//부모가 가진 Move, Skill등을 자식등을 통해서 변화를 주겠다는 것을 암시하는 한정자

public class Akali : Champion
{
	public override void Move()
	{
		base.move();
		Debug.log("hi");
	}
}

//이를 다른 클래스에서 Akali.Move(); 하게되면 출력값은 hihi 가 될 것이다.
//부모가 가진 vitual move에 override된 move를 추가 실행했기 때문이다.

foreach(var cb in champ)
{
	ch,move();
}

public new void Move()
{
	Debug.log("hello");
}
//를 하게 될 경우 부모가 가진 정의를 날리고 아예 함수를 재정의 하게 된다.
//즉, 실행하게 되면 hi가 아닌 hello가 나오게 되는 것이다.

통상 base에 주는 함수 MPreduce

ovveride되는 함수 Effect(Anim) Effect(Char)

public : 같은 클래스 내에서 접근가능

protected : 상속받은 자들만 접근가능

private : 외부접근불가

 

  • 헤더파일과 상속(monobehavior) 차이?

모듈화 : 최대한 그룹핑(기능적인 기본 사양을 공유하는 unit기준)을 줘서 상속은 갖다 쓸 개념만 모아놓는 것

(모듈화에대한 개념) using은 거기에 있는 클래스, 함수를 갖다쓰겠다.

 

🐥 자습

내가 들은 자습시간에 수강한 강의는 유니티를 활용한 VR게임 개발이었다.

https://www.udemy.com/course/unityspaceshooter/

 

Learn Unity Games Engine & C# By Creating A VR Space Shooter

A beginners guide to Unity focusing on making a virtual reality game for the worlds leading headset. ***UPDATED 2022!***

www.udemy.com

이 강의는 필요한 에셋을 무료로 제공하고, 이를 활용해서 시각적으로도 아름다운 게임을 직접 만들 수 있었다.

만들고 있는 게임

🐥 2주차 총평

 
내가 가졌던 궁금증을 확실하게 해결 할 수 있었던 시간이었다.
확실하게 알지 못했던 c#, 유니티에 대해 개요, 역사부터 객체지향이 지닌 특징까지 쉽고 다양한 사례를 들어 설명해주시고 추가 질문에대한 시간도 많이 내주셔서 내가 평소에 궁금했던점과 알고있던것보다 더 자세하게 알 수 있었던 시간이었다. 더해서 강사님이 과제를 재미있게 할 수 있도록 게임을 접목한 과제를 내주셨고(정상적으로 스크립트를 작성하고 유니티에서 재생을 누르면 특정한 화면이 나온다) 보너스 과제들도 추가해주셨다(정답을 맞추면 스타벅스 쿠폰이 따른다!) 덕분에 지루하지않게 공부를 할 수 있었던 한주였다

 

 

 

 


유데미코리아 바로가기 : https://bit.ly/3b8JGeD

 

Udemy Korea - 실용적인 온라인 강의, 글로벌 전문가에게 배워보세요. | Udemy Korea

유데미코리아 AI, 파이썬, 리엑트, 자바, 노션, 디자인, UI, UIX, 기획 등 전문가의 온라인 강의를 제공하고 있습니다.

www.udemykorea.com

본 포스팅은 유데미-웅진씽크빅 취업 부트캠프 유니티 1기 과정 후기로 작성되었습니다.