본문 바로가기
Unity Boot Camp

Udemy STARTERS (유데미 스타터스) Unity 취업 부트캠프 8주차 - Character controller, Audio 재생, 화면 캡처, retr0의 유니티 (Unity C#) 게임 프로그래밍 에센스

by 개발하는 디토 2022. 8. 14.

이번주는 강의 듣고, 구글링해서 원하는 기능 찾아보고, 원하는 기능 만들고, 다시 검색하고... 

수업 때는 토이 프로젝트를 진행했는데 그 과정에서 배운 몇 가지 유용한 내용을 정리하고자 한다.

retr0의 유니티 (Unity C#) 게임 프로그래밍 에센스

자습 시간에 retr0의 강의를 들었는데 강의 중 기록해둘만한 부분을 정리한다.

 

섹션 2 15번 강의

MonoBehaviour

Unity의 모든 component는 monoBehaviour를 상속함.

MonoBehaviour를 상속함으로써,

  • 컴포넌트로 게임 오브젝트에 추가될 수 있음
  • Unity의 통제를 받음
  • Unity 이벤트 메시지*를 감지할 수 있음

*Unity 이벤트 메서드

이름만 맞춰 구현하면 설정된 시간에 자동으로 실행되는 함수. Awake(), Start(), Update(), OnTriggerEnter() 등등…

 

Message

Unity 이벤트 메서드 역시 일종의 메시지를 보내는 것. Unity에서 메시지를 보낼 때, 받는 이를 생각하지 않고, 메시지를 받을 때 보낸 이를 궁금해하지 않음. 메시지에 명시된 기능을 받는 쪽에서 가지고 있으면 실행하고, 없으면 무시함.

유니티에서는 초기화 method 같은 것은 처음에 한번은 무조건 실행해야 함. Start(), Awake() 등을 활용함. 하지만 Unity 이벤트 method는 이름만 맞추어 스크립트에 적어두면 실행해야 할 타이밍에 자동으로 실행됨. 

 

Broadcasting

브로드캐스팅이란 메시지를 무차별적으로 많이 보내는 것. 메시지를 무차별적으로 모든 게임 오브젝트에 보내서, 그 메시지에 해당하는 기능을 가지고 있다면 해당 기능이 모든 게임 오브젝트에서 실행되게 할 수 있음. 즉, Walk()라는 함수를 가진 오브젝트가 10개 있고, 한번의 브로드캐스팅으로 Walk()라는 메시지가 어디선가 날아오면 그 모든 오브젝트의 Walk 함수가 실행됨. 모든 컴포넌트의 스크립트가 호출되는 것을 말함.

 

메시지 & 브로드캐스팅 시스템을 통해

게임이 시작되었을 때, 게임 중, 게임 끝났을 때 실행해야 할 기능(함수)을 굳이 복잡하게 어디선가 참조하지 않고도 알아서 실행되게끔 함. 따로 참조하지 않아도 Unity 이벤트 메서드는 알아서 불려 실행되므로 각 게임 오브젝트, 컴포넌트의 독립성을 보장함.

Unity의 모든 component는 MonoBehaviour 기반으로 작동하며 각 Component는 메시지를 받을 수 있고, 이 메시지가 요청하는 기능을 가지고 있으면 실행함.

 

섹션 3 17번 강의

부동소수점 float → 부정확, 대신 성능 good

고정소수점 double → 정확, 대신 성능 bad

게임은 워낙 성능이 중요한 분야이기 때문에 double을 쓰지 않고 float를 사용

 

섹션 3 19번 강의

Scope 함수의 범위

함수 밖에서는 함수 내부에서 선언한 변수에 접근할 수 없음

void 리턴(함수 외부로 알려줄 값)이 없음
함수 이름 앞에 자료형을 적을 경우 return 해당 자료형 변수를 통해 함수 외부로 값을 알려주어야 함.

 

섹션3 20번 강의

형변환(casting)

int → float 암묵적 casting 가능 float → int 암묵적 casting 불가능, 명시적 casting만 가능

조건문

C#은 if - else if - else의 조건문 키워드를 사용함 A && B : A, B 모두 참이어야 true A || B : A, B 둘 중 하나만 참이어도 true A ! = B : A, B 같지 않아야 true A == B : A, B 같아야 true

 

섹션3 21번 강의

switch-case : 검사할 조건이 많아 if문이 한없이 길어질 때 유용 조건이 아닌 변수가 들어옴

int year = 2022;
switch(year){
	case 2012:
		Debug.Log("레미제라블");
		break;
	case 2017:
		Debug.Log("트랜스포머5");
		break;
	default:
		Debug.Log("해당 없음");
		break;
}

 

섹션3 22번 강의

class와 object의 개념을 잘 정리해둠.

1) jack, nate, annie라는 class가 각각 설정되어 있고, 2) nate = jack; 으로 수정했을 때,

값을 복제하는 것이 아니라 참조를 하게 됨. jack의 정보를 수정하면 nate로 접근했을 때도 값이 바뀌어 있음. 값에 의한 참조가 아닌 실제 참조이기 때문.

Call by Reference 변수는 실존하는 대상(오브젝트)을 가리키는 화살표일 뿐. 참조만 함.

 

Pass by reference (원본 참조)  vs.  Pass by value (복제)

출처 www.penjee.com 구글링으로 찾았다

 

 

Character controller

Character controller component를 추가하면 Capsule에 Rigidbody 달아서 x, z 회전 constraint 주고, Ray 같은 것으로 캐릭터 오브젝트가 땅에 닿아있는지 체크하는 과정을 생략할 수 있다. Character component가 달린 오브젝트는 넘어지지 않으며, Character controller 컴포넌트에는 바닥과 닿아있는지 체크하는 함수가 내장되어 있다.

움직일 캐릭터(ex. Capsule)에 character component를 달아주고, 아래와 같은 코드를 달아주면 캐릭터가 땅에 닿았는지 체크할 수 있다.

땅에 닿았을 때만 점프를 하게끔 하기 위해서 지면과 캐릭터가 닿아있는지 확인하는 식으로 사용한다. 

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
    CharacterController cc;
    // Start is called before the first frame update
    void Start()
    {
        cc = GetComponentInChildren<CharacterController>();       
    }
    
	void Update()
    {
        var groundedPlayer = cc.isGrounded; // 바닥 체크하는 변수가 내장되어 있음.
        
    }    
}

 

 

Audio

배경음악

배경 음악을 재생하고 싶다면 아무 오브젝트에 Audio Source component를 달아주고 Audio Clip에 원하는 음악 소스를 넣어준다.

 

버튼 효과음

Hierarchy 창에서 UI-Button 추가 - 버튼에서 Audio Source component 추가 - 버튼 OnClick()에 + 버튼 눌러 버튼이 눌렸을 때 실행할 이벤트 추가

Button에 Audio Source component 추가 후 Audio Source를 버튼 OnClick() 이벤트 대상으로 넣어줌
AudioSource - PlayOneShot

버튼 자기 자신에 달린 Audio Source component를 빈 곳에 넣어주고 실행할 수 있는 함수 중 AudioSource 아래 있는 PlayOneShot을 누르면 버튼을 누를 때마다 효과음이 재생된다.

 

 

Screenshot

스크린샷을 찍으려면 스크린 크기와 같은 크기의 Render texture 변수를 만들고, 카메라에 있는 RenderTexture를 받아서 저장하면 된다. 

MonoBehaviour 없이 public static class를 만들어 스크린 저장을 하는 mehod를 만들어준다. 굳이 static으로 하는 이유는 별 거 없이 이 클래스를 프로그램 내 유일한 클래스로 만들어 인스턴스를 만들지 않고도 다른 클래스 어디서나 가져다 쓸 수 있게 하기 위함이다. 

using UnityEngine;

public static class SaveScreen
{
    public static void SaveRTToFile(RenderTexture rt)
    {
        //static RenderTexture
        // 전체 프로세스에 하나밖에 없어서 .active를 바로 쓸 수 있었던 것
        // RenderTexture.active : 실제 화면에 보이는 텍스쳐
        RenderTexture.active = rt;
        Texture2D tex = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false); // png 파일에 쓰일 재료
        tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); // 픽셀 읽어옴 (사각형, destX, destY) 넣어줌
        RenderTexture.active = null; // 비우기 // 이 캡처화면을 카메라에 띄우지 않기 위해

        // 1920 x 1080
        // 4등분  960 x 540 만들고 싶을 때
        // destX, destY 이용해서 어떤 픽셀을 어디에 받아올지 선택할 수 있음.


        byte[] bytes;
        bytes = tex.EncodeToPNG(); // texture를 사진으로
        
        string path = Application.persistentDataPath + ".png";
        System.IO.File.WriteAllBytes(path, bytes);
        Debug.Log("Saved to " + path);

    }
}

간단히 방법을 설명하면

  1. 외부에서 Render texture를 받아서 (scene 내 카메라로부터 보고 있는 장면의 RenderTexture를 뽑아옴) 
  2. 저장하고 싶은 스크린 샷 크기를 설정해 Texture 2D로 만들어두고
  3. 만들어놓은 Texture 2D에 가져온 Render texture 중 어느 부분을 담은 것인지 ReadPixels() 함수를 통해 선택한다.  
  4. EncodeToPNG() 함수를 통해 texture를 사진으로 변환하고
  5. System.IO.File.WriteAllBytes(경로, 사진) 을 활용해 원하는 경로에 저장한다.

ReadPixels( 스크린 크기, destX, destY ) : 스크린 크기를 넣어주고 그 중 x, y 픽셀 중 몇 번째 픽셀부터 받아올 것인지를 destX, destY 픽셀을 통해 결정한다.

 

이렇게 스크린샷을 저장하는 함수를 만들어놨으면, 이제 스크린샷 버튼을 눌렀을 때 해당 함수를 불러주기만 하면 된다. 

/// MonoBehaviour가 붙어 있는 class에 아래의 함수를 넣어준다.
/// 스크린샷 버튼 OnClick 이벤트에 이 함수를 달아준다.

    public void OnClickScreenShot()
    {
        // RenderTexture를 가져와서 캡처하는 방법
        Camera.main.cullingMask = LayerMask.GetMask("Default", "Water", "Ignore Raycast", "TransparentFX"); //UI 빼고 나머지
        RenderTexture rt = new RenderTexture(Screen.width, Screen.height, 1); // 스크린 가로, 세로, depth
        rt = Camera.main.activeTexture;
        SaveScreen.SaveRTToFile(rt);
    }

 

 

 

후기

이번 주에는 자습시간을 굉장히 알차게 보냈다. retr0 강의도 들었고, 만들고 싶은 것이 있어서 거의 자습시간을 구글링 & unity 만들기에 썼다. 

오브젝트의 직선 이동이 아닌 곡선 이동을 하고 싶었는데, 수업 시간에 강사님이 베지어 곡선을 언급했던 것이 기억났다. 그래서 유튜브 강의 보고 베지어 곡선을 만들었는데 오브젝트가 안 움직여서 삽질하다가 코드를 찬찬히 읽어보면서 해결했다. 유튜브 강의 보고 한 거지만 어쨌든 수업에서 가르쳐주지 않은 부분을 혼자 삽질하면서 해결해서 뿌듯했다. 

코드 긁어오다 보면 경로 때문에 삽질할 때가 많은데, 보조 강사님 덕분에 순식간에 해결하기도 했다. Asset 폴더에서 prefab 같은 것을 넣어놓고 게임 실행 중간에 불러오고 싶어서 Resources를 활용하는 코드를 긁어왔는데 안 되는 거다. 이건 좀 오래 걸릴 것 같은 감이 들어서 보조강사님에게 노트북 가져가니까 원인을 알 수 있었다. Resources라는 폴더를 만들었어야 하는데 안 만든 거다. 알고보니 코드를 긁어온 블로그의 코드블록 위쪽에 Resources 폴더를 Asset 폴더 안에 만들어달라고 설명이 적혀 있었는데 내가 급해서 제대로 안 읽은 거였다...ㅋㅋㅋㅋㅋ Resources가 특별한 폴더인 것은 아냐고 물어보면서 간단히 설명을 해주셨다. 

요즘 들어 게임 플레이 도중에 동적으로 이벤트를 추가하거나, 오브젝트를 추가하는 내용을 수업 시간에 자주 배우는데, 아직은 인스펙터 창에서 직접 아이템을 넣어주는 게 더 편하다...ㅋㅋㅋㅋㅋ 하지만 게임 중간에 생긴 오브젝트나, 컴포넌트를 달아줘야 하는 오브젝트가 너무 많은 경우에는 동적으로 코드를 이용해 작업하는 것이 훨씬 효율적이니 잘 배워야겠다는 생각이 든다.

작은 오류 하나에도 1시간씩 잡아먹는 Unity 초보지만 언젠가는 나아지겠지.

 

 

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

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

 

새로운 가능성의 시작, 유데미 x 웅진씽크빅

글로벌 최신 IT 기술과 실무 교육을 입문부터 심화까지! 프로그래밍, 인공지능, 데이터, 마케팅, 디자인 등 세계 최고의 강의를 경험하세요.

www.udemykorea.com

댓글