타이머를 설정하는것은 정말 빈번하게 사용하게됩니다. 그렇기 때문에 좀더 좋은 형태를 가지도록 구현하고자 정리하려고 합니다.

 

Update를 이용한 방법

Private float time = 0f; 
Void Update() 
{ 
    this.time += Time.deltaTime; 
    if(this.time > 5f) 
    { 
        Debug.Log(“Do something!”); 
        this.time = 0; 
    } 
} 
[출처] [유니티 C#] - 타이머 설정하기|작성자 자판기

 

Time.deltaTime은 지난 프레임이 완료되는게까지 걸린 시간을 말합니다.

저희가 만든 time프로퍼티에 매 프레임마다 더해줌으로써 현재까지 걸린시간을 측정할 수 있습니다.

그 time프로퍼티가 5초를 넘는순간 어떠한 작업을 하고 다시 0초로 초기화해줘서 5초마다 일정한 시간을 가지고 어떠한 반복작업을 하는것이 가능해집니다.

 

이런 식으로 update를 이용하는 방법으로 간단하고 직관적으로 코드를 작성할수있지만 저는 이보다 좀더 복잡한 상황에서 사용하기 좋을수있는 coroutine을 사용해서 구현하고자합니다.

 

타이머 만드는 3개지 방법

검색을 하던 도중 코루틴과 Invoke의 차이점에 대해 묻는 질문을 발견했고, 나는 명확하게 대답하지 못해 해당 지식에 대한 정리를 해두고자 글을 작성한다.

🌐 코루틴

Cooperate(협력하다) 할때 Co와 routine이 합쳐진 Co-routine은 여러개의 루틴이 동시에 실행되게 해주며 멀티쓰레드가 아닌 유니티에서 마치 멀티쓰레드 처럼 보여 병렬적인 구조를 만드는 기능이다.

코루틴을 사용할 때는 Yield return을 해주어야 하는데, 이를 통해서 현재 위치를 기억하고 다른 루틴에게 수행권한을 넘겨주고 다시와서 처리하는 방식이다.

멀티 쓰레드 같지만 단일 쓰레드이기에 Race Condition과 같은 멀티 쓰레드에 문제점을 고민하지 않아도 된다.

💽 코루틴 특징

  • 코루틴은 해당 GameObject가 inactive일때 동작하지 않는다.
  • 정지 된 코루틴은 다시 GameObject가 Active 되더라도 다시 동작하지 않는다.(다시 실행을 시켜주어야한다.)
  • yield return new WaitForSeconds(시간) 이게 너무 편하다.. 함수 중간 중간 쉬었다가 동작 시킬 수 있는데 매우 간편하고, Update를 통해 Timer를 만들어 시간을 재는것 보다 효율적이다.왜 효율적인가?
    5초 뒤 동작을 위해 코드를 구현할 떄 Update에서는 초당 50프레임 정도를 동작하면서 250번을 호출하면서 동작을 하는데, 코루틴은 엔진에게 5초 뒤에 깨우라고 맡기고 쉬다가 동작하니 더 효율적일 수 밖에 없다.

🎇 Invoke

인보크는 코루틴에 비해 비교적 간단하다. 함수를 대신 실행시켜준다. 또한 간단한 방법으로 지연시간 뒤에 함수를 동작하게도 할 수 있다.

Invoke( "함수명"(string) , 지연시간(float));

다만 Invoke는 Reflection을 통해 값을 가져오는데 이 방식이 코루틴 보다는 느리다. 코루틴이 메서드 자체를 인자로 받아가는 것과는 다르게 메서드의 이름을 받아간다.

Reflection
프로그램 실행 도중에 객체의 정보 조사, 다른 모듈에 선언된 인스턴스를 생성, 기존 개체에서 형식을 가져오고 해당하는 메서드를 호출, 접근할 수 있는 강력한 기능
-> 코루틴도 string으로 부를 수 있는데 이때도 리플렉션이 동작되는건가 궁금한데 찾아도 잘 나오지 않는다. 더 공부해보고 추가 글을 남겨야겠다.

👓 인보크 특징

  • Invoke는 GameObject가 비활성화 되더라도 동작을 한다.
  • InvokeRepeating을 통해 지속 반복 동작을 시킬 수 있다.
    -> CancelInvoke, 오브젝트를 파괴하여 종료해주어야 한다.

✅ 차이점

결국은 글에서 언급하면서 나오긴 했지만 다시 정리하자면

  • 코루틴은 GameObject가 활성화 일때만 동작, 인보크는 파괴 전 까지 동작
  • 코루틴은 매개변수 전달 가능, Invoke는 불가능
  • 코루틴은 TimeScale이 0인 경우에도 동작 시킬 수 있다.
    -> yield return new WaitForSecondsRealtime()를 사용
  • 리플렉션의 차이로 코루틴의 속도가 조금 더 빠르다.

공부중인 내용이라 틀릴 수도 있습니다. 추가로 부족한 부분이 있으면 조언 부탁드립니다.

Couroutine을 이용하는 방법

Void Start()
{ 
    StartCoroutine(“timerCoroutine”);
} 

IEnumerator testCoroutine() 
{ 
    While(true) 
    { 
        Debug.Log(“Do something!”); 
        yield return new WaitForSeconts(5f); 
    } 
    yield return null; 
} 
[출처] [유니티 C#] - 타이머 설정하기|작성자 자판기

 

StartCoroutine()는 파라미터로 주어진 IEnumerator타입의 메소드를 실행시켜주는 역할을합니다.

IEnumerator타입 함수는 yield의 사용을 할수있어서 함수가 호출되고 나서 저희가 함수로 다시 돌아오고 어떻게 동작하는지 확인 할수있게 해줍니다.

 

Yield return new WaitForSecond()은 IEnumerator타입 함수가 재시작 할때까지 얼마나 오랫동안 기다려야하는지를 결정하는 유연성을 제공해줍니다. 파라미터로 원하는 시간을 넘겨주면 그시간동안 다음 동작을 기다립니다. 그후에 더이상의 이함수의 반복이 필요하지않은 부분에서 yield return null로 탈출해주면됩니다.

 

이 코루틴을 이용한 타이머는 타이머가 필요한 동작을 각각 분리하여 구현을 할수있다는것이 장점이 될수있습니다.

 

update로 하기에는 다양한 조건을 검사하거나 동시에 여러개의 타이머가 필요할때는 코루틴을 사용해 구현하는것이 편리할것같다고 생각합니다.

'AR > 24 KMOOC 비주얼심화과정' 카테고리의 다른 글

Unity 타이머  (0) 2024.07.06
7/5 수업  (0) 2024.07.05
7/3 강의 마커기반 AR  (0) 2024.07.03
7/1 FaceTracking2  (1) 2024.07.03
4일차 FaceTracking (6.30 강의)  (3) 2024.06.30

타이머를 설정하는것은 정말 빈번하게 사용하게됩니다. 그렇기 때문에 좀더 좋은 형태를 가지도록 구현하고자 정리하려고 합니다.

 

Update를 이용한 방법

Private float time = 0f; 
Void Update() 
{ 
    this.time += Time.deltaTime; 
    if(this.time > 5f) 
    { 
        Debug.Log(“Do something!”); 
        this.time = 0; 
    } 
} 
[출처] [유니티 C#] - 타이머 설정하기|작성자 자판기

 

Time.deltaTime은 지난 프레임이 완료되는게까지 걸린 시간을 말합니다.

저희가 만든 time프로퍼티에 매 프레임마다 더해줌으로써 현재까지 걸린시간을 측정할 수 있습니다.

그 time프로퍼티가 5초를 넘는순간 어떠한 작업을 하고 다시 0초로 초기화해줘서 5초마다 일정한 시간을 가지고 어떠한 반복작업을 하는것이 가능해집니다.

 

이런 식으로 update를 이용하는 방법으로 간단하고 직관적으로 코드를 작성할수있지만 저는 이보다 좀더 복잡한 상황에서 사용하기 좋을수있는 coroutine을 사용해서 구현하고자합니다.

 

타이머 만드는 3개지 방법

검색을 하던 도중 코루틴과 Invoke의 차이점에 대해 묻는 질문을 발견했고, 나는 명확하게 대답하지 못해 해당 지식에 대한 정리를 해두고자 글을 작성한다.

🌐 코루틴

Cooperate(협력하다) 할때 Co와 routine이 합쳐진 Co-routine은 여러개의 루틴이 동시에 실행되게 해주며 멀티쓰레드가 아닌 유니티에서 마치 멀티쓰레드 처럼 보여 병렬적인 구조를 만드는 기능이다.

코루틴을 사용할 때는 Yield return을 해주어야 하는데, 이를 통해서 현재 위치를 기억하고 다른 루틴에게 수행권한을 넘겨주고 다시와서 처리하는 방식이다.

멀티 쓰레드 같지만 단일 쓰레드이기에 Race Condition과 같은 멀티 쓰레드에 문제점을 고민하지 않아도 된다.

💽 코루틴 특징

  • 코루틴은 해당 GameObject가 inactive일때 동작하지 않는다.
  • 정지 된 코루틴은 다시 GameObject가 Active 되더라도 다시 동작하지 않는다.(다시 실행을 시켜주어야한다.)
  • yield return new WaitForSeconds(시간) 이게 너무 편하다.. 함수 중간 중간 쉬었다가 동작 시킬 수 있는데 매우 간편하고, Update를 통해 Timer를 만들어 시간을 재는것 보다 효율적이다.왜 효율적인가?
    5초 뒤 동작을 위해 코드를 구현할 떄 Update에서는 초당 50프레임 정도를 동작하면서 250번을 호출하면서 동작을 하는데, 코루틴은 엔진에게 5초 뒤에 깨우라고 맡기고 쉬다가 동작하니 더 효율적일 수 밖에 없다.

🎇 Invoke

인보크는 코루틴에 비해 비교적 간단하다. 함수를 대신 실행시켜준다. 또한 간단한 방법으로 지연시간 뒤에 함수를 동작하게도 할 수 있다.

Invoke( "함수명"(string) , 지연시간(float));

다만 Invoke는 Reflection을 통해 값을 가져오는데 이 방식이 코루틴 보다는 느리다. 코루틴이 메서드 자체를 인자로 받아가는 것과는 다르게 메서드의 이름을 받아간다.

Reflection
프로그램 실행 도중에 객체의 정보 조사, 다른 모듈에 선언된 인스턴스를 생성, 기존 개체에서 형식을 가져오고 해당하는 메서드를 호출, 접근할 수 있는 강력한 기능
-> 코루틴도 string으로 부를 수 있는데 이때도 리플렉션이 동작되는건가 궁금한데 찾아도 잘 나오지 않는다. 더 공부해보고 추가 글을 남겨야겠다.

👓 인보크 특징

  • Invoke는 GameObject가 비활성화 되더라도 동작을 한다.
  • InvokeRepeating을 통해 지속 반복 동작을 시킬 수 있다.
    -> CancelInvoke, 오브젝트를 파괴하여 종료해주어야 한다.

✅ 차이점

결국은 글에서 언급하면서 나오긴 했지만 다시 정리하자면

  • 코루틴은 GameObject가 활성화 일때만 동작, 인보크는 파괴 전 까지 동작
  • 코루틴은 매개변수 전달 가능, Invoke는 불가능
  • 코루틴은 TimeScale이 0인 경우에도 동작 시킬 수 있다.
    -> yield return new WaitForSecondsRealtime()를 사용
  • 리플렉션의 차이로 코루틴의 속도가 조금 더 빠르다.

공부중인 내용이라 틀릴 수도 있습니다. 추가로 부족한 부분이 있으면 조언 부탁드립니다.

Couroutine을 이용하는 방법

Void Start()
{ 
    StartCoroutine(“timerCoroutine”);
} 

IEnumerator testCoroutine() 
{ 
    While(true) 
    { 
        Debug.Log(“Do something!”); 
        yield return new WaitForSeconts(5f); 
    } 
    yield return null; 
} 
[출처] [유니티 C#] - 타이머 설정하기|작성자 자판기

 

StartCoroutine()는 파라미터로 주어진 IEnumerator타입의 메소드를 실행시켜주는 역할을합니다.

IEnumerator타입 함수는 yield의 사용을 할수있어서 함수가 호출되고 나서 저희가 함수로 다시 돌아오고 어떻게 동작하는지 확인 할수있게 해줍니다.

 

Yield return new WaitForSecond()은 IEnumerator타입 함수가 재시작 할때까지 얼마나 오랫동안 기다려야하는지를 결정하는 유연성을 제공해줍니다. 파라미터로 원하는 시간을 넘겨주면 그시간동안 다음 동작을 기다립니다. 그후에 더이상의 이함수의 반복이 필요하지않은 부분에서 yield return null로 탈출해주면됩니다.

 

이 코루틴을 이용한 타이머는 타이머가 필요한 동작을 각각 분리하여 구현을 할수있다는것이 장점이 될수있습니다.

 

update로 하기에는 다양한 조건을 검사하거나 동시에 여러개의 타이머가 필요할때는 코루틴을 사용해 구현하는것이 편리할것같다고 생각합니다.

'AR > 24 KMOOC 비주얼심화과정' 카테고리의 다른 글

Unity 타이머  (0) 2024.07.08
7/5 수업  (0) 2024.07.05
7/3 강의 마커기반 AR  (0) 2024.07.03
7/1 FaceTracking2  (1) 2024.07.03
4일차 FaceTracking (6.30 강의)  (3) 2024.06.30

BallController Script를 만든다

 

SetBallPosition()이라는 함수를 만든다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BallController : MonoBehaviour
{
    Rigidbody rb;  //녹색은 값을 넣어줘야함.
    //공이 준비 상태인지 확인하는 변수
    bool isReady = true;
    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        //리지드 바디의 물리 능력을 비활성화
        rb.isKinematic = true;
     
    }

    // Update is called once per frame
    void Update()
    {
        //공을 카메라 전방 하단에 배치
        SetBallPosition(Camera.main.transform);
    }

    void SetBallPosition(Transform anchor)
    {
        //카메라의 위치로부터 일정 거리만큼 떨어진 위치 설정
        // Vector3 = anchor로 부터 앞으로 0.5+ anchor로 부터 아래로 0.2
        Vector3 offset = anchor.forward * 0.5f + anchor.up * -0.2f;
        //공의 위치를 offset만큼 이동
        transform.position = anchor.position + offset;
    }
}

Ball에 스크립트를 끌어다 놓고

 

 

공을 발사하고 3초후 공을 다시 만들어 주기위해 타이머를 만들어야 하는데 수업에서는 invoker를 사용하였다.

3초가 지나면 Ball을 Reset()해줘 위치를 초기화 해준다.

void Update()에서 터치후 드래그시 공에 addForce로 공을 약간 위로 나가게 해준다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BallController : MonoBehaviour
{
    Rigidbody rb;
    // 공이 준비 상태인지 확인하는 변수
    bool isReady = true;
    // 터치 시작 지점
    Vector2 startPos;
    // 공을 초기화 할 시간
    float resetTime = 3f;
    // 포획 확률
    float captureRate = 0.5f;
    // 포획 결과 텍스트
    public Text result;

    // Start is called before the first frame update
    void Start()
    {
        // 포획 결과 텍스트 초기화
        result.text = "";
        rb = GetComponent<Rigidbody>();
        // 리지드 바디의 물리 능력을 비활성화
        rb.isKinematic = true;
    }

    // Update is called once per frame
    void Update()
    {
        if (!isReady)
        {
            return;
        }
        // 터치가 입력 되었고, 공이 준비 상태라면
        if (Input.touchCount > 0 && isReady)
        {
            Touch touch = Input.GetTouch(0);
            // 만약 터치를 시작했다면
            if (touch.phase == TouchPhase.Began)
            {
                // 시작 지점 저장
                startPos = touch.position;
            }
            // 터치가 끝났다면
            else if (touch.phase == TouchPhase.Ended)
            {
                // 드래그한 y축 거리를 계산
                float dragDistance = touch.position.y - startPos.y;
                // 카메라를 기준으로 45도 각도 계산
                Vector3 throwAngle = (Camera.main.transform.forward
                    + Camera.main.transform.up).normalized;
                // 물리 능력 활성화
                rb.isKinematic = false;
                // 준비 상태 변경
                isReady = false;
                // 던질 방향과 드래그 거리만큼 공에 물리적 힘을 더함
                rb.AddForce(throwAngle * dragDistance * 0.005f, ForceMode.VelocityChange);
                // 3초 뒤에 공의 위치와 속도 초기화
                Invoke("ResetBall", resetTime);
            }
        }

        // 공을 카메라 전방 하단에 배치
        SetBallPosition(Camera.main.transform);
    }

    void SetBallPosition(Transform anchor)
    {
        // 카메라의 위치로부터 일정 거리만큼 떨어진 위치 설정
        // Vector3 = anchor로부터 앞으로 0.5 + anchor로부터 아래로 0.2
        Vector3 offset = anchor.forward * 0.5f + anchor.up * -0.2f;
        // 공의 위치를 카메라 위치로부터 offset만큼 이동
        transform.position = anchor.position + offset;
    }

    void ResetBall()
    {
        // 물리 능력 비활성화
        rb.isKinematic = true;
        // 속도 초기화
        rb.velocity = Vector3.zero;
        // 준비 상태로 변경
        isReady = true;
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (isReady)
        {
            return;
        }
        // 포획 확률을 랜덤한 값으로 설정
        float draw = Random.Range(0, 1f);
        // 만약 랜덤 값이 captureRate 보다 작다면 포획 성공
        if (draw <= captureRate)
        {
            result.text = "포획에 성공했습니다!";
        }
        // 그렇지 않다면 포획 실패
        else
        {
            result.text = "포획에 실패하여 도망쳤습니다.";
        }
        // 마커 오브젝트 제거
        Destroy(collision.gameObject);
        // 공 비활성화
        gameObject.SetActive(false);
    }
}

하이라키+를 눌러 Leagacy Text를 추가해준다.

Canvas Scaler는 Scale with Screen Size로 해주고 해상도는 다음과 같이해준다.

 

BallController.cs
0.00MB

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BallController : MonoBehaviour
{
    Rigidbody rb;
    // 공이 준비 상태인지 확인하는 변수
    bool isReady = true;
    // 터치 시작 지점
    Vector2 startPos;
    // 공을 초기화 할 시간
    float resetTime = 3f;
    // 포획 확률
    float captureRate = 0.5f;
    // 포획 결과 텍스트
    public Text result;

    public GameObject effect;
    // Start is called before the first frame update
    void Start()
    {
        // 포획 결과 텍스트 초기화
        result.text = "";
        rb = GetComponent<Rigidbody>();
        // 리지드 바디의 물리 능력을 비활성화
        rb.isKinematic = true;
    }

    // Update is called once per frame
    void Update()
    {
        if (!isReady)
        {
            return;
        }
        // 터치가 입력 되었고, 공이 준비 상태라면
        if (Input.touchCount > 0 && isReady)
        {
            Touch touch = Input.GetTouch(0);
            // 만약 터치를 시작했다면
            if (touch.phase == TouchPhase.Began)
            {
                // 시작 지점 저장
                startPos = touch.position;
            }
            // 터치가 끝났다면
            else if (touch.phase == TouchPhase.Ended)
            {
                // 드래그한 y축 거리를 계산
                float dragDistance = touch.position.y - startPos.y;
                // 카메라를 기준으로 45도 각도 계산
                Vector3 throwAngle = (Camera.main.transform.forward
                    + Camera.main.transform.up).normalized;
                // 물리 능력 활성화
                rb.isKinematic = false;
                // 준비 상태 변경
                isReady = false;
                // 던질 방향과 드래그 거리만큼 공에 물리적 힘을 더함
                rb.AddForce(throwAngle * dragDistance * 0.005f, ForceMode.VelocityChange);
                // 3초 뒤에 공의 위치와 속도 초기화
                Invoke("ResetBall", resetTime);
            }
        }

        // 공을 카메라 전방 하단에 배치
        SetBallPosition(Camera.main.transform);
    }

    void SetBallPosition(Transform anchor)
    {
        // 카메라의 위치로부터 일정 거리만큼 떨어진 위치 설정
        // Vector3 = anchor로부터 앞으로 0.5 + anchor로부터 아래로 0.2
        Vector3 offset = anchor.forward * 0.5f + anchor.up * -0.2f;
        // 공의 위치를 카메라 위치로부터 offset만큼 이동
        transform.position = anchor.position + offset;
    }

    void ResetBall()
    {
        // 물리 능력 비활성화
        rb.isKinematic = true;
        // 속도 초기화
        rb.velocity = Vector3.zero;
        // 준비 상태로 변경
        isReady = true;
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (isReady)
        {
            return;
        }
        // 포획 확률을 랜덤한 값으로 설정
        float draw = Random.Range(0, 1f);
        // 만약 랜덤 값이 captureRate 보다 작다면 포획 성공
        if (draw <= captureRate)
        {
            result.text = "포획에 성공했습니다!";
        }
        // 그렇지 않다면 포획 실패
        else
        {
            result.text = "포획에 실패하여 도망쳤습니다.";
        }
        //이펙트생성
        Instantiate(effect, collision.gameObject.transform.position, Camera.main.transform.rotation);
        // 마커 오브젝트 제거
        Destroy(collision.gameObject);
        // 공 비활성화
        gameObject.SetActive(false);
    }
}

Asset Store에서 이펙트를 찾아 다운로드해주고

Import해준다.

스크립트에 effect변수에 import한 이펙트를 끌어다 놔준다. UI LegacyText로 끌어다 놔준다

실행해서 마커를 촬용하면 고양이가 나오고 볼을 드래그해서 맞추면 폭발이 일어나는데 확율에 의해 성공과 실패가 나뉜다. 

실제 해보면 공의 초기 위치와 고양이의 위치가 비슷해 고양이가 공위에 있다. AddForce가 앞으로 날라가기때문에 맞지 않는다. 공위로 고양이가 있을때 억지로 가깝게 해서 맞춰줘야야만 한다. 난 공을 좀더 카메라쪽(-) 고양이를 뒤로 위치시켜 거리를 만들어 공이 앞으로 날라가다 맞게 해주었다

'AR > 24 KMOOC 비주얼심화과정' 카테고리의 다른 글

Unity 타이머  (0) 2024.07.08
Unity 타이머  (0) 2024.07.06
7/3 강의 마커기반 AR  (0) 2024.07.03
7/1 FaceTracking2  (1) 2024.07.03
4일차 FaceTracking (6.30 강의)  (3) 2024.06.30

이전에 마스크를 얼굴에 씌워봤는데 이번에는 동영상을 올려보겠다.

하이라키+를 누르고 CreateEmpty만들고 이름을 FaceModel로 하고 Transform을 리셋한다. 삼점을 누르면 된다.

 

FaceModel을 선택하고 MeshFilter, MeshRendere, MeshCollider, AR Face, AR Mesh Visualizer를 추가한다.

 

얼굴에 덮을 이미지를 적당히 찾는다 png가 좋다.

유니티 Material Folder로 가져온후 인스펙터에서 Transparecy 체크하고 apply

Material을 하나 만들고 M_Face로 하나 만들고 Redering Mode를 Cutout PNG를 Albedo 왼쪽에 끌어다 놓는다.

 

하이라키 FaceModel을 선택하고 머터리얼 M_Face를 끌어다 Materials Element 0 에 넣어준다.

이제 하이라키의 FaceModel을 프로젝트 Prefabs폴더에 끌어다 놓고 프리팹으로 만든다.

만든 프리팹을 하이라키의 AR Session Orign을 선택하고 AR Face Manager컴포넌트의 Face Prefab에 끌어다  적용한다.

하이라키의 FaceModel은 지운다.

빌드앤런 해보면 잘된다. 마스크가 3D로 적용된다 .

첫날은 MyMask라는 빈 오브젝트에 Mask라는 Quad에 M_Mask를 머터리얼로 얹은 2D 평면이었지만 이번에 만든 FaceModel은 3D화 되었다.

 

빈오브젝트에 이것저것 넣어서 만들기 귀찮으면

하이라키에서 우클릭후 AR Default Face를 추가하고 M_Face 머터리얼을 추가해고  AR Face Manager 프리팹에 적용해도 같은 효과가 난다.

 

이제 동영상을 마스크 대신 띄워보자 인터넷에서 영상을 캡쳐해서 머터리얼 폴더로 끌고오자.

하이라키에 CreateEmpty를 만들고 이름을 FaceVideo로 하자

VideoPlayer Component를 추가하고 비디오를 끌어다 Source에 연결해주자 Loop 체크하고 Mute를 체크해서 소리를 꺼준다.

 

프로젝트창 우클릭후 Create>RenderTexture를 만든다. 이름은 FaceVideoRT로 변경하고 이걸 하이라키의 FaceVideo의 TargetTexture에 넣어준다.

프로젝트창에 Create>Material만들고 이름을 M_FaceVideo로 한다. 알베도 왼쪽에 FaceVideoRT를 넣어준다.

 

아까 만든 FaceModel 프리팹을 열어 Matarial에 M_FaceVideo를 연결해준다. 

하이라키의 AR Session Orign>AR Face Manager의 Face Prefab에 Face Model을 연결해주자

정리해보면 M_FaceVideoRT 를 FaceVideoRT>알베도에 놓고 FaceVideoRT 를 M_FaceVideo->Target Texture 에 넣고 M_FaceVideo를 Face Mdel->Material-에 넣고 Face Mode을 AR FaceManager->Face prefab에 넣는다.

빌드런 해보면 잘된다.

 

Cavas 버튼을 2개더 복사해서 좌우로 펼쳐준다

 

버튼을 선택하고 이미지를 지워주자

 

Text를 Image Video로 바꾸고 폰트사이즈를 48로 키워준다.

UI_Manager 스크립트에 머터리얼 행 변수와 SwitchMaterial()함수를 만들어준다

    public Material[] faceMats;

    public void SwitchMaterial(int num)
    {

    }

하이라키의 Canvas를 선택하고 UI Manager(Script)의 Face Mats +를 2번 눌러 Element를 2개 만들고 Material을 2개 연결해준다.

 

2개의 버튼이 함수는 SwitchMaterial로 똑같지만. Button Image OnClick()은 0,ButtonVideo OnClick()은 1로 설정한다.

해본면 안된다. 아직 SwitchMaterial()함수를 정의 안해서 그런것 같다.

눈코입 위치 인식하기

C#스크립트를 만들고 FindDetection 이라고 한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARCore;
using Unity.Collections;

public class FindDetection : MonoBehaviour
{
    public ARFaceManager afm; 
    public GameObject cube;  //얼굴에 띄워줄 cubeObject
    List<GameObject> faceCubes = new List<GameObject>();
    ARCoreFaceSubsystem subSys;
    NativeArray<ARCoreFaceRegionData> regionData;
    // Start is called before the first frame update
    void Start()
    {
        //얼굴 위치 표시를 위한 큐브 3개 생성
        for(int i = 0; i < 3; i++)
        {
            GameObject go = Instantiate(cube);
            faceCubes.Add(go);
            go.SetActive(false);
        }
        //얼굴 인식 함수 연결
        afm.facesChanged += OnDetectPoints;  //얼굴이 변화될때 아래 정의된 함수를 델리게이션 추가
        subSys = (ARCoreFaceSubsystem)afm.subsystem; //서브시스템의 정보를 가져와 저장한다.
    }
    //얼굴이 인식될 대마다 실행할 함수
    void OnDetectPoints(ARFacesChangedEventArgs args)
    {
        //얼굴 정보가 갱신된 것이 있다면
        if (args.updated.Count > 0)
        {
            //인식된 얼굴에서 특정 위치를 가져옴
            subSys.GetRegionPoses(args.updated[0].trackableId, Allocator.Persistent, ref regionData);
            //인식된 얼굴을 특정 위치에 오브젝트 배치
            for(int i = 0; i < regionData.Length; i++)
            {
                faceCubes[i].transform.position = regionData[i].pose.position;
                faceCubes[i].transform.rotation = regionData[i].pose.rotation;
                faceCubes[i].SetActive(true);
            }
        }
        //얼굴 정보를 잃었다면
        else if (args.removed.Count > 0)
        {
            //큐브오브젝 비활성화
            for( int i = 0; i< regionData.Length; i++)
            {
                faceCubes[i].SetActive(false);
            }
        }
    }
    // Update is called once per frame
    void Update()
    {
        
    }
}

하이라키+를 눌러 빈게임 오브젝트를 만들고 이름을 FaceElements로 한다  FindDetection을 끌어다 놓고 AR Session Origin을 끌어다 afm에 연결해준다.

하이라키+ 3D 오브젝트>Cube를 추가한다. 스케일을 0.02로 한다.

Cube를 프리팹폴더로 옮겨서 프리팹으로 만들고 FaceElements를 선택해서 Cube프리팹을 끌어다 놓는다.

빌드앤런 해보면 잘된 아까 페이스 위에 큐브 3개가 나온다.

https://www.youtube.com/watch?v=CkjSBEv4PW0&t=9s

 

이전 프로젝트가 있는 분은 그대로 해도 되고 새로운 씬을 하나 만들고

AR Session Orign, AR Session을 추가하고 Main Camera를 지워주자.

먼저 프로젝트와 마찬가지고 AR Session Origin에 AR Plane Manager컴포넌트를 추가하고 AR Default Palne프리팹을 연결해주자

  하이라키에 AR Camera를 선택하고 우클릭후 Create Empty해서 이름을 ShootPoint로 해준다. 이걸 끌어다 AR Camera밑으로 놓는다. ShootPoint는 이제 AR Camera의 자식이고 화면의 가운데에 위치한다.

Z축 값을 0.01로 변경한다. 유니티에서 Z축은 앞쪽이다. 카메라의 약간 앞쪽에 위치하게 된다.

이제 날아갈 공을 만들자 하이라키의 우클릭후 3D Object>Sphere를 추가하자 이름을 Ball로 변경

이제 Ball을 앞으로 날라가게 하자 Ball을 선택하고

Inspector창 아래 Add Component를 눌러 Rigidbody를 추가하자. 이 컴포넌트를 게임오브젝트가 물리적 특성을 갖게한다. 디테일을 보면 무게, 중력 충돌처리등이 있다. 무게를 1로 변경한다.

Ball의 Scale(크기)를 0.2정도로 줄인다.

ShootBall>Prefab폴더를 만들어주고 Ball을 끌어다 프리팹을 만들어주고 하이라키상의 Ball은 지워주자

ShootBall>Script 폴더를 만들어주고 Script를 만들자

이름은 바로 ShootBall로 변경하자 안그러면 클래스 이름이 안 맞아 에러가 난다.

하이라키의 우클릭후 EmptyObject를 하나 만들고 이름을 ShootBallManager로 하자.

코드를 쳐주고 유튜브 강의는 터치에 대한 입력이 없어서 터치입력을 추가했고 실행해보니 공이 너무 많이 생성돼 앞으로 나가지를 못했다. 타이머를 만들어 쿨타임을 적용해 0.2초마다 나가게 해주었다. 안드로이드폰은 라이더 센서가 없어 면을 생성하는데 카메라를 좀 돌려야한다. 실제 공을 발사해보니 AR Default Plane이 좀 높게 설정되어있었다. 

유니티가 처음이신 분들은 코드가 어려울수도 있다. RigidBody는 물리적인 처리를 하는 부분이라 오브젝트를 움직이게 할때는 AddForce를 사용해야한다. Transform을 이용해서 움직이면 Rigidbody와 처리가 얽혀 더더덕 거린다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ShootBall : MonoBehaviour
{
    public GameObject ball; //공오브젝트
    public Transform camObj;  //카메라오브젝트
    public Transform shootPoint;  // 슛포인트 위치
    private float coolTimer=0f;  //타이머
    private bool bShoot = true;  //쏠수 있는지 
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        //터치가 일어나고 쏠수 있다면
        if (Input.touchCount > 0 && bShoot)
        {
            //공을 발사
            ShootBallObj();
            bShoot = false;
        }
        //쏠수 없다면
        if (!bShoot)
        {
            //타이머를 갱신하고
            coolTimer += Time.deltaTime;
            //일정한 시간이 지났으면
            if (coolTimer > 0.2f)
            {
                bShoot = true;//슛을 가능하게 해주고
                coolTimer = 0; //타이머를 리셋해준다
            }
        }

    }

    public void ShootBallObj()
    {
        GameObject tObj = Instantiate(ball); //공을 생성해줌
        //공의 위치를 발사위치로 옮김
        tObj.transform.position = shootPoint.transform.position; 
        //공의 발사 방향을 계산
        Vector3 tVect = (shootPoint.transform.position - camObj.transform.position).normalized;
        //리지드바디 컴포넌트 참조를 얻어옴
        Rigidbody tR = tObj.GetComponent<Rigidbody>();
        // 리지드바디에 힘을 전달해 앞으로 나가게 한다.
        tR.AddForce(tVect * 100f);
    }
}

방금만든 스크립트를 끌어다 하이라키의 ShootBallManager에 놔준다. 하이라키는 게임오브젝트 객체만 존재할수 있으므로 이렇게 해야한다.  연결후 ShootBallManager를 선택하고 Inspector뷰 아래 ShootBall의 파라메터들을 연결해준다. The Ball은 프리팹에서 나머지는 하이라키에서 끌어다 놓으면 된다.

빌드전 빌드 세팅즈에 가서 씬을 추가해주고

PlayerSetting을 눌러 앱 이름도 바꿔주자.

빌드후 화면을 터치해보면 공이 앞으로 날라간다.

https://www.youtube.com/watch?v=UuoNTYZaT7k&list=PLATff1khpIscTMN2KXxagrTkBCwdzOeol&index=2

 

프로젝트는 새로 만들어도 되고 만들때마다 용량이 늘어나므로 기존 프로젝트에 New Scene을 하는걸 추천한다. 3D면 된다.

File>Save를 하면 이름을 물어보는데 난 DebugTest로 했다.

Window>Package Manager에 AR Foundation, ARCore가 잘 설치되어 있어야 한다 기타 빌드 설정도 잘되어 있어야하고 핸드폰도 개발자모드로 되어 있어야하고 연결하고 승인도 해줘야한다. 잡일이 많다.

하이라키 +를 누르거나 우클릭후 AR Session, AR Session Orign을 추가하고 Main Camera는 지워준다.

이제 AR Foundation기능을 테스트 해보자  AR Foundation이 지원하는 기능은 다음과 같은데

특징설명

 

세션 대상 플랫폼에서 AR을 활성화, 비활성화하고 구성합니다.
장치 추적 실제 공간에서 장치의 위치와 회전을 추적합니다.
카메라 장치 카메라에서 이미지를 렌더링하고 조명 추정을 수행합니다.
비행기 감지 평평한 표면을 감지하고 추적합니다.
이미지 추적 2D 이미지를 감지하고 추적합니다.
객체 추적 3D 객체를 감지하고 추적합니다.
얼굴 추적 인간의 얼굴을 감지하고 추적합니다.
신체 추적 인체를 감지하고 추적합니다.
포인트 클라우드 특징점을 감지하고 추적합니다.
레이캐스트 추적된 항목에 광선을 비춥니다.
앵커 공간상의 임의의 지점을 추적합니다.
메싱 환경의 메시를 생성합니다.
환경 프로브 환경의 큐브맵을 생성합니다.
폐색 AR 콘텐츠를 물리적 객체로 가리고 인간 세분화를 수행합니다.
참가자들 공유된 AR 세션에서 다른 기기를 추적합니다.

일단 하이라키위를 우클릭하면 나타나는 것들부터 해보자

AR Default Point Cloud를 추가하자

AR Session Origin을 클릭후 Inspection뷰에서 Add Component를 클릭하고 AR Point라고 검색한후 AR Point Manager를 추가하자. 그럼 Point Cloud Prefab이라는 Item이 생기고 None에 프리팹을 연결해줘야한다..

프로젝트뷰에 Prefab폴더를 만들고 AR Default Point Cloud를 끌어다 놓으면 Prefab이 생성된다.  하이라키의 있는 것들은 GameObject라는 객체인데 이것들을 끌어다 프로젝트에 놓으면 Prefab으로 바뀐다. Prefab은 Class라고 생각하면 된다. 객체에서 클래스를 만드어 주는거다. 보통은 클래스를 만들고 클래스이용 객체를 만드는 것의 반대이다. 객체지향 어려워요. 참 프리팹을 만든후에는 AR Default Point Cloud는 지워주자. 끌어다 놓을때는 이름을 잘보자 프로젝트뷰가 아이콘아 아니라 리스트로 나오는건 우측 삼점을 누르고 One Columb Layout을 선택하면 된다.

이제 빌드를 해보자 File>BuildSetting를 눌러서 Add Open Scenes를 눌러 현재 씬을 추가해줘야한다.

좌측아래 PlayerSettings(Project Setting에서도 접근가능)를 눌 앱이름을 설정하자.

 

오른쪽아래 빌드앤런을 누르면 파일명을 물어보면 build라는 폴더를 만들고 들어가서 적당히 이름만든다. 클

세이브할꺼냐고 물어보면 Save

이때 부터 핸드폰을 잘봐라 대기화면이면 풀어주자. 프로그레스바에서 device에 copy한다는 메세지가 나와야 한다. 물론 빌드폴더에서 apk를 핸드폰으로 카피해서 깔아도 되지만 이게 편하다.

핸드폰 화면을 대기화면에서 풀고 잘 보면 아래 팝업이 뜬다. 당황하지 말고 확인 위에 세부정보 더보기를 누르고 다시 확인위에 무시하고 설치하기 누른다.

다음 카메라사용 승인해주면 드디어 앱이 뜬다. 포인트가 안 나온다면 좀 기다려 보고 앵글을 움직여 보면 나온다. 포인트는 오브젝트의 엣지부분을 찾는 기능 같다.

 

다음은 하이라키 뷰위를 우클릭후 AR Default Plane을 추가 하고

프리팹 폴더에 끌어다 프리팹을 만들자. 그럼 파란색으로 변하고

AR Session Origine에 AR Plane Manager 컴포넌트를 추가하고

AR Default Plane 프리팹을 AR Plane Manager컴포넌트의 Prefab 참조에 연결해준다

Detection Mode는 원하는 면을 선택할수 있는데 현재는 그냥 두자

 

빌드해주자 잘보면 방법이 똑같다 AR Default XXXX를 추가하고 프리팹을 만들고 AR Session Origne에 AR XXXX Manager를 컴포넌트에 추가하고 프리팹을 연결해준다.

빌드해보면 Point와 Plane인식을 할것이다. Plane인식은 약간 시간이 걸리는것 같다. 그리고 정확하지 않은것 같다. 아이폰은 라이더가 내장되어 있어 정확안듯 하

마지막으로 하이라키에 우클릭후 XR>AR Default Face를 추가하고 프리팹폴더에 끌어다 프리팹만들고 지워주자

AR Session Origine에 AR Face Management 컴포넌트를 추가하고 AR Default Face Prefabs를 연결해준다.

Face Detect를 하기 위해 모델이 있다면야 후방으로 해도 되지만 자신으로 테스트하기 위해 전방카메라를 선택해보자. AR Session Orign>AR Camera를 선택해서 Inspector View아래 Face Direction이 있는데 World->User로 바꾸준다. World가 후방이다.

Face Tracking에서는 Point, Plane Manager가 필요 없다고 해서 지우라는데 난 귀찮아서 안지웠는데 잘되었다  지우는것 대신 불필요한 부분을 비활성화 하는 방법도 있을것 같다. 컴포넌트 이름 왼쪽을 언체크하면 된다.

실행화면 내 눈 코 입을 만들어주고 표정까지 트래킹 해준다. 얼굴을 돌려보면 나름 3D로 모델을 만들어서 맵핑해준다. 유튜브는 이빨과 눈동자도 보이는데  버전에 따라 좀 다른듯 

오른쪽이 유튜브 강좌 화면 눈동자와 이빨이 보임.

 

'AR > AR Foundation' 카테고리의 다른 글

AR 개발환경구축하기  (0) 2024.07.02
소개  (0) 2024.07.02
iOS / Unity 2022.3.5f1 + AR FOUNDATION / ARKit  (0) 2024.06.30
유니티 Unity AR Foundation  (0) 2024.06.30

3,91f 버전 설치

우리집이 아니라 처음부터 다시해야한다. ㅠㅠ APK도 추가하고 핸드폰 드라이버도 깔아야한다. 기종마다 다르니 메이커에 가서 다운 받아야 한다.

하이라키에 +XR>AR Sesssion 추가

하이라키에 +XR>AR Sesssion Origin 추가  AR Camera를 선택하고 Facing Direction을 User로 한다. 전면카메라는 뜻한다. 참고로 World는 후면카메라

 

하이라키 +XR>AR Default Face 추가

하이라키의 AR Default Face를 끌어다 프리팹으로 만든다. 이름을 MyMask로 한다. 프리팹 폴더를 만들어 넣어도 된다. 프리팹을 만들었으면 하이라키의 AR Default Face는 지우자

히이라키 AR Session Origin에 AR Face Manager 컴포넌트를 추가하자

방금 만든 프리팹 MyMask를 끌어다 놓자

빌드andRun을 눌러보자 빌드하고 실행해보면 전면카메라를 보면 노란 마스크가 보여야한다. 

좀 멋있는 마스크를 적용하기 위해 구글에서 Mask PNG를 이미지로 검색해 투명한 배경이 있는 마스크를 골라 다운로드 한다. 

이걸 Asset폴더에 Material을 만들고 끌어다 놓는다.

Material Folder를 만들고

머터리얼을 하나 Create하고 M_Mask라고 한다.

Inspector Albedo왼쪽에 M_Mask를 끌어다 놓는다. Rendering Mode는 cutout으로 한다.

이제 머터리얼을 만들었으니 이 머터리얼을 오브젝트에 띄워줘야한다.

하이라키에서 3D Object에서 Quad를 추가하고 Mask로 이름 바꾸주고 사이즈를 변경한다.

머터리얼도 적용해준다.

이상태라면 마스크가 얼굴 중앙에 오므로 좀 올려주가 위해 다시 CreateEmpt를 Create하고 MyMask로 이름을 바구고 방금 만든 Mak를 자식으로 끌어다 놓고 위치를 0.025로 올린다.

이제 MyMask를 프리팹으로 만들고 AR Session Origne>AR Face Manager의 FacePrefab을 바꾼다.

빌드하면 얼굴에 가면이 보인다.

이제 UI를 만들어 가면을 껏다 켰다 해보겠다. 하이라키+ UI>Legacy>Button을 선택하면 Cavas의 자식으로 Button이 생긴다.

Cavas를 선택하고 UI Scale Mode를 1080x1920으로 바꾼다.

2D 모드로 바꾼다.

Button을 선택하고  앵커는 아래쪽 중앙 포지션 사이즈를 다음과 같이 바군다.

그럼 버튼이 화면 아래 생긴다.

버튼에 이미지를 입혀주기 위해 Mask PNG를 ctrl-D카피한다 이름을 Mask_Btn으로 바꾼다. Inspector 뷰에서 Text Type을 2D and UI로 바꾼다 Transparency도 체크한다. Apply

하이라키의 Button을 클릭하고 Image를 Mask_btn으로 끌어다 넣어준다. 그럼 UI가 Mask로 바뀐다.

오랜만에 스크립트를 하나 만들고 UI_Manager라고 하자

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARCore;

public class UI_Manager : MonoBehaviour
{
    public ARFaceManager faceManager;

    public void ToggleMask()
    {
        foreach(ARFace face in faceManager.trackables)
        {
            if (face.trackingState == UnityEngine.XR.ARSubsystems.TrackingState.Tracking)
            {
                // face오브젝트 상태를 반대로 변경
                face.gameObject.SetActive(!face.gameObject.activeSelf);
            }
        }
    }
    // Start is called before the first frame update
    void Start(){}

    // Update is called once per frame
    void Update(){ }
}

이제 UI_Manager Script안의 ToggleMask()를 버튼과 연결해야한다.

 그런데 스크립트는 게임오브젝트와 연결이 안되기 때문에 스크립트를 일단 하라라키의 Cavnas아이콘위에 놓으면 자동으로 컴포넌트로 연결한다. 끌어다 놓으면 된다. 

연결후 AR SEssion Origne을 끌어다 FaceManger와 연결해준다.

다음 버튼을 선택하고 OnClick() +를 누르고 Canvas를 끌어다 놓는다. ToggleMask()함수를 선택한다.

마지막으로 가면이 처음에는 안보이게 

MyMask Prefab을 열고 체크박스를 해제하자.

이제 빌드런 해보면 처음에는 마스크가 안 나온지만 밑 버튼을 누르면 나온다.

 

TitleManager 스크립트 변경 

TitleManager.cs
0.00MB

타이틀 화면의 BGM

타이틀 화면의 BGM은 TitleManager 스크립트에서 실행합니다. Start메서드에 다음과 같이 추가합니다. 

SoundManager클래스의 soundManager변수는 맨처음 생성된  SoundManager가 저장된 변수입니다.

이 값으로 SoundManager클래스의 메서드를 호출할 수 있습니다. BGMType.title을 인수로 전달해 PlayBgm메서드를 호출할 수있습니다.

void Start()  {
    string sceneName = PlayerPrefs.GetString("LastScene");      //저장 된 씬
    if (sceneName == "")
    {
~생략
    }
    //타이틀 BGM 재생
    SoundManager.soundManager.PlayBgm(BGMType.Title);
}

게임중/보스전 BGM

RoomManager.cs
0.00MB

게임중 각 씬의  BGM은 RoomManager의 Start 메서드에서 실행합니다. 현재 저장된 현재의 씬이름을 PlayerPrefs.GetString 으로 읽어 보스 스테이지의 씬 이름인지 확인후 재생할 BGM을 바꿉니다.

Start()

    void Start()
    {
        //플레이어 캐릭터 위치
        //출입구를 배열로 얻기
~생략
         // 씬 이름 얻기
        string scenename = PlayerPrefs.GetString("LastScene");
        if (scenename == "BossStage") {
            //보스 BGM 재생
            SoundManager.soundManager.PlayBgm(BGMType.InBoss);
        }   else{
            // 게임 중 BGM 재생
            SoundManager.soundManager.PlayBgm(BGMType.InGame);
        }
    }

재시도후 SE

UIManager클래스의 Retry 메서드에도 다음과 같이 사운드 재생코드를 추가합니다. soundManager의playingBGM에 BGMType.None을 설정한 후 초기화하면 씬을 불러온 뒤 각 스테이지의 BGM이 재생됩니다.

UIManager.cs
0.00MB

Retry()

    public void Retry()
    {
        //HP 되돌리기
        PlayerPrefs.SetInt("PlayerHP", 3);

        //BGM 제거
        SoundManager.plyingBGM = BGMType.None;
        
        //게임 중으로 설정
        SceneManager.LoadScene(retrySceneName);   //씬 이동
    }

 

UIManager스크립트 수정

보스 스테이지의 마지막 기능입니다. 보스를 쓰러뜨리고 열쇠로 문을 열고 나가면  "GAME CLEAR" 표시하고 게임을 종료합니다. 게임 클리어 표시는 UIManager 스크립트에서 처리합니다.

UIManager.cs
0.00MB

GameClear()

doorNumber가 100이면 게임 클리어로 판단돼 Exit스크립트에서 호출됩니다. 보스 방 맨 안쪽 문의 doorNum를 100으로 설정합니다.

//게임 클리어
public void GameClear()
{
    //화면 표시
    mainImage.SetActive(true);
    mainImage.GetComponent<Image>().sprite = gameClearSpr;//「GAMR CLEAR」 설정
    //조작 UI 숨기기
    inputPanel.SetActive(false);
    //게임 클ㄹ어 
    PlayerController.gameState = "gameclear";
    //3초 뒤에 타이틀 화면으로 이동
    Invoke("GoToTitle", 3.0f);
}

GoToTitle()

//타이틀 화면으로 돌아가기
void GoToTitle()
{
    PlayerPrefs.DeleteKey("LastScene");     //저장되어있는 씬을 제거
    SceneManager.LoadScene("Title");        //타이틀 씬으로 돌아가기
}

Exit스크립트 수정

doorNumber가 100이면 게임 클리어로 판단돼 Exit스크립트에서 호출합니다. 그렇지 않으면 원래대로 처리합니다.

private void OnTriggerEnter2D(Collider2D collision){
    if (collision.gameObject.tag == "Player") {
        if (doorNumber == 100)   {
            //BGM 정지
            SoundManager.soundManager.StopBgm();
            //SE 재생 (게임 클리어)
            SoundManager.soundManager.SEPlay(SEType.GameClear);
            //게임 클리어
            GameObject.FindObjectOfType<UIManager>().GameClear();
        } else  {
            string nowScene = PlayerPrefs.GetString("LastScene");
            SaveDataManager.SaveArrangeData(nowScene); // 배치데이터 저장
            RoomManager.ChangeScene(sceneName, doorNumber);
        }
    }
}

 

Main Camera에 어태치된 CameraManager 클래스는 플레이어를 화면 중앙에 오도록 카메라를 조정합니다. 이게보스보드에서는 불편합니다. 따라서 Player폴더의 CameraManager  스크립트를 수정해 플레이어와 보스 중앙을 비추도록 합니다.

 

변수

otherTarget에 외부에서 Boss를 연결합니다.

public GameObject otherTarget;

Update()

두 오브젝트를 사이의 위치를 반환하는 Lerp()메서드의 세번째 인자를 0.5f를 사용해 otherTarget와 Player사이의 중간 지점을 구합니다. 

void Update() {
    GameObject player = GameObject.FindGameObjectWithTag("Player");
    if (player != null)  {
        if(otherTarget != null)  {
            Vector2 pos = Vector2.Lerp(player.transform.position,
                                       otherTarget.transform.position,
                                       0.5f);
            //플레이어 위치와 연동
            transform.position = new Vector3(pos.x, pos.y, -10);
        }  else{
            //플레이어 위치와 연동
            transform.position = new Vector3(player.transform.position.x,
                             player.transform.position.y,
                                             -10);
        }
    }
}

 게임 실행하기

이제 카메라가 보스와 플레이어 중간을 비춥니다.

+ Recent posts