- 간단하게 프로그래밍을 할 수 있는 분들
- 변수(int, float, double), 조건문(if), 반복문(while, for), 함수(메서드)를 이해하고 있다
프로그래밍을 처음하는 분들은 힘들 수도 있습니다
- 유니티의 기본적인 조작을 할 수 있다
- 유니티에서
C#
을 사용해서 스크립트를 만들고 사용할 수 있다 - 유니티 물리엔진을 이해하고 사용할 수 있다
- 강체(Rigidbody): Velocity, Force, Mass, ...
- 충돌: Collision, Collider
- 기초적인 UI를 만들 수 있다
- 버튼 클릭
- 씬 이동
- HP바
- ...
- 게임의 데이터를 저장하고 불러올 수 있다
- 유니티를 하면서 발생하는 오류들을 해결할 수 있다
- 구글링, Stackoverflow, Manual, ... 검색 많이 해보기
일주일에 한번(시험기간 제외) / 총 7번 수업
- 2시간 수업
- 1시간 30분: 유니티 개념 강의
- 20분: 실습
- 10분: 휴식 및 숙제 체크
- 숙제
- 매주 10분짜리 숙제가 있을 수 있다
- 숙제는 남아서 같이하고 가셔도 됩니다!
"Visual Studio Community"와 "Unity 2018.. LTS"가 설치된 노트북
- 유니티 소개
- 유니티 실행
- 유니티 기본 용어
- 레이아웃
- 뷰
- 조작법
- 카메라
- 빈 게임 오브젝트
- 기본 오브젝트들
숙제: 책상 만들기
- Material을 사용해보자
- 스크립트 개념
- Start()
- Update()
- 이벤트 함수들의 실행 순서
- Quiz. Class vs Instance
- public인 변수들
- Debug.Log()
- 입력 처리
- Input.GetKey(키코드)
- Key, KeyDown, KeyUp
- Input.GetMouseButton(0) // 0: 좌클릭, 1: 우클릭, 2: 중간(휠) 클릭
- 입력 받아서 플레이어 움직이기
- Input.GetKey(키코드)
- Vector3
- 내적(dot)/외적(cross)
- 사칙연산
- struct 주의점
- Quiz. struct vs class, 복사할 때 value type과 reference type의 동작 방식
transform.position.x = 2.3f;
가 사실은transform.GetPosition().x = 2.3f;
였다! 그래서 미리 컴파일러가 막아준다
- Translate - self vs global
- 처음보는 함수 ➡ 유니티 매뉴얼
- Frame과 deltaTime
- Framerate Independent하게 만들자!
Update() { "오른쪽으로 한칸 움직여라" }
는 컴퓨터 성능과 비례한다- 모든 물체를 시간과 비례하도록 움직이게 해야한다
- 물체의 이동과 회전
- 에셋스토어
숙제: Flappy Bird의 새의 점프 구현하기
- 계속 점프가 가능해야 함
- 컴퓨터 성능과 무관해야 함
transform.position
만 이용해서 점프 구현
결과: https://iamgroooooot.github.io/cien-unity-2020/jump-using-position/
using UnityEngine;
public class CustomJump : MonoBehaviour
{
public float jumpPower = 5f; // 점프 Force
private const float GRAVITY = -9.81f; // 중력 가속도
private Vector3 vel; // 현재 속도
private Vector3 acc; // 현재 가속도
private void Start()
{
// 속도 초기화
vel = Vector3.zero;
// 중력 설정
acc = new Vector3(0, GRAVITY, 0);
}
void Update()
{
// 스페이스바 누르면 점프!
if (Input.GetKeyDown(KeyCode.Space))
{
vel.y = jumpPower;
}
// 가속도 적용
vel += acc * Time.deltaTime;
// 땅으로 떨어지거나 하늘로 날아가지 않습니다
if(transform.position.y < -0.5f)
{
if (vel.y < 0) vel = Vector3.zero;
}
else if(transform.position.y > 5)
{
if (vel.y > 0) vel = Vector3.zero;
}
// 속도만큼 이동시킨다
transform.Translate(vel * Time.deltaTime);
}
}
- 물리엔진이란?
- 연산 타이밍?
FixedUpdate()
- 강체(Rigidbody) 컴포넌트
- 강체 - 물리에서의 정의와 유니티에서의 정의)
- 물리엔진을 사용해보자
rb = GetComponent<RigidBody>()
rb.velocity = new Vector3(0, 1, 0)
rb.AddForce(0, 500, 0)
- 충돌
- Collider
- Collision / Trigger
- Enter, Stay, Exit
숙제: 오늘 배운
Collider
/Trigger
/Collision
사용한 아무런 유니티 프로젝트 만들기
제출: 위의 유니티 프로젝트를 GitHub에 올린 후 링크 여기 톡방에 투척!
- GitHub에서 새로운 저장소(Repository)를 만든다.
- 저장소의 이름은 자유롭게 설정
- Description해도 되고 안해도 됨 (간략한 저장소 설명)
- Public으로 설정
- Initialize with README는 해도 되고 안해도 됨 (README는 프로젝트를 설명하는 파일임)
.gitignore
를 unity로 설정- Add License해도 되고 안해도 됨 (내 저장소 저작권 설정하는 부분, MIT가 책임질 필요도 없고 무난함)
- 내 컴퓨터로
Clone
한다 - 작업한 유니티 파일을 전부
Clone
한 저장소로 옮긴다 Stage
에 모두 올린다- 적절한 메시지와 함께
Commit
하기 Push
한다!- GitHub에 들오가서 잘올라갔는지 확인한다
- 링크 톡방에 투척한다
- 프리팹(Prefab)
- 미사일 프리팹 만들기
- 미사일 생성하기
Instantiate(복제대상)
- 게임오브젝트 찾기
- Find - name, tag, type
- 코루틴을 위해 알아야 할 C# 문법
- 람다식, IEnumerator, yield
- 코루틴(Coroutine)이란?
- 예제) Update Timer / Coroutine Timer
미사일 쏘는 적기 만들기(시간이 없어서 못했음)플레이어가 적기의 미사일에 맞으면 "아야" 출력
숙제. https://github.com/CIEN-Club/workshop-guestbook의 README.md를 읽고 저장소에 Push를 해서 방명록 남기기
강의 때 설명한 코드들
강의 때 설명용으로 보여드린 코드들입니다. 강의자료에는 없기 때문에 따로 올립니다. 복습용으로 사용하세용!
(1) 좌클릭할 때 Instantiate하기
using UnityEngine;
public class SpawnManager : MonoBehaviour
{
public GameObject missilePrefab;
GameObject temp;
int count = 0;
void Update()
{
// "Fire1"은 좌클릭 말고도 다른 키(터치, 조이스특, ...)도 인식함.
if (Input.GetButtonDown("Fire1"))
{
temp = Instantiate(missilePrefab);
temp.name = "Missile" + count++;
temp.transform.position = Vector3.zero;
}
}
}
(2) Find By
- Name: 이름을 바꾸지 못하게 된다
target = GameObject.Find("Target");
target.transform.position = Vector3.zero;
- Tag: 코드에 노출이 잘 안된다
target = GameObject.FindGameObjectWithTag("Target");
target.transform.position = Vector3.zero;
- Type: 매우 느리다
target = GameObject.FindObjectOfType<TargetCtrl>().gameObject;
target.transform.position = Vector3.zero;
- Drag and Drop, Singleton, ...
(3) Lambda
와 Func
/Action
Func<T1, T2, ..., TResult>
= 값을 반환하는 Lambda식
// 값을 반환하는 람다식 대리자 형식으로 변환해서 호출해보기
Func<int, int, int> addition = (x, y) => x + y;
Debug.Log(addition(1, 2));
Func<int, int, bool> isSame = (x, y) => x == y;
Debug.Log(isSame(1, 2));
Action<T1, T2, ...>
= 값을 반환하지 않는 Lambda식
// 값을 반환하지 않는 람다식 대리자 형식으로 변환해서 호출해보기
Action helloWorld = () => Debug.Log("Hello, World!");
helloWorld();
Action<string> helloName = name => Debug.Log("Hello, " + name);
helloName("Groot");
- 람다식을 사용하는 경우 예시 (코루틴에서
WaitWhile
,WaitUntil
)
public int hp;
void Start()
{
hp = 100;
StartCoroutine(DeadOrAlive());
}
IEnumerator DeadOrAlive()
{
Debug.Log("Waiting for player to be dead");
yield return new WaitUntil(() => hp <= 0);
Debug.Log("Player is dead");
}
(4) IEnumerator 열거자란? Current
, MoveNext()
IEnumerator GiveMeNumber()
{
yield return 1;
yield return 2;
yield return 3;
}
void Start()
{
IEnumerator enumerator = GiveMeNumber();
for(int i = 0; i<4; i++)
{
Debug.Log(i + "번쩨의 Current: " + enumerator.Current);
Debug.Log(i + "번쩨의 MoveNext의 반환값: " + enumerator.MoveNext());
}
}
(5) 일반적인 함수 vs 코루틴
- 일반적인 함수
using UnityEngine;
public class PrintTest : MonoBehaviour
{
void Print1()
{
Debug.Log("1-1");
Debug.Log("1-2");
Debug.Log("1-3");
}
void Print2()
{
Debug.Log("2-1");
Debug.Log("2-2");
Debug.Log("2-3");
}
private void Start()
{
Print1();
Print2();
}
}
- 코루틴
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PrintTest : MonoBehaviour
{
IEnumerator Print3()
{
Debug.Log("1-1");
yield return null;
Debug.Log("1-2");
yield return null;
Debug.Log("1-3");
yield return null;
}
IEnumerator Print4()
{
Debug.Log("2-1");
yield return null;
Debug.Log("2-2");
yield return null;
Debug.Log("2-3");
yield return null;
}
private void OnEnable()
{
StartCoroutine(Print3());
StartCoroutine(Print4());
}
}
(6) 업데이트 타이머 vs 코루틴 타이머
- 업데이트 타이머
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TimerTest : MonoBehaviour
{
float time = 0;
float limitTime = 3f;
private void Update()
{
time += Time.deltaTime;
if(time >= limitTime)
{
Debug.Log(limitTime + " seconds passed.");
time = 0;
}
// 업데이트에서 호출할 다른 함수들
}
}
- 코루틴 타이머
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TimerTest : MonoBehaviour
{
private void OnEnable()
{
StartCoroutine(TimerEvent(3));
}
IEnumerator TimerEvent(float limitTime)
{
while (true)
{
yield return new WaitForSeconds(limitTime);
Debug.Log(limitTime + " seconds passed.");
}
}
}
🌜공지🌛
다음주(5/26) - 5주차 스터디 없습니다
제가 다음주부터 부담되는 과제+팀플+퀴즈가 하나씩 있어서 강의가 불가능합니다. 강의자료 없이 그냥 할까도 생각했는데 강의할 시간도 없을 것 같습니다ㅠㅠㅠ.
원래는 저번 주에 배운 코루틴과 Overriding같은 객체지향적인 코드에 익숙해지며 간단한 AI를 여러개 만들어보려고 했습니다. AI는 FSM(유한상태기계)으로 같이 설계해보려고 했으나 못할 것 같습니당..ㅠㅠ 암튼 간단한 AI를 제작할 때 FSM이 찰떡? 유용합니다. 나중에 간단하게 적 AI를 구현하려고 하는데 막힐 때가 있을 수 있습니다. 그때 FSM을 유튜브같은 것으로 공부하시면 도움될 겁니당. 일단 예시 코드를 아주 간단하게 올려드리겠습니다. 처음 보시면 이해하기 어려울 수도 있습니다.
죄송합니다.
- 적기 추가 및 이동
- 적 이동 패턴 처리
- 코루틴, Update, Invoke 비교해보기
- 피격 처리
- Tag
- 적기 생성 매니저 구현
- 바로 앞에서 멈추는 유도탄 만들기
- 랜덤으로 생성 됨
- 3초 후 자동으로 Destroy()
- 주인공을 추적
바로 앞에서 멈추는 유도탄 만들기
- 유니티의 UI란?
- UI를 위한 개념들
- 화면 해상도
- Canvas
- Anchor
- Render Mode (Overlay, Camera, World)
- 텍스트
- 버튼
- 이미지
- Fill을 이용한 다양한 활용법
- 이미지 버튼
- World 게임오브젝트의 마우스 감지
- 씬(Scene) 이동
- 점수창 만들기
강의 때 설명한 코드들
강의 때 설명용으로 보여드린 코드들입니다.
- 텍스트 바꾸기
using UnityEngine;
using UnityEngine.UI;
public class TextCtrl : MonoBehaviour
{
private Text targetText;
public int score = 99;
// Start is called before the first frame update
void Start()
{
targetText = GameObject.Find("MyTargetText").GetComponent<Text>();
if (targetText != null)
targetText.text = "점수: " + score;
else
Debug.Log("GameObject 체크 ㄱㄱ");
}
}
- 버튼 클릭
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class OnClickFunctions : MonoBehaviour {
int score = 0;
UnityEngine.UI.Text textInstance = null;
private void Start()
{
textInstance = GameObject.Find("MyTargetText").GetComponent<UnityEngine.UI.Text>();
}
public void OnClickButtonTest()
{
Debug.Log("버튼을 눌렀습니다.");
}
public void OnClickCat()
{
Debug.Log("고양이을 눌렀습니다.");
textInstance.text = "고양이 누른 횟수: " + ++score;
}
public void MoveToSampleScene()
{
SceneManager.LoadScene("Scenes/SampleScene");
}
}
- 이미지 Fill 활용하기: Cool Time
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CoolTime : MonoBehaviour
{
private Image coolTimeImage;
// Start is called before the first frame update
void Start()
{
coolTimeImage = GameObject.Find("CoolTime").GetComponent<Image>();
StartCoroutine(ShowCoolTime(coolTimeImage, 4f));
}
IEnumerator ShowCoolTime(Image img, float coolTime)
{
float delta = 1f / coolTime;
img.fillAmount = 1f;
while (img.fillAmount >= 0)
{
yield return new WaitForSeconds(1);
img.fillAmount -= delta;
}
}
}
- World의 게임오브젝트 마우스 클릭
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OnCubeClick : MonoBehaviour {
// Collider 필요!
private void OnMouseDown()
{
Destroy(gameObject);
}
}
- 세이브 파일
- Key로 사용하기 좋은 Enum Type
- PlayerPref
- Save
- Load
- Instance 복구하기
- Json, CSV, ...
- 빌드
- 안드로이드/iOS App 빌드
- PC 빌드
- Web 빌드
- GitHub Page로 공유하기
강의 때 설명한 코드들
강의 때 설명용으로 보여드린 코드들입니다.
- Enum(열거형)
public enum VariableType
{
eUserName = 0,
eUserScore,
// …
}
- Enum을 key로하는 PlayerPref
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameVariableManager : MonoBehaviour
{
public static GameVariableManager instance = null; // for singleton
private void Awake()
{
instance = this;
}
/// <summary>
/// 타입 이넘값으로 값을 저장해준다.
/// string value 전용
/// </summary>
public void SaveVariable(VariableType type, string value)
{
// 이넘값 변환
string key = type.ToString();
// 변환한 값을 그대로 넣어주자.
PlayerPrefs.SetString(key, value);
}
/// <summary>
/// 타입 이넘값으로 값을 저장해준다.
/// Int value 전용
/// </summary>
public void SaveVariable(VariableType type, int value)
{
// 이넘값 변환
string key = type.ToString();
// 변환한 값을 그대로 넣어주자.
PlayerPrefs.SetInt(key, value);
}
/// <summary>
/// 타입 이넘값으로 값을 저장해준다.
/// Float value 전용
/// </summary>
public void SaveVariable(VariableType type, float value)
{
// 이넘값 변환
string key = type.ToString();
// 변환한 값을 그대로 넣어주자.
PlayerPrefs.SetFloat(key, value);
}
/// <summary>
/// 타입 이넘값을 바탕으로 저장된 값을 불러온다.
/// </summary>
public string LoadStringVariable(VariableType type)
{
// 이넘값 변환
string key = type.ToString();
// 불러오기
string returnValue = PlayerPrefs.GetString(key);
Debug.Log("LoadValue[" + key + "] = " + returnValue.ToString());
return returnValue;
}
/// <summary>
/// 타입 이넘값을 바탕으로 저장된 값을 불러온다.
/// </summary>
public int LoadIntVariable(VariableType type)
{
// 이넘값 변환
string key = type.ToString();
// 불러오기
int returnValue = PlayerPrefs.GetInt(key);
Debug.Log("LoadValue[" + key + "] = " + returnValue.ToString());
return returnValue;
}
/// <summary>
/// 타입 이넘값을 바탕으로 저장된 값을 불러온다.
/// </summary>
public float LoadFloatVariable(VariableType type)
{
// 이넘값 변환
string key = type.ToString();
// 불러오기
float returnValue = PlayerPrefs.GetFloat(key);
Debug.Log("LoadValue[" + key + "] = " + returnValue.ToString());
return returnValue;
}
}
- Instance 저장하고 불러오기
public class Charactor
{
int age;
string name;
string materialPath;
Material material;
// Instance를 string으로 변환
public override string ToString()
{
// Format은 Json, CSV, ..., 또는 내가 정의
return name+ ", " + age + ", " + materialPath;
}
// 저장한 string 데이터로 Instance 다시 불러오기
public Charactor(string classInfo)
{
// ,를 기준으로 split
this.name = "name부분";
this.age = System.Convert.ToInt32("age부분");
this.materialPath = "materialPath부분";
// Resources 폴더 안의 materialPath에 있는 Material을 불러온다
material = Resources.Load<Material>(materialPath);
}
}
Source Tree로 버전 관리하기
- Git이 필요한 이유
- Stage(=Index에 기록)하기
- Commit하기
- Remote 추가하기
- Origin Remote로 GitHub 사용
- Push하기
- Clone하기
- Reset (Soft, Hard, Mixed)
- Revert
- Checkout, Detached HEAD
- 이전 Commit 수정하기
- Pull / Fetch&Merge하기
- Branch 만들기
- Fast Forward Merge하기
- 3-Way Merge하기
- 3-Way Merge에서 발생할 수 있는 Conflict 해결하기
- Visual Studio로 Conflict 해결하는 법
- Stash로 다양한 문제 해결해보기
- 오래된 저장소에서 Pull하기
- 다른 브랜치로 Commit 옮기기
- .gitignore 파일 설정하기