지우너

[Unity 3D Run] (아직 못함) 캐릭터를 가리는 오브젝트의 투명도 처리 본문

Project

[Unity 3D Run] (아직 못함) 캐릭터를 가리는 오브젝트의 투명도 처리

지옹 2023. 5. 27. 12:52

장애물이 캐릭터를 가리는 현상을 방지하기 위해 카메라와 캐릭터 사이의 장애물이 있을 경우 장애물을 반투명하게 만드는 작업을 해주고 싶었다.

 

실패1

처음에는 오브젝트에 메테리얼이 적용되지 않은 상태(장애물은 그냥 이 상태로 쓰려고 했었다)여서, SpriteRenderer를 선언하여 투명도 값을 바꾸려고 시도했다. 하지만 캐릭터가 떨어졌을 때 추락사를 담당하는 FallingDeath 오브젝트가 Obstacle 스크립트를 함께 사용하고 있었으며, 이 부분에서 오류가 나서 if ( ObstacleObject !=null) 이런 식으로 조건문을 넣어줬었는데, 투명도 조절에는 실패했다. 

 

실패2

GameObject[] obst; MeshRenderer objMesh;

이런 식으로 장애물 오브젝트의 배열을 선언하고, obst = GameObject.FindGameObjectsWithTag("Obstacle"); 를 이용하여 Obstacle 태그가 설정된 게임 오브젝트를 모두 배열에 저장했다. 그리고 이들의 MeshRenderer 컴포넌트를 objMesh 변수에 저장해주었다. 이 방법은 유니티 2D 게임 제작 p290, p294를 보며 구현해보려고 했으나, 책에 나온 것은 문을 통과했을 때 다른 방으로 이어지게 만드는 코드였기에 응용하기가 어려웠다... 책에서는 start 함수에 변수를 적용하고 만약 문 번호가 같다면 캐릭터를 1번 문에서 2번 문으로 이동하는 식으로 만들었으나, 내가 하고 싶었던 것은 update 함수에서 장애물이 캐릭터를 가리는지(이 부분을 아래 링크를 보기 전까지는 캐릭터의 z값을 장애물의 z값과 비교하는 식으로 만들었었다) 계속 확인하는 방식이었다.

더보기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Obstacle : MonoBehaviour
{
    GameObject target;
    private Player player;
    GameObject[] obst;
    MeshRenderer objMesh;

    void Start()
    {
        target = GameObject.FindGameObjectWithTag("Player");
        player = target.GetComponent<Player>();

        obst = GameObject.FindGameObjectsWithTag("Obstacle");
        for (int i = 0; i < obst.Length; i++)
        {
            GameObject obstacleObj = obst[i];
            objMesh = obstacleObj.GetComponent<MeshRenderer>();
        }
        //objMesh = GetComponent<MeshRenderer>();
        //ObjSprRend = GetComponent<SpriteRenderer>();
    }

    void Update()
    {
        if (player.transform.position.z > this.transform.position.z)
        {
            TransparencyControl();
        }
    }

    void TransparencyControl()
    {
        objMesh.material.color = new Color(objMesh.material.color.r, objMesh.material.color.g, objMesh.material.color.b, 0.3f);
        //ObjSprRend.material.color = new Color(ObjSprRend.material.color.r, ObjSprRend.material.color.g, ObjSprRend.material.color.b, 0.3f);
    }

    void OnCollisionEnter(Collision collision)
    {

        if (collision.gameObject.tag == "Player")
        {
            // 플레이어가 접촉했다면 플레이어Die(GameOver, Camera Stop)
            player.Die();
        }
    }
}

 

방법3 레이캐스트 이용하기!

레이캐스트가 있다는 건 알고 있었지만, 정확히 뭔지, 어떻게 쓰는 건지는 몰랐다. 저번에 유튜브 강의를 따라하며 한번 썼던 기억만 흐리게 있는 정도였는데, 이번 기회를 통해 좀 자세히 공부하는 것 같다. 어디서 사용했는지 기억이 안 났었는데 아래의 책을 보며 공부하다가 생각이 났다. 책과 해당 강의 모두 총을 쏴서 적에게 데미지를 줄 때 이 기능?을 사용. 

 

[Unity] 오브젝트반투명 처리하기

 

두군데를 레이를 쏠 준비를 합니다. 첫번쨰는 몸뒤에 오브젝트가 걸리는지 확인하기위해 몸뒤 방향, 거리 , 캐릭터위치 를 구합니다,

두번쨰는 캐릭터에서부터 카메라까지 걸리는 오브젝트가 있는지 확인하기 위해 캐릭터에서 카메라의 방향, 거리, 캐릭터 위치를 구합니다

 

몸 뒤에 오브젝트가 걸리는지 확인은 왜 해야하는 건지 모르겠다. 카메라와 캐릭터 사이에 걸리는 오브젝트만 처리하면 되는 거 아닌가...? 다르게 처리해야하는 부분이 있는 건데 생각을 못하고 있는 거겠지? 일단 내가 만들고 있는 게임 에서는 캐릭터가 맵을 뛰어다니는 형태(상하좌우 이동+ 점프의 형태로 조작하는 것)가 아니기 때문에 캐릭터와 카메라 사이의 오브젝트만 처리해주면 될 것 같다.

 

더보기

유니티 - [RayCast, RayCastAll, 레이캐스트]

레트로의 유니티 게임 프로그래밍 에센스 2권 p654

- 레이캐스트Raycast

보이지 않는 광선을 쐈을 때 광선이 다른 콜라이더와 충돌하는지 검사하는 처리. 이때 사용하는 광선을 레이라고 부르며 Ray 타입으로 레이의 정보만 따로 표현할 수도 있습니다.

- RaycastHit

레이캐스트를 실행했을 때 레이가 콜라이더를 가진 게임 오브젝트와 충돌하면 RaycastHit 타입으로 충돌 정보가 생성된다. RaycastHit 오브젝트를 살펴보면 레이와 충돌한 게임 오브젝트, 충돌한 위치, 충돌한 표면의 방향 등을 알 수 있다.

 

레트로의 유니티 게임 프로그래밍 에센스 1권 p227

- 머터리얼의 구성(셰이더+텍스쳐)

텍스쳐는 표면에 입히는 이미지 파일. 그림으로 비유했을 때 밑그림, 스케치.

셰이더는 주어진 입력에 따라 픽셀의 최종 컬러를 결정하는 코드. 질감과 빛에 의한 반사와 굴절 등의 효과를 만든다. 그림으로 비유했을 때, 물감.

 

다시 말해, 레이를 쏴서 카메라와 캐릭터 사이에 있는 오브젝트는 셰이더에 접근해서 투명도를 낮춰야 한다는 이야기인 것 같다.

그러면 RaycastHit 타입의 변수를 생성하여 카메라에서 캐릭터의 방향으로 레이를 쏴서 충돌 정보를 저장.

 

Unity C# > UnityEngine : LayerMask

[Unity] Raycast Layermask 설정

레트로의 유니티 프로그래밍 에센스 2권 p733

레이어 마스크는 특정 레이어를 가진 게임 오브젝트에 물리 또는 그래픽 처리 등을 적용시킬 때 사용. 여기서는 whatIsTarget에 대응하는 레이어를 가진 게임 오브젝트를 좀비 AI가 자동으로 추적하게 만들기 위해서 사용했다.

 

Raycast 를 사용하여 타겟에게 ray 를 쏴서 처리하고 있을때, 자신과 타겟사이에 오브젝트가 끼어들면 ray를 쏘지 못하게 되죠.
당연한 말이지만, raycast 사용중 자신과 타겟사이에 오브젝트가 끼어들어도 계속 타겟에게 ray를 쏘고 싶을때가 있습니다.

이럴경우 layermask를 쓰면 아주 좋은데요 , 우선 Raycast 오버로딩된 함수에 layermask를 어덯게 넣고 쓰는지 알아 보겟습니다.

 

우선 아래 사진처럼 타겟대상(사진에선 Player) 에게 Layer 를 설정해주세요

 

Legacy Shaders/Transparent/Diffuse

https://docs.unity3d.com/kr/current/Manual/shader-TransDiffuse.html

 

Transform.TransformDirection

https://m.blog.naver.com/kzh8055/140154125006

방향(벡터)을 로컬 좌표계 기준에서 월드 좌표계 기준으로 변환한다. 결과적으로 로컬 좌표계 기준에서 월드 좌표계 기준으로 변환한다는 것은 전부 부모 자식을 기준으로 한 좌표에서 절대적인 게임 월드의 원점 기준 좌표로 표현하도록 바꾸는 작업.

위 함수와 역으로 function InverseTransformDirection(direction : Vector3) : Vector3

함수가 있는데 TransformDirection 와는 반대로 인자 directon(월드기준)을 호출자의 지역좌표계 기준으로 바꿔 반환한다.

 

로컬좌표와 월드좌표(글로벌좌표)

https://timeboxstory.tistory.com/128

Transform.TransformDirection 설명에서 방향(벡터)을 로컬 좌표계 기준에서 월드 좌표계 기준으로 변환하는 거라고 했는데, 로컬좌표와 월드 좌표가 뭔지 몰라서 검색해봤다.

 

레트로의 유니티 게임 프로그래밍 에센스 1권 p394

3D 공간에 배치하는 3D 오브젝트는 위치를 표현한 값, 즉 좌표를 가진다. 원점을 기준으로 (x, y, z)에서 x는 오른쪽, y는 위쪽, z는 앞쪽으로 이동한 위치이다. 이렇게 좌표를 측정할 기준이 될 원점의 위치와 x, y, z축 방향을 설정하여 물체가 어디에 배치되어 있는지 표현하는 기준과 체계를 좌표계Coordinate System라고 부른다. 좌표계는 공간에서 어떤 방향으로 얼마만큼 이동한 거리에 배치할 것인지 결정하는 기준. 간단히 말해, 씬 창에 표현되는 평행이동 툴의 화살표가 좌표계를 표현한 것이다.

 

좌표계는 여러 종류가 존재한다.

1. 전역 공간 2. 오브젝트 공간 3. 지역 공간

- 전역 공간

전역 공간(=월드공간)은 월드의 중심(=게임 월드의 원점(0, 0, 0))이라는 절대 기준이 존재하며, 모든 오브젝트가 원점에서 얼마 만큼 떨어져 있느냐가 오브젝트의 좌표가 된다.

전역 공간에서 xyz 방향을 정하고, 좌표를 계산하는 기준을 월드, 글로벌 좌표계 Global Coordinate System이라고 한다.

- 오브젝트 공간

자기 자신을 기준으로 한 위치 측정.

- 지역 공간

지역 공간은 게임 월드나 오브젝트 자신이 아닌 자신의 부모 오브젝트를 기준으로 한 지역 좌표계로 좌표를 측정한다. 인스펙터 창에 보이는 위치, 회전, 스케일은 언제나 지역 공간을 기준으로 표시된다. 부모 오브젝트가 존재하지 않으면 지역 좌표계와 전역 좌표계가 일치하므로 지역 공간 상의 위치가 곧 전역 공간 상의 위치가 된다. 

자식 게임 오브젝트는 부모 게임 오브젝트에 묶여 있으며, 부모가 움직이면 함께 움직인다.

 

<실습으로 알아보는 지역 위치와 전역 위치>

위치가 (0, 0, 0)인 큐브를 생성

위치가 (2, 0, 0)인 구를 생성

구를 큐브의 자식으로 만든 후, 큐브의 위치를 (-2, 0, 0)으로 변경(큐브의 자식인 구도 큐브와 함께 위치가 변경된다)

구 오브젝트의 위치를 인스펙터 창에서 확인해보면 여전히 (2, 0, 0)으로 설정되어 있다.

구를 큐브의 자식에서 풀어주면(부모 자식 해제) 구의 위치가 (2, 0, 0)에서 (0, 0, 0)으로 변경되어 있다.

(0, 0, 0)은 구의 실제 전역 위치이고, (2, 0, 0)은 큐브를 기준으로 하여 측정된 지역 위치인 것. 다시 말해 부모인 큐브에서 (2, 0, 0) 만큼 떨어져 있다는 의미가 된다. 위치 뿐만 아니라 회전과 스케일도 부모를 기준으로 측정된다.

 

magnitude

레트로 유니티 게임 프로그래밍 에센스 1권 p364, 벡터 (vector)

'방향'과 '길이(크기)'라는 두 가지 속성을 가진 물리량벡터라고 한다. 즉, 벡터는 방향과 크기magnitude를 가진다.

속도, 변위, 가속도, 힘 등 크기와 방향을 동시에 나타내는 물리량이 여기에 속한다. 프로그래밍에서는 파티클 시스템이나 빛, 카메라 등을 표현할 때 벡터를 사용한다. 벡터는 화살표의 방향과 길이로 비유할 수 있다.

(1, 1)이라는 Vector2는 게임 월드에서 2가지 의미를 가진다.

- 상대 좌표: (내가 어디 있는지는 모르겠지만)현재 좌표에서 (1, 1)만큼 더 가려고 한다.

- 절대 좌표: 게임 세상 속 나의 좌표가 (1, 1)이다.

벡터는 절대적인 기준 없이 (현 위치에서) 상대적으로 어느 방향으로 얼마 만큼의 크기(길이)로 갈 것인지 표현한다.

벡터의 값은 오직 화살표의 방향과 화살표의 길이만 표현한다. 벡터의 방향과 크기가 별개의 값이라는 것은 방향은 같지만, 크기만 다른 벡터가 존재할 수 있다는 의미이다.

벡터의 크기는 선분의 길이를 의미하며, 피타고라스의 정리를 이용해서 구할 수 있다. 벡터의 크기는 모든 원소(2차원이라면 xy, 3차원이라면 xyz)를 제곱하여 더한 값의 제곱근이 된다. 벡터의 크기는 화살표의 '길이'에 대응되므로 음수가 될 수 없다.

 

p371

벡터의 덧셈: A+B = A 만큼 이동한 상태에서 B 만큼 이동하겠다는 의미.

A를 위치, B를 이동을 표현하는 벡터로 해석하면 현재 위치 A에서 B 만큼 더 이동한 위치가 A+B라고 해석도 가능

p373

벡터의 뺄셈: B-A는 A에서 B까지 가려면 어떤 방향으로 얼마 만큼 가야하는지를 나타냄

뺄셈은 두 수 사이의 '간격'을 구하는 것.

(목적지)-(현재위치)= 현재 위치에서 목적지 까지의 방향과 거리

벡터의 뺄셈으로 어떤 물체가 다른 물체를 추적할 때 어떤 방향으로 얼마만큼 가야 하는지 알 수 있다.

예를 들어 몬스터가 A(1, 3)에 있고, 플레이어가 B(-2, 8)에 있다면 몬스터가 플레이어를 잡기 위해 이동해야 할 방향과 거리는 B-A=(-2, 8)-(1, 3)=(-3, 5)가 된다. 

즉, 몬스터가 이동할 방향은 (-3, 5)의 방향벡터인 (-0.5, 0.9), 이동할 거리는 (-3, 5)의 크기인 5.83....이 된다.

Vector3.magnitude

https://m.blog.naver.com/yoohee2018/221159368623

오브젝트 간 거리를 체크할 때 사용. float Distance = (Camera_Move_OJ.position - CharPos).magnitude;는 (변수 이름부터 Distance긴 했지만...)카메라와 캐릭터 사이의 거리를 구하는 코드였다.

 

위 블로그의 코드를 사용하려고 했는데, 정작 나는 카메라 참조?를 할당해서 카메라의 position을 구하는 것도 못하고 있었다...

너무 어려운 기능을 만들고 싶어했던 걸까... 좀 더 공부한 다음에는 구현할 수 있을까.... 일단 이거는 여기서 멈춰두고 캐릭터 이동 스크립트를 수정해야겠다... 이동에 코드를 너무 복잡하게 쓴 것 같은 느낌...?