// 게임 오브젝트와 스크립트가 활성화 되었을 때 매 프레임마다 실행 됨(fps설정이 60이라면 매 초당 60번 실행됨)
스크립트 추가
게임 오브젝트에 스크립트를 추가해야지 코드를 사용할 수 있다.
스크립트 추가
컴파일 에러
메서드에 인자가 있다면 정해진 형식대로 넣어주어야 한다.
컴파일 에러컴파일 에러2
double 형식을 float형식으로 바꿔줬다.
해결해결2
Mover.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Mover : MonoBehaviour
{
// 게임 오브젝트와 스크립트가 활성화 되었을 때 딱 한 번만 실행됨
void Start()
{
// Mover스크립트를 플레이어에 추가했기 때문에 transform을 찾으면 지금 선택된 개체의 transform을 지칭함
// transform.Translate(1, 0, 0);
}
// 게임 오브젝트와 스크립트가 활성화 되었을 때 매 프레임마다 실행 됨(fps설정이 60이라면 매 초당 60번 실행됨)
void Update()
{
transform.Translate(0.1f, 0, 0);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameObjectExample : MonoBehaviour
{
// Start is called before the first frame update
[SerializeField]
private GameObject goPrefab;
void Start()
{
// 1. 오브젝트 생성
// PrimitiveType.미리 정의된 오브젝트들
// 0,0,0에 생성됨
// GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
// PrimitiveType.Capsule;
// PrimitiveType.Cylinder;
// PrimitiveType.Plane;
// PrimitiveType.Quad;
// PrimitiveType.Sphere;
// 2. 오브젝트 파괴
// Destroy(go);
// Destroy(go, 3f);
// 커스터마이징 사용할 때 아니면 사용 X
// DestroyImmediate(go);
// 3. 오브젝트의 활성화 여부 확인
// 본인이 체크되어있냐 (부모 활성화에 상관 없이 체크면 true 체크해제면 false)
// 자식이 켜있고 부모가 꺼져있을 때
// ex) preload 미리 불러오기 외부로부터 동적으로 생성 - SetActive 켜져있음
// 이후 부모를 끄는 상태가 있음
// Debug.Log(go.activeSelf);
// 하이어라키상에서 활성화 되어있는지 여부를 알려줌 (부모 비활성화시 비활성화시 체크 false 체크해제 false)
// Debug.Log(go.activeInHierarchy);
//4. Tag 확인
// GameObject의 꼬리표(표식)
// 한 오브젝트 당 하나만 가능
// Array GameObject[] 가 있을 때
// foreach를 돌려서 go.tag == "Player"; go.CompareTag("tag"); 등 물리 엔진을 다룰 때 사용
// 프로젝트를 이관할 때 Export Pakage와 폴더 전체를 전송하는 경우 중 각 폴더만 Export하면
// Project Setting(태그, 등등) 이 포함이 안 된다.
// export 패키지는 중간중간 필요한 리소스를 받을 때 사용한다.
// Jenkins는 파일 / 셋팅, 깃허브 연동 => 프로젝트를 서버에서 빌드
// 소스코드를 GIT, SVN, Mercurial, Collaborate 버전관리툴을 사용해서 올린다.
// 젠킨스로는 바뀐 부분만 올려짐 세팅 같은 건 바꿀 수 없게 세팅을 해놓음
// boolean 반환
// Debug.Log(gameObject.CompareTag("GOExample"));
// Find는 하이러키 창의 위에서부터 찾음
//5. Tag로 검색 - 해당 태그를 가지고있는 오브젝트 저장
// GameObject goWithTag1 = GameObject.FindWithTag("Player");
// GameObject goWithTag2 = GameObject.FindWithTag("Finish");
// Debug.Log(goWithTag1 != null);
// Debug.Log(goWithTag2 != null);
// Transform.Find("");
// 자식들만 가져올 수 있는데 Active false도 가져올 수 있음
// FindWithTag("");
// GameObject.Find("");
// SetActive가 꺼진 애들은 가져오지 못함
// GetComponent(Active or not Active).gameObject();
// 선택해서 가져올 수 있다.
//6. 이름으로 검색
GameObject goWithFind1 = GameObject.Find("FindWithName");
// GameObject goWithFind2 = GameObject.Find("FindWithName2");
// Debug.Log(goWithFind1 != null);
// Debug.Log(goWithFind2 != null);
// 사용자가 정의한 게임오브젝트 생성
GameObject goTest = Instantiate(goPrefab);
goTest.name = "InstanceSphere";
goTest.transform.parent = goWithFind1.transform;
goTest.transform.localPosition = Vector3.zero;
goTest.transform.localRotation = Quaternion.identity;
goTest.transform.localScale = Vector3.one;
}
void Update()
{
}
}
트랜스폼
TransformExample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TransformExample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// Transform 반환
Transform tr1 = transform.Find("FindInChild");
Transform tr2 = transform.Find("FindInChild2");
// GameObject go1 = new GameObject();
// Transform과 GameObject는 상호참조관계
// Transform trG = go1.transform;
// GameObject goT = tr1.gameObject;
Debug.Log(transform.GetChild(0).name);
// tr1의 오브젝트를 맨 밑으로 보내겠다.
tr1.SetAsLastSibling();
Debug.Log(transform.GetChild(0).name);
// tr1의 오브젝트를 맨 위로 보내겠다.
tr1.SetAsFirstSibling();
Debug.Log(transform.GetChild(0).name);
// tr1을 index(1)번으로 보내겠다.
// 언제쓰냐 GetComponents 다 가져올 때 특정 순서를 가져옴 Getcomponent는 첫 번째 것 가져옴
// Sprite (0,0,0) (0,0,0) 두 개가 있을 때 하이어러키 상에서 맨 아래에 있는게 제일 앞에 보임 (z축은 의미가 없음)
// => 인덱스 0번부터 그리기(렌더링하기) 때문에
tr1.SetSiblingIndex(1);
// tr1의 부모 이름 가져오기
Debug.Log(tr1.parent.name);
// 최상위 것 가져옴
// tr1.root
}
void Update()
{
// 부모 자식 관계에서 자식 스크립트에서 transform.position은 월드 좌표 기준 position이다.
// 컴파일 에러
// transform.position은 Vector3이다. Vector는 struct(값에 의한 복사)이므로 못 씀
// transform.position.x += 0.01f;
// 부모 자식 관계에서 자식 스크립트에서 transform.rotation은 월드 좌표 기준 rotation이다.
// Vector3 vecP = transform.localPosition;
// vecP.x += 0.01f;
// transform.localPosition = vecP;
// Vector3 vecR = transform.localRotation.eulerAngles; -> Vector3
// vecR.x += 0.01f;
// transform.localRotation = Quaternion.Euler(vecR);
// lossyScale은 root 스케일(월드) localScale은 로컬 스케일(로컬)
// Vector3 vecS = transform.lossyScale;
// vecS.x += 0.01f;
// 에러
// transform.lossyScale = vecS;
// Euler 3축 Quternion 4축
// Update는 초당 30번 실행된다. 30fps
// 1Unit = 1M
}
}
컴포넌트
ComponentExample.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class ComponentExample : MonoBehaviour // 컴포넌트 상속
{
void Start()
{
// MonoBehaviour를 상속받는 클래스들은 new로 생성하면 안 된다.
// GameObject를 생성하고(이미 있을 시 그냥) AddComponent로 컴포넌트를 붙여야한다.
// 변수로 컴포넌트를 선언하면 asrc.~ 등 바로 접근이 가능하다.
AudioSource asrc = transform.AddComponent<AudioSource>(); // Camera, GameObject (MonoBehaviour)를 상속하는 것이라면 가능
// 이미 있으면 Getcomponent로 이 클래스에 붙어있는 컴포넌트를 가져옴
AudioSource asrc2 = transform.GetComponent<AudioSource>();
transform.GetComponent<Component>();
// 아래 방식으로 Active가 꺼진 애들을 가져올 수 있다.
GameObject go = new GameObject();
GameObject.Find("");
go.transform.Find("");
go.GetComponentInChildren<MeshRenderer>(true);
MeshRenderer[] meshArray = go.transform.GetComponentsInChildren<MeshRenderer>(true);
foreach (var mesh in meshArray)
{
mesh.enabled = false;
}
Component[] coms = transform.GetComponentsInChildren<Component>(true);
AudioSource[] asrcArray = transform.GetComponentsInChildren<AudioSource>(true);
}
void Update()
{
}
}
카메라
// Camera
// Clear Flags는 배경을 뭘로 할거냐 SkyBox, Solid Color,
// Depth Only 카메라의 뒷배경은 투명 - UI, Sprite, Video, WebPage ex) 로그인창 => 3rd party Library, 내부카메라 WebPage
// 이 때 기존 카메라에 UI 띄우는 경우도 있는데 최적화 Draw Call 문제와 UI.Object가 카메라에 같이 비치는 문제가 있다.
// => 웬만하면 UI 카메라와 게임 카메라를 분리를 한다.
// Don't Clear는 (Blit Clear가 클리어를 하는데 전체 프레임을 한 번 지워준다) => 이 클리어를 안 하겠다는 거임
// Culling Mask - Layer 개념 비슷
// 1번 카메라는 오브젝트만 비추고 2번 카메라는 캐릭터만 비추고 싶을 때 Post Processing(후처리 과정) ex) 사진 필터
// 캐릭터에만 효과를 주고싶을 때 Layer와 Culling Mask를 사용
// Projection
// - Perspective : 원근 적용
// FOV(FiledOfView) : Vertical/Horizontal 60도만큼 화면을 찍어서 가져옴
// - Orthographic : 원근 적용 X (카메라 각도가 직각)
// Size : Unity Unit 1(1m) 중점 기준으로 a만큼 사이즈가 잡힘(높이) 이에 대해 가로는 해상도에 따라 달라짐
// 16:9일 때 세로가 5라면 세로는 5/9*16이 됨
// - Clipping Planes
// Near : 제일 가까운 부분 (~보다 가까워지면 안 보임)
// Far : 가시거리 (~가 넘어가면 안 보임)
// - ViewportRect
// X, Y 는 좌측 아래를 기준으로 0~1의 위치
// W, H 는 화면의 0~1만큼의 크기
// TargetTexture
// Render Textrue를 만들고 타겟 텍스처에 넣고 Material 알베도 부분에 입혀서 오브젝트에 입히면
// CCTV같이 입혀진다.
// Occlusion Culling
// Occlusion 계산은 CPU를 사용 Occlusion 가리고 안 가리고는 GPU를 사용
// 겹쳐보이는 부분이 있을 때 안 그려줌
// 인스펙터 위의 static 옆에 Occluder (가리는 애), Occludee (가려지는 애) 잘 선택해서 체크
// GPU와 CPU에 사용량에 따라 사용
// Occulsion -> Yellow box 형성 기준
// 1. Occlusion Area
// 2. Dynamic Area
// 파란색 박스가 unit을 나누는 기준
씬, 벡터 , Input, 물리
씬
SceneExample.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneExample : MonoBehaviour
{
// 씬을 관리하는 경우
// 1. 실제로 게임상/기능상 화면전환/기능전환
// 2. 리소스 관리 차원 -> 던전분리 ex) RPG Dungeon이동
// 3. OpenWorld RPG - Object Pooling, LOD 관리
// 1번 씬에서 2번씬으로 이동시 1번 씬에 있던 오브젝트는 모두 삭제됨 (Additive를 안 했다고 가정)
void Start()
{
DontDestroyOnLoad(this.gameObject); // 씬을 로드를 했을 때 파라미터 부분을 Destroy 안 하겠다.
// File - Build Setting - Scenes In Build에 씬을 드래그앤드롭으로 추가해야함
// 인덱스를 통해서 불러오는 방식
// SceneManager.LoadScene(1);
// 씬의 이름을 통해서 불러오는 방식
// 비슷할 때 풀 경로로 써주면 됨
// SceneManager.LoadScene("2");
// SceneManager.LoadScene("2", LoadSceneMode.Single);
// Scene 1이 살아있는 상태로 불림
// 뒤에 생긴 카메라가 앞에 씬 카메라 덮어씌워짐(UI 설명할 때 처럼 한 번 더 그림)
// 웬만하면 Addtive 씬에는 카메라를 안 넣음 (일부러 겹치고 카메라를 사용해야될 때만 사용)
SceneManager.LoadScene("Scenes/2", LoadSceneMode.Additive);
}
}
벡터
Day5.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
public class Day5 : MonoBehaviour
{
// Vector -> struct 방향 / 크기
// △x △y 방향
Vector3 vec = new Vector3(0,0,0);
Vector3 vec2 = new Vector3(10,10,10);
float currentVel;
// 배열 선언 : 한정자 자료형[] 변수이름 = new 자료형[사이즈];
private Vector3[] vecArray = new Vector3[3];
// 리스트 : 동적 배열 List<자료형/클래스/구조체> 변수명 = new List<앞과 같음>();
private List<Vector3> vecList = new List<Vector3>();
// 딕셔너리 : Dictionary<키의 자료형/인스턴스형, 값의 자료형/인스턴스형> 변수명 = new Dictionary<앞과 동일>();
private Dictionary<string, Vector3> vecDictionary = new Dictionary<string, Vector3>();
private float prev;
private float threshhold = 2;
// Start is called before the first frame update
void Start()
{
// 벡터의 크기
Debug.Log(vec2.magnitude);
// 벡터 정규화 (0~1)사이
// normailized되지만 vector2의 값이 바뀌지는 않음
Debug.Log(vec2.normalized);
// 값이 바뀌는 건 함수
// vec2.Normalize()
// vec2.Scale()
//Vector3.one; (1, 1, 1)
//Vector3.zero; (0, 0, 0)
//Vector3.left; (-1, 0, 0)
//Vector3.right; (1, 0, 0)
//Vector3.up; (0, 1, 0)
//Vector3.down; (0, -1, 0)
//Vector3.forward; (0, 0, 1)
//Vector3.back; (0, 0, -1)
// 두 벡터 사이의 거리를 잼
// ex) 충돌 처리
Debug.Log(Vector3.Distance(vec, vec2));
Transform obj = transform;
Transform obj2 = transform;
Vector3.Distance(obj.position, obj2.position);
// 간단한 충돌처리 각 오브젝트에 가상의 원을 씌움
// 각 원의 센터를 그림
// 반지름 r1, r2일 때
// 원이 겹치는 부분 계산 중심의 거리 < r1 + r2 이면 충돌
vecList.Add(vec2);
vecList.Add(vec);
vecList.Add(vec);
vecList.Add(vec);
// List의 한도값 - 갯수 한정 (따로 안 줬을 때 기본 값은 메모리가 계속 두 배로 늘어남)
// vecList.Capacity = 10;
// ~을 포함하는지 확인하고 bool 값을 반환
// vecList.Contains(vec);
// vecList의 1번 인덱스에 vec값을 넣음 (인덱스 1번부터 우측으로 밀려남)
// vecList.Insert(1, vec);
// vec을 앞에서부터 찾아서 없앰
// vecList.Remove(vec);
// (LINQ)
// 나중에 배움
// 자바의 Map 개념이랑 비슷
// 사전 : 단어, 뜻 Dictionary : 키(key), 값(value)
// vecDictionary.Add("Test", vec);
// Vector3 o = vecDictionary["Test"];
// vecDictionary.Add("vec2", vec2);
// 참조는 없으면 에러 뜰 수 있음
// vecDictionary["Test"].Normalize();
// 대입은 권장하지 않음 => 원래 값이 있거나 키가 null 값일 수 있기 때문
// vecDictionary["vec"] = vec;
// 갖고있는 키를 다 반환
// vecDictionary.Keys
Debug.Log(vecList[0]);
Debug.Log(vecDictionary["vec"]);
}
// Update is called once per frame
void Update()
{
var currentVel = Vector3.one;
// Lerp(Linear Interpolation) 선형 보간
// time 값에 따라 vec과 vec2 사이의 거리를 반환(0~1)
Debug.Log(Vector3.Lerp(vec, vec2, 0.5f));
// clamp는 값을 잘라놓은 것(그냥lerp) unClamp 잘라놓지 않은 것
// 값에 비례해서 자름(-1이면 거리의 -1배만큼 vec에서 거리만큼 전으로 계산해서 반환)
// Vector3.LerpUnclamped(vec, vec2, -1f);
// 2D게임을 만들 땐 Vector3.Lerp 대신 Mathf.Lerp를 써도 쓸만함
// (0~1) 비례해서 0.5만큼의 거리만큼 계산
// Mathf.Lerp(vec.x, vec2.x, 0.5f);
// 50f라는 실제 값을 넣었을 때 비율을 반환
// vec.x가 0이고 vec2.x가 100일 때 50을 넣었다치면 그 비율인 0.5가 나옴
// Mathf.InverseLerp(vec.x, vec2.x, 50f);
// ex) vec과 vec2 사이에 2초만에 도착하고 싶을 때
// transform.position = Vector3.Lerp(vec, vec2, Time.time * 0.5f);
// (Mathf.Sin(Time.time) + 1) / 2) -> 0부터 1 => vec, vec2 사이를 왔다갔다함
// transform.position = Vector3.Lerp(vec, vec2, (Mathf.Sin(Time.time) + 1) / 2);
// 위의 Sin 함수를 쓴 것과 같은 효과
// Sigmoid를 사용 활성함수 0과 1으로 가까워질수록 변화값이 작고
// 0.5로 가까워질수록 변화값이 크다.
// transform.position = Vector3.SmoothDamp(vec, vec2, ref currentVel, Time.deltaTime);
// Debug.Log(Vector3.SmoothDamp(vec,vec2, ref currentVel, Time.deltaTime));
}
}
Input
// Input
private float prev;
private float threshhold = 2;
//Update문 안에서
// 현재 마우스 위치
// vec2 = Input.mousePosition;
// 0:좌클릭, 1:우클릭, 2:휠버튼
// Input.GetMouseButton(0)
// 엔터는 KeyCode.Return을 씀
// KeyPadEnter는 우측 숫자패드의 엔터이다.
// GetKey, GetMouseButton 등 받으면
// 키보드 이벤트 3가지
// 눌렀을 때, 누르고 있을 때, 누른걸 땠을 때
// Input.GetKeyDown(KeyCode.L); // 눌렀을 때
// Input.GetKey(KeyCode.L); // 누르고 있을 때
// Input.GetKeyUp(KeyCode.L); // 누른걸 땠을 때
// Update문에서 GetKey는 프레임당 한번 씩 받음(캐릭터 이동 등)
// 한 번만 받아야되면 GetKeyDown이나 GetKeyUp 사용
// 위와 같음
// Input.GetMouseButtonDown(0);
// Input.GetMouseButton(0);
// Input.GetMouseButtonUp(0);
// esc버튼인데 모바일로 가면 backButton이다.
// Input.GetKey(KeyCode.Escape);
//Input.GetKey(KeyCode.A);
//Debug.Log(Input.GetKey(KeyCode.A));
//Input.GetMouseButton(0);
//0 for left button, 1 for right button, 2 for the middle button
//Input.GetKeyDown(KeyCode.A);
//Input.GetKeyUp(KeyCode.A);
// 기기에 있는 가속도계 => 핸드폰 기울이는 로직을 사용할 때 씀
// Input.acceleration
// 기기 방향
// Input.compass
// 기기 회전 값을 잡을 때
// Input.gyro
// 기기 터치 관련
// Input.touches
// 모바일 멀티터치 index 0 1 2 3 4 ~~
// 터치에 따라 리스트가 구현이 되어있음
Touch t = Input.GetTouch(0);
Touch t2 = Input.GetTouch(0);
// 멀티 터치 갯수를 반환
// Input.touchCount;
// 위와 동치 touches는 멀티터치를 배열로 갖고있음
// Input.touches.Length;
// 터치 이동한 만큼 델타 벡터를 계산
// t.deltaPosition;
// 줌인 줌아웃
if (Input.touchCount == 2)
{
if (Mathf.Abs(Vector2.Distance(t.position, t2.position) - prev) > threshhold) return;
if (prev > Vector2.Distance(t.position, t2.position))
{
//pinch in
}
if (prev < Vector2.Distance(t.position, t2.position))
{
//pinch in
}
prev = Vector2.Distance(t.position, t2.position);
}
물리
FixedUpdateEx.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FixedUpdateEx : MonoBehaviour
{
// 유니티 엔진, 렌더링 엔진, 물리 엔진
// 옛날엔 3D 밖에 지원을 안 했음 => 원래는 3D 물리 엔진만 지원했음(PhysX)
// static
// 3D rigidbody
// Kinematic : 물리법칙 받지 X but 충돌처리는 됨 ex) 맵
// Dynamic : 물리법칙 O ex) 플레이어
// 2d UGUI 나중에 2D물리엔진 지원 (Box2d)
// 강체, static, dynamic, kinematic(3D의 Static과 동치)
// 2D rigidbody
// Kinematic - AddforceV에는 움직이지는 않는데 간접적인 힘에는 움직임
// Static : 3D Kinematic과 동치 - 아예 움직이지 않음
// Dynamic - 다 움직임
// Rigidbody는 물리의 영향을 받아야 될 때 붙이면 됨
// transform.position.x 조작은 Render 엔진 부분이라 static이어도 이동하긴 함
// velocity.x 조작은 이동 안 함
// Animation의 움직임이 기본적으로 transform이나 물리엔진 이동부분을 override함
// 두 객체의 충돌구현
// 적어도 둘 중 하나는 rigidbody를 가져야함
// collider는 둘 다 가져야함
// rigidbody info에서 속도나 이런 거 확인 가능
// mass : 질량
// drag : 위치에 대한 저항
// Angular Drag : 각도에 대한 저항(회전 저항)
// Use Gravity : 중력 체크
// Is Kinematic : 물리 체크
// Interpolate : 충돌 검출은 프레임별로 일어나는데 전의 프레임을 참고함
// Extrapolate : 충돌 검출에 다음 프레임을 참고함
// 객체의 Collider에 줄 수 있고
// Project Setting에서 Physics 부분에 넣으면 모든 물체에 기본으로 적용됨
// Physics Material
// - Dynamic Friction : 움직이고 있을 때 마찰력
// ex) 공이 굴러가고 있을 때 마찰력
// - Static Friction : 멈춰 있을 때 마찰력
// ex) 다른 공에 부딪혔을 때 멈춰있던 공의 마찰력
// - Bounciness : 탄성 계수
// - Friction Combine : 두 개체 사이의 마찰력
// -- averge 평균을 내겠다 minimum 작은 쪽의 마찰력을 내겠다
// -- Maximum 더 큰 값을 내겠다 multiply 곱해서 내겠다
// Bounce Combine : 두 개체 사이의 탄성력
// -- averge 평균을 내겠다 minimum 작은 쪽의 마찰력을 내겠다
// -- Maximum 더 큰 값을 내겠다 multiply 곱해서 내겠다
// Physics
// Default Material
// 기본 Physics Material
// Bounce Threshold
// 다른 물체에 부딪혔을 때 부딪치는 물체의 속력이 이 값보다 낮을 시 탄성 작용 없음
// Sleeping Threshold
// Sleep 모드일 때 일정 값보다 큰 힘이 작용했을 때 Awake 상태가 되는 기준 값
// Default Contact Offset => 얼마를 붙어있는 걸로 기준을 잡을래?
// contact는 모서리의 버텍스를 가져오기 때문에 각 모서리의 길이보다 짧으면 붙었다고 가정
// Queries Hit Backfaces
// 면에 대해서 hit을 감지하는데 뒷면에서 올 때도 감지할거냐?
// Enable Adaptive Force
// 가중치 ex) 당구 벽에 공 겹쳤을 때 하중
// Cloth Gravity
// 천(옷) 중력
// Collision Detection
// - Discrete : 고정
// - Continuous : 더 잦은 충돌 검출 (부하up)
// ex) 유저가 벽뚫기 하는 경우에 씀
// - 속도가 빠를 때(deltapos가 클 때)
// - Collider 빈 부분이 있거나
// - Collider 검출이 안 되거나
// Contraints - 6개 다 키면 Kinematic과 동치
// - Freeze Position 앞으로만 움직여야 되면 다른 좌표 값을 체크(고정됨)
// - Freeze Rotation 달려가다가 옆에서 쳤을 때 주사위 굴러가듯이 넘어가면 Rotation을 막아버림
// ex) 좌우 각도만 있으면 된다 Freeze Rotation의 X,Z를 체크함
// Capsule Collider vs Sphere Collider
// Capsule Collider Height를 1로하면 Sphere와 같아짐
// 실무에서는 Capsule Collider를 더 많이 씀 (범용성이 넓음)
// Collider
// Edit모드에서 크기 조절 가능
// Trigger vs Collision
// Trigger : 범위 침범 O
// ex) Rpg 특정 장소에 도달(보이지 않는 공간을 만들고 플레이어 도달) => Trigger 사용
// Rpg에선 인스턴싱 오브젝트가 많을 시(의도하지 않게 길막 생길 수 있음) 웬만하면 트리거
// Collision : 범위 침범 X
// ex) 당구 게임, 퍼즐 게임이나 어드벤처 게임에서 길을 막을 오브젝트
// Joint : 연결을 시켜주거나 상호작용이 가능하게 만들어줌
// Fixed Joint
// 처음 생성된 거리가 유지가 됨
// - Connected Body(rigidbody 참조해야함)
// - Break Force 일정 값이 넘어가면 끊어짐 Infinity면 절대 안 부셔짐
// - Break Toque 일정 회전 값이 끊어짐 Infinity면 절대 안 부셔짐
// ex) cut the rope(공룡 사탕 먹는 게임) spring Joint 사용
// Character Joint
// 3D 모델 흔들리는 모습 다 구현 가능
// Hinge Joint
// 문 경첩 수직 Axis를 기준으로 회전 가능
// axis 움직이는 축
// 기준점이 되는 좌표
// Spring Joint
// 매달려서 연결 됨
// Terrain
// Mesh Collider
// 3D 객체를 다루는 파일에서 자주 나옴
// Shaded WireFrame에서 볼 수 있음
// 더 자세히 콜라이더 체크를 할 수 있음
// Mesh collider 일반 상태에서는 trigger가 불가능하다
// - Convex
// Convex Hull (구멍채우기)
// 체크해야 Trigger가 가능하다
// 컨벡스헐 알고리즘 - 가장 바깥의 점을 찾아서 이어줌
// 도형에서 안쪽으로 파여져 있는 부분이 컨벡스
// 이렇게 하기 싫으면 버텍스를 다 검출하여
// 컨벡스헐 생성이 안 되면 콜라이더를 생성하여 계산
// ex) 스왐피는 동적생성
// - 정점을 가져와서 MeshFilter에 CreateMesh를 실행하여
// Mesh배열을 가져온 후 메시 콜라이더에 넣음
// 메시를 구획으로 나누어서 동적 생성
// 물줄기가 떨어짐 - 원형의 콜라이더에 파티클을 씌운 것임
// 물 - 흘러야됨 / 뭉쳐져야됨
// 지름보다 작은 콜라이더를 넣고 - 뭉쳐짐
// Friction을 0으로 넣음 - 흘름
// Bounceness를 0.6정도로 하면 더 자연스러움
// Wheel Collider
// 레이싱 게임을 목적으로 만든 콜라이더
// 댐핑, 서스펜션 등등이 있음
// Rigidbody2D
// - Auto Mass 질량을 자동으로 계산해줌(size 기반)
// Distance Joint
// 거리를 조정할 수 있음
// Effector 2D
// Collider에 Used By Effector을 체크 해야함
// Buoyancy Effector 2D
// Particle System Collision
// - Lifetime Loss
// Collision과 만나면 빨리 사라지게 할 수 있다.
// Collider를 타고 파티클이 진행할 수 있다.
private ForceMode m;
Rigidbody m_Rigidbody;
public float m_Thrust = 20f;
private int m_nCount = 0;
void Start()
{
//Fetch the Rigidbody from the GameObject with this script attached
m_Rigidbody = GetComponent<Rigidbody>();
// 물리 끄고싶을 때는 키네마틱을 켜버림
// m_Rigidbody.isKinematic = true;
// 중력 가속도 * 시간 => 속도 / 속도 * 시간 => 거리 // 9.81 * 0.02 * 0.02
}
// Update는 Fps -> Cpu(프레임)에 종속적
// Render쪽에 주로 쓰임 (시각적)
// - 사람이 초당 볼 수 있는 한계에 맞춰져 있음
// Frame과 Delta.Time은 반비례 관계
// FixedUpdate는 Tick(Cpu 시간을 재는 단위 - 고정)에 종속적
// => 프레임 저하에 상관 X(프레임에 종속 X) - 0.02초에 한 번 불림
// 실무에선 0.1초에 한 번으로 불림
// 물리 엔진은 예측 개념이 조금 있는데
// 프레임이 끊겼을 때 순간 이동을 한다거나 버그가 생길 수 있음
void FixedUpdate()
{
// 정속
// m_Rigidbody.velocity = new Vector3(0, 5f, 0);
// 방향을 가진 힘을 줌 (월드 포지션 기준)
// 한 번 더 감싸는 큐브가 있다고 쳤을 때 부모 객체의 각도를 돌렸을 때
// 예상치 못한 방향으로 힘이 가해질 수 있음
// m_Rigidbody.AddForce();
// 해당 트랜스폼이 바라보는 방향에 대한 벡터
// transform.forward;
// 특정 지점에 힘들 줌 (토크랑 같이 먹음)
// m_Rigidbody.AddForceAtPosition();
//F(힘) = ma
//a(가속도) = F / m(질량)
//a = deltaV / deltaT
//a = (v2 - v1) / (t2 - t1) 가속도
//a - 9.8(중력) = v2 / t2
// m = 1, F = 20, t2 = 0.02
// v1 = 0 t1 = 0
// a - 9.8 = v2 / t2
// v2 = (a - 9.8) * t2
// v2 = (F / m - 9.8) * t2
// v2 = (F - 9.8) * t2
// v2 = (20 - 9.8) * 0.02
// v2 = 0.2 RigidbodyInfo에서 확인 가능
// Ex)
// v2 = (a - 9.8) * t2
// v2 = (F/m - 9.8) * t2
// v2 = (F - 9.8) * 0.02
if (Input.GetKeyDown(KeyCode.A))
{
m_nCount = 50;
m = ForceMode.Force;
// force * deltaTime / mass
// F = 20 * 0.02 / 1
// v2 = 0.204
}
if (Input.GetKeyDown(KeyCode.S))
{
m_nCount = 50;
m = ForceMode.Acceleration;
// force * deltaTime
// F = 20 * 0.02
//
}
if (Input.GetKeyDown(KeyCode.D))
{
m_nCount = 50;
m = ForceMode.Impulse;
// force / mass
// F = 20 / 1
//
}
if (Input.GetKeyDown(KeyCode.F))
{
m_nCount = 50;
m = ForceMode.VelocityChange;
// force
// F = 20
}
if (m_nCount > 0)
{
m_nCount--;
m_Rigidbody.AddForce(transform.up * m_Thrust, m);
Debug.Log(m_Rigidbody.velocity);
Debug.Log(Time.fixedDeltaTime);
}
// 원래 개념은 카메라에서 눈에 보이지않는 Ray(선) 을 쐈을 때 처음으로 걸리는 객체
// 유니티에서의 개념은 쏘는 부분(object, 좌표)에서 쏴서(필터링) 특정 객체가 맞으면 true를 반환
// 제일 처음 만난 콜라이더를 반환함
// 다 반환받고 싶으면 CastAll을 붙임 => 반환값 Array
RaycastHit hit;
// 현재 포지션에서 forward 방향으로 레이저를 100fm 만큼 쐈을 때 맞으면 true
// 거리 안 쓰면 무한 : float.PositiveInfinity
// Layer는 특정 객체를 검출하게 할 수 있다.
// out hit
// RaycastHit은 구조체로 값 전달밖에 못 하는데 out으로 전달하면 참조로 받아
// hit을 Raycast함수문 탈출 후에도 사용할 수 있다.
if (Physics.Raycast(transform.position, Vector3.forward, out hit, 100f, LayerMask.NameToLayer("UI")))
{
hit.transform.GetComponent<GameObject>().SetActive(false);
}
// 캐스트 되는 범위를 박스 형태로 날림
if (Physics.BoxCast(transform.position, Vector3.one, Vector3.forward, out hit))
{
}
RaycastHit[] hits = Physics.BoxCastAll(transform.position, Vector3.one, Vector3.forward);
// Physics.CapsuleCast()
// Physics.SphereCast();
// Physics.Linecast();
}
// Edit - Project Setting - Physics Layer Collision Matrix - 체크를 해제하면 충돌을 막을 수 있다.
// OnTriggerEnter/Stay/Exit
// Collider 영역에 진입했을 때
private void OnTriggerEnter(Collider other)
{
}
// Collider 영역에 있을 때
private void OnTriggerStay(Collider other)
{
}
// Collider 영역에서 나왔을 때
private void OnTriggerExit(Collider other)
{
}
// OnCollisionEnter/Stay/Exit
// 부딪쳤을 때
private void OnCollisionEnter(Collision collision)
{
// 사각형 사각형
// 충돌 포인트들을 가져올 수 있음
// collision.contacts
// 부딪친 충돌체의 컴포넌트들을 가져올 수 있음
// collision.transform.GetComponent<>();
}
// 붙어있는 상태
private void OnCollisionStay(Collision collision)
{
}
// 부딪쳤다 떨어졌을 때
private void OnCollisionExit(Collision collision)
{
}
}
실습 과제
물리를 이용한 플랫폼형 게임을 만들어봤다.
실습과제
한 주 후기
이번 주에는 게임 오브젝트부터 각 컴포넌트가 무슨 일을 하는지, 또 유니티에서 물리 법칙이 어떤 방식으로 이루어지는지를 배웠다. 현실의 물리 법칙(ex : F = ma)이 내부 물리 법칙 계산과 같은 것에 놀랐고 이 값들을 조정하여 나만의 게임을 만들 수도 있다는 것을 알았다.
실습 과제를 진행하면서 어려운 점이 많았다. 내 프로젝트는 2D로 진행했는데 3D를 주로 다뤄보다가 2D 컴포넌트를 쓰려니 까먹고 바꿔써야 하는 걸 안 바꿔 쓴 적이 많았다. 프로젝트 중 첫 난관은 Vector.Velocity의 x축으로 속도를 조절하려니 중력 값이 override돼서 플레이어가 생각대로 움직이지 않는 것이었다. 이는 그냥 내가 수동으로 중력 값을 넣어줘서 해결하였다. 두 번째 난관은 Raycast부분에서 어쩔 때는 hit이 되고 어쩔 때는 hit이 안 되는 것 때문에 애를 먹었다. DrawRay로 선을 그려가면서 해보았지만 안 되는 것에 시간을 많이 낭비한 것 같다. 여기서 첫 번째 문제는 3D환경에서 사용하던 것처럼 Physics를 사용한 것이었고 두 번째 문제는 바닥의 끝으로 갔을 때 Ray로 검출하다보니 캐릭터 transform의 위치 때문에 검출이 안 될 때가 있던 것이었다. 이후에 첫 번째 문제는 Physics2D를 사용해서 해결하였고 두 번째 문제는 Boxcast로 바닥을 체크해 해결하였다.
이번 주차에 토이 프로젝트를 진행하면서 느낀 점은 기능 기능을 따로 구현하는 것이 아니라 작은 게임을 처음부터 구현한다는 점이 좋았다. 처음부터 스스로 구현하다보니 어떻게 구현하면 좋을지에 대해서 먼저 생각하는 시간도 있고 그 생각대로 프로그래밍을 하는 것과 그 구현방식에 따른 다른 구현 방법도 달라지는 것이 재밌었다. 그리고 내가 부족한 점이 어느 부분에 있다는 것을 알 수 있어서 좀 더 성장할 수 있었다고 생각이 들었다.
이전의 개발 교육 기간에 들었던 이론들을 기반으로 눈에 보이는 결과물이 있는 실습 시간으로 들어오니까 학습 성취도도 늘고 재밌게 교육에 참여할 수 있었던 것 같다. 학습이 재밌고 내가 성장하는 걸 보면서 동기부여도 되는 것 자체도 재밌는 것 같다. 이 마음가짐으로 끝까지 교육을 수행했으면 좋겠다. 다음주도 파이팅!
이 강좌에서 나는 Sprite Shape로 각도에 따라 Sprite의 모양을 다르게 지정할 수 있고 Trail 컴포넌트를 이용하여 총알이나 무기의 잔상을 남길 수 있다는 것을 배웠고 Camera의 Clear Flags에서 Depth only나 Don't Clear 모드일 때 오브젝트 이동 시 잔상이 남는다는 것을 배웠다.
이 강좌 추천 대상
이 강좌는 쉽게 유니티의 전반적인 개념과 기초적인 C# 스크립팅 개념들, 간단한 스프라이트 렌더링 예제를 포함하여 유니티를 처음 접하는 사람도 쉽게 배울 수 있을 것 같아 입문자에게 추천하고싶다.
2D 스프라이트 렌더링
3D 스프라이트 렌더링
후기
이 강의에서는 유니티의 전반적인 개념들을 다루고 있다보니 예제가 좀 부족하다고 생각이 들었다. 나는 예제를 풀어가면서 이해하고 학습하는 것을 좋아하는 편인데 개념만 들으려다보니 조금 지루하기도 했다. 강의 후반부의 C# 스크립팅이나 LifeCycle같은 부분은 스타터스 강의를 들을 때 이미 배웠던 부분이라 복습한다는 생각으로 들었던 것 같다. 하지만 입문자의 입장에서 이 강의는 매우 친절하고 개념적으로 잘 알려주는 것 같아서 좋을 거라고 생각한다. 특히 무료강의라 듣기에 부담도 덜한 것 같다.
이 다음 강의로 유니티 2D 기초 강의와 유니티 3D 기초 강의가 있던데 그 강의에서 여기서 배운 개념들을 활용하는 식으로 되지 않을까 싶어 다음 강의들이 기대가 된다.
카멜 케이스 : 단어들이 연속적으로 이어질 때 첫 단어를 제외하고 각 단어의 첫글자를 대문자로 사용
ex) playerName
- 회사마다 가이드가 다름(미리 숙지를 하면 좋음)
코딩 컨벤션(표준)
- 변수이름 설정하는 게 중요
조건문
if (조건)
{
// 실행문
}
반복문
for문
for (var i = 0; i < 5; i++)
{ Debug.Log(i);
}
Coroutine
yield return
7.4 (월) 수업 내용 요약
변수 => 데이터형을 메모리에 올리는 것
배열 – int[] student = new int[5];
배열만으로 다루기 어려움
=> 클래스를 만들자
클래스 : 어떤 문제를 해결하기 위한 데이터를 만들기 위해 추상화(쓰고자 하는 내용)
ex) 챔피언의 hp mp status를 필요한 것만 빼놓은 것
캐릭터 A(name)가 공격(Attack)을 하면 메소드 {데미지(명사) 10이 박힌다(동사)} .
이런 개념이 합쳐지면 클래스가 됨
메서드
형식
public void Move () {}
접근한정자 반환형(리턴형) 메서드이름 매개변수{뭐할지}
ex)
public int Add(int a, int b)
{
return a + b;
}
int a = 1;
int b = 3;
int result = a + b;
debug.Log(result);
int newResult = Add(1, 3);
debug.Log(newResult);
- 반환형 void – 반환 값이 없음 (return 안 써줘도 됨)
- 반환형 나머지(int, string , , ,) 맞는 형이 return에 나와야 됨
- 매개변수 – 받을 땐 형식 명시 줄 땐 그냥 형식에 맞춰서 보내면 됨
- 함수를 래핑도 가능 Debug.Log(string str);를 시간을 찍히게 MyLog(string str);로 만들 수 있음
클래스
Champion champ = new Champion();
클래스 변수명 = new한정자 클래스 생성자 ;
ex)
class Champion
{
int hp;
int mp;
int Add(int a, int b)
{
return a + b;
}
}
ex) int[] arr = new int[]4;
- new는 클래스 new 한정자이다. 자료형과는 다르게 참조형 자료형이다.
- int, string 등등은 값형 자료형
오버로딩
함수 이름은 같은데 매개변수의 타입이 다름
- 오버로딩으로 구현하면 같은 함수를 작성했을 때(Add를 썼을 때 위아래 방향키를 눌러서 다른 함수를 선택할 수 있음 => 개발 편의성)
ex)
public float Add(float a, float b)
{
return a + b;
}
// 매개변수가 같고 반환 값이 다른 경우는 에러 뜸
한정자
ex)
class System
{
int hp;
int lv;
public int GetHp()
{
return hp;
}
private void SetLvUp()
{
lv++;
}
}
class Program
{
static void Main(string[] args)
{
System system = new System();
int hp_ = system.GetHp();
// system.SetLvUp(); 에러 – private라서
}
}
유니티에서 inspector에서 접근하고 싶을 때 코드 상으로는 private여야 한다면 무지성 public말고
[SerializedField]
private int hp = 0; 이런 식으로 선언해야 한다.
생성자
ex)
Champion champ = new Champion();
champ.hp = 500;
Champion champ = new Champion();
champ.hp = 600;
Champion champ = new Champion();
champ.hp = 700; 일 때 항상 넣기 힘드니까
class Champion
{
int _hp;
int _mp;
// 외부에서 접근하니까 public public Champion(int hp, int mp)
{
_hp = hp;
_mp = mp;
}
}
소멸자
ex)
~Champion()
{
Debug.Log(“Free”);
_hp = 0;
_mp = 0;
}
프로퍼티(property)
ex)
public int HP
{
// champ.HP => get을 호출
// 무조건 return을 써줘야됨
get
{
return _hp;
}
// champ.HP = -100; => set을 호출
// 무조건 value라는 값을 써줘야됨
set
{
if (value < 0)
{
value = 0;
}
_hp = value;
}
}
// 다른 클래스에서
int A = champ.HP; => get
champ.HP = -100; => set
// HP 값 자체에는 접근 가능, 받아올 수 있음
public int HP { get; set; }
// 가져오는 건 가능한데 넣는 건 작업을 막을 수 있음
public int MP { get; private set; }
값 자료형, 참조 자료형, 클래스 자료형
값 자료형
ex)
int a = 20;
TestValue(a);
Debug.Log(a); // 출력 값 20
void TestValue(int a)
{
a = 10;
}
참조 자료형
ex)
int[] a = new int[1]{ 20 };
TestValue(a);
Debug.Log(a[0]); // 10 출력
void TestValue(int[] a)
{ a[0] = 10;
}
클래스 자료형
ex)
class TestValueClass
{
public int value = 0;
}
void TestValue(TestValueClass t)
{
t.value = 10;
}
// 다른 클래스에서
TestValueClass tvClass1 = new TestValueClass();
tvClass1.value = 20;
TestValue(tvClass1);
Debug.Log(tvClass1.value);
string a = “Test”;
Debug.Log(a);
TestValue(a);
Debug.Log(a);
TestValue(string a)
{
a = “Test2”;
}
상속
부모로부터 자식이 속성이나 특성을 물려받음
// 챔피언 아칼리 아리 아무무
// 속력 2 3 4
// Move(); SkillQ,W,E,R 챔피언마다 조건이 다름
public class Champion
{
public int _lv = 1;
public int _hp = 650;
public int _mp = 0;
public void Move()
{
Debug.Log(“Move”);
}
public virtual void SkillQ()
{
Debug.Log(“SkillQ”);
}
public virtual void SkillW()
{
Debug.Log(“SkillW”);
}
public virtual void SkillE()
{
Debug.Log(“SkillE”);
}
public virtual void SkillR()
{
Debug.Log(“SkillR”);
}
}
// 다른 클래스
public void new Move()
{ Debug.Log(“Move”);
}
가상 함수
virtual – 부모 객체의 함수를 자식들을 통해서 변화를 줄 수 있음
한정자
base 한정자 - 부모
new 한정자 – 덮어씀
접근 한정자
public – 외부 접근 가능
protected – 자식(파생 클래스)에서 접근 가능
private - 같은 클래스 내에서 접근 가능
외의 개념들
모듈화 : 기능적인 기본 사양을 공유하는 그룹핑을 지은 unit
상속 : 이미 부모에서 구현된 거에서 갖다 쓰는 것
라이브러리 : using을 통하여 갖다 씀
[Serializable] - 클래스 위에 쓰여서 인스펙터에 정보가 뜨게 함
이번 주 과제들
배열 안의 회색 값 찾기롤 챔피언 과제(Json의 텍스트 파일의 정보를 가져오기)롤 챔피언 과제(로그 띄우기)
한 주 후기
드디어 개발교육 첫 주차가 끝났다. 이번 주 수업은 C#과 관련된 수업으로 진행하였고 부족했던 부분을 다시 공부할 수 있는 시간이었다. 강사님이 수업이 끝나고 유니티를 같이 다뤄보면서 진행할 수 있는 과제를 내주셨는데 배열부분에서 코드를 이해하는데 조금 걸렸지만 끝까지 해낼 수 있었다.
개발 교육을 들으면서 질문이 여럿 생겼는데 enum의 선언 부분이나 접근 한정자 부분에서 이해하는데 꽤나 걸렸던 것 같다. 클래스 안에서 선언 되었는지와 클래스 밖에서 선언 되었는지에 따라 enum변수에 접근할 수 있는지가 달라지는 것을 배웠다. enum 선언에서 클래스 밖에서 선언될 때(namespace레벨)는 private와 protected는 쓰이지 않고(class도 internal과 public만 쓰임) 기본적으로 아무것도 안 쓰면 internal이라 같은 어셈블리 안이면 어디든 사용 가능하고 클래스 안에서 선언될 때는 선언하려는 변수의 한정자보다 범위가 같거나 넓은 한정자로 enum을 선언해야한다는 것을 배웠다.
원래는 이런 것들이 왜 이렇게 되는지에 대해서 의문을 가지지 않고 그냥 되는 길을 찾아서 생각없이 진행했다면 이번처럼 질문을 통해서 이해를 하는 방향으로 가야지 기억에 더 잘 남는다는 것을 알게 되었다. 특히나 이번엔 친절하게 설명해주시는 강사님이 계셔서 조금이라도 이해가 가지 않으면 물어보고 개념을 확실히 잡고 가야겠다고 생각했다.
아직 내가 직접 뭔가를 만들기엔 조금 부족한 것 같다. 그리고 정답지를 안 보고도 끝까지 만들어보는 습관을 들어야 겠다고 생각했다. 이러한 연습을 통해 스스로 생각한 것을 구현할 수 있을 것이라고 생각한다.
이번 주 마지막 과제에서 살을 붙이는 작업이 이후의 과제들이라는데 내가 한 과제들이 이어진다는게 재밌을 것 같다. 다음 주도 열심히 해야겠다. 파이팅!!
(학습적) : 현실 적용, 학습자가 얼마나 수용 했는지 - 자신감 갖고 해(틀려도 괜찮아)
* 황효현 교수님의 콘텐츠 기획의 기초 내용 중
시스템 기획 vs 콘텐츠 기획
시스템 기획 - 게임 내적 구성요소(재미)
ex) 시나리오, 설정, 레벨(맵), 캐릭터
게임 콘텐츠 - 기초 구조 및 동작원리
ex) 캐릭터가 지능형, 체력형, 아이템형으로 나뉨
플로우 차트 그려보기
플로우 차트 : 서비스 개발 과정에서 이해관계자가 서로 이해하기 위해 서비스와 기능 위주로 작성하는 구조도
스토리라인
** 우연히 인간의 땅에 내려온 신이 숲을 산책하던 중, 잘 가꿔진 숲을 보고 이 숲을 관리한 짐승들에게 선물을 주기로 했습니다.
신은 숲에 사는 모든 동물에게 이 소식을 전하고, 해가 지기 전에 가장 먼저 신이 있는 곳을 찾아오는 동물들에게 포상을 하기로 했습니다.
숲에 숨어 있는 신을 찾아주세요.
** 황효현 교수님 강의 중 ppt 내용
시스템 기획
플랫폼형 게임으로 동물 캐릭터를 선택하여 제한시간 내에 장애물을 피하고 100m 골라인을 넘으면 신을 찾는 게임의 플로우 차트이다.
펑션트리
펑션트리 : 시스템 기능 간의 종속성을 보여주는 다이어그램
펑션트리
위는 이 주간 만든 역사교육 메타버스의 펑션트리의 내용을 캡쳐한 사진이다.
피그마
피그마1
피그마2
위 두 사진은 역사교육 메타버스의 펑션트리의 내용을 기반으로 간단한 애니메이션으로 나타낸 것이다.
한 주 후기
이번 주는 기획의 실습적인 부분 위주로 교육을 들었던 것 같다. 지난 주는 시장 조사와 경쟁자 조사 그리고 사용자 분석을 통하여 우리가 제공할 서비스의 틀을 만들었다면 이번 주는 그것들을 구체화하여 세세하게 글로 작성(펑션트리)하고 프로토타입으로 보여주는 작업(피그마로 제작)까지 했다.
우리조는 지난 주부터 마지막 결과물까지 오면서 기획 방향을 총 4번정도 바꿨던 것 같다. 처음 아이디어는 재난 상황에 대피하는 안전교육이었다가 한국사 교육으로 바뀌고 또 한국사 교육 내에서도 스토리 모드로 표현하여 시뮬레이션 형식으로 하다가 퍼즐 게임을 메인으로 역사 교육을 하는 것으로 바꾸고 또 스토리 모드와 퍼즐을 합쳤다가 마지막엔 각 챕터별로 퀘스트를 진행하고 그 퀘스트 안에 미니게임을 만들어서 각 챕터 진행 후에는 역사적 사실을 알려주는 방식으로 바뀌었다. 글만 봐도 어지럽고 무슨 말인지 이해가 어려운 만큼 다사다난 했던 것 같다. 조원 간의 의견 대립이 있었는데 그것을 조율하고 또 다시 아이디어를 내는 과정이 꽤나 재밌었다. 결과물이 어느정도 만들어지는 게 보여서 끝까지 마무리 할 수 있었다. 이번에 의견 대립이 있을 때 그냥 말로 표현하는 식으로 내 주장을 펼쳤는데 다음 번엔 각 입장의 예시를 찾아서 비교하여 우리 과제에서 어떤 식으로 표현하는 게 좋은 지 상의하는 식으로 해봐야겠다.
이번 주로 기획 수업이 마무리되는데 나중에 같이 일하게 될 기획자의 입장을 조금이나마 이해할 수 있게 되지 않을까 하는 생각이 들었다. 기획을 교육해주신 담당 교수님과 보조 강사님이 보여주신 열정에 다들 으쌰으쌰 한 것 같다.
다음 주에는 개발 교육을 들어간다. 설렘 반 두려움 반인 마음이지만 다음 주에는 꼭 인간 스펀지가 되어서 강사님의 지식들을 흡수하고 체화해야겠다. 다음 주도 파이팅!