using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
public class ARPlaceOnPlane : MonoBehaviour
{
public ARRaycastManager raycastManager;
public GameObject placeObject;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void updateCenterObject()
{
Vector3 screenCenter = Camera.current.ViewportToScreenPoint(new Vector3(0.5f, 0.5f, 0));
List<ARRaycastHit> hits = new List<ARRaycastHit>();
raycastManager.Raycast(screenCenter, hits, TrackableType.Planes);
if (hits.Count > 0)
{
Pose placePose = hits[0].pose;
placeObject.SetActive(true);
placeObject.transform.SetPositionAndRotation(placePose.position, placePose.rotation);
}
else
{
placeObject.SetActive(false);
}
}
}
저희가 만든 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()를 사용
StartCoroutine()는 파라미터로 주어진 IEnumerator타입의 메소드를 실행시켜주는 역할을합니다.
IEnumerator타입 함수는 yield의 사용을 할수있어서 함수가 호출되고 나서 저희가 함수로 다시 돌아오고 어떻게 동작하는지 확인 할수있게 해줍니다.
Yield return new WaitForSecond()은 IEnumerator타입 함수가 재시작 할때까지 얼마나 오랫동안 기다려야하는지를 결정하는 유연성을 제공해줍니다. 파라미터로 원하는 시간을 넘겨주면 그시간동안 다음 동작을 기다립니다. 그후에 더이상의 이함수의 반복이 필요하지않은 부분에서 yield return null로 탈출해주면됩니다.
이 코루틴을 이용한 타이머는 타이머가 필요한 동작을 각각 분리하여 구현을 할수있다는것이 장점이 될수있습니다.
update로 하기에는 다양한 조건을 검사하거나 동시에 여러개의 타이머가 필요할때는 코루틴을 사용해 구현하는것이 편리할것같다고 생각합니다.
저희가 만든 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()를 사용
StartCoroutine()는 파라미터로 주어진 IEnumerator타입의 메소드를 실행시켜주는 역할을합니다.
IEnumerator타입 함수는 yield의 사용을 할수있어서 함수가 호출되고 나서 저희가 함수로 다시 돌아오고 어떻게 동작하는지 확인 할수있게 해줍니다.
Yield return new WaitForSecond()은 IEnumerator타입 함수가 재시작 할때까지 얼마나 오랫동안 기다려야하는지를 결정하는 유연성을 제공해줍니다. 파라미터로 원하는 시간을 넘겨주면 그시간동안 다음 동작을 기다립니다. 그후에 더이상의 이함수의 반복이 필요하지않은 부분에서 yield return null로 탈출해주면됩니다.
이 코루틴을 이용한 타이머는 타이머가 필요한 동작을 각각 분리하여 구현을 할수있다는것이 장점이 될수있습니다.
update로 하기에는 다양한 조건을 검사하거나 동시에 여러개의 타이머가 필요할때는 코루틴을 사용해 구현하는것이 편리할것같다고 생각합니다.
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로 해주고 해상도는 다음과 같이해준다.
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);
}
}
실행해서 마커를 촬용하면 고양이가 나오고 볼을 드래그해서 맞추면 폭발이 일어나는데 확율에 의해 성공과 실패가 나뉜다.
실제 해보면 공의 초기 위치와 고양이의 위치가 비슷해 고양이가 공위에 있다. AddForce가 앞으로 날라가기때문에 맞지 않는다. 공위로 고양이가 있을때 억지로 가깝게 해서 맞춰줘야야만 한다. 난 공을 좀더 카메라쪽(-) 고양이를 뒤로 위치시켜 거리를 만들어 공이 앞으로 날라가다 맞게 해주었다
프로젝트창 우클릭후 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->TargetTexture 에 넣고 M_FaceVideo를 Face Mdel->Material-에 넣고 Face Mode을 AR FaceManager->Face prefab에 넣는다.
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프리팹을 끌어다 놓는다.
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를 눌러 현재 씬을 추가해줘야한다.
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로 모델을 만들어서 맵핑해준다. 유튜브는 이빨과 눈동자도 보이는데 버전에 따라 좀 다른듯
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()함수를 선택한다.