<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>mkccip</title>
    <link>https://mkccip.tistory.com/</link>
    <description>mkccip 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Tue, 27 Oct 2026 16:10:12 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>mkccip</managingEditor>
    <item>
      <title>3D 탑뷰 슈터 - 1</title>
      <link>https://mkccip.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;0.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제목 그대로의 게임을 만들어보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D: 2D가 아니라 3D다. 유니티의 3D URP 를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탑뷰: 1인칭 3인칭이 아니라, 탑뷰 방식으로 구현해보고자 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슈터: 총 게임이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제작하면서 여러모로 배울 점이 많을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브로 버전을 관리하고 협업 기능에 익숙해져보기로 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 깃허브 데스크톱을 이용하여 두 명이서 각자 게임 제작을 해본 바 있지만, 그때는 gitignore도 제대로 사용하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 리포지토리를 만들고, 내 컴퓨터 로컬 저장소에 클론한다. 그곳에 미리 생성해 둔 유니티 프로젝트를 잘라내어 붙인다. 유니티의 gitignore를 사용하도록 리포지토리를 설정해두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;381&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tSoVl/dJMcaiEHQvz/Pw0Sz3KW1V5YB7PwZmkkuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tSoVl/dJMcaiEHQvz/Pw0Sz3KW1V5YB7PwZmkkuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tSoVl/dJMcaiEHQvz/Pw0Sz3KW1V5YB7PwZmkkuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtSoVl%2FdJMcaiEHQvz%2FPw0Sz3KW1V5YB7PwZmkkuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;898&quot; height=&quot;381&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에셋 중 격자 무늬의 텍스쳐를 제공해주는 것이 있어서 가져와서 바닥에 깔았다. 전용 머터리얼에서 타일링을 하여 정수 단위의 좌표가 로 주인공(캡슐)의 위치가 텍스쳐의 흰색 점의 위치와 같도록 조정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캡슐의 scale을 1, 1, 1로 잡고, 바닥의 크기를 80x80, 타일링을 10x10으로 설정했다.&amp;nbsp;&lt;br /&gt;이런 수치들이 딱 맞아 떨어지는 지점을 찾아낸 건 우연이다. 텍스쳐 원본 이미지 파일의 규격, 캡슐의 크기, 바닥의 크기 같은 것을 수학적으로 미리 계산하여 에셋으로 사용했다면, 크기에 따라 정확한 수치들을 알 수 있었을 것이다. absolute scale 단위에서 조정한 에셋을 사용하였다면!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 low-level 의 게임 관리 시스템을 생각해보자. 그리고 그 위에서 다른 모든 것들이 동작하도록 해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리 시스템에 큰 관여, 종속이 없을 스크립트를 생각해보자-&amp;gt; 플레이어 조작 스크립트!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 movePosition을 사용했다. 그러나 너무 딱딱하게 움직이는 모습이 마음에 들지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 캐릭터가 움직일 때에 키에서 손을 때더라도, 아주 살짝 가속도 방향대로 조금 가서 멈추는 방식을 원했다. 따라서 rigidbody에 AddFroce 메소드를 사용하여 움직이고, 캐릭터의 rigidbody의 Linear Damping(이동 저항) 값을 조절해서 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #000000;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;    private void FixedUpdate()
    {
        Vector3 moveDir = new Vector3(moveInput.x, 0, moveInput.y);
        
        Vector3 nowSpeed = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);

        if(nowSpeed.magnitude &amp;lt; maxSpeed)
        {
            rb.AddForce(moveDir * moveSpeed, ForceMode.Acceleration);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력은 InputSystem을 통해 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 제공되는 input actions를 복제하여 가져왔다. 그리고 해당 에셋에 대해서 C# Class를 생성하도록 설정하고, 해당 클래스를 플레이어가 움직이는 스크립트에서 객체(인스턴스)로 만들어 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;============&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인풋 시스템을 통해 입력을 받아와서 내 스크립트에 써먹는 방법을 간단히 적어둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;1. Awake 수준에서 인풋 액션s c# 클래스의 객체를 생성하고, 원하는 액션에 함수를 구독한다. 차후 스크립트의 오브젝트가 파괴되는 순간, Ondestroy에서 함수를 구독 해제해주는 과정을 거쳐줘야 메모리 낭비, 누수를 막는다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;color: #000000;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;private void Awake()
{
    actions = new My_InputSystem_Actions();
    actions.Player.Move.performed += OnMovePerformed;
    actions.Player.Move.canceled += OnMoveCanceled;

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;2. 사용하려는 액션 맵을 활성화한다. 기본 값은 모든 맵들이 꺼져 있고, 내가 활성화해서 써야한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #000000;&quot;&gt;&lt;code&gt;    private void OnEnable()
    {
        actions.Player.Enable();
    }
    private void OnDisable()
    {
        actions.Player.Disable();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;3. 구독해둔 함수들에 대한 코드를 작성한다. 매개변수를 통해 값을 전달받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;color: #000000;&quot;&gt;&lt;code&gt;    private void OnMovePerformed(InputAction.CallbackContext context)
    {
        moveInput = context.ReadValue&amp;lt;Vector2&amp;gt;();
    }
    private void OnMoveCanceled(InputAction.CallbackContext context)
    {
        moveInput = Vector2.zero;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다식을 활용하여 간단하게 구현하고, 구독 해제 과정이 굳이 필요 없게 만들 수도 있다고 한다. 그러나 일단은 위와 같은 방법론을 유지한 채로 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;============&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방향 벡터를 정규화하는 과정을 거치지 않는 이유는, 방향을 받아오는 인풋 액션에서 Digital Normalized 해오도록 모드를 설정했기 때문이다. 이것은 키보드의 wasd와 화살표 입력에만 대응되어 있고, 그 외의 입력수단(조이스틱 등)에는 적용되지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;움직이는데 플레이어에 덜덜덜 떨림이 생긴다. 단순히 움직이기만 해도 떨렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 몰라서 카메라가 플레이어를 따라다니게 만든 뒤에 보아도 똑같이 떨렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이 문제의 발생 가능성이 두 개 중 하나라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. FixedUpdate와 카메라 사이의 불균형 2. addForce 로 힘을 가할 때 최고 속도를 제한하면서 발생.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 문제는 힘을 가해서 이것의 속도가 내가 원하는 속도를 넘어설 때에(if), 최대 속도로 제한시키는 코드를 짤 때 생긴다(=). 나는 이미 그것을 방지하기 위해서 최대 속도보다 낮을 때에만 addForce로 힘이 가해지도록 하는 방식을 사용하여 속도를 인위적으로 조작하는 행위 자체를 하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 문제가 원인이었는데, 정확히 문제를 설명하면, AddForce는 Rigidbody에 가해지는 물리적 상호작용이므로, 당연히 FixedUpdate에서 실행된다. FixedUpdate의 실행 주기는 초당 50회가 기본 설정이다. 그런데 화면의 렌더링 프레임은 60, 대부분은 그 이상(가변)이다. 따라서 화면의 새로고침 속도와 맞지 않아서 생기는 문제인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 jitter, 떨림 문제라고 한다. 어느 쪽이든 간에, 서로의 새로고침 주기가 같거나, 정수배 차이가 나지 않으면 발생하는 문제다. 화면 새로고침 주기마다 일정하게 물리적 움직임이 새로고침 되지 않고, 어떨 땐 1번, 어떨 땐 2번, 어떨 땐 0번... 화면이 새로고침 되는 순간과 불일치하기 때문에 떨림으로 보이는 것이다. (따라서 정수배 차이가 나는 경우에는 주기가 일치하므로 자연스럽게 보인다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 해결하려면 카메라가 화면을 렌더링해서 찍는 것을 FixedUpdate에 맞춰주면 간단하다. 시네머신 브레인(cinemachine brain)의 설정을 바꿔주면 된다. 그러나 최대 프레임이 50으로 제한되고, 캐릭터만 부드럽게 움직일 뿐, 원천적인 문제는 해결되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 새로운 해결책: Rigidbody에는 Interpolate 옵션이 있다. '보간', 즉 렌더링 카메라가 새로고침 되는 시점에 맞추어 항상 물체가 움직이도록 보이게, 물체 사이사이에 두 이동위치 사이의 위치로 보이게 해주는 것이다. 이전 프레임을 기반으로 보간, 다음 프레임을 예측해서 보간, 두 방식이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Interpolate: 현재 물리 틱의 위치와 직전의 물리 틱의 위치, 두 위치 사이의 위치를 선형 보간(Lerp)하는 형태로 화면에 보인다. 현재 틱을 사용해서 선형 보간하므로, 렌더링 상에 현재 틱의 물리 상황이 보이지 않고, 1물리 틱 이전의 화면이 보이는 문제가 있다. 확정된 두 점 사이의 보간이라 정확하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Extrapolate: 현재 물리 틱의 위치와 해당 물체의 속도, 가속도를 가지고 다음 물리 틱 위치를 예상하여 보간한다. 따라서 1물리 틱의 지연은 없다. 그러나 예측하여 보간 작업을 하므로, 순간적인 속도, 가속도 변화가 가해지는 순간에 예상하던 경로를 벗어나며 튕김 현상이 발생할 수 있다. 오차 발생이 잦다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 방식 중 무엇을 사용해야 하나? 일반적이면 그냥 interpolate를 쓰는 것이 좋다. 왜냐하면, 실제로 아주 조금의 물리적 움직임의 지연이 존재하나, 화면에 예쁘고 부드럽게 보이는 것이 중요하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Extrapolate를 '반드시' 사용해야 하는 순간은, 1물리 틱의 지연조차 쉽게 넘길 수 없는 초고속 상황, 멀티플레이에서 이미 네트워크 지연이 존재할 때에 지연을 조금이라도 줄여야 하는 상황, 이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작디작은 내 프로젝트에서 두 방식을 사용했을 때의 눈에 확! 띄는 차이는 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 interpolate 방식을 선택했다. 해결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시네머신 카메라를 사용하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목적: 편하기 때문이다! 타게팅하여 쫓아가거나, 특정한 경로를 따라 움직이게 한다던가... 모두 직관적이고 간단하다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시네머신은 패키지 매니저를 통해, 유니티 레지스트리 소스에서 검색하여 인스톨해야 쓸 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cinemachine Followe를 생성하여, 탑 뷰의 시선으로 카메라를 위치시켰다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1741&quot; data-origin-height=&quot;1023&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Naurf/dJMcaixYeQx/tQMK32eX6nh6LoOKLZ58oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Naurf/dJMcaixYeQx/tQMK32eX6nh6LoOKLZ58oK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Naurf/dJMcaixYeQx/tQMK32eX6nh6LoOKLZ58oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNaurf%2FdJMcaixYeQx%2FtQMK32eX6nh6LoOKLZ58oK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1741&quot; height=&quot;1023&quot; data-origin-width=&quot;1741&quot; data-origin-height=&quot;1023&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라의 섬세한 위치, 움직임 방식은 맵을 만들고, 적을 설정하고, 플레이를 지속하면서 수정해나가야 할 것이다. 일단은 임시적으로 거치만 해 둔 상태.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;follow target만 설정해두고 실행하여 움직여보았더니, Rotation control이 자동적으로 플레이어가 이동하는 방향을 향해 회전하도록 설정되어 있었다. 멋있게 보이긴 한다. 이런 걸 별다른 코드 작성 없이 쓸 수 있으니 시네머신을 쓰지...!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>유니티 게임개발/3D 탑뷰 슈터</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/26</guid>
      <comments>https://mkccip.tistory.com/26#entry26comment</comments>
      <pubDate>Sat, 5 Sep 2026 01:44:07 +0900</pubDate>
    </item>
    <item>
      <title>우주정거장 - 5 (왕)</title>
      <link>https://mkccip.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;정리하고 끝마치기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;21.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tKvNz/dJMcacj6vKe/g9I36FK9VrKddFpXXLNgt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tKvNz/dJMcacj6vKe/g9I36FK9VrKddFpXXLNgt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tKvNz/dJMcacj6vKe/g9I36FK9VrKddFpXXLNgt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtKvNz%2FdJMcacj6vKe%2Fg9I36FK9VrKddFpXXLNgt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;793&quot; height=&quot;385&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 UI를 만들었다. UI Toolkit을 사용했다. 군대에 있을 때에 간단하게 사용법을 알아둬서, 기억이 흐려졌지만 예전에 만들어 둔 것들을 보고 간단하게 모양과 기능만 살렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI 요소들은 클릭과 같은 상호작용에 써먹기에 너무 편리해서 좋다. 그리고 UI 요소의 Render Mode를 화면 overlay가 아니라, World Space로 하여 게임 속 실제적 요소로 쓰기 좋다. (동아리 공모전에서 방탈출 게임에 사용했던 방식!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;22.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라를 전부 시네머신으로 교체했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라의 연출적 움직임 구현, 가상 카메라의 서열로 메인 카메라 이동 등, 편리한 것이 많다는 걸 뒤늦게 찾아보고 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌표를 스크립트 상에 적어두는 것이 아니라, 시네머신 버츄얼 카메라를 미리 배치해두고, 스테이지를 옮길 때 마다 우열을 바꾸어주었다. 자동으로 스무스하게 카메라 구도를 이동해주니, 편하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Start를 누르고 1스테이지로 줌 인 되는 장면은 Dolly를 설치하여, 해당 spline을 따라가도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카트가 움직이는 방식을 구현하는 것이 어려웠는데, 인스펙터 상의 automatic dolly를 켜고 Method를 설정해둔 다음, 게임 오브젝트를 비활성화 해두었다가 켜서 움직이게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애초에 오브젝트가 활성화되어 있는 상태에서 움직이게 하려면, 따로 특별한 코드 몇 줄로 되는 것이 아니라, 일반적으로 오브젝트들 옮기듯이, 설치된 spline 좌표를 따라 transform을 움직여주어야 한다... 그냥 오브젝트 꺼져있다 켜지는 것으로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음부터는 시네머신을 기본적으로 사용할 것을 감안하고 시작해야겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxco4e/dJMcagz3SHG/ZzQ7QUQ4q0bpIr3skWhv4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxco4e/dJMcagz3SHG/ZzQ7QUQ4q0bpIr3skWhv4k/img.png&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;276&quot; data-is-animation=&quot;false&quot; style=&quot;width: 73.3224%; margin-right: 10px;&quot; data-widthpercent=&quot;74.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxco4e/dJMcagz3SHG/ZzQ7QUQ4q0bpIr3skWhv4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxco4e%2FdJMcagz3SHG%2FZzQ7QUQ4q0bpIr3skWhv4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1099&quot; height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhWf5E/dJMcadi84AC/LKKVGkN1amwZ4dAChRpJgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhWf5E/dJMcadi84AC/LKKVGkN1amwZ4dAChRpJgK/img.png&quot; data-origin-width=&quot;212&quot; data-origin-height=&quot;153&quot; data-is-animation=&quot;false&quot; style=&quot;width: 25.5148%;&quot; data-widthpercent=&quot;25.81&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhWf5E/dJMcadi84AC/LKKVGkN1amwZ4dAChRpJgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhWf5E%2FdJMcadi84AC%2FLKKVGkN1amwZ4dAChRpJgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;212&quot; height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔딩 씬을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨셉은 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'각 방마다 공조 시설을 작동시켜 환풍구를 통해 끝방으로 외계인을 몰아 넣었고, 도착한 끝방을 폭파시켜, 모두와 함께 우주 공간으로 날아가버린다.'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;엔딩.gif&quot; data-origin-width=&quot;2342&quot; data-origin-height=&quot;1116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtCoDF/dJMcaheLpyD/UrbI72IsQyQ7bOpzcNtnkK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtCoDF/dJMcaheLpyD/UrbI72IsQyQ7bOpzcNtnkK/img.gif&quot; data-alt=&quot;GIF로 쪄왔더니 용량 한계가 있다. 꼭 아래 영상을 참고해주시길 바랍니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtCoDF/dJMcaheLpyD/UrbI72IsQyQ7bOpzcNtnkK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dtCoDF/dJMcaheLpyD/UrbI72IsQyQ7bOpzcNtnkK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2342&quot; height=&quot;1116&quot; data-filename=&quot;엔딩.gif&quot; data-origin-width=&quot;2342&quot; data-origin-height=&quot;1116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;GIF로 쪄왔더니 용량 한계가 있다. 꼭 아래 영상을 참고해주시길 바랍니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=tAE6GZicdXY&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=tAE6GZicdXY&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=tAE6GZicdXY&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/blqEZ7/dJMb8RSa744/aAH4SBrXs4WX27T5b9iCxK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/d450Pk/dJMb8XkyNVE/wznaOKqYcGaHUuyh5kYzr0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;테스트 플레이&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/tAE6GZicdXY&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 것들이 당장은 적절하게 움직여준다. 디테일을 깎고, 겉모습을 중시하는 건 당장 할 일이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현하고 싶은 것은 모두 끝냈다. 애초에 게임성이 뛰어난 것이 아닌, 내가 공부한 베이직한 유니티 스킬을 십분 활용해보는 것이 목표였기 때문에, 불만은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 학기도 시작되었다. 나는 각성해야 한다. 학업에서 좋은 점수를 받는 것, 정당하게 쉬거나 여유를 가지는 것 외에 게으름을 부리는 시간을 줄이는 것, 그 때에 더 공부하고, 길을 찾고, 자기 계발을 해야만 한다. (운동도...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 남과 비교하여, 조급한 마음에 걸려 넘어지지만 않도록, 조심 또 조심하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글쓰기에 대한 욕망은 줄어들지 않는다. 한가지 확실한 것은: 게임 제작을 일로 받아들이는 상태이기에 내가 괴롭고, 글쓰기는 여가이기 때문에 내가 즐겁다는 사실이다. 그것은 역전될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 일 하나를 끝까지 밀어 붙여보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #557a74; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;별을 노래하는 마음으로&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #557a74; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;모든 죽어가는 것들을 사랑해야지&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #557a74; text-align: justify;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 나에게 주어진 길을 걸어가야겠다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>유니티 게임개발/우주정거장</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/25</guid>
      <comments>https://mkccip.tistory.com/25#entry25comment</comments>
      <pubDate>Sat, 5 Sep 2026 01:22:10 +0900</pubDate>
    </item>
    <item>
      <title>우주정거장 - 4</title>
      <link>https://mkccip.tistory.com/24</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;19.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 스테이지를 클리어한 이후, 다음 스테이지로 이동하는 흐름을 구현해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 스테이지마다 다른 건 적의 종류와 배치다. 나는 스테이지를 관리하는 스크립트에서 클래스를 새로 하나 만들어서 인스펙터 상으로 관리해보려 하였으나, Scriptable Object 기능이 이럴 때를 위해 존재한다고 생각이 들어서 사용해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786765501523&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using NUnit.Framework;
using System;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public struct spawnTransform
{
    public Vector3 position;
    public Vector3 rotation;
}

[System.Serializable]
public class EnemyDataforStage
{
    public string enemyName;
    public List&amp;lt;spawnTransform&amp;gt; spawnPos = new List&amp;lt;spawnTransform&amp;gt;();
    public bool isPermanent = false;
    public float timeLack;

}

[CreateAssetMenu(fileName = &quot;StageData&quot;, menuName = &quot;Scriptable Objects/StageData&quot;)]
public class StageData : ScriptableObject
{
    public List&amp;lt;EnemyDataforStage&amp;gt; enemyDataList;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소환되는 적의 종류를 기록하는 클래스(좌표와 회전을 동시에 기록하는 구조체)를 리스트로 삼는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌표와 회전은 처음에는 튜플로 해보려 했는데, 구조체로 해야 편집이 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fp8t2/dJMcaf1Vth6/5xaThYACxhgzI3M6ZCEs00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fp8t2/dJMcaf1Vth6/5xaThYACxhgzI3M6ZCEs00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fp8t2/dJMcaf1Vth6/5xaThYACxhgzI3M6ZCEs00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFp8t2%2FdJMcaf1Vth6%2F5xaThYACxhgzI3M6ZCEs00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;489&quot; height=&quot;500&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이지별 적 데이터를 만들어서, 에셋으로 저장하고, 스테이지 관리자 스크립트에 리스트에 에셋을 차례대로 넣어서 스테이지 데이터를 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이지 매니저 스크립트가 절실히 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적 생성 기능을 만들면서 필요성을 느껴(애초에 필수적인 것이었으므로...) 스테이지 매니저 스크립트를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이지 이동은 카메라를 좌, 우로 이동하면서 우주를 엿보이게 하는 방식으로 최초 구상이 되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라가 자리할 위치는 일일이 좌표를 입력해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 리스폰하는 로커의 스크립트도 저장해두었다. 각 요소의 순서는 스테이지 순서와 동일하게.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccJdjq/dJMcaixGi0s/UTLa3X7JlTavGAHaL2ggW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccJdjq/dJMcaixGi0s/UTLa3X7JlTavGAHaL2ggW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccJdjq/dJMcaixGi0s/UTLa3X7JlTavGAHaL2ggW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccJdjq%2FdJMcaixGi0s%2FUTLa3X7JlTavGAHaL2ggW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;475&quot; height=&quot;461&quot; data-origin-width=&quot;475&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라의 움직임은 코루틴에서 Lerp를 사용하고, AnimationCurve를 사용하여 움직임을 원하는 방향성을 가지도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로커가 제일 골칫덩이다. 어떻게 통제할까 고민했는데, 원형은 각각의 스테이지에 놔 두고, 타이밍마다 리스폰 관련 스크립트만 각각에서 켰다 껐다 해주기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이지가 끝남은 환풍구로 날려 보낸 적들이 모두 회수되는 순간이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장면 전환은 다양한 요소가 동시에 한데 어우러져 생겨나므로, 서로를 참조하고 호출할 일이 많아졌고, 이를 위해서 매니저급의 스크립트들에 대해서는 거의 전부에 싱글톤 기법을 적용하여 별 다른 조치없이 아무 곳에서나 호출하여 사용할 수 있게 해두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 연결성, 결합성이 높아지다 보니, 수정해야 할 것도 늘고, 흐름이 복잡해지는 경우도 잦다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 한 번 제대로 수정하여 완성해두니, 그 틀에 맞추어 스크립터블 오브젝트를 만들고, 새 맵을 만들고 하면 별 다른 조치 없이 잘 작동하지만, 결국 서로 중추적으로는 다른 역할을 맡으나, 때때로 서로의 도움이 필요한 경우가 존재한다는 이유로, 서로에 대한 의존성을 크게 늘려버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작은 프로젝트는 큰 문제 없으나, 조금 더 기능이 많아지고 규모가 커지고 싱글톤 남발 지옥에 당도해버릴지 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요할 때는 써야 하지만, 별로 좋은 습관이 아니라는 점을 이곳 저곳에서 주워들었기 때문이다. 실제로 그러한 지옥에 빠져보면서&amp;nbsp; 문제가 몸소 체감된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대안책으로 옵저버 방식의 디자인 패턴을 주로 제시하였다. 방송(broadcast) 방식으로 각 함수를 방송에 구독시켜 실행되도록 하는 것이다. 일단 프로젝트가 사실상 완성되었다 판단된 이후에, 전체적으로 해당 방식으로의 수정 작업을 시작해보아야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>유니티 게임개발/우주정거장</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/24</guid>
      <comments>https://mkccip.tistory.com/24#entry24comment</comments>
      <pubDate>Sat, 15 Aug 2026 13:19:39 +0900</pubDate>
    </item>
    <item>
      <title>우주정거장 - 3</title>
      <link>https://mkccip.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;14.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터가 증발하며 사라지는 연출은 쉐이더를 변경하는 것으로 구현했는데, 게임을 빌드하여 실행해보니, 쉐이더가 바뀌면서 텍스쳐들이 잘 넘어가지 않는 문제, 설정값들이 잘 넘어가지 않는 문제를 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로의 레퍼런스 명칭(_BaseMap 같은 거)이 같으면 자동으로 넘겨준다고 알고 있었는데, 에디터 상에서 실행할 때와는 어떤 차이가 있는지, 잘 되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 쉐이더가 아니라, 머터리얼 자체를 교체해버리는 방법을 사용했다. 머터리얼 단위로 쉐이더부터 텍스쳐까지 미리 설정해두면, 머터리얼 교체로 확실하게 문제 없이 바꿔줄 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킨 렌더러에서 머터리얼들의 목록을 .Mateirlas 로 배열의 형태로 받아온다. material 형식의 배열에 담긴 머터리얼들을 하나하나 바꿔준 다음, 다시 skin.materials = ~ 로 바뀐 정보를 넣어줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(문득 의문: 어차피 덮어씌워 줘야하는 거면, 처음에 기존 머터리얼 받아오는 과정 없이, 그냥 새 배열 만들어서 머터리얼 넣어두고, 나중에 덮어 씌우면 되는 거 아닌가 -&amp;gt; 맞았음! ;; 굳이 가져와서 하는 경우는 한, 두 개만 골라서 바꿔버릴 때에 하는 건데, 나는 전부 다 바꿀 예정이었으므로 그럴 필요 없었음. 하지 않는 편이 성능적으로 아주 조금 이득.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Renderer.materials와 sharedMaterials 프로퍼티(실질적으로 메소드)의 차이점: 전자는 복사본을 반환하고, 후자는 원본의 주소를 담아서 반환. 전자의 머터리얼의 속성을 변경하면 복사본을 변경하는 꼴. 후자는 머터리얼의 속성 변경이 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 머터리얼 자체를 교체하는 건 둘 다 배열의 원소를 통째로 바꿔줘도 불가능. 전자는 당연하고, 후자는 결국 참조가 담긴 새 배열이므로... 둘 다 최종적으로는 변경이 끝난 배열을 다시 프로퍼티에 = 로 할당해줘야 함.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;15.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 스테이지에서 모든 목표가 완수되면, 천장의 환풍구가 열리고, 그곳으로 주인공 제외 모든 적이 빨려들어가도록 만들고자 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능을 크게 두 가지로 구분하면 1. 목표 지점을 향해 (빨아)당기기 2. 특정 순간 이후로는 오브젝트를 씬에서 완전히 벗어나도록 밀어낸 후 파괴.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786500307067&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    void OnTriggerStay(Collider other)
    {
        Rigidbody rb = other.GetComponent&amp;lt;Rigidbody&amp;gt;();

        if (rb != null)
        {
            // 중심점을 향하는 방향 벡터 계산
            Vector3 direction = (suctionPos - other.transform.position).normalized;
            rb.AddForce(direction * pullForce * Time.fixedDeltaTime, ForceMode.Force); 
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당기는 건 벡터 계산으로 방향을 구한 뒤, ForceMode.Force로 지그시 힘을 가해 하늘을 향해 올라가도록 함. 적의 이동 스크립트는 비활성화. 대상 선정은 트리거 박스 콜라이더를 활성화하고, 안에 있는 콜라이더들 중 리지드바디를 가진 오브젝트를 전부 대상으로 삼음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786500332731&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; private void OnTriggerEnter(Collider other)
 {
     Rigidbody rb= other.GetComponent&amp;lt;Rigidbody&amp;gt;();
     rb.AddForce(new Vector3(0, 50, 0), ForceMode.Impulse);
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표지점은 실제 환풍구 위치보다 살짝 아래-&amp;gt;트리거 콜라이더에 감지될 만큼 올라오면, 위를 향해 순간적으로 힘을 가해 씬에서 배제시킴. 이후 파괴.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적을 생성할 때에, 오브젝트 풀링 방식을 한 번 이용해보기로 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리 만들어두고 재활용하므로 GC부담 줄임. 자주 등장하는 대상에 적용하면 아주 좋음. (총알같은거)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 크게 부담되지 않는 경우, 한 번이나 두 번 할 경우, 인스턴스 생성 방식을 써도 큰 문제 없겠음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트 풀링 방식을 위해서 필요한 단계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 생성해둔 오브젝트를 저장/관리하는 매니저 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 적 생성 코드를 매니저에 게임 오브젝트를 요청하는 방식으로 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 적 죽음 단계를 매니저에게 오브젝트를 돌려주는 방식으로 변경&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 오브젝트가 돌아왔을 때, 상태를 '초기화'하는 코드가 반드시 필요!!!!!!!(재활용하기 위함!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.1. 초기화시점은 돌아왔을 때, 아니면 새로 가져가진 직후? 후자가 코드 작성 편의성은 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 풀링 시스템 매니저 스크립트를 새로 만들었다. 스크립트 안에 한 종류의 적을 기록하는 클래스를 만들고, 해당 클래스를 리스트의 자료형으로 삼아, 에디터에서 적의 종류를 늘릴 수 있도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트는 인스턴스화하여 싱글톤 기법을 통해 어디서든지 접근하여 사용할 수 있게 해두었다. (접근 방식 변경 가능성 염두.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정보를 토대로 게임이 실행될 때, 모든 적들을 비활성화 상태로 생성해둔 뒤, 종류별로 생성한 큐에 넣어둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딕셔너리를 이용해서, 적의 이름을 문자열(string)으로 받아서 key 삼아 value로 게임 오브젝트를 큐에서 꺼내어 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트를 돌려 받으면, 다시 비활성화하고 큐에 넣어둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--&amp;gt;고민 및 문제 발생: 게임 오브젝트를 제공할 때, setActive를 한 다음에 곧바로 GameObject 자료형을 리턴하는 방식으로 만들었는데, 오브젝트를 받아 온 다음에 그것의 움직임과 회전을 변경하여서 사용하니, 씬이 로드된 직후에 위치와 회전이 내가 원하지 않는 곳으로 가 있는 경우가 잦았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 해결 자체는 간단하게, 게임 오브젝트를 매니저에게서 받아 올 당시부터, 이름 뿐만 아니라 위치와 회전값 정보를 제공하여, 활성화 이전에 tranform 정보를 반영해두고, 활성화해버리는 방식을 사용하여 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 정확하게, 왜 하필 맨 처음 오브젝트에 대해서만, 아까와 같은 방식을 사용했을 때에 그러한 문제가 발생하였는지 모르겠다. 공부가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(AI에게 물어보니, start에서 적 생성 코루틴을 실행하고, start 이후 곧바로 fixedupdate가 실행된다. transform.position은 옮겨졌지만, 물리 연산을 수행하는 rigidbody.position은 바뀌지 않았다! 적은 rigidbody 기반으로 움직이므로, 문제가 발생한 것.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(따라서 처음 이후에는 생명주기가 맞아 떨어지면서 위치가 잘 반영되는 것이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 풀링 방식으로 적을 생성하는 것에 성공했다. 새로운 매니저 스크립트를 만들고, 기존의 스크립트를 수정하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범용성은 중요하다. 적당히 범용성을 고려한 상태로 디자인해야, 확장할 때에 문제가 없다. 그렇다고 해서 너무 큰 차원에서 생각하다가 제대로 완성하지도 못하는 것 보다는, 뭐라도 제대로 만드는 게 더 좋다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;17.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방 끝에 있는 버튼을 누르면 클리어가 되도록 하는 걸 만들려고 한다. 버튼을 누르면 환풍기가 작동하고, 적들이 다 빨려 올라가도록 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 구현을 위해서는 아래의 과정이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 버튼 오브젝트 가지기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 캐릭터와 버튼이 상호작용하도록 코드 작성하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 적들이 환풍구에 잘 끌려가도록 하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼은 테스트 씬에서 직육면체와 원통을 합쳐서 대충 만들었다. 빈 부모 오브젝트 아래에 두 자식으로 배치함. 애니메이션 클립을 만들어서 버튼이 눌렸을 때에 실린더가 들어갔다 나오는 것 하나, 가만히 있는 일반 상태 하나(애니메이터에서 로직을 잘 짜내기 위함)를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터의 인풋 액션에 버튼 입력을 감지하는 'Interaction' 액션을 추가하고, E 버튼을 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼에게 캐릭터의 상호작용을 감지하는 스크립트를 넣어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터의 상호작용 스크립트는 1. 주변에 상호작용 가능한 객체가 있는가 2. E 버튼이 눌렸는가, 두 가지 조건이 동시에 검사되어야 한다. 1번 조건은 버튼에 콜라이더를 달고, 버튼의 태그를 Interactable로 설정하여 태그 검사를 하도록 한다. 그리고 OnTriggerEnter, OnTriggerExit로 감지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(OnTriggerStay를 사용하여, 영역 안에 있는 상태를 감지하고, 그 상태에서 E버튼을 누르도록 하려 했는데, 두 가지 문제가 생겼다. 하나는 OnTriggerStay는 콜라이더 영역 검사를 FixedUpdate주기에 맞춰 계속 하므로 최대한 사용하지 않을 필요성이 있음. 다른 하나는 이상하게도 E버튼을 누름에도, 코드가 간헐적으로, 비규칙적으로 작동하고 안하고를 반복하는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 문제는 Enter, Exit만 사용하고, 굳이 Stay조건이 없어도 되도록 여러 조건을 코드에 추가해주었다. 두번째 문제는, OnTriggerStay는 FixedUpdate고, 사용자 입력 감지는 Update의 주기로 돌아가므로, 서로 일치하지 않아 우연히 맞아 떨어질 때를 빼곤 입력이 무시되었던 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 OnTrigger~ 관련 코드는 항상 콜라이더에 대한 정보를 public한 변수에 저장하고 빼는 방식만을 사용하는 것이 사실상 정석이고, Update 함수 내에서 키보드 입력 및 해당 변수가 null이 아닌지 검사하여 상호작용할 수 있도록 하는 것이 올바르다!!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 조건에 따른 상호작용은 가능해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 고민이 생긴다: 상대방이 상호작용을 받을 때, 적의 종류는 여러가지가 될 텐데, 상호작용하기 위해서는 상대 콜라이더 오브젝트의 컴포넌트를 가져와서 함수를 실행 할 필요가 있음. 플레이어는 이름, 태그 따위로 가져올 적의 컴포넌트를 매번 구분해줘야 하는가? 적을 만들 때 마다 코드 수정이 필요한가?&amp;lt;&amp;lt;&amp;lt;&amp;lt; 최악!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1안: 상호작용을 인지하는 스크립트, 적 마다의 개별적 움직임을 담당하는 스크립트, 두 개를 상호작용 대상이 가지게 하자 -&amp;gt; 그렇게 하더라도, 결국 대상마다의 스크립트를 사용하는 과정에서 우려와 비슷한 문제 발생 가능성 있음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2안: 초안이 되는 상호작용 받이 클래스를 하나 만들고, 그걸 상속받아 대상의 개별 스크립트를 만드는 것!&amp;lt;&amp;lt;&amp;lt; 아주 바람직하고 좋은 아이디어!!!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다형성 성질을 실전에서 직접, 유용하게 사용한 나의 첫 사례인 것 같다. 내가 필요에 따라서 상속을 이용할 줄은 몰랐다. 어렵고 복잡하고 쓰임새를 알기 어려워도, 배워 놓으니까 확실이 쓸모가 있다. 기초적으로 여겨지는 요소들에 대해서는 특히나 개인 판단보다는, 일단 알아두는 편이 무조건 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스가 아니고, Interface를 하나 만들어서 Interact 함수를 꼭 가지도록 설계했다. 그리고 해당 인터페이스를 Monobehavior와 함께 상속받아 함수를 구현하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당장은 개별 상호작용 대상 관련 스크립트가 하나 뿐이지만, 나중에 더 생겨나더라도, 해당 인터페이스를 상속받고 함수를 구현해두면, 캐릭터 입장에서는 인터페이스의 이름으로 컴포넌트를 가져와서 저장한 뒤, Interact 함수만 실행하면 되니, 이전에 만들어 둔 코드 수정은 전혀 필요 없고, 상속과 구현만 조금 신경 써 주면 된다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786592826368&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using UnityEngine;	//인터페이스

public interface IBasicInteraction 
{
    public void Interact(); //상호작용 원형
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1786592938037&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System.Collections.Generic;	//버튼 상호작용
using UnityEngine;

public class FanButton : MonoBehaviour, IBasicInteraction
{
	...

    public void Interact()
    {
        anime.SetTrigger(&quot;Btn_Clicked&quot;);
  		...
        suction.SetActive(true);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1786593018698&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Update()	//캐릭터의 상호작용 코드
{
	...
    
    if (currentInteractable != null &amp;amp;&amp;amp; playerInput.actions[&quot;Interaction&quot;].WasPressedThisFrame()) { 
        currentInteractable.GetComponent&amp;lt;IBasicInteraction&amp;gt;().Interact();  
        //상호작용 스크립트 원형을 이용해서
        //수정 없이 늘 컴포넌트를 가져오고 함수를 실행할 수 있게!!
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상호작용 구현 후, 버튼을 눌러 환풍기가 작동하도록 하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 코드에 환풍기를 드래그 앤 드랍으로 종속시켜버리고, 활성화 상태를 조정하는 방식을 택했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 문제 발생: 적들이 하늘을 향해 떠오르긴 하지만, 여전히 앞으로 향해 나아가는 코드 때문에 결국 벽의 끝에 다다른다. 그보다 더 강한 힘으로 끌어 당겨 볼 생각도 했지만, 실제로 유체역학적인 빨아당김이 아니기에, 적당한 강도로 슬며시 당겨주는 게 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환풍기를 실행할 때, 적의 움직임 관련 코드를 전부 꺼버리기로 결심했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 적은 하나 뿐이지만, 앞으로 적의 종류 또한 늘려나갈 것이다. 따라서 위와 같이 인터페이스를 하나 만들어서 적 스크립트의 기본형을 만들어주고, 해당 컴포넌트 접근을 한 번의 코드로 가능하게 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적들을 풀링 방식으로 관리하도록 변경한 이후, 적들은 풀링 매니저의 자식으로 존재하는, 각각의 적을 모아둔 부모 오브젝트의 자식으로 존재한다. 따라서 환풍기가 작동되면, 큐에 모든 적들을 넣어버리고, 하나하나 꺼내면서 1. 적 움직임 스크립트 꺼버리기 2. 콜라이더의 트리거를 활성화하여 물리충돌이 따로 없게 해버리기. 를 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(적의 종류에 따라 콜라이더 트리거 활성화 여부는 다시 고려해보아야 하는 사안이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;18.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트를 짜며 테스트하는 도중, 캐릭터의 움직임에 비정상적인 요소를 발견해버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;걷다가 간헐적으로 자꾸 급감속하며 느려졌다가, 다시 원래대로 걷는 현상인데, 감속이 너무 심하지도 않아서 눈치채는게 늦었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(초기에 눈치채지는 못했지만, 감속은 바닥 블럭과 블럭을 넘어가는 틈새에서 발생하고 있었다... 이걸 미리 알았으면 좋았을텐데.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실험하기 위해 씬을 바꾸고 프리팹으로 플레이어만 가져와 다른 방해요소가 아무것도 없는 평지를 걷게 했다. 그러니 아무리 봐도 부자연스러운 모습이 없었다. 캐릭터의 스크립트나 컴포넌트 설정 문제가 아니라면, 뭐가 문제인가? -&amp;gt; 발을 딛는 콜라이더들의 문제일까? 로 귀결됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 씬에서 바닥 타일 틈새에서만 그 현상이 일어남을 목격하고, 바닥에 하나의 큰 콜라이더를 깔아놓은 뒤에 움직이도록 하였더니, 걸림 현상이 깔끔하게 사라져버림. 콜라이더 사이를 넘어갈 때의 문제였음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에셋을 가져와서 맵으로 쓰다보니 발생한 문제. 해당 에셋은 작은 단위의 조각을 조립하는 방식을 지원하였는데, 각 조각 하나 하나 마다 콜라이더를 가지고 있음. 콜라이더 수가 많은 것도 문제고, 콜라이더 사이를 오갈 때의 문제점도 확인되었으므로, 개별 콜라이더는 전부 삭제, 넓은 바닥 콜라이더 하나를 낮게 깔아서 문제 해결함!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;377&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBGzxG/dJMb99UWJdp/aZncTIAsBkUPFQHwJQvL30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBGzxG/dJMb99UWJdp/aZncTIAsBkUPFQHwJQvL30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBGzxG/dJMb99UWJdp/aZncTIAsBkUPFQHwJQvL30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBGzxG%2FdJMb99UWJdp%2FaZncTIAsBkUPFQHwJQvL30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;377&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>유니티 게임개발/우주정거장</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/23</guid>
      <comments>https://mkccip.tistory.com/23#entry23comment</comments>
      <pubDate>Wed, 12 Aug 2026 14:16:13 +0900</pubDate>
    </item>
    <item>
      <title>우주정거장 - 2</title>
      <link>https://mkccip.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;9.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라의 배치를 신경쓰기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 쿼터뷰를 원했는데, 점프해서 회피하는 경우, 거리감이나 높이감을 정확하게 하기 위해서는 1인칭이나 3인칭이 가장 적합하다는 걸 알았다. 하지만 나는 고정된 채로 좌, 우로만 움직이는 쿼터뷰를 원한다. 각도를 이리저리 조절해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 카메라가 옆에서 내부와 캐릭터를 비추어야 하므로, 한쪽 옆벽과 윗벽의 머터리얼을 변경하여 (알파 값 높은 것-&amp;gt;투명)투명하게 만들어서 보이게 했다. 또는 카메라의 Clipping Planes의 Near값을 늘려서 해결하려고도 했지만, 캐릭터가 점프하는 순간에 해당 경계를 넘어서 투명해져 단면이 보이는 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 완전히 새로운 해결책: 카메라가 렌더링하고 하지 않을 게임 오브젝트를 결정하는, Culling Mask를 사용했다. Culling Mask는 Layer들의 목록을 보이고, 해당 목록에서 체크된 것들만 해당 카메라에서 렌더링하는 방식이다. 카메라의 시야에 담길 부분들의 게임 오브젝트의 layer를 새로 생성한 레이어 하나로 지정해두고, 해당 레이어를 체크 해제했다. 그렇게 카메라 수준에서 문제 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터가 점프 중에 공중에서 '착지'하는 모션을 보이면서 속도가 0이 되어버리는 현상을 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해보니, Grounded 판별 변수가 상황에 따라서 전혀 변하지 않고, 계속 true값만을 가졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;황야의 test scene에서도 테스트해도 똑같은 상황이 일어났다... 플레이어 오브젝트 그 자체에 충돌 판정 오류의 원인이 있다고 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 폭발효과를 만들 때에 분리되는 사지의 콜라이더는 비활성화 되어 있다가-&amp;gt;폭발 이후에 활성화하므로 문제가 없다. (혹시 몰라 더블체크함.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 적과의 충돌을 감지하기 위해서 플레이어의 최상단 부모에 넣어둔 캡슐 콜라이더였다. 해당 콜라이더가 발끝까지 길어서, 발끝에 생성되는 충돌 감지 영역과 겹쳐서 계속 true값만 가지고 있던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌 판정 함수를 다시 살펴보면&lt;/p&gt;
&lt;pre id=&quot;code_1786326664330&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        grounded = Physics.CheckSphere(spherePosition, 0.2f, LayerMask.GetMask(&quot;Default&quot;),
            QueryTriggerInteraction.Ignore);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이어를 통해 구분하여 감지할지 말지 결정한다. 캡슐형 콜라이더가 속한 게임 오브젝트의 레이어를 새로 생성한 Player 레이어로 변경하니, 공중에서 정상적으로 ground가 false가 된다. 해결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사지가 흩부려진 뒤, 플레이어의 잔해가 이동 중인 적의 경로를 방해하는 문제가 발생했다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 그냥 무시하고 넘어가려 했는데, 잔해가 사라지는 과정 중에도 적이 앞으로 가지 못하거나 하는 것은 부자연스러우므로, 잔해에 대해서는 적'만' 그것의 물리적 상호작용을 무시하고 달려나갈 수 있도록 설계하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 스크립트 상에서 Physics.IgnoreCollision 코드를 사용하기. 단, 콜라이더들중 일 대 일로 무시하도록 설정하므로, 번거로울 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Project Settings에서 Physics의 Settings의 Shared의 Layer Collision Matrix를 설정해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dV9FMK/dJMcahZGbDS/kvdWK13wuarmZkGx7HW4n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dV9FMK/dJMcahZGbDS/kvdWK13wuarmZkGx7HW4n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dV9FMK/dJMcahZGbDS/kvdWK13wuarmZkGx7HW4n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdV9FMK%2FdJMcahZGbDS%2FkvdWK13wuarmZkGx7HW4n0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;693&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이곳에서는 레이어 단위에서 각각의 게임 오브젝트간의 충돌 여부를 설정할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ignore 레이어를 새로 만들고, Enemy레이어와의 충돌을 껐다. 이후 스크립트를 수정하여 폭발하여 날아가는 동안에, 잔해 오브젝트들의 레이어를 Default, Player에서 Ignore로 변경했다. 처음부터 Ignore로 설정해 두지 않는 이유는, 충돌하여 폭발하는 건 감지해야 하니깐!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제해결!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스카이 박스와 빛 문제를 해결하려 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 스카이 박스는 당연히 우주 공간의 그것으로 바꾸었다. 에셋 스토어에서 Geoff Dallimore의 'Real Stars Skybox Lite'를 다운받아 사용하였다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1763&quot; data-origin-height=&quot;989&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgvXgT/dJMcag7xEJw/kmTsNtuKmGdHmCTxPkIRWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgvXgT/dJMcag7xEJw/kmTsNtuKmGdHmCTxPkIRWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgvXgT/dJMcag7xEJw/kmTsNtuKmGdHmCTxPkIRWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgvXgT%2FdJMcag7xEJw%2FkmTsNtuKmGdHmCTxPkIRWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1763&quot; height=&quot;989&quot; data-origin-width=&quot;1763&quot; data-origin-height=&quot;989&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벽으로 완전히 감싸기도 했고, 스카이 박스도 바꾸니, Environment light로 인한 내부 조명이 사라졌다. 쓸모 없는 내부 그림자 문제 해결을 위해서 Directional light도 끄니, 완전히 어두워져버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 Spot 조명을 천장에 설치하려 했다. 밝기와 플레이어에게 실시간 그림자가 생기는 부분은 좋았다. 그러나, 이상하게도 씬 뷰에서와 게임 뷰에서는 '실행 전'에는 밝았는데, '실행 후'에는 갑자기 어두워져버리는 현상이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직도 정확한 원인은 잘 모르겠지만, 조명의 모드가 realtime이었던 것이 문제였나보다. Mixed로 바꾸어주고, 라이트맵을 구워주니, 실행 전/후의 변화가 없어졌다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Area light는 오직 baked된 형태만 지원하므로 실시간 그림자는 그려주지 못한다. 그래서 Spot light를 각 모서리에 가깝게 하여 4개 설치하여 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정 중에서 멍청한 실수를 하나 저질렀는데, 바로 라이트맵을 굽는 중에 lightmapper를 Progressive 'CPU'로 해두고 있었던 것이다! Lightmap Resolution이 50일 때에 2분이 걸렸는데, 경험이 부족하여 어느 정도에 어느 정도의 시간이 걸리는 지 몰랐다. 50으로 해 두어도 이 정도 걸리는데, 오브젝트가 더 많아지고 퀄리티를 올리려면, 신중하게 베이킹하고, 라이트맵들을 따로 백업해두고, 아니면 해상도를 극도로 낮게 하여 감만 보고, 이렇게 해야겠다고 생각했었다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실은 라이트 매퍼가 GPU로 설정되어 있지 않았던 문제였다! 설정만 바꾸어주니, RTX 5070은 2초가 안되어서 CPU로는 2분이 넘게 걸리던 작업을 끝냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끔찍한 실수이다! 잘 확인하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어의 죽음-&amp;gt;재조립 후 부활&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재조립 애니메이션 같은 건 엄두가 안 났다. 안쪽이 잘 보이지 않는 캡슐에서 튀어나오도록 하기로 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;투박한 맛이 있게, 죽을 때 마다 캐비넷에서 튀어나오도록 하면 재미있겠다고 생각하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb7Dqr/dJMcaa7rPsX/VykHCwkEwhKyGlEGNK7LuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb7Dqr/dJMcaa7rPsX/VykHCwkEwhKyGlEGNK7LuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb7Dqr/dJMcaa7rPsX/VykHCwkEwhKyGlEGNK7LuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb7Dqr%2FdJMcaa7rPsX%2FVykHCwkEwhKyGlEGNK7LuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;651&quot; height=&quot;356&quot; data-origin-width=&quot;651&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #141414; text-align: start;&quot;&gt;VIS Games의 'Locker' 에셋을 사용했다. 로커와 문으로 나뉘어져 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #141414;&quot;&gt;크기를 캐릭터와 맞추고, 우선 애니메이션을 만들었다. 문이 열리고, 닫히고, 가만히 있는 클립을 각각 만들었다.&lt;/span&gt;&lt;span style=&quot;color: #141414;&quot;&gt;(가만히 있는 클립은 굳이 왜? -&amp;gt; 애니메이터에서 애니메이션 클립 사이의 트랜지션을 만들 때, Entry에서 idle상태, 즉 대기 중 상태의 state에 넣어 둘 애니메이션 클립이 필요하기 때문이다. 그렇지 않다면, 곧바로 열리거나 닫히는 모션이 튀어나와버린다. Entry에서 다음 state 사이의 transition에는 condition을 넣을 수 없다!)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #141414;&quot;&gt;클립들의 Loop를 끄고, 문을 여는, 닫는 트리거를 각각 하나 씩 만들어서 트랜지션에 붙여주었다. 애니메이션을 잘 작동하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #141414;&quot;&gt;이미 프리팹화 되어 있는 플레이어를 instantiate하는 건 쉬운 일이었다. 그러나 언제 로커의 문이 열리고 닫히는지, 그 타이밍을 재는 것이 생각보다 어려운 일이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리거 설정의 어려움을 체감했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 적의 공격을 증발시키는 속성을 로커에도 달아두었다. Tag가 EraserWall이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어의 리스폰은 새로 스크립트를 작성하여, 로커에 컴포넌트로 달아두었다. 씬 내에 플레이어가 없음이 감지되면, 플레이어를 로커 내에 소환한 뒤, 열리는 트리거를 작동시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닫히는 트리거는, 로커 안쪽부터 문의 범위보다 조금 길게, 직육면체 콜라이더 영역을 설정했다. is Trigger를 체크해두고, 스크립트 상에서는 OnTriggerExit 함수를 이용하여 범위에서 벗어난 순간, 문을 닫는 트리거를 작동시켰다. 거기다가, 캐릭터가 문을 나선 이후에 다시 해당 콜라이더로 진입하여 나갔을 때에 트리거를 건드리지 않게 하기 위해서, 콜라이더를 껏다켰다 해보려고도 했으나, 쉽게 bool 변수를 하나 만들어서, 막 소환되어 문 열고 나갈 수 있으면 true, 한 번 나간 뒤에는 false로 하여 스크립트가 작동하지 않게 해 두었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 좋았다. 문제는, 문이 열려있는 상태에서 어떠한 이유로 죽어버리면, 닫힘 없이 열려있는 상태로 소환되는 게 보일 뿐더러, 이후에 문이 닫히면, 문 열림 트리거는 불이 들어와 있는 상태이므로, 혼자서 멋대로 먼저 열려버리는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 플레이어의 소실이 확인되는 즉시, 문 열림 여부를 확인 후, 열려 있다면, 닫아버리도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 경우의 수, 분기를 잘 따져보는 것, 그리고 애니메이터의 트리거 속성은 한 번 불을 올리면, 그것이 사용되기 전까지는 혼자서는 내려가지 않는다, 는 사실을 잘 인지하고 있어야 겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>유니티 게임개발/우주정거장</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/22</guid>
      <comments>https://mkccip.tistory.com/22#entry22comment</comments>
      <pubDate>Mon, 10 Aug 2026 11:17:11 +0900</pubDate>
    </item>
    <item>
      <title>우주정거장 - 1</title>
      <link>https://mkccip.tistory.com/21</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;내가 학습하고 있는 내용들을 종합해서, 관련 기능들을 구현하는 것을 중점으로 두는 게임 제작을 해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 정도 진전이 있을 때 마다 기록을 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5BsrG/dJMcaf1Pdzc/D6vsVFbYl6kckwynsZYGx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5BsrG/dJMcaf1Pdzc/D6vsVFbYl6kckwynsZYGx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5BsrG/dJMcaf1Pdzc/D6vsVFbYl6kckwynsZYGx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5BsrG%2FdJMcaf1Pdzc%2FD6vsVFbYl6kckwynsZYGx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;729&quot; height=&quot;397&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 구현이 중점이라지만, 그것들을 잘 버무려서 '게임'의 형태로 빚어내고자 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 아이디어가 필요하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D에, 내가 학습한 것들이 모두 적용될 만한 주제... 사실 학습한 것들이 모두 게임 제작에 필수적인 것들이라서, 뭔들 만들면 다 쓰일 것임을 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어 사고의 흐름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 '우주'가 떠올랐다. 우주에서 뭔가 하는 걸 만들고 싶다 -&amp;gt; 우주정거장 안에서 무슨 일이 일어나는게 편리하겠다 -&amp;gt; 위기상황? 외계인 침투? -&amp;gt; 의도치 않은 외계생물 침투에 공격을 피하고 적을 공격하는 게임 -&amp;gt; 외계생물 침투가 일어나서(흔한 일) 사람은 대피하고, 로봇이 조립되어 점프 기반으로 회피 및 공격(어떻게?)하여 끝자락의 컨트롤 패널까지 도달, 고전적인 해결책(에이리언, 해치를 열어서 모든 외계 생물을 우주 밖으로 공기와 함께 날려보내기. 로봇도 날아가면서 끝.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어 완성!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최우선적으로, 플레이어 움직임을 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 3D 캡슐 하나를 세워두고, 다양한 방식으로 움직임이게 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 transform.Translate를 사용하였는데, 차후 적들과의 콜라이더 기반 상호작용을 할 거면 리지드바디 기반이 좋으리라 생각하였다. 가능하면 물리 엔진에게 여러모로 맡기는 편이 좋으리라 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리지드바디에 AddForce를 사용하면, Translate를 사용할 때와는 확연히 다른 부분이 두 가지 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a. 방향키를 누르는 만큼 가속될 때, 가속의 한계치를 따로 설정해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b. 방향기에서 손을 땔 때, 감속되는 방식과 미끄러짐을 해결해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a, b를 동시에 해결하기 위해, FixedUpdate에 넣을 속도조절용 함수를 하나 만들었다.&lt;/p&gt;
&lt;pre id=&quot;code_1786023636261&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void LimitVelocity()
{
    Vector3 flatVelocity = new Vector3(rb.linearVelocity.x, 0f, rb.linearVelocity.z);

    // 수평 속력(magnitude)이 지정한 최대 속도(maxSpeed)를 초과하는지 검사
    if (flatVelocity.magnitude &amp;gt; 5f)
    {

        // 초과했다면, 현재 방향(normalized)을 유지한 채 길이만 maxSpeed로 맞춘 새로운 벡터 생성
        Vector3 limitedVelocity = flatVelocity.normalized * 5f;

        // X, Z는 제한된 속도를 적용하고, Y는 기존의 낙하/상승 속도를 그대로 유지하여 덮어쓰기
        rb.linearVelocity = new Vector3(limitedVelocity.x, rb.linearVelocity.y, limitedVelocity.z);
    }

    if (flatVelocity.magnitude &amp;lt; prevSpeed &amp;amp;&amp;amp; flatVelocity.magnitude &amp;lt; 4.8f)
    {
        rb.linearVelocity = new Vector3(0f, rb.linearVelocity.y, 0f);

    }

    prevSpeed = flatVelocity.magnitude; 

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드의 중요한 부분은 y축은 0으로 두어 수평 속력 계산에 영향을 주지 않는 것, 그리고 제한된 속도를 적용할 때, y축의 속도는 다시 그대로 부여해주는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;maxSpeed를 5f로 설정했다. normalized되면 방향성만 남고 길이가 0이 되므로, 곧바로 속도 크기(속력)을 곱해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정지는 특정 속력 이하가 될 때, 그리고 '감속중'임이 확실할 때에 정지한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'감속중'임을 감지하기 위해, 직전 프레임 속도보다 현재 속도가 느림을 검사한다. (가속 중에 원하는 속력보다 낮다고 0으로 만드는 문제를 해결하기 위함.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감속 문제는 이후 마찰력 문제와 physical material 문제로 이어지는데, 해당 글에서 후술한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점프를 구현하려고 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인풋 시스템으로 입력을 받고, 캐릭터에 Player Input 컴포넌트를 넣어 스크립트 상에서 입력을 이용할 수 있게 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Player Input의 behavior는 Send Message이다. 따라서 스크립트 상에서 ('On' + 액션 이름) 의 조합으로 함수 이름을 지으면, 해당 액션에 해당되는 입력이 들어왔을 때, 함수가 호출되며, InputValue 형식의 value를 인자로 받아온다.&lt;/p&gt;
&lt;pre id=&quot;code_1786067800694&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public void OnJump(InputValue value)
    {
        if (grounded)
        {
            rb.AddForce(Vector3.up * 8f, ForceMode.Impulse);
            anime.SetTrigger(&quot;Jump&quot;);
            //grounded = false;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수는 인풋시스템의 액션 이름이 Jump이다. 앞에 On을 붙인 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점프에서 가장 중요한 건, 역시 해당 캐릭터가 점프가 가능한 지 조건을 검사하는 것이다. 바닥에 땅을 딛고 있는가, 즉 bool 형식의 grounded 변수의 값을 통제하는 함수가 중요하리라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 이런 식의 로직을 떠올렸다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작할 때는 반드시 땅에 있을 것-&amp;gt;점프 가능-&amp;gt;점프 함수 실행 시 점프 중임-&amp;gt;점프 불가능-&amp;gt;땅에 닿음-&amp;gt;점프 가능 ... 반복.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 바닥에 닿음을 판단하는 코드가 필요했다. 2D 상에서는 보통 raycast를 이용했다. 3D에서도 불가능한 방법은 아니지만, 한 줄기의 ray가 어디서 어디를 향하는지에 따라 엄밀한 판단이 불가능해진다. (양 발 아래에 두 개의 레이가 각각 붙어있다면 모를까)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 새로운 방법을 모색하던 중, 상대적으로 가볍게 콜라이더 충돌을 판단하는 방법을 알게 되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1786068163766&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private void GroundedCheck()
    {
        Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y+0.1f,
            transform.position.z);
        grounded = Physics.CheckSphere(spherePosition, 0.2f, LayerMask.GetMask(&quot;Default&quot;),
            QueryTriggerInteraction.Ignore);
            anime.SetBool(&quot;Grounded&quot;, grounded);     
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Physics.CheckSphere / Physics.CheckCapsule / Physics.CheckBox&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중점을 정하고, 각 도형에 필요한 정보를 더 넣어주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임오브젝트에 매겨진 레이어를 검사하고, isTrigger 체크된 콜라이더는 무시하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 조건에 해당하는 게임 오브젝트가 함수 실행 당시에 포착된다면, true, 그렇지 않으면 false를 반환한다. GroundedCheck 함수는 FixedUpdate 상에서 조건없이 계속 호출하며, grounded 여부를 확인한다. (따라서 점프 코드에서 grounded 변수를 통제 할 필요는 전혀 없어졌다. GroundedCheck에서 쭉 감시중이고, 그 결과만 가져다 쓰는 방식이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;raycast를 사용할 때와 비슷하게, Gizmos를 통해 씬 뷰에서 검사용 Sphere를 띄워 보여줄 수 있다. 위치, 크기를 눈으로 미리 볼 수 있으므로 아주 유용하다.&lt;/p&gt;
&lt;pre id=&quot;code_1786068812312&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private void OnDrawGizmosSelected() //시각화
    {
        Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
        Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);

        if (grounded) Gizmos.color = transparentGreen;
        else Gizmos.color = transparentRed;

        // 에디터에서 오브젝트를 선택했을 때 바닥 판정 구체를 시각적으로 그려줌
        Gizmos.DrawSphere(
            new Vector3(transform.position.x, transform.position.y + 0.1f,
            transform.position.z), 0.2f);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바닥에 닿으면 색이 변하도록 되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바닥에 닿아 있는지는 판별이 끝났고, 실제로 점프를 위해서 위를 향하도록 힘을 가했다. 위에 있던 코드를 다시 가져왔다.&lt;/p&gt;
&lt;pre id=&quot;code_1786068932023&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public void OnJump(InputValue value)
    {
        if (grounded)
        {
            rb.AddForce(Vector3.up * 8f, ForceMode.Impulse);
            anime.SetTrigger(&quot;Jump&quot;);
            //grounded = false;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AddForce를 이용했다. 위를 향해(y좌표를 양의 방향으로 변화) 힘을 주어 점프하는 효과를 주고, 따로 상한을 정하지는 않았다. 단순히 AddForce 당시에 가해지는 힘을 조절했을 뿐이다. 그 외에는 물리엔진에 맡겨 중력에 어울리도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더 계층 구조를 정리하고, 새로운 기능을 시험하는 씬을 하나 새로 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 씬에서 기능을 하나 하나 만든 뒤, 메인 씬으로 가져오는 방식으로 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더는 에셋의 종류별로 정리하고, 또 테스트 씬에 쓰이는 것과 메인 씬에 쓰이는 것을 나누어 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에셋 스토어에서 3D 에셋을 가져와 겉보기를 꾸미려 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'Barking Dog'의 '3D Free Modular Kit'을 이용해 간단하게 공간을 조성하였고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티의 기본 에셋인 'Starter Assets: Character Controllers | URP'를 이용해 캐릭터 모델링 및 애니메이션을 이용했다. 캐릭터를 조작하는 코드는 새로 짠 위의 것을 가져다 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V6SIj/dJMcagfpdjl/UpkUpTdK6uMehXmmrOLByK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V6SIj/dJMcagfpdjl/UpkUpTdK6uMehXmmrOLByK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V6SIj/dJMcagfpdjl/UpkUpTdK6uMehXmmrOLByK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV6SIj%2FdJMcagfpdjl%2FUpkUpTdK6uMehXmmrOLByK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1432&quot; height=&quot;374&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벽면이 둥그스름하므로, 물리 엔진 특유의 끼임 현상이 때때로 발생하였다. 또한 점프에서 큰 문제가 있었는데, 점프 후 공중에 캐릭터가 떠 있는 상태에서 콜라이더가 있는 벽에 닿으니, 그대로 떠 있을 뿐 추락하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하고자 하는 문제는 세 가지.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a. 끼임 현상 해결: 맵을 구성하는 오브젝의 메시의 콜라이더가 아닌, 그 조금 앞쪽에 따로 '보이지 않는 벽' 역할을 하는 박스 콜라이더를 설치했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b. 점프 시, 공중에서 방향 조작 가능 문제 해결: 공중에서 캐릭터의 방향성을 조종할 수 있는 것은 게임에 따라 다양한 강도로 조절되는 것이다. 나는 우주 공간의 현실성을 살리기 위해, 어느 방향으로 점프를 하였다면, 착지하기 전 까지는 어떤 조작도 먹지 않도록 하고자 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786069975965&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    public void OnMove(InputValue value)
    {
        Vector2 moveInput = value.Get&amp;lt;Vector2&amp;gt;();
        inputDir = new Vector3(moveInput.x, 0f, moveInput.y);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786070293608&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    void Update()
    {
        GroundedCheck();
        UpdateFriction();
            
        if (grounded)
        {
            dir = inputDir;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786070004230&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    private void FixedUpdate()
    {
        if (!(dir.x == 0 &amp;amp;&amp;amp; dir.z == 0))    //이동이 일어나는 조건.
        {
            rb.AddForce(dir * 30f, ForceMode.Force);
            rb.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 15f * Time.deltaTime);
            anime.SetFloat(&quot;Speed&quot;, 3);
        }
        else
        {
            anime.SetFloat(&quot;Speed&quot;, 0);
        }
     }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 OnMove 내에서 방향을 받은 후 그 안에서 바로 AddForce를 하여 움직이도록 했다. 그러나 이렇게 하면 방향키를 누를 때, 땔 때 등, 입력이 가해질 때만 호출되는 함수 안에 있으므로 예상치 못한(무엇을? ) 문제 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 OnMove에는 입력값이 들어오면 받아두는 역할만 하고, Update에서 땅에 붙어있는 순간에만 FixedUpdate로 반영될 위치 정보를 전달해준다. 따라서 공중 상태라면, dir는 inputDir을 반영하지 않고, 처음 지시된 dir의 방향성을 가진 채로 추가 가속 없이 그대로 움직인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;c. 점프 후 콜라이더 벽에 붙을 시, 땅에 떨어지지 않는 문제.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b 문제의 해결책과 결합되어, 이전에는 벽에 붙어도 키 조작으로 떨어트릴 수 있긴 했는데, 이젠 조작이 불가능하므로 한 번 붙으면 완전히 조작 불능이 되어 버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니, 마찰력(Friction)에 의한 문제라고 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 콜라이더는 Physic Material을 가지는데, 물리 머터리얼이 설정되지 않은 경우는 기본값으로 설정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 머터리얼은 속성으로 Dynamic Friction, Static Friction, Bounciness, Friction Combine, Bounce Combine을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 세 속성은 float로 0~1로 조절 가능하고, 뒤의 두 속성은 Average, Multifly, Minimum, Maximum으로 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값은 0.6, 0.6, 0, Average, Average 이다. (프로젝트 세팅의 피직의 디폴트 머터리얼을 바꿔줄 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벽에 붙었을 때, 두 콜라이더 사이에 맞닿은 수직면은 서로 밀어붙이는 힘(공중에서는 해당 방향으로 계속 가려 하므로!), 즉 수직항력을 일으킨다. 마찰력은 표면이 닿은 상태에서 (수직항력) * (physic material)의 마찰 계수로 정해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 계산되는 마찰력 수치가 물체의 무게로 인한 중력의 힘보다 커지게 되면, 문제가 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벽 타기, 벽 비비기, wall sticking 문제로 알려져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결하기 위해서는, 피직 머터리얼을 만들어서 내부의 마찰력 속성을 조절해주고, 머터리얼을 지정해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;393&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RE9gc/dJMcajpyXkc/6dIBOnFwloBKfpCYo3Vafk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RE9gc/dJMcajpyXkc/6dIBOnFwloBKfpCYo3Vafk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RE9gc/dJMcajpyXkc/6dIBOnFwloBKfpCYo3Vafk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRE9gc%2FdJMcajpyXkc%2F6dIBOnFwloBKfpCYo3Vafk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;393&quot; height=&quot;176&quot; data-origin-width=&quot;393&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찰력을 0으로 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 새로운 문제가 발생하였다. 마찰력이 0이니, 캐릭터가 자연적으로 감속하지 못하고 자꾸 마지막으로 입력된 방향을 향해 미끌리는 현상이 생기는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도 경사면에서 고정되지 않거나, 이동하는 발판 위에 서면 같이 이동하지 않는 문제 등이 일어났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이에 대한 문제 해결도 필요하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 마찰력 수치를 1로 수정한 피직 머터리얼을 새로 하나 만든 뒤, 캐릭터가 공중에 든 상태인가, 땅에 붙었는가(grounded)를 토대로 피직 머터리얼을 '교체'해주었다. 공중에 떠 있으면 마찰력 0, 땅에 붙어 있으면 마찰력 1, 그렇게 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 해결책도 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;콜라이더를 두 개 만들어서 벽면에 비벼질 여지가 큰 몸통 부분은 마찰력 0, 직접 바닥에 닿는 발바닥에 새 콜라이더를 만들어 마찰력 1로 설정하는 것.&lt;/li&gt;
&lt;li&gt;또는 물리 엔진에 맡기지 않고, 스크립트 상에서 입력이 없을 시에 속도를 0으로 만들어 버리는 등, 인공적인 마찰력을 구현하는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1안이 제일 현실적으로 보이긴 하다. 내 방식은 아무래도 번거롭다! 그 사실만 머리에 넣어두고, 일단 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;날아오는 적을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 직육면체에 콜라이더를 넣고, 충돌 시에 tag를 검사하여 벽에 부딪히면 소멸, 캐릭터에 부딪히면 캐릭터가 소멸하도록 코드를 짰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터가 점프하여 피할 수 있는 높이에, 옆으로 돌아서 피할 수도 있게 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리팹으로 만들고, 지정한 좌표 중 랜덤으로 하나에 소환되도록 spawnManager를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 모델을 멧돼지의 형상으로 변경해두었다. 임시 적(enemy)이다. 튜토리얼 느낌?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐릭터가 공격받았을 때의 상태를 어떻게 할까, 고민했는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주인공이 로봇이므로 사지가 박살난 다음에, 다시 조립되는 방식 + 스테이지를 클리어할 수록 해당 스테이지에 로봇 재조립 장비를 활성화하여 체크포인트 삼기, 로 진행하기로 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사지가 분해되는 것을 구현하려면 확인해야 하는 것이 있다. Skinned Mesh Renderer에서 얼마나 많은 부위가 개별적으로 독립되어 있고, 각각에 머터리얼과 텍스쳐가 독립적으로 적용되어 있는가? -&amp;gt; 이건 모델러가 모델을 만드는 순간에 결정하는 것이고, 수정하기 위해서도 관련 지식이 필요하므로, 에셋 스토어에서 받아온 에셋이 그렇게 나눠져 있길 바랄 뿐이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 유니티 기본 에셋은 Body, Legs, Arms로 나뉘어져 있었다. 또한 다리와 팔도 하나의 맵을 쓰지만, 텍스쳐 파일 안에서 영역을 나누어 쓰므로, 분리할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciRiBj/dJMcahrVNrX/ei5L1bYq6dtTS6aoNwbkq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciRiBj/dJMcahrVNrX/ei5L1bYq6dtTS6aoNwbkq1/img.png&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;406&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;44.73&quot; style=&quot;width: 44.2077%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciRiBj/dJMcahrVNrX/ei5L1bYq6dtTS6aoNwbkq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciRiBj%2FdJMcahrVNrX%2Fei5L1bYq6dtTS6aoNwbkq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zhnrg/dJMcaixz02e/qffd6NJ22CoUe3RIfsV1fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zhnrg/dJMcaixz02e/qffd6NJ22CoUe3RIfsV1fk/img.png&quot; data-origin-width=&quot;454&quot; data-origin-height=&quot;339&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.6295%;&quot; data-widthpercent=&quot;55.27&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zhnrg/dJMcaixz02e/qffd6NJ22CoUe3RIfsV1fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzhnrg%2FdJMcaixz02e%2Fqffd6NJ22CoUe3RIfsV1fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;454&quot; height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽이 이상적이고, 오른쪽과 같은 경우는 메쉬에 매핑된 텍스쳐가 분리되지 않은 부분을 강제로 잡아 당겼기 때문에 발생하는 현상이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 부위에 box collider를 미리 잘 만들어두고, 비활성화 해두었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌이 발생하면, 박스 콜라이더들을 활성화하고, 애니메이터를 끈 다음(엄청 중요하다. 애니메이터는 움직임을 강제하므로, idle상태라도 그것을 유지하려 한다. 반드시 애니메이터의 영향을 받지 않은 상태여야 자유로운 움직임이 가능하다!) 폭발력을 가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1786074882122&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rb.AddExplosionForce(1f, transform.position, 1f, 2f, ForceMode.Impulse);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사방으로 날아가는 것 까지는 구현했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eDEKYF/dJMcahL8ypw/6p0lggLKFBwfwkkNixlxH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eDEKYF/dJMcahL8ypw/6p0lggLKFBwfwkkNixlxH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eDEKYF/dJMcahL8ypw/6p0lggLKFBwfwkkNixlxH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeDEKYF%2FdJMcahL8ypw%2F6p0lggLKFBwfwkkNixlxH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;354&quot; height=&quot;281&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처참한 모습.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로봇이니까, 몇 초 뒤, 부품들이 사라지고, 새로운 로봇이 조립되어 등장해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부품들은 증발하듯 사라지게 해야하는데, 단순히 투명해지는 것보다는 일렁이며 사라지길 바랬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜토리얼 중에 쉐이더 그래프로 디졸브 효과를 구현한 예시가 있어 그것을 이용해보려 하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URP/Lit 기반의 쉐이더 그래프를 만들고, 쉐이더를 교체하더라도 문제 없도록 base, normal, metallic map 등을 다 설정할 수 있게 만들었다. Alpha 값에 대해서만 simple noise와 float 속성의 'DissolveAmount'를 Step으로 섞어 달아 두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 스크립트 상에서 DissolveAmount 속성의 값만 0에서 1로 부드럽게 옮겨주면 자연스레 사라질 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;녹음 2026-08-07 140459.gif&quot; data-origin-width=&quot;234&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IwyLo/dJMcaazyqNk/UEIlwaVwJ5XqZCcAFXBWK1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IwyLo/dJMcaazyqNk/UEIlwaVwJ5XqZCcAFXBWK1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IwyLo/dJMcaazyqNk/UEIlwaVwJ5XqZCcAFXBWK1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/IwyLo/dJMcaazyqNk/UEIlwaVwJ5XqZCcAFXBWK1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;234&quot; height=&quot;164&quot; data-filename=&quot;녹음 2026-08-07 140459.gif&quot; data-origin-width=&quot;234&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786079221013&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private IEnumerator Dissolve()
{
    yield return new WaitForSeconds(2f);
    Material[] mats = skin.materials;

    foreach (Material mat in mats)
    {
        mat.shader = dissloveShader;
    }
    float elapsedTime = 0f;

    while (elapsedTime &amp;lt; 3f)
    {
        elapsedTime += Time.deltaTime;

        float currentDissolve = Mathf.Lerp(0f, 1f, elapsedTime / 3f);

        foreach (Material mat in mats)
        {
            mat.SetFloat(&quot;_DissolveAmount&quot;, currentDissolve);
        }
        //mats[0].SetFloat(&quot;DissolveAmount&quot;, currentDissolve);// 배열 문제가 아니야!
        yield return null;
        Debug.Log(&quot;In the middle&quot;);
    }

    foreach (Material mat in mats)
    {
        mat.SetFloat(&quot;_DissolveAmount&quot;, 1f);
    }
    
    Destroy(gameObject);

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의해야 할 점: 쉐이더 그래프의 속성을 스크립트 상에서 지정하고 사용할 때에는, 반드시 'Reference' 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;392&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8lleX/dJMcaf8zrFh/u8ISQc8bngEUYlGUbQyBhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8lleX/dJMcaf8zrFh/u8ISQc8bngEUYlGUbQyBhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8lleX/dJMcaf8zrFh/u8ISQc8bngEUYlGUbQyBhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8lleX%2FdJMcaf8zrFh%2Fu8ISQc8bngEUYlGUbQyBhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;274&quot; data-origin-width=&quot;392&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉐이더 그래프에서 속성을 선택하고 인스펙터를 살펴보면, Name에는 내가 지은 이름이 잘 들어 있다. 그러나 Reference에는 앞에 언더바가 추가되어 있다. 쉐이더 그래프 상에 보이는 이름과 스크립트 상에서 사용되는 이름이 다르다! 기본적으로 앞에 언더바를 추가하여 설정된다. 이름을 지으면 앞에 언더바 붙이는게 기본 설정이니, 꼭 주의를 기울여야만 한다.!!!!!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 Lit 쉐이더에 설정들을 다 넣어두고, 스크립트 상에서 쉐이더를 교체하면, 같은 레퍼런스값을 가진 놈들은 자동으로 넘겨받아 해당 텍스쳐나 값을 똑같이 가진다. 따라서 디졸브를 위해서 쉐이더를 바꾸더라도, Lit기반의 텍스쳐들은 각각의 맵으로 잘 넘어가서, 디졸브 효과가 일어나기 전에 표면이 보라색(오류)이 되거나, 어색해지는 문제가 없도록 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>유니티 게임개발/우주정거장</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/21</guid>
      <comments>https://mkccip.tistory.com/21#entry21comment</comments>
      <pubDate>Fri, 7 Aug 2026 12:45:54 +0900</pubDate>
    </item>
    <item>
      <title>플레이어 이동과 점프</title>
      <link>https://mkccip.tistory.com/20</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;플레이어 이동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Input System 사용만을 고려함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3D 환경을 고려함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전 정보&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Transform: 씬에 존재하는 모든 게임 오브젝트가 반드시 가지고 있는 컴포넌트(클래스)(빈 게임 오브젝트 생성을 해도 Transform은 포함되어 있다). 해당 게임 오브젝트의 위치, 회전, 크기 정보를 담고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rigidbody: 게임 오브젝트가 씬 내의 물리법칙을 따르도록 하는 컴포넌트(클래스). 중력, 질량 등을 설정하고 영향받는다.Collider 컴포넌트를 통해 충돌 처리를 하므로 보통 같이 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Update(): 매 프레임마다 호출됨. 프레임은 렌더링과 관련됨. 프레임은 렌더링을 통해 만들어지기 때문. 기기의 성능에 따라 렌더링 성능이 높으면, 프레임을 많이(적게) 생성하고(게임에 따라 다를 수 있다), 따라서 Update호출 횟수도 그에 맞춰진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FixedUpdate(): 프레임과 상관없이 고정된 시간 간격으로 호출됨. 프로젝트 세팅에서 값을 변경해줄 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Time.deltaTime: 어느 프레임의 다음 프레임을 생성하는 데에 걸리는 시간이다. 값이 실시간으로 변하는데, 바로 전의 프레임 생성 시간이 위 시간이 된다. 평균적으로 생각하면, 1초에 60프레임을 생성한다고 가정하면 Time.deltaTime 값은 1/60 일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Transform 을 통한 이동&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 오브젝트의 좌표에 직접 손을 대는 것. 물리적인 상호작용(충돌 등)에 크게 개의치 않는 상황에서 사용하는 이동법.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.1 transform.Translate: 트랜스폼 내부의 Translate 메소드 사용.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1785770922114&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Update()
{
    transform.Translate(moveDir * speed * Time.deltaTime, Space.World);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Update에서 사용함. moveDir은 당연히 정규화된 벡터(길이1)임. 맨 뒤에 Space,World, Space.Self를 통해 이동 기준을 절대, 상대로 선택할 수 있음.(생략하면 기본값 로컬.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Update는 프레임에 종속되어 호출됨. Time.deltaTime이 없었다면 생성되는 프레임에 따라 코드 호출 횟수가 가변적이게 되고, 그럼 속도도 가변적이게 되는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Time.deltaTime은 1초를 1초 동안 생성된 프레임 개수로 나눈 것으로 볼 수 있으므로, 분자 분모 곱해져서 1만 남게 됨. 속도가 모든 게임 환경에서 동일하게 됨. (코드 호출 횟수가 변하는 만큼 보정해주는 작업이 Time.deltaTime을 곱해주는 것.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방향성+속도만 있으므로, 코드가 호출되는 한 움직임을 멈추지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속도라는 게 비유인거지, 사실을 기존의 3차원 position에 방향성과 길이가 있는 벡터를 더한 걸 다시 position에 값 할당해주는 것과 같으므로, 코드가 호출될 때마가 speed의 값(벡터의 길이)만큼 '순간 이동'하는 것과 마찬가지. 즉 프레임 당, 코드 호출 당 위치가 불연속적으로 변함. &lt;u&gt;물리적인 연속을 띄지 않음. (얇은 벽 통과 문제 있음. 터널링 현상.)(그래서 물리적 엄밀함이 요구되는 때에는 사용 X)&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.2 transform.position에 곧바로 벡터를 더해주거나 값을 할당하기.&lt;/p&gt;
&lt;pre id=&quot;code_1785773393518&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Update()
{
    transform.position += moveDir * speed * Time.deltaTime;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 Translate와 완전히 같은 작용을 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1785774046837&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;transform.position = 
	Vector3.Lerp(transform.position, target.position, lerpSpeed * Time.deltaTime);
transform.position = 
	Vector3.MoveTowards(transform.position, target.position, moveSpeed * Time.deltaTime);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vector3.Lerp를 사용하면 거리에 비례하여 점차 이동속도가 느려진다. 무한 수렴 문제를 조심해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(주로 코루틴을 사용하여 n초 동안 a에서 b까지 이동을 구현.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MoveTowards는 등속 운동하여 목적지에 도달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 transform을 직접 건드리는 이동법은 물리적인 요소를 크게 고려하지 않는 경우(주로 rigidbody를 사용하지 않는 오브젝트)에 대해서만 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(transform을 건드릴 때 왜 FixedUpdate를 쓰면 안되는가? -&amp;gt; 렌더링 주기, 프레임 생성 주기에 맞추어 실행되므로 움직임이 자연스럽게 따라가주는 것이므로, FixedUpdate같이 프레임 생성 주기를 무시하는 경우에는 논리적인 이동은 균일하나 눈에 보이는 '화면' 수준에서 끊김 문제가 발생함.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Rigidbody를 통한 이동&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;유니티의 물리 엔진을 활용한 이동이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2.1 Rigidbody.linearVelocity 값 부여&lt;/p&gt;
&lt;pre id=&quot;code_1785815296862&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Rigidbody rb;
...
void FixedUpdate()
{
    rb.linearVelocity = new Vector3(moveDir.x * speed, rb.velocity.y, moveDir.z * speed);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어느 게임 오브젝트의 리지드바디가 가진 속도값을 지정한다. FixedUpdate 내에 쓰이므로 게임 오브젝트의 속도값이 주변 환경과 무관하게 코드의 Vector3 값으로 유지된다. (y값은 중력의 영향력을 그대로 받도록 기존의 값을 반영하게 써준다.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;당연히 일회성으로 한 번 호출하기만 하면, 해당 속도가 지정된 직후, 물리 환경의 영향을 받아 감속 등이 일어난다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(주로 비현실적인 움직임을 일으키므로 비추천됨. 1인칭 FPS에서 점프와 같은 일을 수행할 때에 즉각적인 속도 변화를 위해 사용됨.)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2.2 Rigidbody.AddForce 부여&lt;/p&gt;
&lt;pre id=&quot;code_1785815824223&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void FixedUpdate()
{
    rb.AddForce(moveDir * speed, ForceMode.Force);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;리지드바디에 반응하는 힘을 특정한 방향으로, 특정한 정도로, 특정한 방법으로 가하여 움직이게 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앞부분은 다른 것과 비슷하게 Vector3 값을 넣는 것이고, 주목할 만한 부분은 힘을 주는 방식을 적는 부분이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ForceMode.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;Force: 연속적으로 물리적인 힘을 가한다. 해당 오브젝트의 질량(Mass)의 영향을 받으며, 현실과 가장 유사하다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;Acceleration: 연속적으로 물리적인 힘을 가한다. 질량이 무시된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;Impluse: 불연속적으로, 순간적으로 힘을 가한다. 질량의 영향을 받는다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;VelocityChange: 순간적으로 힘을 가한다. 질량을 무시한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;연속적으로 힘을 가한다는 의미: 코드에 지시된 벡터값만큼이 1초 동안 물리계산 주기에 맞추어 나뉘어 가해져서, 최종적으로 가해지는 힘의 양이 같도록 한다. 따라서 FixedUpdate 내에 배치하여, 물리계산 주기에 맞게 코드가 실행되도록 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;불연속적이란?: 코드가 실행되는 단 1회에 코드상의 벡터값만큼의 힘을 그대로 가한다. Update, FixedUpdate에서 사용하면 안되겠다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AddForce를 사용하면 애매하게 미끄러지며 움직임이 딱 맞게 마무리되지 않는 경우가 빈번히 발생한다. 다양한 물리 요소의 개입 때문이다. 신경 써 주어야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.3 Rigidbody.MovePosition 목표 위치로 물리적인 이동을 수행&lt;/p&gt;
&lt;pre id=&quot;code_1785821948538&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void FixedUpdate()
{
    // 현재 위치 + (방향 * 속력 * 물리엔진 시간)
    Vector3 targetPosition = transform.position + (moveDir * speed * Time.fixedDeltaTime);
    rb.MovePosition(targetPosition);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력된 백터 상으로 이동한다. transform.position = ... 과 유사하나, 출발지점과 목표지점의 직선 경로를 계산하여 해당 경로를 따라서 빠르게 이동한 것으로 간주하여 물리 계산을 한다. (순간이동 한 것은 맞지만, 경로 상의 물리 계산은 빠트리지 않는 것. transform.position은 정말로 순간이동이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4 Rigidbody.position 값 부여하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;transform.position을 이용하는 경우, transform이동 후 물리 엔진의 데이터 동기화 과정과, 충돌 판정을 위한 주변 환경 캐싱 과정이 필연적으로 발생한다. Rigidbody.position을 사용하면 그러한 과정이 곧바로 리지드바디 단에서 계산되므로 성능 면에서 이점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Rigidbody 컴포넌트가 붙는 오브젝트라면, 리지드바디의 포지션을 사용하는 것이 이득이란 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;점프&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상술한 방식을 y축에 대해서 적용하여 다양하게 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Rigidbody.AddForce의 Impluse로 구현하기. 질량의 영향을 받아 점프하도록 할 수 있음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Rigidbody.linearVelocity에서 기존 이동관성은 유지하고 y값만 갈아끼우기. 일정한 높이와 타이밍을 유지함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Transform 기반 이동과 점프:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;물리엔진의 영향을 받지 않고 의도대로 정확히 움직임. 목표 좌표까지 명확하게 이동 가능.&lt;/li&gt;
&lt;li&gt;관성 없이 즉각적인 조작감.&lt;/li&gt;
&lt;li&gt;물리엔진 개입이 없으므로 성능 부하가 낮다.&lt;/li&gt;
&lt;li&gt;순간이동 문제, 물리적 상호작용 부재, 복잡한 상황에 대한 직접 구현.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rigidbody 기반 이동과 점프:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;물리엔진에 종속되어 있으므로 주변 환경 및 중력, 관성 등 자연스러운 움직임을 구현함.&lt;/li&gt;
&lt;li&gt;충돌 판정이 자연스럽고 쉬움.&lt;/li&gt;
&lt;li&gt;머터리얼 교체로 조작감이 자연스럽게 바뀜.&lt;/li&gt;
&lt;li&gt;물리 엔진 특유의 물리적 돌발상황, 관성 제어 문제, Transform 대비 연산 비용 문제.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 장단점이 확실하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트들의 용도에 따라 명확히 구분해서 사용하자.&lt;/p&gt;</description>
      <category>유니티 구현&amp;amp;기술</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/20</guid>
      <comments>https://mkccip.tistory.com/20#entry20comment</comments>
      <pubDate>Tue, 4 Aug 2026 02:08:28 +0900</pubDate>
    </item>
    <item>
      <title>소리</title>
      <link>https://mkccip.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;(개인적으로)가장 중요하게 생각하는 게임 속 요소이다. 음악, 효과음, 이게 적재적소에 쓰이는 것 만으로도 유저 경험(UX)를 압도적으로 끌어 올릴 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소리는 시각에 비해 간접적인 정보다. 시각이 주가 되고, 소리는 그리 깊게 주의를 들이지 않으면 부가 정보로 처리된다. 그러므로 자연스러움(당연함)을 추구할 때(발걸음 소리, 환경음, 마법 효과음 ... ), 또 본능적인(무의식적) 피드백을 줄 때(효과음) 소리는 아주 중요하다. 아주아주!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(audio&amp;nbsp;is&amp;nbsp;very&amp;nbsp;important&amp;nbsp;for&amp;nbsp;creating&amp;nbsp;a&amp;nbsp;&lt;u&gt;sense&amp;nbsp;of&amp;nbsp;believability&amp;nbsp;in&amp;nbsp;your&amp;nbsp;digital&amp;nbsp;experiences&lt;/u&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 속에서 소리는 크게 두 가지로 나눌 수 있다. 게임 속 환경에서 실제로 일어나는 것이냐(diegetic, 4의 벽 안에서 일어나는 일) 혹은 UI와 상호작용 하거나, 음악과 같은 효과음이냐(non-diegetic, 4의 벽 바깥에서 일어나는 일).&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 유니티 안에서도 소리에 관한 컴포넌트는 역할에 따라 크게 두 가지로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Audio Listener: 청자. 씬 내에 단 하나만 존재해야 한다. 따라서 카메라가 여러 개 있고, 여러 카메라로 사람을 비추더라도 각 카메라마다 리스너가 하나 씩 있을 수는 없다. 컴포넌트 속성이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Audio Source: 화자. 소리 파일을 담고 있고, 소리를 내는 역할을 담당한다. 용도대로, 수십 수백 개가 있을 수 있다. 오디오 포맷은 다양하게 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오디오 소스에서 모노, 스테레오, 5.1등의 설정을 해줄 수 있다. 세부적인 설정은 전부 오디오 소스에서 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션에 소리를 넣기 위해서는 애니메이션 클립의 원 에셋 단에서 조치가 가능하다. anim, fbx 파일 형태로 존재하는 애니메이션 파일에 Animation 옵션에 들어가면 클립 생성 등을 할 수 있다는 건 이미 이전에 다뤘다. 여기서 아래에 가면 Events 항목이 존재하는데, 해당 애니메이션을 프레임 단위로 보면서 원하는 프레임 위치에 마킹을 해두고, 여러 신호를 송출할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;1283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1T05r/dJMcag7q5gc/d9PtK6oEQi0QSYvOcckgz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1T05r/dJMcag7q5gc/d9PtK6oEQi0QSYvOcckgz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1T05r/dJMcag7q5gc/d9PtK6oEQi0QSYvOcckgz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1T05r%2FdJMcag7q5gc%2Fd9PtK6oEQi0QSYvOcckgz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;310&quot; height=&quot;1283&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;1283&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Function: 해당 애니메이션이 실행되면서 지정한 프레임에 도달하면, 해당 애니메이션이 실행되는 Animator 컴포넌트가 담긴 게임 오브젝트 자신 안에 소속된 스크립트들에 Function 에 적힌 이름의 함수가 존재하나 찾는다. 존재한다면 그것을 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 0개 라면 receiver 없음 에러, 1개라면 그것 실행, 2개 이상이라면(여러 스크립트에 동일한 이름) 그것들을 전부 실행해버린다. public이든 private이든 찾아서 실행한다. 그러나 외부에서 찾아와 실행하는 것이므로 public으로 해 두는 편이 좋을 것 같다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 방식으로 시그널링을 할 수 있고, 오디오 소스와 연결된 스크립트를 통해 소리 재생을 할 수 있다. 소리가 재생될 때에는, 어느 순간에 나는 소리를 조금씩 다르게 여러 개 준비하여, 리스트에 넣어 랜덤 재생되게 하여, 반복적이고 지루하지 않도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(예: 캐릭터가 발을 딛을 때 마다 같은 발걸음 소리가 아닌, 조금씩 다른 발걸음 소리가 매번 다르게 랜덤하게 재생되도록 한다. 현실감 업!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bscdbZ/dJMb991Cwwp/xL1vPC73k2KqQhwpOYHH90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bscdbZ/dJMb991Cwwp/xL1vPC73k2KqQhwpOYHH90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bscdbZ/dJMb991Cwwp/xL1vPC73k2KqQhwpOYHH90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbscdbZ%2FdJMb991Cwwp%2FxL1vPC73k2KqQhwpOYHH90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;301&quot; height=&quot;624&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소리는 기본적으로 2D로 설정된다. 2D 오디오 소스가 재생되는 순간, 오디오 리스너가 어디 있던지 같은 볼륨 좌우 같은 스피커로 소리가 들려온다. 소리의 범위 설정, 가까워질 수록 커지고 멀 수록 작아지는, 3D 상호작용을 더하려면 Audio Source 컴포넌트의 Spatial Blend를 0(2D)에서 1(3D)로 조절해주어야 한다. 0~1 사잇값을 두기도 하는데, 섞어 쓰는 기법은 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3D 소리로 전환되면 아래쪽의 3D Sound Settings에서 설정한 것이 소리에 적용되기 시작한다. 도플러 효과의 수준부터 소리가 들리는 최대/최소거리도 설정할 수 있다(반구 형태의 범위로 표시됨).&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것이 Spread 옵션이다. 최대 거리에서 소리를 들으면, 캐릭터(또는 카메라)의 위치에 따라서 좌, 우가 구분되어 들려온다. 현실에서 고개를 돌리면 소리의 원천으로부터의 귀의 위치에 따라 좌/우로 들려오는 소리가 달라짐을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 수치는 0이다. 0이면 좌우에 따라 다르게 들려오고, 180으로 두면 어느 곳에서던지 해당 오디오 소스의 소리는 좌우 관계없이 모노로 들려오고, 360이면(최대)왼쪽에 들리는 건 오른쪽, 오른쪽은 왼쪽, 이렇게 뒤바뀐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오디오 소스의 게임 오브젝트적 특성과 의도에 따라 다르게 조절해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Distance를 넘어서도 소리가 어느 수준 이하에서 거의 동일한 볼륨으로 계속 들려올 수 있다. 이것은 Volume Rolloff가 로그 함수 형태를 띄도록 설정되었기 때문이다. (Logarithmic Rolloff)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 그래프는 모든 설정 수치를 보기 좋게 시각화한 것이다. 여기서 볼륨 그래프가 거리가 멀어짐에 따라 로그적으로 줄어드는데, 0.5 조금 아래서부터는 아주아주 완곡하게 내려가는 것으로 보인다. 로그 그래프는 실제 세상에서 소리가 감쇄하는 것을 잘 표현하지만(금방 감소하나, 완전히 사라지기까진 오래 걸림), 거대한 자연같은 맵이 아니라면 게임적으로 수정이 필요하겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HqIXp/dJMcaalOMT5/PsvXziitHS7aHIAKZ2mMAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HqIXp/dJMcaalOMT5/PsvXziitHS7aHIAKZ2mMAK/img.png&quot; data-alt=&quot;(모든 설정 수치는 아래쪽의 그래프에 총체적으로 보기 좋게 표현된다.)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HqIXp/dJMcaalOMT5/PsvXziitHS7aHIAKZ2mMAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHqIXp%2FdJMcaalOMT5%2FPsvXziitHS7aHIAKZ2mMAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;327&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;327&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;(모든 설정 수치는 아래쪽의 그래프에 총체적으로 보기 좋게 표현된다.)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Volume Rolloff 설정을 Linear Rolloff로 바꾸어주면 된다. 최대거리 바깥에서 소리가 전혀 들려오지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 Custom으로 하여, 그래프를 마음대로 만져주어도 좋다. 로그 기반에 최대 거리를 벗어나면 소리만 들리지 않도록 수정한다던가, 하는 방식으로...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소리가 재생될 때, 소리가 주변 환경에 부딪혀 다르게 들려오는 것은 리얼타임으로 시뮬레이션 되지 않는다. 자원을 많이 먹을 것이 분명하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Audio Reverb Zone이 그 역할을 한다. 구형의 영역을 지정하여, 해당 영역에서만 재생되는 소리가 어떤 식으로 들려올지 설정할 수 있다. 동굴 안에서 소리가 울리거나 하는 등, 말이다. 많은 프리셋을 이용하여도 되고, 아래쪽의 여러 수치들의 슬라이더를 조절하여 커스텀할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Audio Source들 사이에서도 Priority값을 지정해줄 수 있다. 0~256사이값을 설정 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 소리들을 128로 설정해두고, 우선순위 낮으면 129이상(무시하기 쉬운 주변 소음들), 우선순위 높으면 127이하(동굴에 떨어지는 물방울소리 같이 단발적이지만 자주 들려야 좋은 소리들)로 설정해둔다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티의 윈도우 중 Audio Mixer가 존재한다. 오디오 믹서는 마치 포스트 프로세싱 과정과 같다. 소리가 재생되기 직전, 최후의 보정을 하는 장소이기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1229&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnUND6/dJMcabLX6uG/nJb3Z52U771gVRkGhIoQZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnUND6/dJMcabLX6uG/nJb3Z52U771gVRkGhIoQZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnUND6/dJMcabLX6uG/nJb3Z52U771gVRkGhIoQZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnUND6%2FdJMcabLX6uG%2FnJb3Z52U771gVRkGhIoQZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1229&quot; height=&quot;390&quot; data-origin-width=&quot;1229&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcEUSj/dJMcagsRNKk/AP0L5TBKlijXfXbeDkkZl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcEUSj/dJMcagsRNKk/AP0L5TBKlijXfXbeDkkZl1/img.png&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;158&quot; data-is-animation=&quot;false&quot; style=&quot;width: 77.9132%; margin-right: 10px;&quot; data-widthpercent=&quot;78.83&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcEUSj/dJMcagsRNKk/AP0L5TBKlijXfXbeDkkZl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcEUSj%2FdJMcagsRNKk%2FAP0L5TBKlijXfXbeDkkZl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckHtAy/dJMcacKQVgP/LDpTfeyS8fxHVtg97Gux3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckHtAy/dJMcacKQVgP/LDpTfeyS8fxHVtg97Gux3k/img.png&quot; data-origin-width=&quot;189&quot; data-origin-height=&quot;353&quot; data-is-animation=&quot;false&quot; style=&quot;width: 20.924%;&quot; data-widthpercent=&quot;21.17&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckHtAy/dJMcacKQVgP/LDpTfeyS8fxHVtg97Gux3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckHtAy%2FdJMcacKQVgP%2FLDpTfeyS8fxHVtg97Gux3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;189&quot; height=&quot;353&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Groups에서 오디오 그룹들을 계층적으로 만들 수 있다. 보통 Master를 최초로 만들고, 그 아래 모든 오디오 그룹을 종속시킨다. 마스터 그룹 아래의 그룹들은 자기들끼리도 다시 계층을 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Audio Source 컴포넌트의 Output 옵션에서 Audio Mixer Group를 선택해줄 수 있다. 폭포 소리를 재생하는 위 오디오 소스는 Water 오디오 믹서 그룹을 아웃풋으로 선택했다. 따라서 오디오 소스 컴포넌트 수준에서의 모든 설정이 적용된 소리는, 최종적으로 오디오 믹서를 거쳐서 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 오디오 믹서에는 Mute나 단순 증폭 기능도 있지만, 아래의 Add를 통해 우하단 이미지와 같은 여러 옵션을 더해줄 수 있다. 플러그인, 이펙터와 같다.&lt;/p&gt;</description>
      <category>유니티 기본</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/19</guid>
      <comments>https://mkccip.tistory.com/19#entry19comment</comments>
      <pubDate>Wed, 29 Jul 2026 17:18:56 +0900</pubDate>
    </item>
    <item>
      <title>포스트 프로세싱(Post-Processing)</title>
      <link>https://mkccip.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mkccip.tistory.com/12&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mkccip.tistory.com/12&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1785285263072&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;빛과 그림자(Lighting, Shadow)&quot; data-og-description=&quot;빛유니티에서 빛은 현실 세계의 빛과 가능한 한 같은 방식으로 보이도록 설계되었다.모든 빛은 발원지, 소스(source)가 있다. 빛이 뿜어져나오기 시작한 위치. 태양, 조명, 혹은 필요에 따라 현실&quot; data-og-host=&quot;mkccip.tistory.com&quot; data-og-source-url=&quot;https://mkccip.tistory.com/12&quot; data-og-url=&quot;https://mkccip.tistory.com/12&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/k4hNZ/dJMb8QeAgjo/PQo2bVdzrrTDPG0hQ7fbIK/img.png?width=1374&amp;amp;height=546&amp;amp;face=0_0_1374_546&quot;&gt;&lt;a href=&quot;https://mkccip.tistory.com/12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mkccip.tistory.com/12&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/k4hNZ/dJMb8QeAgjo/PQo2bVdzrrTDPG0hQ7fbIK/img.png?width=1374&amp;amp;height=546&amp;amp;face=0_0_1374_546');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;빛과 그림자(Lighting, Shadow)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;빛유니티에서 빛은 현실 세계의 빛과 가능한 한 같은 방식으로 보이도록 설계되었다.모든 빛은 발원지, 소스(source)가 있다. 빛이 뿜어져나오기 시작한 위치. 태양, 조명, 혹은 필요에 따라 현실&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mkccip.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 글 중반부에 언급했던 포스트 프로세싱에 관한 내용이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트 프로세싱은 어떤 이미지나 콘텐츠에 가해지는 최종적인 변형이다. 흔히 보이는 필터와는 다른 것이, 필터는 2D 기반에 평면 이미지의 색상만을 데이터로 삼지만, 포스트 프로세싱은 렌더링 단계에서 작동하며 3D 기반에 물리적인 환경을 고려한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트 프로세싱은 전역(글로벌, Global)단위와 로컬(Local)단위로 적용 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트 프로세싱을 담당하는 Volume 컴포넌트의 최상단 옵션인 Mode를 Global&amp;lt;-&amp;gt;Local 로 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Global의 경우, 별다른 조치가 필요 없이, 모든 카메라에 대한 해당 볼륨의 포스트 프로세싱 설정이 적용된다. Local의 경우, 해당 컴포넌트가 적용된 게임 오브젝트의 '콜라이더' 영역 안에 카메라가 들어갔을 때에 포스트 프로세싱이 적용된다. 이때, 글로벌의 포스트 프로세싱은 로컬 포스트 프로세싱보다 후순위로, 글로벌이 적용되어 있어도 로컬에 덮어 씌워진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 포스트 프로세싱 정보는 Volume Profile asset 파일로 저장된다. 프로파일만 변경해주면 전혀 다른 느낌을 주는 포스트 프로세싱으로 바꿀 수 있는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;머터리얼처럼 새로 만들고 수정하고가 자유롭다. 에셋 자체를 프로젝트에서 선택하여 인스펙터에서 수정할 수 있지만, 어느 볼륨에 해당 프로파일을 적용한 다음에 그아래서 수정도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sqvOR/dJMb991B2DP/inJGCfERVHwyt0qXHbnhU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sqvOR/dJMb991B2DP/inJGCfERVHwyt0qXHbnhU0/img.png&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;1023&quot; data-is-animation=&quot;false&quot; style=&quot;width: 51.6796%; margin-right: 10px;&quot; data-widthpercent=&quot;52.29&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sqvOR/dJMb991B2DP/inJGCfERVHwyt0qXHbnhU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsqvOR%2FdJMb991B2DP%2FinJGCfERVHwyt0qXHbnhU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;317&quot; height=&quot;1023&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lJmdQ/dJMcab6alrM/kSToXmzAX15B750JwiHAA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lJmdQ/dJMcab6alrM/kSToXmzAX15B750JwiHAA0/img.png&quot; data-origin-width=&quot;328&quot; data-origin-height=&quot;1160&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.1576%;&quot; data-widthpercent=&quot;47.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lJmdQ/dJMcab6alrM/kSToXmzAX15B750JwiHAA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlJmdQ%2FdJMcab6alrM%2FkSToXmzAX15B750JwiHAA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;328&quot; height=&quot;1160&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;왼: 프로파일 에셋 자체 선택 우: 볼륨에 적용한 프로파일을 즉석에서 수정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRORkK/dJMcah6r5cg/pAREVOmQe0KOCAlQaCI06K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRORkK/dJMcah6r5cg/pAREVOmQe0KOCAlQaCI06K/img.png&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;371&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.6821%; margin-right: 10px;&quot; data-widthpercent=&quot;51.28&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRORkK/dJMcah6r5cg/pAREVOmQe0KOCAlQaCI06K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRORkK%2FdJMcah6r5cg%2FpAREVOmQe0KOCAlQaCI06K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U1axJ/dJMcaazsjcP/P2VDMFG5WXLSfXBe1JkhA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U1axJ/dJMcaazsjcP/P2VDMFG5WXLSfXBe1JkhA1/img.png&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;357&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.1551%;&quot; data-widthpercent=&quot;48.72&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U1axJ/dJMcaazsjcP/P2VDMFG5WXLSfXBe1JkhA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU1axJ%2FdJMcaazsjcP%2FP2VDMFG5WXLSfXBe1JkhA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;288&quot; height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맨 아래의 Add Override를 눌러 포스트 프로세싱 효과들을 추가할 수 있다. 개별에 대한 설명은 생략한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tonemapping(톤 매핑)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 HDR에 대한 이해가 필요하다. High Dynamic Range는 기존의 색상 영역에서의 밝기의 가장 밝음과 가장 어둠에서 더욱 밝고 어두운 빛의 밝기를 나타내는 기술이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6If5s/dJMcad3UOYa/PltWbXqkNjIfRG03IfuBaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6If5s/dJMcad3UOYa/PltWbXqkNjIfRG03IfuBaK/img.png&quot; data-alt=&quot;https://learn.unity.com/pathway/creative-core/unit/post-processing/tutorial/create-your-own-post-processing-profile?version=6.3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6If5s/dJMcad3UOYa/PltWbXqkNjIfRG03IfuBaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6If5s%2FdJMcad3UOYa%2FPltWbXqkNjIfRG03IfuBaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;430&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://learn.unity.com/pathway/creative-core/unit/post-processing/tutorial/create-your-own-post-processing-profile?version=6.3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표현 가능한 밝기의 차이가 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tLS8u/dJMcaidelI0/IGFLaDy8uHxORFM33kL5I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tLS8u/dJMcaidelI0/IGFLaDy8uHxORFM33kL5I1/img.png&quot; data-alt=&quot;https://learn.unity.com/pathway/creative-core/unit/post-processing/tutorial/create-your-own-post-processing-profile?version=6.3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tLS8u/dJMcaidelI0/IGFLaDy8uHxORFM33kL5I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtLS8u%2FdJMcaidelI0%2FIGFLaDy8uHxORFM33kL5I1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;608&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://learn.unity.com/pathway/creative-core/unit/post-processing/tutorial/create-your-own-post-processing-profile?version=6.3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수치적으로는 같은 수준의 밝기 차이더라도, 대비가 더욱 명확해지는 모습이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 밝은, 이란 표현에 어울리는 밝기를 표현할 수 있게 되었지만, HDR로 제작되는 콘텐츠가 HDR을 지원하지 않는 기기에서 재생되는 것 또한 고려하지 않을 수 없는 문제이다. HDR을 지원하지 않는 LCD 모니터에서 HDR 콘텐츠를 실행하면 색감이 제대로 표현되지 못하거나(물 빠진 색상...), 모니터 성능 대비 너무 밝은 부분과 너무 어두운 부분의 정보를 그냥 날려버리는 문제가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 톤 매핑 기술이 존재한다. 제한적인 하드웨어 성능(모니터 성능)에서 이미 HDR로 제작된 콘텐츠를 문제 없이 실행하기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;톤 매핑 기술은 극단적인 밝기에 대해서 해당 밝기를 리매핑(re-mapping)하여 모니터가 감당할 수 있는 수준의 밝기로 조절한다. (괜찮게 보이던 부분 또한 약간의 색 변경이 들어간다. 0~1 사이의 일반적인 밝기가 있고, -5~5의 HDR 밝기가 있다면, 그것을 0&lt;br /&gt;~1사이로 압축해 넣는 작업이기 때문이다. 색 서로 간의 밸런스를 위해 표시에 문제가 없어 보이는 색들 또한 변화가 필요한 것이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;톤 매핑 기술에 대한 이해는 끝. 그렇다면 유니티에서 실용적으로 어디에 쓰이는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt;유니티에서 과도한 밝기가 주로 발생하는 때는 보통 머터리얼 단에서 빛을 emission(HDR을 쓴다)하는 오브젝트가 포스트 프로세싱 과정에서 Bloom 효과를 써버릴 때 이다. 따라서 포스트 프로세싱의 설정에 존재하는 Tonemapping 옵션을 추가하여 Bloom과 같이 사용해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRHi2m/dJMcahFgrjk/jVvYFPA8lhLghRHrxXFmd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRHi2m/dJMcahFgrjk/jVvYFPA8lhLghRHrxXFmd0/img.png&quot; style=&quot;width: 50.5329%; margin-right: 10px;&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;341&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.13&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRHi2m/dJMcahFgrjk/jVvYFPA8lhLghRHrxXFmd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRHi2m%2FdJMcahFgrjk%2FjVvYFPA8lhLghRHrxXFmd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;816&quot; height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yBrIY/dJMcaiYqiNZ/TRG60KtrPAM980kY6WkOK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yBrIY/dJMcaiYqiNZ/TRG60KtrPAM980kY6WkOK0/img.png&quot; style=&quot;width: 48.3043%;&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;334&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;48.87&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yBrIY/dJMcaiYqiNZ/TRG60KtrPAM980kY6WkOK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyBrIY%2FdJMcaiYqiNZ%2FTRG60KtrPAM980kY6WkOK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;334&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9zRef/dJMcacjElUC/JjpSaJfMAXntyRou4zimwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9zRef/dJMcacjElUC/JjpSaJfMAXntyRou4zimwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9zRef/dJMcacjElUC/JjpSaJfMAXntyRou4zimwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9zRef%2FdJMcacjElUC%2FJjpSaJfMAXntyRou4zimwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;790&quot; height=&quot;338&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;톤 매핑 모드의 비교다 차례대로 None(적용안함), Neutral, ACES다. 두 모드 사이에 차이가 분명하므로 필요에 따라 잘 구분하여 사용해야 한다. (톤 매핑 방식이 서로 완전히 다르다고 한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;------------------------------&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬러 그레이딩(Color grading): 영화 등에서 사용되는 기술로, 영상물의 전체적인 분위기를 잡기 위해 특정한 방향으로 밝기, 색상을 보정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서부극은 누런 색감, 영화 메트릭스는 인위척인 초록 빛깔이 영화 내내 느껴진다. RAW 촬영 데이터에 대해서 세심한 컬러 그레이딩 작업을 거친 결과물이다. 작품 내내 느껴지는 '스타일'을 표현하는 데에 아주아주 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티에도 비슷한 작업을 수행할 수 있는 도구가 있다. 포스트 프로세싱의 Color Adjustments다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노출도, 대비, 컬러 필터, 색공간 변환, 색 포화도 를 조절할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Lift Gamma Gain도 추가하여 사용할 수 있다. 세 가지의 color collector는 각각 어두운 부분, 중간 영역, 밝은 하이라이트 부분에 대한 색상 조절이 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/paPJz/dJMcadJAuqV/BDmTXKI7kM5f9dCSTYzVs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/paPJz/dJMcadJAuqV/BDmTXKI7kM5f9dCSTYzVs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/paPJz/dJMcadJAuqV/BDmTXKI7kM5f9dCSTYzVs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpaPJz%2FdJMcadJAuqV%2FBDmTXKI7kM5f9dCSTYzVs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;318&quot; height=&quot;410&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도 포스트 프로세싱에는 많은 효과를 지원한다. 필요에 따라 찾아보고 사용할 수 있다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>유니티 기본</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/18</guid>
      <comments>https://mkccip.tistory.com/18#entry18comment</comments>
      <pubDate>Wed, 29 Jul 2026 14:33:09 +0900</pubDate>
    </item>
    <item>
      <title>카메라</title>
      <link>https://mkccip.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;카메라는 중요하다. 아주 중요하다. 결국 카메라가 비추어야 고심하여 만들어 둔 모든 것이 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇을 어디서 어떻게 비추느냐에 따라 화면의 느낌은 전혀 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실 세계의 촬영 기법은 거의 그대로 유니티 속에서 사용 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라의 여러 기능에 대해서 잘 알아두면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(카메라를 누르고 Ctrl shift f 를 누르면, 씬 내에서 내가 바라보는 시점을 정확히 카메라가 바라보도록 위치를 바꿔준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라 오브젝트를 우클릭하면 그 외에도 카메라를 정렬하는 다양한 옵션들이 미리 준비되어 있다. (다른 게임 오브젝트들도 전부 마찬가지이지만, 카메라에 대해서 특히나 유용하다.))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/df6vAl/dJMcaalNL7z/882UvZGv3xUQguUeysIgAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/df6vAl/dJMcaalNL7z/882UvZGv3xUQguUeysIgAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/df6vAl/dJMcaalNL7z/882UvZGv3xUQguUeysIgAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdf6vAl%2FdJMcaalNL7z%2F882UvZGv3xUQguUeysIgAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;431&quot; height=&quot;668&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더 타입: Base와 Overlay가 있다. Base가 기본값이다. Base는 통상적인 카메라와 거의 똑같이 작동한다. Base 카메라 한 대만 있으면 렌더링이 가능하다(단독 사용 가능).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Overlay는 한 대 밖에 없다면 사용이 불가하다. Overlay가 수 백대 있어도, 결국은 Base 모드인 카메라가 최소한 한 대는 있어야 한다. 이유는: Overlay 카메라는 이미 카메라를 통해 비춰지는 장면 위에(overlay) 새로 덧그리는 역할밖에 하지 못하기 때문이다. 따라서 오버레이 카메라는 반드시 어느 Base 카메라의 Camera 컴포넌트의 Stack 옵션 내에 등록되어 있어야 제기능을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(그럼 오버레이 모드를 왜 쓰냐? -&amp;gt; 베이스 카메라는 씬 내 실제 장면만 렌더링하고, UI나 포스트 프로세싱은 오버레이 카메라 한 대 씩 렌더링하게 하여, 카메라들을 쌓는 방식이다! )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Projection: Perspective(원근)와 Orthograpic(직교)가 있다. Perspecitve는 실제로 우리가 보는 방과 같이 원근감(가까운 것은 크게, 먼 것은 작게)을 주는 카메라이다(깊이감을 반영한다. Z축 거리). Orthograpic은 원근감이 완전히 배제되고, 사물의 절대적인 크기와 비율만을 화면에 보인다. 2D 게임이나, 일부 UI에 적합한 방식이다. 당연히 3D 게임의 현실감을 위해서는 카메라의 렌더 모드는 Perspective 방식이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;녹음 2026-07-28 130731.gif&quot; data-origin-width=&quot;2084&quot; data-origin-height=&quot;950&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RuGvz/dJMcafAJemK/kxjEzzbMzWskfkAZbyjKV0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RuGvz/dJMcafAJemK/kxjEzzbMzWskfkAZbyjKV0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RuGvz/dJMcafAJemK/kxjEzzbMzWskfkAZbyjKV0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/RuGvz/dJMcafAJemK/kxjEzzbMzWskfkAZbyjKV0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2084&quot; height=&quot;950&quot; data-filename=&quot;녹음 2026-07-28 130731.gif&quot; data-origin-width=&quot;2084&quot; data-origin-height=&quot;950&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k9zol/dJMcaf1JrXP/XyYGVRYVc9KDcZvH62MqeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k9zol/dJMcaf1JrXP/XyYGVRYVc9KDcZvH62MqeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k9zol/dJMcaf1JrXP/XyYGVRYVc9KDcZvH62MqeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk9zol%2FdJMcaf1JrXP%2FXyYGVRYVc9KDcZvH62MqeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;284&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r1toX/dJMcabZrUp5/WMGXB6GaYybzTjbz5UFEjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r1toX/dJMcabZrUp5/WMGXB6GaYybzTjbz5UFEjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r1toX/dJMcabZrUp5/WMGXB6GaYybzTjbz5UFEjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr1toX%2FdJMcabZrUp5%2FWMGXB6GaYybzTjbz5UFEjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1103&quot; height=&quot;745&quot; data-origin-width=&quot;1103&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Field of View(FoV, 시야각)은 가시 환경의 범위이다. 각도로 표시된다. GIF와 같이, 값을 작게하면 가까워지는 것 처럼 보이고, 크게하면 멀리 가는 것 처럼 보인다. 하지만 실제로 카메라를 옮기는 것과는 다른 것이, GIF아래의 이미지에서 좌측은 FoV가 30, 우측은 60이고, 거리감을 비슷하게 맞추었다. 이때 FoV가 60인 것이 더 많은 정보가 배경에 담기는 것을 알 수 있다. 멀어지는 것 처럼 느껴지는 것은, 한 번에 담는 배경의 데이터가 많아지므로 작게 보이기 때문인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FoV는 멀미와 큰 연관이 있다. FoV 값은 정도에 따라 현실을 왜곡하기 때문이다. 개인에게 어색함이 느껴지지 않는 수치로 잘 조절하여야 3D 게임 시에 발생하는 멀미를 줄일 수 있다. (60~90 recommended)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(Orthograpic 투영 방식일 때는, FoV 대신 Size 옵션이 존재한다. 모든것의 사이즈가 동일한 비율로 커졌다 작아지며, 여전히 원근감 없이 배경에 담기는 객체수만 늘어난다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qDKfd/dJMcaijUGjx/e7vOGifR5A9KUEQ6bQOpK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qDKfd/dJMcaijUGjx/e7vOGifR5A9KUEQ6bQOpK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qDKfd/dJMcaijUGjx/e7vOGifR5A9KUEQ6bQOpK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqDKfd%2FdJMcaijUGjx%2Fe7vOGifR5A9KUEQ6bQOpK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;491&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clipping Planes&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Near과 Far가 있는데, 보다시피 카메라와 가까운 곳, 먼 곳에 화면에 담기는 한계를 설정하는 평면이 총 두 개 있고, 그 사이에 있는 것들이 렌더링 된다. Near는 카메라와 가까운 평면의 거리, Far는 카메라와 먼 평면의 거리를 나타낸다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;545&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmyIpA/dJMcadXcDcW/svGMIq4khjrO5M5qIkekR0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmyIpA/dJMcadXcDcW/svGMIq4khjrO5M5qIkekR0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmyIpA/dJMcadXcDcW/svGMIq4khjrO5M5qIkekR0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmyIpA%2FdJMcadXcDcW%2FsvGMIq4khjrO5M5qIkekR0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1148&quot; height=&quot;545&quot; data-origin-width=&quot;1148&quot; data-origin-height=&quot;545&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Near 값보다 물체가 카메라와 가까워진다-&amp;gt;카메라를 통과한것으로 간주, 렌더링하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3D 게임에서 너무 가까이 다가가거나 할 때에 오브젝트가 천천히 잘려 보이는 현상-&amp;gt;Near 평면 한계치 도달)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;녹음 2026-07-28 150033.gif&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1vVAa/dJMcac42wXQ/MuW3rVpIqFe8xcY5gsb2o0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1vVAa/dJMcac42wXQ/MuW3rVpIqFe8xcY5gsb2o0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1vVAa/dJMcac42wXQ/MuW3rVpIqFe8xcY5gsb2o0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/1vVAa/dJMcac42wXQ/MuW3rVpIqFe8xcY5gsb2o0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1976&quot; height=&quot;486&quot; data-filename=&quot;녹음 2026-07-28 150033.gif&quot; data-origin-width=&quot;1976&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Far 값보다 물체가 카메라와 멀어진다-&amp;gt;너무 멀리있는 것은 보이지 않는 것으로 간주, 렌더링하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(먼 곳에 있는 산, 건축물 등이 Far 평면 안으로 들어오면 갑자기 렌더링되어 생겨나보일 수 있음. Fog(연기)등을 Far 면 근처에 설정하여 자연스럽게 만든다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이에 지장이 가지 않는 선에서 Near과 Far 사이의 값을 최소화해야 성능에 도움이 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클리핑 플래인 안에 있더라도 어떤 게임 오브젝트는 렌더링 하고 싶을 수도, 하고싶지 않을 수도 있다. 게임 오브젝트 하나 하나에 대해서 설정해주는 것 보다는, 큰 틀에서 여러 게임 오브젝트를 한꺼번에 지정해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;330&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r5LSy/dJMcacjD04G/McTWzCiE9ISgTgmjczvI4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r5LSy/dJMcacjD04G/McTWzCiE9ISgTgmjczvI4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r5LSy/dJMcacjD04G/McTWzCiE9ISgTgmjczvI4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr5LSy%2FdJMcacjD04G%2FMcTWzCiE9ISgTgmjczvI4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;330&quot; height=&quot;96&quot; data-origin-width=&quot;330&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 오브젝트의 인스펙터 상단에는 Tag와 Layer를 지정해줄 수 있다. 카테고리 지정 방법이 크게 두 가지 있다고 생각할 수 있다. 하지만 용도와 쓰임새가 크게 다르다. Tag는 스크립트 내부에서 대상을 구분할 때에 사용되고, Layer는 물리 충돌, 카메라 렌더링 제어 등에 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지는 Culled 레이어를 추가해서 Capsule에 지정해주었다. 카메라로 돌아와보면, 카메라 컴포넌트의 렌더링 옵션에 Culling Mask(쿨링 마스크)가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;771&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCJ3NP/dJMcahyxZsZ/LFVFi7eerat922Tz3qyluK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCJ3NP/dJMcahyxZsZ/LFVFi7eerat922Tz3qyluK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCJ3NP/dJMcahyxZsZ/LFVFi7eerat922Tz3qyluK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCJ3NP%2FdJMcahyxZsZ%2FLFVFi7eerat922Tz3qyluK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;316&quot; height=&quot;771&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;771&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택 가능한(생성된) 레이어들이 표시된다. 선택된 레이어에 속한 게임 오브젝트만 해당 카메라를 통해 렌더링된다. 즉 캡슐은 렌더링되지 않는다. (그러나 물리적인 충돌 등, 카메라에 보이지 않을 뿐 존재하므로, 충돌 등은 일어난다. 주의해야 하는 지점.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lighting에서 언급한 것과 같이, Skybox가 Procedual한 것이라면, 하늘의 밝기와 Directional light(연동된 태양)가 주변광에 영향을 끼친다. 그리고 카메라마다 Backgroundd를 변경하는 경우가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Background Type을 Skybox, Solid Color, Uninitialized가 존재한다. Skybox는 Lighting의 Environment에서 설정한 것과 동일한 배경이 보이고, Solid Color를 선택하면 하단에 생겨나는 배경색 선택창을 통해 선택된 단색으로 스카이박스가 대체되어 보인다. 물론 해당 카메라에 대해서만!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgOKcj/dJMcajwnyRN/lPdZvVudYYzkQlrUjLoNU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgOKcj/dJMcajwnyRN/lPdZvVudYYzkQlrUjLoNU1/img.png&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;374&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.6851%; margin-right: 10px;&quot; data-widthpercent=&quot;49.26&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgOKcj/dJMcajwnyRN/lPdZvVudYYzkQlrUjLoNU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgOKcj%2FdJMcajwnyRN%2FlPdZvVudYYzkQlrUjLoNU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;769&quot; height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCEEeO/dJMcajwnyRA/TaJRpCVloA0T6zSIdmkP10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCEEeO/dJMcajwnyRA/TaJRpCVloA0T6zSIdmkP10/img.png&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;381&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.1521%;&quot; data-widthpercent=&quot;50.74&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCEEeO/dJMcajwnyRA/TaJRpCVloA0T6zSIdmkP10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCEEeO%2FdJMcajwnyRA%2FTaJRpCVloA0T6zSIdmkP10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;807&quot; height=&quot;381&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 단색은 주변광에 전혀 영향을 끼치지 않으며, 기존의 Skybox의 빛 영향이 그대로 남아있을 뿐이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라를 이용한 시점들에서 신경써야 하는 것들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1인칭뷰: 캐릭터 머리에 카메라가 달림-&amp;gt;내 시야에서 나 자신이 보이는 형태, 애니메이션에 신경써야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;, 3인칭뷰, 탑-다운뷰, 쿼터뷰(등축도법, isometric), 고정뷰...&lt;/p&gt;</description>
      <category>유니티 기본</category>
      <author>mkccip</author>
      <guid isPermaLink="true">https://mkccip.tistory.com/17</guid>
      <comments>https://mkccip.tistory.com/17#entry17comment</comments>
      <pubDate>Tue, 28 Jul 2026 13:48:05 +0900</pubDate>
    </item>
  </channel>
</rss>