인공지능캡스톤디자인 계획서

캡스톤디자인 번호	2022-02
캡스톤디자인 분야	Unity
캡스톤디자인 지도교수	노기섭 교수님
캡스톤디자인 명	(국문) 넥스트 플래그
	(영문) NEXT FLAG
캡스톤디자인 책임자	팀 명		ECG(Enjoy Collaboration Game)
	학 과		컴퓨터 정보 공학과
	성 명					학 번
	휴대폰					E-mail
캡스톤디자인 세부현황	번 호	세부역할			성 명		전 공		학 번
	1	디자인 작업
	2	사운드 작업
	3	프로그래밍
캡스톤디자인 수행기간	2022년 3월 18일부터 2022년 6월 10일 까지(13주)
2022년도 캡스톤디자인을 성실히 수행하고자 캡스톤디자인 계획서를 제출합니다. 2022 . 03 . 18 . 캡스톤디자인 책임자 서명 인공지능소프트웨어 전공PD 귀하

1. 요약서

※ 본 요약서는 캡스톤디자인 전체에 대한 요약서임. 캡스톤디자인 전체의 총괄적인 내용을 위주로 간략하게  1page 이내로 작성함

캡스톤디자인 번호	2022-02
캡스톤디자인 명	NEXT FLAG
캡스톤디자인 책임자

○ 캡스톤디자인 목표

C# 스크립트를 사용하는 Unity 게임 제작을 도와주는 엔진을 이용하여 플랫폼 액션 게임 장르의 게임을 제작하는 것이 이번 프로젝트의 목표입니다. 플레이어의 캐릭터 점점 올라가는 난이도의 스테이지를 클리어하여 최종 스테이지에 도착하는 것이 게임의 방향성입니다. 단순한 게임 플랫폼 게임의 장르는 금방 지루함에 빠지기 때문에 게임 전체적인 스토리를 제작하여 최대한 질리지 않도록 제작을 목표로 하고 있습니다.

○ 캡스톤디자인 내용

게임 제작에 도움을 주는 엔진은 대표적으로 Unity와 Unreal 엔진이 있습니다. 그중에서 편리한 유니티를 이용하여 누구든지 쉽게 할 수 있고 금방 적응할 수 있는 플랫폼 게임을 제작하는 것입니다. 게임 자체적으로 있는 현실에 불가능한 물리법칙을 이용해 각 스테이지의 다양한 퍼즐을 풀어 스테이지를 클리어해야 합니다.

○ 캡스톤디자인 개발에 따른 기대성과

플랫폼 게임은 누구든지 쉽게 할 수 있고 어려워하지 않는 장르의 게임입니다. 대표적으로 일본 닌텐도의 슈퍼 마리오 시리즈가 있듯이 세계적으로 흥행을 기록할 수 있는 게임의 장르입니다. 비록 간단하고 허술할 수도 있고 현대 사회에 안 맞는 게임의 장르일 수 있지만 특정 연령층과 pc와 안드로이드에서 동시적으로 즐길 수 있도록 개발하여 어디서든 짧은 소소한 시간에 사람들에게 즐거움을 줄 수 있는 게임이라 생각합니다.

캡스톤디자인 계획서

캡스톤디자인 명 :

캡스톤디자인 책임자 :

1. 캡스톤디자인의 필요성

요즘 게임 시장의 흐름은 한 가지 장르에 묶여있는 경우가 많이 있습니다. 양산형 게임들의 출시와 pay to win 시스템으로 게임을 이용하는 유저들은 쉽사리 어느 게임이든 아무거나 하려고 하지 않으려 합니다. 최근 게임 시장에서 양산형 저질 게임들을 고퀄리티 비주얼로 포장하여 매출에만 집중하는 게임에서 비록 인디게임이고 간단한 게임이라도 짧은 기간 자신의 노력으로 소소한 즐거움과 성취감을 느끼는 플랫폼 게임이 필요합니다.

2. 캡스톤디자인의 목표 및 내용

가. 캡스톤디자인 디자인 목표

과거 플랫폼 게임의 대표적인 게임은 소닉과, 슈퍼 마리오 시리즈입니다. 오락실부터 현재 데스크탑을 넘어 vr까지 현재까지 사랑을 받는 시리즈입니다. 물론 플랫폼 게임 장르 하나로 여기까지 오진 않았지만 최근 인터넷 게임 방송에서 많이 사랑을 받는 점프킹, 피코파크 둘 다 플랫폼 게임입니다. 과거 플랫폼 게임은 한가지 컨샙으로 인기를 얻었다면 현재 플랫폼 게임은 각 다른 컨샙의 재미와 다양한 방향성으로 인기를 끌고있습니다. 흔히 있는 컨샙과 다르게 다양한 컨샙의 플랫폼 게임을 제작하는것이 이번 프로젝트의 목표입니다.

나. 캡스톤디자인 내용

	게임의 전체적인 목적인 다양한 퍼즐의 스테이지를 클리어하는 것입니다. 클리어 할 수 있는 방법은 맵에 숨어있는 클리어 깃발을 획득하는 것 입니다. 깃발을 획득함으로써 해당 스테이지는 클리어 하고 다음 스테이지로 갈 수 있는 방법입니다. 그리고 해당 스테이지의 존재하는 텍스처 들을 모아 최종 스테이지로 갈 수 있는 최적의 조건을 얻는 것입니다.

3. 캡스톤디자인의 추진전략 및 체계

Unity Engine & C# 을 팀원 전체가 다룰 수 있도록 학습해보도록 합니다.

2. 게임의 장르를 플랫폼으로 선정하여 캐릭터 디자인과 시나리오를 구상합니다.

3. 게임 UI와 Unity 소스코드를 분석/작성합니다.

4. 작품을 중간중간 디버깅 테스트를 하며 팀원들 각자 맡은 부분을 수정합니다.

5. 기능에 문제가 없을 경우 최적화 작업을 합니다.

6. 이 모든 내용을 ppt에 담고 소스코드와 베타 테스트 내용을 녹화하여 발표를 합니다.

7. 전체 기능 및 베타테스트 실행 후 전체 테스트를 진행하여 작업을 마무리 합니다.

4. 캡스톤디자인의 기대효과 및 활용방안

현재 게임 시장은 큰프로젝트의 게임들의 점유율이 낮아지고 있습니다.

오히려 인디게임, 저예산 게임들의 흥행이 계속되고 있습니다.

뱀파이어 서바이벌, 점프킹, 피코파크 등등 저예산 인디게임들의 흥행의 이유는 인터넷 방송 미디어와

유튜브와 같은 미디어들에서의 인플루언서들의 플레이 리뷰를 통해 홍보 효과가 가장 크다고 생각합니다.

장기적으로 게임을 점유율을 유지시키기 위해 지속적인 업데이트와 난이도 조절 등 게임의 운영 쪽에 있지만

게임을 이용한 유저들의 직접적인 피드백을 수용하여 인디게임 개발자일지 언정 게임 플랫폼에서의 신용을 얻어

다음 프로젝트에서도 좋은 작품을 인정받을 수 있다고 생각합니다.

6. 작업분할 구조도(WBS, Work Breakdown Structure)

7. 소요장비 및 재료

장비 및 재료 명	규격	수량	용 도	비 고
개인용 컴퓨터 (PC)		3	작업
웹 브라우저 (Chrome)		3	정보 및 자료 검색
한컴오피스 한글 2010		3	문서 작성
Microsoft Power Point		3	PPT 제작
스마트폰		3	팀원 간 연락
Visual Studio Code		3	소스코드 작성
Unity		3	유니티 소스코드 작성
Microsoft Excel		3	일정 관리
Github Desktop		3	소스코드 관리
Unity Hub		3	엔진 버전관리
OBS Studio		3	영상 녹화
Zoom		3	온라인 화상회의
aseprite		3	디자인 이미지 제작

8. 캡스톤디자인 추진일정 계획

	세부 추진 내 용				추 진 일 정 (월)
					3	4	5	6
	1	팀 학습	기초	엔진 학습
	2	기능 구현 및 베타 테스트	기본	기초 기능
	3			통합
	4		응용	디자인 제작
	5			오류 수정
	6			통합 테스트
	7			유지/보수
	8
		:
		:
		:

9. 구매 품목

물품명 및 가격	스펙 및 부품	주소
게임 사운드	소프트웨어/음원 파일	미정
게임 디자인	소프트웨어/이미지 파일	미정
폰트	소프트웨어/폰트 파일	미정
총액	미정

캡스톤디자인 지도교수 동의서

번호	학번	성명	연락처	비고
1
2
3

위의 학생(들)에 대한 캡스톤디자인 지도교수 선임에 다음과 같이 동의합니다.

- 다              음 -

전 공	직 위	지도교수 성명	날 인
인공지능 소프트웨어	교수	노기섭

2022년   03월   18일

청주대학교 인공지능소프트웨어 전공PD 귀하

캡스톤 디자인 결과보고서(結果報告書)

목  차

1. 시스템 구조 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	16
1.1. 시스템 설계도 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	16
2. 유니티 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	17
2.1 유니티 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	17
2.1.1. 유니티 시작하기 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	17
2.1.2. 유니티 플랜 선택∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	17
2.1.3. 설치프로그램 다운로드 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	17
2.1.4. 유니티 설치 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	18
2.1.5. 유니티 로그인 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	18
2.1.6. 유니티 엔진 설치 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	18
2.1.7. 유니티 엔진 버전 설치 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	19
2.2. 유니티 설정 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	20
2.2.1. 유니티 배치 설정 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	20
2.2.2. VS CODE 연동 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	21
2.2.3. 테스트 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	21
2.3. 게임 플레이 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	22
2.3.1. 소스 코드 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	22
2.3.2. 테스트 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	22
3. 소스코드 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	46
4. 마무리 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	47
4.1. 향후 추진 계획 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	47
4.2. 정리 ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙	47

시스템 구조

1.1. 시스템 설계도

Reset : 오브젝트를 생성 후 인스펙터 뷰에서 리셋을 눌러줄 때 실행됩니다. 객체의 속성을 초기 값으로 설정해 줄 때 사용합니다.

Awake : 스크립트가 실행될 때 한번만 호출됩니다. 모든 오브젝트가 초기화 된 후 호출되기 때문에 GameObject.Find 같은 명령문을 안전하게 사용할 수 있습니다. Awake 함수는 언제나 Start 함수 전에 호출되므로 초기화 순서를 정할 수 있게 합니다. (Coroutine 사용 불가)

OnEnable : 인스펙터뷰에서 체크를 통해서 게임 오브젝트를 활성화 할 때 사용됩니다. 활성화 할 때마다 호출 됩니다.

Start : Update 함수가 호출되기 전에 한번만 호출됩니다. 다른 스크립트의 모든 Awake가 모두 실행된 이후에 실행됩니다. 스크립트가 활성화 되어 있어야합니다.

FixedUpdate : 주로 물리 엔진을 사용하는 경우 일정 시간 간격으로 힘을 가할 때 사용하는 함수입니다. 발생 주기가 일정합니다. (Default : 0.02초)Update : 프레임마다 호출되는 함수로 게임의 핵심 로직에 사용됩니다. 스크립트가 활성화 되있어야 사용 가능하고, 주기가 일정하지 않습니다.

LateUpdate : 모든 Update 함수가 실행되고 나서 호출됩니다. 주로 카메라 이동 로직에 사용합니다.

OnDisable : 게임 오브젝트 또는 스크립트가 비 활성화 되었을 때 호출됩니다. (Coroutine 사용 불가)

OnDestroy : 오브젝트 생존기간의 마지막 프레임이 업데이트 된 후 실행됩니다. (Object.Destroy 또는 씬 종료 시)

OnApplicationQuit : 응용프로그램 종료 전에 모든 오브젝트에서 호출됩니다. 에디터에서는 플레이 모드를 중지하면 나타납니다.

2. 유니티

2.1.1 유니티 설치

https://unity.com/kr/download로 접속후 시작하기 버튼을 클릭 합니다.

2.1.2 유니티 플랜 선택하기

무료 개인 라이센스를 사용해야 하므로 Personal 시작하기 버튼을 클릭 합니다.

2.1.3 설치 프로그램 다운로드

Windows용 다운로드를 클릭하여 다운로드를 합니다.

2.1.4 유니티 설치

동의함 > 설치 > 마침 버튼을 클릭하여 진행 합니다.

2.1.5 유니티 로그인

유니티 허브가 실행되면 회원가입 및 로그인을 하여 진행 합니다.

2.1.6 유니티 엔진 설치

유니티 엔진을 설치하기 위해 Install Editor 버튼을 클릭 합니다.

2.1.7 유니티 엔진 버전 설치

유니티 엔진 버전을 확인한 후 설치 합니다.

2.2 유니티 설정

2.2.1 유니티 배치 설정

게임에 만들기 앞서 자신이 사용하는 엔진을 편리하게 사용하기 위해 UI 배치가 중요합니다. 그래서 사용자가 작업할 때 불편한 점이 없도록 자신에게 맞게 배치를 해주는 작업입니다.

2.2.2 유니티 배치 설정

유니티는 C# 기반이면서도 게임 내 자체코드가 존재하기 때문에 VS CODE로 UNITY와 연동을 해야 코드를 해석할 때 편리한 작업을 할 수 있습니다. Edit > Preferences > External Tools 에 들어와 External Script Editor에 Visual Studio Code로 연동을 해주면 작업을 할 준비가 완료 됩니다.

2.2.3 테스트

게임 내 작업을 하면서 테스트를 할 경우 상단 중앙에 위치한 재생버튼을 클릭하여 Scene에는 고정화면으로 되어 있으며 Game 내에서 자신이 작업한 오브젝트를 확인할 수 있습니다.

게임 오브젝트 및 아이템(Game object & Item)

여러 가지 아이템 오브젝트를 배치하여 게임 내 배치되어 있는 아이템들을 습득하여 조건을 맞춰야 게임 클리어가 가능하게 설정 하였습니다.

1-1 소스코드

Coin.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36	cusing System.Collections; using System.Collections.Generic; using UnityEngine; public class Coin : MonoBehaviour { [SerializeField] private Sprite[] coinSprite; private SpriteRenderer spr; void Start() { spr = GetComponent<SpriteRenderer>(); StartCoroutine(PlayerAni()); } IEnumerator PlayerAni() { int idx = 0; while(true) { yield return new WaitForSeconds(.25f); spr.sprite = coinSprite[idx]; idx = idx < coinSprite.Length - 1 ? idx += 1 : 0; } } private void OnTriggerEnter2D(Collider2D collision) { if(collision.gameObject.tag == "Player") { SoundManager.Instance.PlaySound(Sound.coin); StageManager.Instance.coin += 1; Destroy(gameObject); } } }

Flag.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18	using System.Collections; using System.Collections.Generic; using UnityEngine; using System; public class Flag : MonoBehaviour { public FlagType flagType; public Action<GameObject> actionHandle; private void OnTriggerEnter2D(Collider2D col) { if (col.tag == "Player") actionHandle?.Invoke(col.gameObject); } }

EndFlag.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21	using System.Collections; using System.Collections.Generic; using UnityEngine; public class EndFlag : Flag { private void Start() { actionHandle = GameClear; } // 게임 클리어 깃발 오브젝트 void GameClear(GameObject player = null) { GameManager.Instance.GameClear(); } }

TeleportFlag.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32	using System.Collections; using System.Collections.Generic; using UnityEngine; using System; public class TeleportFlag : Flag { public Vector2[] targetPos; public int idx; private void Awake() { actionHandle = Teleport; } // 텔레포트 void Teleport(GameObject player) { Debug.Log("텔레포트 "); var _player = player.GetComponent<PlayerController>(); if(!_player.isTeleport) { SoundManager.Instance.PlaySound(Sound.flag); _player.isTeleport = true; player.transform.position = targetPos[idx]; idx = idx < targetPos.Length - 1 ? idx += 1 : 0; StartCoroutine(_player.Co_TeleportValueChange()); } } }

2. 몬스터(Monster)

게임 클리어를 방해하게 만드는 오브젝트로 게임을 하는 플레이어는 어떤 방식으로 몬스터를 피해 클리어를 해야 할지 생각을 하게 만들면서 구상 하게 되었습니다.

2-1 소스코드

Monster.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64	using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class EnemyTartetPos { public Vector2[] pos; public int idx; } public class Enemy : MonoBehaviour { public EnemyTartetPos pos; public Sprite[] spriteAni; private SpriteRenderer spr; public float speed; public EnemyDir enemyDir; private void Awake() { pos.idx = (int)enemyDir; spr = GetComponent<SpriteRenderer>(); StartCoroutine(Ani()); Debug.Log(name+" "+pos.pos[pos.idx]); } void Update() { if (!GameManager.Instance.isGame) return; transform.Translate((pos.pos[pos.idx] - (Vector2)transform.position).normalized * speed *Time.deltaTime); if(Vector2.Distance(transform.position,pos.pos[pos.idx])<= .1f) { pos.idx = pos.idx < pos.pos.Length - 1 ? pos.idx += 1 : 0; spr.flipX = !spr.flipX; } } IEnumerator Ani() { int idx = 0; while(true) { yield return new WaitForSeconds(.2f); spr.sprite = spriteAni[idx]; idx = idx < spriteAni.Length - 1 ? idx += 1 : 0; } } private void OnTriggerEnter2D(Collider2D collision) { if(collision.tag == "Player") { Destroy(collision.gameObject); GameManager.Instance.GameOver(); } } }

3. 게임 시스템 및 매니저(System & manager)

게임 시스템  : 게임 시작 전 UI를 담당하며 전체적인 게임 시스템을 담당합니다.

게임 매니저 : 클리어 방식의 게임을 보다 효율적이게 만들기 위해서는 유니티 에서 지원을 하는 게임 매니저를 사용하면 수월하게 시스템 자원을 관리할 수 있으며 용량을 줄일 수 있습니다.

3-1 소스코드

DataManager.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60	using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; public class DataManager : Singleton<DataManager> { public int currentStage { get { return PlayerPrefs.GetInt("currentStage", 0); } // 커런트 스테이지 로드 set { PlayerPrefs.SetInt("currentStage", value); // 커런트 스테이지 저장 } } public List<Stage> list; protected override void Awake() { base.Awake(); DontDestroyOnLoad(this); } public void Quit() { Application.Quit(); } public void NewGameStart() { currentStage = 0; // 최대 클리어한 스테이지 값 SceneManager.LoadScene("GameScene"); } public void LoadGame() { SceneManager.LoadScene("GameScene"); } public Stage SpawnStage() { Debug.Log("c s : " + currentStage); Stage stage = Instantiate(list[currentStage], list[currentStage].transform.position, Quaternion.identity); // 맵생성 stage.coin = stage.transform.Find("Coins").childCount; Debug.Log(stage.coin); return stage; } public int GetStageCoin() { return list[currentStage].coin; } }

GameManager.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76	using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviour { public static GameManager Instance; public GameObject gameClearPopUp; public GameObject gameOverPopUp; public GameObject gameAllClearPopUp; public Image fadeImg; public bool isGame = true; private void Awake() { //Time.timeScale = 1; Instance = this; } public void GameClear() // 게임클리어 { GameManager.Instance.isGame = false; if(DataManager.Instance.currentStage < 3) { DataManager.Instance.currentStage += 1; //스테이지 증가 } else if (DataManager.Instance.currentStage == 3) { fadeImg.gameObject.SetActive(true); gameAllClearPopUp.SetActive(true); } //Time.timeScale = 0; //타임 값 0 fadeImg.gameObject.SetActive(true); // Fade Image On gameClearPopUp.SetActive(true); // 게임 클리어 팝업 On } public void GameOver() { fadeImg.gameObject.SetActive(true); //Fade IMage On gameOverPopUp.SetActive(true); // GameOver PopUP On } public void GameAllClear() { fadeImg.gameObject.SetActive(true); gameAllClearPopUp.SetActive(true); } public void GotoLobby() // 로비씬으로 이동 { SceneManager.LoadScene("MenuScene"); } public void ReTry() // 죽었을때 재시작 { SceneManager.LoadScene("GameScene"); } public void NextStage() // 클리어했을떄 다음스테이지 시작 { SceneManager.LoadScene("GameScene"); Debug.Log("Next Stage"); } }

SoundManager.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23	using System.Collections; using System.Collections.Generic; using UnityEngine; public class SoundManager : MonoBehaviour { public static SoundManager Instance; public AudioClip[] clips; private void Awake() { Instance = this; } public void PlaySound(Sound sound) // 사운드 재생 { var obj = Instantiate(new GameObject()); obj.AddComponent<AudioSource>(); obj.GetComponent<AudioSource>().clip = clips[(int)sound]; obj.GetComponent<AudioSource>().Play(); } }

MenuManager.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18	using System.Collections; using System.Collections.Generic; using UnityEngine; public class MenuManager : MonoBehaviour { public void NewGame() { DataManager.Instance.NewGameStart(); } public void LoadGamde() { DataManager.Instance.LoadGame(); } }

StageManager.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54	using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class StageManager : MonoBehaviour { public static StageManager Instance; public Text coinTx; public GameObject player; [HideInInspector]public GameObject endFlag; private int _coin; public int coin { get { return _coin; } set { _coin = value; coinTx.text = _coin.ToString() + " / "+_stageCoin.ToString(); if(_coin == _stageCoin) { endFlag.SetActive(true); SoundManager.Instance.PlaySound(Sound.flagOn); // 클리어 깃발 등장 } } } private int _stageCoin; private void Awake() { if(Instance == null) { Instance = this; } } private void Start() { var stage = DataManager.Instance.SpawnStage(); // 스테이지 생성 endFlag = stage.transform.Find("Flags").Find("EndFlag").gameObject; _stageCoin = stage.coin; // 클리어해야할 코인값 coin = 0; // 계산할 코인값 player.transform.position = stage.playerInitPos; // 플레이어 위치설정 } }

Constant.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33	using System; [Flags] public enum PlayerState { None = 0, Idle = 1 << 0, Move = 1 << 1, Jump = 1 << 2, Drop = 1<< 3, Die = 1 << 4, All = int.MaxValue }; public enum FlagType { Normal, Teleport, EndGame } public enum EnemyDir { Left, Right } public enum Sound { coin, flagOn, flag, jump }

Singleton.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21	using System.Collections; using UnityEngine; public class Singleton<T> : MonoBehaviour where T : Singleton<T> { public static T Instance; protected virtual void Awake() { if (Instance == null) { Instance = (T)this; } else if (Instance != this) { Destroy(gameObject); } DontDestroyOnLoad(gameObject); } }

4.플레이어(Player)

게임에서 주인공 같은 존재이며 플레이어가 없는 게임은 거의 존재하지 않습니다. 다양한 조작키를 통하여 게임 내 조건을 달성하여 클리어를 하기 위한 필수적인 존재입니다.

4.1 소스코드

PlayerController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201	using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using System.Linq; [System.Serializable] public class SpriteInformation { public Sprite[] spriteArr; public bool isLoop; } [System.Serializable] public class PlayerSprites { public SpriteInformation idle; public SpriteInformation move; public SpriteInformation jump; public SpriteInformation die; public SpriteInformation drop; } public class PlayerController : MonoBehaviour { public PlayerSprites playerSprites; private SpriteInformation currentSpriteArr; private SpriteRenderer sr; private Rigidbody2D rg; public PlayerState ps; public float speed; private int idx; private float c = 0.1f; private float a = 0; /*[HideInInspector]*/public bool isTeleport; private void Awake() { sr = GetComponent<SpriteRenderer>(); rg = GetComponent<Rigidbody2D>(); } void Start() { ps = PlayerState.Idle; currentSpriteArr = playerSprites.idle; idx = 0; } void Update() { if (!GameManager.Instance.isGame) return; if ((ps & PlayerState.Die) != 0) return; a += Time.deltaTime; if(c <= a) { a = 0; sr.sprite = currentSpriteArr.spriteArr[idx]; // 애니메이션 재생 idx = currentSpriteArr.isLoop ? idx < currentSpriteArr.spriteArr.Length - 1 ? idx += 1 : 0 : idx < currentSpriteArr.spriteArr.Length - 1 ? idx += 1 : currentSpriteArr.spriteArr.Length - 1; } if(Input.GetKeyDown(KeyCode.Space)) { Jump(); } Move(); if(transform.position.y <= -10) { GameManager.Instance.GameOver(); } } public IEnumerator Co_TeleportValueChange() { yield return new WaitForSeconds(1f); isTeleport = false; } private void SetStateData(PlayerState AddState) { // (ps & PlayerState.Jump) != 0 << 값이 있음 // (ps & PlayerState.Jump) == 0 << 값이 없음 ps |= AddState; // idle << move << jump << die //jump // 값에 die가 없고 jump가 있으면 jump 애니 // move // 값에 die, jump가 없고 move가 있으면 move애니 // idle // 값에 move,jump,die 없고 idle있으면 idle 애니 if ((ps & PlayerState.Jump) != 0 && (ps & PlayerState.Die) == 0) { // 점프 데이터 if (currentSpriteArr != playerSprites.jump) currentSpriteArr = playerSprites.jump; } if ((ps & PlayerState.Move) != 0 && (ps & PlayerState.Die) == 0 && (ps & PlayerState.Jump) == 0) { // 이동 데이터 if (currentSpriteArr != playerSprites.move) currentSpriteArr = playerSprites.move; } if ((ps & PlayerState.Idle) != 0 && (ps & PlayerState.Die) == 0 && (ps & PlayerState.Jump) == 0 && (ps & PlayerState.Move) == 0) { // Idle 데이터 if (currentSpriteArr != playerSprites.idle) currentSpriteArr = playerSprites.idle; } } private void Move() { var h = Input.GetAxisRaw("Horizontal"); transform.Translate(new Vector3(h, 0, 0) *speed* Time.deltaTime); if (h == 0) // Idle로 상태변경 { ps &= ~PlayerState.Move; SetStateData(PlayerState.Idle); } else //Move로 상태변경 { SetStateData(PlayerState.Move); } if(h == -1) //좌우변경값 변경 { sr.flipX = true; } else if(h == 1) // 좌우변경값 변경 { sr.flipX = false; } } private void Jump() { if ((ps & PlayerState.Jump) != 0) return; SoundManager.Instance.PlaySound(Sound.jump); idx = 0; // 인덱스 초기화 rg.AddForce(Vector2.up * 6.8f, ForceMode2D.Impulse);// 점프 SetStateData(PlayerState.Jump); //상태변경 } private void OnCollisionEnter2D(Collision2D collision) { if(collision.gameObject.tag == "Block") //착지시 idle(가만히 있는 상태)로 변경 { ps &= ~PlayerState.Jump; SetStateData(PlayerState.Idle); } } }

5. 마무리

5.1. 향후 추진 계획

이번 프로젝트는 최대한 게임 입문자를 위한 간단한 스테이지, 흔히 튜토리얼 이라 부르는 형식의 스테이지로 구성하였습니다. 앞으로의 계획은 좀 더 난이도 있는 스테이지를 구현할 계획이며 챕터를 늘릴 계획입니다.

챕터가 늘어나고 스테이지 가 늘어날수록 다양한 오브젝트를 추가할 계획이며 몬스터 오브젝트 중에는 원거리 몬스터를 추가하며 일정 시간이 지나면 바닥이 꺼져 플레이어가 떨어져 사망하게 되는 함정 기믹을 계획 중에 있습니다.

5.3. 정리

이번 프로젝트를 진행하면서 게임 즐기는 것에 관심이 있고 직접 제작이나 구현을 해보지 못했었는데 각자 조원들과 유니티 엔진으로 게임을 개발하게 되면 자기가 좋아하는 분야에 관심을 둘 수 있어 수월한 작업이 될 것 이라고 예상했습니다. 하지만 처음 접해보는 엔진이며 생각하는 대로 구현되지 않아 난관을 겪었는데 그럴 때 마다 조원들끼리 유니티 컨퍼런스 영상이나 자료 등 각종 자료 수집을 통하여 학습하면서 진행하게 되었고 지금도 많이 부족하다고 느끼지만 게임 개발을 좀 더 수월하게 할 수 있게 되다보니 자신감을 얻으며 다양한 코딩을 접목해볼 수 있는 기회가 된 것 같아 뿌듯함을 느꼈습니다.