오늘 학습 진행 내용
라이브 세션
DAY 2 게임 개발 과정에 대해 알아보고 언리얼 엔진과 놀아보기!
게임 개발 과정 간접 체험해보기
영화가 감독, 배우, 촬영감독, 음향감독 등 수많은 전문가가 손을 거쳐 만들어지는 것처럼, 게임 개발에도 다양한 능력을 가진 사람들이 모여 협업
게임 개발 직군
게임 기획자
스토리 라인을 구상, 레벨 디자인, 게임 규칙을 면밀하게 정함
또한 난이도를 조정하고 게임의 핵심 재미를 정이하는 것도 기확자의 중요한 역할
아티스트(그래픽 디자이너, 애니메이터)
기획자가 만든 설계도를 바탕으로 눈으로 불 수 있도록 구체화 하는 사람
컨셉 아티스트
게임의 분위기와 컨셉을 정의하는 일러스틀ㄹ 그려 전체적인 톤 앤 매너를 결정
3D 모델러
캐릭터,몬스터,건물,무기 등등 게임 세계를 구성하는 모든 물체를 3D 모델로 만듬
애니메이터
만들어진 3D 모델에 생명을 불어 넣는 역할
캐릭터가 다릴고 점프하고 공격하는 모든 동작 부드럽게 이어지게 하는 역할
프로그래머
게임 기획자가 상상한 규칙과 아티스가 만든 리소스에 생명과 움직임을 부여하는 사람
클라이언트 프로그래머
플레이어가 직접 눈으로 보고, 손으로 조작하는 모든 상호 작용에 관련된 부분 구현
그래픽 및 사운드 연동 및 UI/UX 개선, 클라이언트 프로그래머는 생각보다 여러 팀과 협업
퍼포먼스 최적화는 클라이언트 프로그래머의 핵심 소양
서버 프로그래머
온라인 게임에서 게임 전체의 질서 유지를 담당하고 데이터의 일관성과 공정성을 보장하는 역할
게임을 흥하게 하지 못할지언정 망하게 할 수 있는 제1의 직업이 서버 프로그래머라 서버 안정성은 항상 필수
사운드 디자이너
좋은 OST와 배경음악은 플레이어 입장에서 여러 감정선을 표현하고 그걸 유저들에게 고스란히 혹은 그 이상으로 전달하는 매채체와 적당한 효과음 연출 등으로 게임의 몰입도를 높여주는 역할
PD
게임 스튜디오에 있는 팀 전체를 오케스트라처럼 지휘하는 사람
사실상, 게임 스튜디오의 리더이자 CEO
게임 개발은 큰 규모, 긴 기간 프로젝트가 많기 때문에 수년 동안 작업하는데 방향을 잃지 않고 목적지까지 도착하게 하는 PD와 PD를 보좌하는 PM의 역할이 중요
보통 마일스톤을 지정해놓고 빌드를 내서 자체 테스트, 포스트모템 진행
일정 관리, 예산 조율까지 개발 외적으로 모든 업무들을 담당하는 포지션
언리얼 에디터 찬찬히 살펴보기
언리얼 프로젝트 생성 시 언리얼 프로젝트 파일(.uproject 확장자) 생성
뷰포트
화면의 중앙에 위치하며, 3D월드를 직접 볼 수 있는 영역
마우스와 WASD로 시점 조작
우클릭 + 휠로 카메라 속도 조절
아웃라이너
디테일
아웃라이너 아래 위치, 선택한 오브젝트 속성 보여주고 편집
위치, 회전 크기를 속성
콘텐츠 브라우저
Ctrl + Space로 열기
에셋 관리
에셋을 드래그 앤 드롭으로 배치 가능
뷰포트 알아보기
간단한 실습
컨텐츠 브라우저 경로 : /All/Game/LearningkKit_Games/BluePrints
해당 경로에서 에셋 선택해서 배치하고 조정해보기
뷰포트 내 오브젝트 용어 정리
월드
레벨이 존재하는 최상위 컨테이너로 모든 엑터와 구성 요소를 포함
하나의 월드에는 하나 이상의 레벨이 포함
게임 로직의 전반적인 상태를 관리
물리 시뮬레이션, 이벤트 처리, 게임 루프를 제어
언리얼 엔진의 API를 통해 월드를 관리하고 상태를 변경
여러 개의 레벨을 동시에 로드하고 관리
레벨
게임 또는 프로젝트의 하나의 씬으로 의미
언리얼 엔진에서 작업하는 공간 정의, 월드의 구성요소를 담고 있음
게임 환경을 설계하고 액터와 이벤트를 배치하는데 사용
여러 레벨을 조합하여 대규모 환경 만들 수 있음
.umap 확장작를 가짐
Type=World로 검색하면 해당 프로젝트 내 레벨 파일 필터링 가능
액터
언리얼 엔진 월드 내에 존재하는 모든 오브젝트
기본적으로 레벨에 배치되거나 생성될 수 있는 개체
게임플레이 로직 담당, 환경 구성 요소를 나타내는 역할
캐릭터, 조명, 카메라, 사운드, 물리 오브젝트 등이 포함
위치,회전,크기와 같은 TransForm 속성을 가짐
블루프린트, C++로 커스텀 액터 만드는 것 가능
폰
폰은 액터의 일종으로 플레이어나 AI가 조종할 수 있는 오브젝트
폰은 컨트롤러에 의해 제어
Possess를 통해서 player 할당하면 해당 폰으로 플레이어가 조종 가능
좌표계 알아보기
로컬 좌표계 월드 좌표게 존재
월드 좌표계 특정 위치에 스폰이 되어야 하는 상황에서 월드 좌표계 사용
나머지 일반적인 상황에선느 로컬 좌표계 사용
트랜스폼
위치(Position) : 오브젝트가 어디에 있는지(W키로 위치 조정 가능)
회전(Rotation) : 오브젝트가 어떤 방향으로 향하고 있는지(E키로 위치 조정 가능)
크기(Scale) : 오브젝트가 얼마나 큰지(R키로 크기 조정 가능)
머터리얼 꾸며보기
캐릭터의 메시에 적용된 머터리얼 변경을 통해서 스킨 변경 가능
오늘 할 일
여러분! 오늘 강의에서 게임 개발 과정에 대한 전반적인 흐름을 살펴보았고, 언리얼 에디터의 기초적인 인터페이스를 익혀보았습니다!
오늘 배운 내용들을 복습하고 더 깊게 익힐 수 있도록 몇 가지 과제를 드릴게요!
W, E, R 키를 사용해 위치, 회전, 크기를 변경해보세요.
트랜스폼 기능 활용해서 캐릭터 다양한 형태로 배치 가능
각각 머터리얼 변경함으로써 다양한 스킨 적용 가능
Landscape 모드 활용해서 레벨 손바닥 모양으로 구성
기존 프로젝트에 존재하던 집,공원, 구름 에셋들 배치해서 마을 구성
BP_NPC와 BP_NPC_AI 블루프린트 구성해서 정해진 구역 내 자동으로 돌아다니는 NPC AI 구성
NPC가 돌아다니는 손바닥 위 마을 시연
C++ 프로그래머스 연습
코드처리하기
문제 설명
문자열 code가 주어집니다.
code를 앞에서부터 읽으면서 만약 문자가 "1"이면 mode를 바꿉니다. mode에 따라 code를 읽어가면서 문자열 ret을 만들어냅니다.
mode는 0과 1이 있으며, idx를 0 부터 code의 길이 - 1 까지 1씩 키워나가면서 code[idx]의 값에 따라 다음과 같이 행동합니다.
mode가 0일 때
code[idx]가 "1"이 아니면 idx가 짝수일 때만 ret의 맨 뒤에 code[idx]를 추가합니다.
code[idx]가 "1"이면 mode를 0에서 1로 바꿉니다.
mode가 1일 때
code[idx]가 "1"이 아니면 idx가 홀수일 때만 ret의 맨 뒤에 code[idx]를 추가합니다.
code[idx]가 "1"이면 mode를 1에서 0으로 바꿉니다.
문자열 code를 통해 만들어진 문자열 ret를 return 하는 solution 함수를 완성해 주세요.
단, 시작할 때 mode는 0이며, return 하려는 ret가 만약 빈 문자열이라면 대신 "EMPTY"를 return 합니다.
제한사항
1 ≤ code의 길이 ≤ 100,000
code는 알파벳 소문자 또는 "1"로 이루어진 문자열입니다.
통과 답안
#include <string>
#include <vector>
#include <iostream>
using namespace std;
string solution(string code) {
string answer = "";
int mode = 0;
for(int idx=0; idx<code.length(); idx++ ){
if(mode==0){
if(code[idx]=='1'){
mode=1;
}
else{
if(idx%2==0){
answer += code[idx];
}
}
}
else if(mode==1){
if(code[idx]=='1'){
mode=0;
}
else{
if(idx>0 && idx%2==1){
answer += code[idx];
}
}
}
}
if(answer.length()==0){
answer="EMPTY";
}
return answer;
}
다른 사람 풀이
#include <string>
#include <vector>
using namespace std;
string solution(string code) {
string answer = "";
//ture == 0 , false == 1
bool mode = true;
for(int i=0; i<code.length(); i++)
{
if(code[i] =='1')
mode = !mode;
switch(mode)
{
case true:
if(i%2 == 0)
{
if(code[i] != '1')
answer+= code[i];
}
break;
case false:
if(i%2 != 0)
{
if(code[i] != '1')
answer+= code[i];
}
break;
}
}
if(answer.empty())
answer = "EMPTY";
return answer;
}
등차수열의 특정한 항만 더하기
문제 설명
두 정수 a, d와 길이가 n인 boolean 배열 included가 주어집니다. 첫째항이 a, 공차가 d인 등차수열에서 included[i]가 i + 1항을 의미할 때, 이 등차수열의 1항부터 n항까지 included가 true인 항들만 더한 값을 return 하는 solution 함수를 작성해 주세요.
제한사항
1 ≤ a ≤ 100
1 ≤ d ≤ 100
1 ≤ included의 길이 ≤ 100
included에는 true가 적어도 하나 존재합니다.
통과 답안
#include <string>
#include <vector>
#include <iostream>
using namespace std;
int solution(int a, int d, vector<bool> included) {
int answer = 0;
for(int i=0; i<included.size(); i++){
if(included[i]){
if(i==0){
answer+=a;
}
else{
answer+= d*i+a;
}
}
}
return answer;
}
다른 사람 풀이
#include <string>
#include <vector>
using namespace std;
int solution(int a, int d, vector<bool> included) {
int answer = 0;
a -= d;
for(auto flag : included)
{
a += d;
if(flag)
answer += a;
}
return answer;
}
주사위 게임 2
문제 설명
1부터 6까지 숫자가 적힌 주사위가 세 개 있습니다. 세 주사위를 굴렸을 때 나온 숫자를 각각 a, b, c라고 했을 때 얻는 점수는 다음과 같습니다.
세 숫자가 모두 다르다면 a + b + c 점을 얻습니다.
세 숫자 중 어느 두 숫자는 같고 나머지 다른 숫자는 다르다면 (a + b + c) × (a2 + b2 + c2 )점을 얻습니다.
세 숫자가 모두 같다면 (a + b + c) × (a2 + b2 + c2 ) × (a3 + b3 + c3 )점을 얻습니다.
세 정수 a, b, c가 매개변수로 주어질 때, 얻는 점수를 return 하는 solution 함수를 작성해 주세요.
제한사항
a, b, c는 1이상 6이하의 정수입니다.
통과 답안
#include <string>
#include <vector>
#include <cmath>
using namespace std;
int solution(int a, int b, int c) {
int answer = 0;
if(a==b && b==c){
answer = (a+b+c)*(pow(a,2)+pow(b,2)+pow(c,2))*(pow(a,3)+pow(b,3)+pow(c,3));
}
else{
if(a==b || a==c || b==c){
answer = (a+b+c)*(pow(a,2)+pow(b,2)+pow(c,2));
}
else{
answer = a+b+c;
}
}
return answer;
}
다른 사람 풀이
#include <string>
#include <vector>
#include <math.h>
#include <set>
using namespace std;
int solution(int a, int b, int c)
{
set<int> s{a, b, c};
if(s.size() == 3)
return a + b + c;
if(s.size() == 2)
return (a + b + c) * (pow(a, 2) + pow(b, 2) + pow(c, 2));
if(s.size() == 1)
return (a + b + c) * (pow(a, 2) + pow(b, 2) + pow(c,2)) * (pow(a, 3) + pow(b, 3) + pow(c, 3));
}
원소들의 곱의 합
문제 설명
정수가 담긴 리스트 num_list가 주어질 때, 모든 원소들의 곱이 모든 원소들의 합의 제곱보다 작으면 1을 크면 0을 return하도록 solution 함수를 완성해주세요.
제한사항
2 ≤ num_list의 길이 ≤ 10
1 ≤ num_list의 원소 ≤ 9
통과 답안
#include <string>
#include <vector>
#include <cmath>
using namespace std;
int solution(vector<int> num_list) {
int answer = 0;
int temp1 =1;
int temp2 =0;
for(auto i : num_list){
temp1 *= i;
temp2 += i;
}
if(temp1 < pow(temp2,2)){
answer = 1;
}
else{
answer = 0;
}
return answer;
}
다른 사람 풀이
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
int solution(vector<int> num_list) {
return accumulate(all(num_list), 1, multiplies<int>()) < pow(accumulate(all(num_list), 0), 2) ? 1 : 0;
}
이어 붙인 수
문제 설명
정수가 담긴 리스트 num_list가 주어집니다. num_list의 홀수만 순서대로 이어 붙인 수와 짝수만 순서대로 이어 붙인 수의 합을 return하도록 solution 함수를 완성해주세요.
제한사항
2 ≤ num_list의 길이 ≤ 10
1 ≤ num_list의 원소 ≤ 9
num_list에는 적어도 한 개씩의 짝수와 홀수가 있습니다.
통과 답안
#include <string>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
int solution(vector<int> num_list) {
int answer = 0;
string even_nums = "";
string odd_nums = "";
for(auto i : num_list){
if(i%2==0){
even_nums +=to_string(i);
}
else{
odd_nums += to_string(i);
}
}
answer=stoi(even_nums) + stoi(odd_nums);
return answer;
}
다른 사람 풀이
#include <string>
#include <vector>
using namespace std;
int solution(vector<int> num_list) {
int answer = 0;
int odd = 0;
int even = 0;
for(int i=0;i<num_list.size();i++)
{
int n = num_list[i];
if(n%2 == 0)
{
even*=10;
even+=n;
}else
{
odd*=10;
odd+=n;
}
}
answer = odd + even;
return answer;
}
마지막 두 원소
문제 설명
정수 리스트 num_list가 주어질 때, 마지막 원소가 그전 원소보다 크면 마지막 원소에서 그전 원소를 뺀 값을 마지막 원소가 그전 원소보다 크지 않다면 마지막 원소를 두 배한 값을 추가하여 return하도록 solution 함수를 완성해주세요.
제한사항
2 ≤ num_list의 길이 ≤ 10
1 ≤ num_list의 원소 ≤ 9
통과 답안
#include <string>
#include <vector>
#include <iostream>
using namespace std;
vector<int> solution(vector<int> num_list) {
vector<int> answer;
answer = num_list;
int end_value = num_list.back();
num_list.pop_back();
int before_value = num_list.back();
if(end_value > before_value){
int add_value1 = end_value - before_value;
answer.push_back(add_value1);
}
else{
int add_value2 = 2*end_value;
answer.push_back(add_value2);
}
return answer;
}
다른 사람 풀이
#include <vector>
using namespace std;
vector<int> solution(vector<int> v) {
// v.back(): 벡터의 마지막 요소
// *(v.end() - 2): 마지막에서 두 번째 요소
// 조건: 마지막 요소 > 마지막에서 두 번째 요소?
// 참이면: (마지막 요소 - 마지막에서 두 번째 요소)
// 거짓이면: (마지막 요소 << 1) → 비트 시프트 = 마지막 요소 * 2
// 조건에 따라 계산한 값을 벡터 마지막에 추가
v.push_back(
v.back() > *(v.end() - 2)
? v.back() - *(v.end() - 2)
: v.back() << 1
);
// 변경된 벡터 반환
return v;
}
수 조작하기 1
문제 설명
정수 n과 문자열 control이 주어집니다. control은 "w", "a", "s", "d"의 4개의 문자로 이루어져 있으며, control의 앞에서부터 순서대로 문자에 따라 n의 값을 바꿉니다.
"w" : n이 1 커집니다.
"s" : n이 1 작아집니다.
"d" : n이 10 커집니다.
"a" : n이 10 작아집니다.
위 규칙에 따라 n을 바꿨을 때 가장 마지막에 나오는 n의 값을 return 하는 solution 함수를 완성해 주세요.
제한사항
-100,000 ≤ n ≤ 100,000
1 ≤ control의 길이 ≤ 100,000
control은 알파벳 소문자 "w", "a", "s", "d"로 이루어진 문자열입니다.
통과 답안
#include <string>
#include <vector>
using namespace std;
int solution(int n, string control) {
int answer = 0;
for(char c : control){
if(c=='w'){
n+=1;
}
if(c=='s'){
n-=1;
}
if(c=='d'){
n+=10;
}
if(c=='a'){
n-=10;
}
}
answer = n;
return answer;
}
다른 사람 풀이
#include <string>
#include <vector>
#include <map>
using namespace std;
map <char, int> m = {{'w', 1}, {'s', -1}, {'d', 10}, {'a', -10}};
int solution(int n, string control) {
int answer = n;
for(char ch : control)
answer += m[ch];
return answer;
}
수 조작하기 2
문제 설명
정수 배열 numLog가 주어집니다. 처음에 numLog[0]에서 부터 시작해 "w", "a", "s", "d"로 이루어진 문자열을 입력으로 받아 순서대로 다음과 같은 조작을 했다고 합시다.
"w" : 수에 1을 더한다.
"s" : 수에 1을 뺀다.
"d" : 수에 10을 더한다.
"a" : 수에 10을 뺀다.
그리고 매번 조작을 할 때마다 결괏값을 기록한 정수 배열이 numLog입니다. 즉, numLog[i]는 numLog[0]로부터 총 i번의 조작을 가한 결과가 저장되어 있습니다.
주어진 정수 배열 numLog에 대해 조작을 위해 입력받은 문자열을 return 하는 solution 함수를 완성해 주세요.
제한사항
2 ≤ numLog의 길이 ≤ 100,000
-100,000 ≤ numLog[0] ≤ 100,000
1 ≤ i ≤ numLog의 길이인 모든 i에 대해 |numLog[i] - numLog[i - 1]|의 값은 1 또는 10입니다.
통과 답안
#include <string>
#include <vector>
#include <map>
#include <iostream>
using namespace std;
string solution(vector<int> numLog) {
string answer = "";
map <int,char> word_map = {{1 , 'w'},{-1,'s'},{10,'d'},{-10,'a'}};
for(int i=0; i<numLog.size(); i++){
if(i>=1){
int f1 = numLog[i];
int f2 = numLog[i-1];
int temp = f1-f2;
answer += word_map[temp];
}
}
return answer;
}
다른 사람 풀이
#include <string>
#include <vector>
using namespace std;
string solution(vector<int> numLog) {
string answer = "";
// numLog 의 첫 번째 값으로 시작 지점 설정
int nResult = numLog[0];
int nAdd = 0;
// 두 번째 값부터 마지막 값까지 순회
for(int nIndex = 1; nIndex < numLog.size(); nIndex++)
{
// 현재 값 - 이전 값 = 이동한 변화량 계산
nAdd = numLog[nIndex] - nResult;
// 다음 반복을 위해 현재 값을 이전 값으로 갱신
nResult = numLog[nIndex];
// 변화량에 따라 문자 기록
switch(nAdd)
{
case 1: // +1이면 'w'
answer.push_back('w');
break;
case -1: // -1이면 's'
answer.push_back('s');
break;
case 10: // +10이면 'd'
answer.push_back('d');
break;
case -10: // -10이면 'a'
answer.push_back('a');
break;
}
}
// 기록된 문자열 반환
return answer;
}
수열과 구간 쿼리 3
문제 설명
정수 배열 arr와 2차원 정수 배열 queries이 주어집니다. queries의 원소는 각각 하나의 query를 나타내며, [i, j] 꼴입니다.
각 query마다 순서대로 arr[i]의 값과 arr[j]의 값을 서로 바꿉니다.
위 규칙에 따라 queries를 처리한 이후의 arr를 return 하는 solution 함수를 완성해 주세요.
제한사항
1 ≤ arr의 길이 ≤ 1,000
0 ≤ arr의 원소 ≤ 1,000,000
1 ≤ queries의 길이 ≤ 1,000
0 ≤ i < j < arr의 길이
통과 답안
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
vector<int> solution(vector<int> arr, vector<vector<int>> queries) {
vector<int> answer = arr;
int length = queries.size();
for(int idx=0; idx<length; idx++){
int switch1 = queries[idx][0];
int switch2 = queries[idx][1];
int temp = answer[switch1];
answer[switch1] = answer[switch2];
answer[switch2] = temp;
}
return answer;
}
다른 사람 풀이
#include <string>
#include <vector>
using namespace std;
vector<int> solution(vector<int> arr, vector<vector<int>> queries) {
// queries의 각 원소 q는 [i, j] 형태의 인덱스 쌍
// arr[i] <-> arr[j] 위치의 값을 서로 교환(swap)하는 작업을 수행함
for (const auto& q : queries)
{
// q[0], q[1] 인덱스의 값을 교환
swap(arr[q[0]], arr[q[1]]);
}
// 모든 교환을 마친 arr을 반환
return arr;
}
수열과 구간 쿼리 2
문제 설명
정수 배열 arr와 2차원 정수 배열 queries이 주어집니다. queries의 원소는 각각 하나의 query를 나타내며, [s, e, k] 꼴입니다.
각 query마다 순서대로 s ≤ i ≤ e인 모든 i에 대해 k보다 크면서 가장 작은 arr[i]를 찾습니다.
각 쿼리의 순서에 맞게 답을 저장한 배열을 반환하는 solution 함수를 완성해 주세요.
단, 특정 쿼리의 답이 존재하지 않으면 -1을 저장합니다.
제한사항
1 ≤ arr의 길이 ≤ 1,000
0 ≤ arr의 원소 ≤ 1,000,000
1 ≤ queries의 길이 ≤ 1,000
0 ≤ s ≤ e < arr의 길이
0 ≤ k ≤ 1,000,000
통과 답안
#include <string>
#include <vector>
#include <iostream>
using namespace std;
vector<int> solution(vector<int> arr, vector<vector<int>> queries) {
vector<int> answer;
for(int j=0; j<queries.size(); j++){
int s = queries[j][0];
int e = queries[j][1];
int k = queries[j][2];
int min_k = 1000000;
if(s<= arr.size()-1 && e<= arr.size()-1){
for(s; s<=e; s++){
if(arr[s]>k && min_k > arr[s]){
min_k = arr[s];
}
}
}
if(min_k==1000000){
answer.push_back(-1);
}
else{
answer.push_back(min_k);
}
}
return answer;
}
다른 사람 풀이
#include <string>
#include <vector>
using namespace std;
vector<int> solution(vector<int> arr, vector<vector<int>> queries) {
vector<int> answer;
int min;
for(auto q:queries){
min = 1000001;
for(int i =q[0]; i<=q[1]; i++){
if(arr[i] >q[2] && arr[i]<min) min = arr[i];
}
if(min == 1000001) answer.push_back(-1);
else answer.push_back(min);
}
return answer;
}