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=C5taq2a3_rQ&list=PLATff1khpIscTMN2KXxagrTkBCwdzOeol&index=1

AR Foundation은 유니티에서 AR을 개발시 사용하는 통합워크플로이다.

 

https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@5.1/manual/index.html

 

AR Foundation | AR Foundation | 5.1.5

AR Foundation AR Foundation enables you to create multi-platform augmented reality (AR) apps with Unity. In an AR Foundation project, you choose which AR features to enable by adding the corresponding manager components to your scene. When you build and ru

docs.unity3d.com

 

특징

AR Foundation은 다음 기능을 지원합니다.

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

 

AR을 개발하기 위해 플랫폼마다  개발킷이 존재하는데 이를 통합했다고 보면  되는데 지원하는 해당기능은 다음과 같다.

Feature ARCore ARKit OpenXR
Session Yes Yes Yes Yes Yes
Device tracking Yes Yes Yes Yes Yes
Camera Yes Yes     Yes
Plane detection Yes Yes Yes Yes Yes
Image tracking Yes Yes Yes    
Object tracking   Yes      
Face tracking Yes Yes      
Body tracking   Yes      
Point clouds Yes Yes      
Raycasts Yes Yes   Yes Yes
Anchors Yes Yes Yes Yes Yes
Meshing   Yes Yes Yes  
Environment probes Yes Yes      
Occlusion Yes Yes      
Participants   Yes    

 

UnityMars를 통해 시뮬레이션도 가능하다고 함. 뭘까?

+ Recent posts