반응형

어느날 지인에게 메쉬를 생성하는법을 알려달라고 연락이 왔다.

해본적은 없지만(메쉬 개념도 없었다...) 재미있을거같아서 한번 시도해보았다.

 

1. 메쉬 생성(vertices, uv) : vertices만 알면 되는거같아서 uv 설정은 하지 않음.

2. Raycast를 이용해서 vertices 값이 유동적으로 바뀔수 있어야한다.

3. Circle 형태여야 함.

 

연락받았을 당시, 어몽어스에서 그림자가 표현되는 것을 상상했다.

그래서 코드를 2D로 만들었다가, 나중에 3D로 만들어야 된다고 뒤늦게 들어서.. 안타깝게도 코드가 2D와 3D가 섞인 코드가 됐다.

 

※※ 메쉬에 대한 개념은 구글링하면 바로나오니 패스한다.


  • 코드
using UnityEngine;
using UnityEngine.Rendering;

public class MeshCreater : MonoBehaviour
{
    public Material meshMaterial;

    private Mesh mesh;
    private MeshFilter meshFilter;
    private MeshRenderer meshRender;

    private void Start()
    {
        GameObject meshGameObject = new GameObject("Mesh", typeof(MeshFilter), typeof(MeshRenderer));
        meshFilter = meshGameObject.GetComponent<MeshFilter>();
        meshRender = meshGameObject.GetComponent<MeshRenderer>();

        meshRender.material = meshMaterial;

        mesh = new Mesh();
        meshFilter.mesh = mesh;
    }

    private void Update()
    {
        CalculateMesh();
    }

    //Calculate vertex vector(circle)
    private Vector3 ConvertAngleToVector(float _angle)
    {
        var rad = _angle * Mathf.Deg2Rad;
        return new Vector3(Mathf.Cos(rad), Mathf.Sin(rad));
    }

    //*** Mesh vertices point : local position of gameObject
    //recalculate vertices for vertices of mesh
    private void CalculateMesh()
    {
        float targetAngle = 360f;
        int rayCount = 360;
        float angleIncrease = targetAngle / rayCount;
        float angle = 0f;
        float dist = 2f;
        
        //Arr Length : ray counts + cneter point
        Vector3[] vertices = new Vector3[rayCount + 1];
        //Triangles : Multiple of 3
        int[] triangles = new int[rayCount * 3];

        //Center point is always Vector3.zero
        Vector3 centerPoint = Vector3.zero;
        //Set the center point to vertices[0] (triangle index 0)
        vertices[0] = centerPoint;

        int vertexIndex = 1;
        int triangleIndex = 0;
        for (int i = 0; i < vertices.Length; i++)
        {
            if (vertexIndex < vertices.Length)
            {
                Vector3 vertex = ConvertAngleToVector(angle);
                RaycastHit hit;

                //Get vertex points
                if (Physics.Raycast(meshRender.transform.position, vertex, out hit, dist))
                {
                    //Recalculate mesh point(local position)
                    //Set the local position(vertices), after Subtract the meshRender position from hit point.
                    vertices[vertexIndex] = hit.point - meshRender.transform.position;
                }
                else
                {
                    vertices[vertexIndex] = vertex * dist;
                }
            }

            //Set triangles
            if (vertexIndex > 1)
            {
                triangles[triangleIndex] = 0;
                triangles[triangleIndex + 1] = vertexIndex - 1;
                triangles[triangleIndex + 2] = vertexIndex < vertices.Length ? vertexIndex : 1;

                triangleIndex += 3;
            }

            vertexIndex++;
            angle -= angleIncrease;
        }

        //Apply mesh info
        mesh.vertices = vertices;
        mesh.triangles = triangles;
    }
}

 

※※ ConvertAngleToVector(float)는 삼각비를 이용해서 각도로 좌표를 구하는 함수다. (Raycast 함수의 direction 값이라 거리값은 필요가 없음.)

※※ 위 함수가 이해가 안간다면 다음글 참고

링크 : [유니티] 삼각비를 이용해 각도로 좌표구하기

 

[유니티] 삼각비를 이용해 각도로 좌표구하기.

삼각비 삼각비는 직각 삼각형에서 각 A에 대해 세 변의 길이가 이루는비가 일정한걸 말한다. 위와 같은 삼각형에서 빗변(r), 대변(y), 이웃변(x)로 확인해보자. 여기서 각 A가 동일하면 다음과 같은

srdeveloper.tistory.com


  • 결과 보기

 

일단 개념을 잡는 쪽에 맞춰둔 코드라, 활용이나 최적화 작업은 목적에 따라 바뀔것 같다.

추가로 빛으로 활용하기 위한 참고 사이트 링크도 올려둠.

 

2d Visibility

The next step is to keep track of which walls the sweep ray passes through. Only the nearest wall is visible. How do you figure out which wall is nearest? The simplest thing is to calculate the distance from the center to the wall. However, this approach d

www.redblobgames.com

 

반응형
반응형

※ 해당 글은 삼각비를 이해해야 적용이 가능하니 혹시 삼각비를 모르시는 분들은 검색해서 알아보시거나, 아래 링크를 참고해주세요~!

링크 : [유니티] 삼각비를 이용해 각도로 좌표구하기

 

[유니티] 삼각비를 이용해 각도로 좌표구하기.

삼각비 삼각비는 직각 삼각형에서 각 A에 대해 세 변의 길이가 이루는비가 일정한걸 말한다. 위와 같은 삼각형에서 빗변(r), 대변(y), 이웃변(x)로 확인해보자. 여기서 각 A가 동일하면 다음과 같은

srdeveloper.tistory.com


 

 

위의 글에서 우리는 삼각비를 통해서 극축과의 각도(θ)와 반지름(r)을 알면 좌표를 구할수 있다는것을 알았다.

그럼 이제 이 원리를 이용해서 유니티로 3D 카메라의 좌표를 구해보도록 하자.

 

  • 구면좌표계

구면좌표계는 2D 극좌표계에서 축이 하나 더 생긴 3D 극좌표계를 말한다. 그림을 보면서 확인해보자.

 

위 그림에서와 같이 극으로 부터 떨어진 점 P의 좌표를 (r, θ, Φ)라고 한다.

방위각(Azimuth)는 말 그대로 방위의 각이고, 앙각(Elevation)은 고도를 말한다. 유니티 좌표계를 기준으로 XZ축이 방위가 되고, Y축이 고도가 된다.

 

그럼 삼각비를 이용해서 점 P의 직교좌표를 구해보자.

위 그림을 기준으로 우리는 2개의 삼각형을 얻을 수 있다.

왼쪽 삼각형은 앙각을 끼고있는 삼각형이고 오른쪽은 방위각을 끼고있는 삼각형이다.

여기서 우리가 제일 먼저 알아야할 값은 빗변(t)의 길이이다. 일단 r값과 Φ을 이용하여 t를 구하게되면,

 

sinΦ = t / r → t = r sinΦ

 

가 되는것을 확인할 수 있다. 이제 t의 값을 구했으니 t와 θ값을 이용해서 x와 z값을 구해보자.

 

x값 : cosθ = x / t → x = t cosθ → x = r sinΦ cosθ

z값 : sinθ = z / t → z = t sinθ → z = r sinΦ sinθ

 

나머지 y값은 y = r cosΦ로 확인할 수 있다.

삼각비를 제대로 이해했고 여기까지 설명을 봤으면 다 이해했다고 믿는다!

 


 

  • 구면좌표계를 이용해서 캐릭터를 중심으로 움직이는 3D카메라 좌표 계산하기.

이젠 코드와 함께 캐릭터를 중심으로 움직이는 카메라의 3D 좌표를 구해볼 차례이다. 코드를 작성하기 전에 코드에 참고한 예시는 다음 그림과 같다.

위 그림을 기준으로 새로 식을 작성해 보면

 

x = r cosΦ cosθ

y = r sinΦ

z = r cosΦ sinθ

 

으로 확인할 수 있다. (여기까지 이해했다면 당신은 좀 더 진화된 수포자다.)

 

위에서의 설명과 다른점이 하나 있는데, 우리는 카메라의 시작 좌표와 반지름(r)을 지정하여 각도를 먼저 구할것이다.

즉, (x, y, z)값을 지정해서 각 축의 값으로 역함수를 이용하여 θ와 Φ의 각도를 구할것이다.

 

※ 역함수로 직각삼각형의 두 변의 길이를 이용하여 극축과의 각도를 구할 수 있다.

 

우리는 (x, y, z)값과 r값을 알고있기 때문에, 방위각(θ)는 역함수 atan으로 구할것이고, 앙각(Φ)은 역함수 asin으로 구한다.

float Azimuth = Mathf.Atan2(_camCoordinate.z, _camCoordinate.x);
float Elevation = Mathf.Asin(_camCoordinate.y / radius);

위와 같이 작성하게되면 우리는 방위각(Azimuth)과 앙각(Elevation)을 얻을 수 있다. 이렇게 각까지 알아냈다면 나머지는 우리가 가진 값들로 직교좌표를 구하면 되는것이다.

 


 

  • 코드
[System.Serializable]
public class SphericalCoordinates
{
    private float radius, azimuth, elevation;

    public float Azimuth
    {
        get { return azimuth; }
        private set
        {
            azimuth = Mathf.Repeat(value, maxAzimuth_Rad - minAzimuth_Rad);
        }
    }

    public float Elevation
    {
        get { return elevation; }
        private set
        {
            elevation = Mathf.Clamp(value, minElevation_Rad, maxElevation_Rad);
        }
    }

    //Azimuth range
    public float minAzimuth_Deg = 0f;
    private float minAzimuth_Rad;

    public float maxAzimuth_Deg = 360f;
    private float maxAzimuth_Rad;

    //Elevation rages
    public float minElevation_Deg = -20f;
    private float minElevation_Rad;

    public float maxElevation_Deg = 40f;
    private float maxElevation_Rad;

    public SphericalCoordinates(Vector3 _camCoordinate, float _radius)
    {
        //방위각 라디안 값(최대, 최소)을 구한다.
        minAzimuth_Rad = Mathf.Deg2Rad * minAzimuth_Deg;
        maxAzimuth_Rad = Mathf.Deg2Rad * maxAzimuth_Deg;
        //앙각 라디안 값(최대, 최소)을 구한다.
        minElevation_Rad = Mathf.Deg2Rad * minElevation_Deg;
        maxElevation_Rad = Mathf.Deg2Rad * maxElevation_Deg;

        radius = _radius;
        //역함수로 방위각과 앙각을 구한다.
        Azimuth = Mathf.Atan2(_camCoordinate.z, _camCoordinate.x);
        Elevation = Mathf.Asin(_camCoordinate.y / radius);
    }

    public Vector3 toCartesian
    {
        get
        {
            //camera position = (r cosΦ cosθ, r sinΦ, r cosΦ sinθ)
            float t = radius * Mathf.Cos(Elevation);
            return new Vector3(t * Mathf.Cos(Azimuth), 
                radius * Mathf.Sin(Elevation), t * Mathf.Sin(Azimuth));
        }
    }

    public SphericalCoordinates Rotate(float newAzimuth, float newElevation)
    {
        Azimuth += newAzimuth;
        Elevation += newElevation;
        return this;
    }
}

public class CamCtrl : MonoBehaviour
{
    private Vector3 lookPosition;
    private Vector3 targetCamPos = new Vector3(0, 1.5f, -4);

    public Transform PlayerTr;
    public SphericalCoordinates sphericalCoordinates;

    void Start()
    {
        //카메라 위치 계산을 위해 x, y, z좌표와 반지름 r값을 넘겨준다.
        sphericalCoordinates = new SphericalCoordinates(targetCamPos, Mathf.Abs(targetCamPos.z));
        transform.position = sphericalCoordinates.toCartesian + PlayerTr.position;
    }

    void Update()
    {
        float horizontal = Input.GetAxis("Mouse X") * -1;
        float vertical = Input.GetAxis("Mouse Y") * -1;

        //플레이어 위치에서 조금더 위쪽으로 자리잡게 만든다.
        lookPosition = new Vector3(PlayerTr.position.x, 
        PlayerTr.position.y + targetCamPos.y, PlayerTr.position.z);

        //플레이어 중심으로 구한 구면좌표를 카메라 위치에 적용
        transform.position = sphericalCoordinates.Rotate
            (horizontal * Time.deltaTime, vertical * Time.deltaTime).toCartesian + lookPosition;

        //목표지점으로 카메라를 보게함
        transform.LookAt(lookPosition);
    }
}

 

※※ 여기서 반지름(radius)값을 조절하면 마우스 스크롤을 통한 카메라 줌인 줌아웃 기능도 추가가 가능하다.

※※ 방위각(Azimuth)와 앙각(Elevation)에 제한을 설정하고 싶다면 minAzimuth_Deg, maxAzimuth_Deg, minElevation_Deg, maxElevation_Deg 값들을 설정해두면 된다.

반응형
반응형

 

  • 삼각비

삼각비는 직각 삼각형에서 각 A에 대해 세 변의 길이가 이루는비가 일정한걸 말한다.

위와 같은 삼각형에서 빗변(r), 대변(y), 이웃변(x)로 확인해보자.

여기서 각 A가 동일하면 다음과 같은 값은 모두 동일하게 이루어진다.

 

1. 이웃변(x) / 빗변(r)

2. 대변(y) / 빗변(r)

3. 대변(y) / 이웃변(x)

 

여기서 1, 2, 3번과 같은 값을 각각 cos, sin, tan으로 명명한다. 다시 정리해보면..

 

1. cosA = x / r

2. sinA = y / r

3. tanA = y / x

 

이렇게 정리해 줄 수 있다. 여기까지 이해됐(외웠...)으면 다음으로 넘어가보자.

 


 

  • 극좌표계

일단, 필자와 같은 수포자들이 '각도로 좌표구하기'로 구글링해서 얻을 수 있는 공식에 대해 알아보자. 각도로 좌표구하기를 검색해서 나오는 공식을 보면

 

radian = degree * Mathf.Deg2Rad

(x, y) = (r * Mathf.cos(radian), r * Mathf.sin(radian))

 

위와 같은 공식을 쉽게 볼 수 있게 된다. 이제 이 공식도 이해를 해보자.

위 그림에 있는 B(r, θ)를 극좌표라고 하는데, 원점에서 떨어진 점 P가 있으면 P(r, θ)로 표시한다. (여기서 r은 극(원점)과 점 P를 잇는 직선의 길이, θ는 각 A를 말한다.)

※ 우리가 보통 알고있는 (x, y)좌표는 직교좌표(데카르트(cartesian) 좌표)이다.

 

우리가 알고싶은것은 점 B의 좌표이다. 점 B의 좌표는 직교좌표로 (x, y)이다.

자, 다시 삼각비를 생각해볼때이다. 삼각비에서 우리는

 

1. cosA = x / r

2. sinA = y / r

 

이것을 이용할 것이다. 뭔가 감이 오지 않는가?

 

1. cosθ = x / r ▶ r * cosθ = x

2. sinθ = y / r ▶ r * sinθ = y

 

즉, (r * Mathf.cos(radian) = x, r * Mathf.sin(radian) = y)가 되는것이다!

다시말해 극좌표 (r, θ) 직교좌표 (r * cosθ, r * sinθ)와 같다.

 

※※ 여기서 극좌표 θ는 radian을 이용한 호도법으로 표시하기 때문에, 우리가 일반적으로 쓰는 degree가 아니라 radian으로 바꿔서 사용해야한다.

 

 

결과적으로 코드를 작성해본다면, 아래와 같은 코드로 작성할 수 있다.

private Vector3 ConvertAngleToVector(float _deg)
{
	var rad = _deg * Mathf.Deg2Rad;
	return new Vector3(Mathf.Cos(rad), Mathf.Sin(rad));
}

 

※※ 필자의 경우 극좌표는 단순히 방향을 알려고할때 사용하기 때문에 r값을 굳이 넣지 않고 사용한다.

※※ 거리도 사용할 경우 cos, sin값에 거리를 곱해주면 된다.

반응형
반응형

 

 

 유니티는 기본적으로 Handheld.Vibrate() 함수로 진동기능이 들어가있지만. 해당 함수로 실행되는 진동은 약 1초가량으로 생각보다 진동이 길다. (진동 시간 조절이 불가능)

 

게임을 제작하다보면 1초의 긴 진동보다 짧은 진동이 필요할때가 생각보다 많다. 따라서, 짧은 진동을 필요로 할때 Handheld.Vibrate()는 완전히 무쓸모다.

 

안드로이드의 경우 AndroidManifest권한 추가에 코드 몇줄만 넣어주면 되지만, 이놈의 iOS는 뭐가 이리 복잡한지.. 아이폰 유저도 아니라 방법 찾는데 생각보다 많이 헤맸다.

 

iOS의 경우 햅틱이라는 기능이 있는데, 6S모델부터 들어가있다고 한다.

 

UISelectionFeedbackGenerator을 통해 햅틱 기능을 사용할수 있는것 같은데, 개발하는 게임들의 최소 버전이 아이폰5 부터라 최대한 낮은 모델에서도 진동 사용이 가능해야했기에 AudioServicesPlaySystemSound를 사용하기로 했다.

 

AudioServicesPlaySystemSound를 사용하기 위해 일단 에디터에 Plugins/iOS 폴더에 Vibrate.mm파일을 만들어준다. 그리고 이어서 플러그인 파일에 코드 작성.

 

#import <UIKit/UIKit.h>
#import <AudioToolBox/AudioToolBox.h>

extern "C" void Vibrate(int _n)
{
    AudioServicesPlaySystemSound(_n);
}

 

그리고 이어서 스크립트를 생성 후, 다음과 같이 작성 후 사용하면 된다.

 

using System.Runtime.InteropServices;

[DllImport("__Internal")]
public static extern void Vibrate(int _n);

※ 참고로 플러그인 파일명과 함수명은 입맛대로 바꿔도 상관없다.

 

인수에 들어가는 int값은 밑에 값들 중 입맛대로 선택해서 넣으면 된다.

 

1519 // Actuate `Peek` feedback (weak boom)

1520 // Actuate `Pop` feedback (strong boom)

1521 // Actuate `Nope` feedback (series of three weak booms)

 

실행해보니 잘 된다. (물론 잘 되는건 6S부터다. 그 밑의 폰들은 당연히 안된다.)

 

 

반응형
반응형

※ 본 포스팅은 유니티 버전 2018.4.20(LTS)을 대상으로한 솔루션입니다.


구글 플레이 업로드를 위해 안드로이드 빌드를 하던중 갑자기 에러를 발견하게 된다.

 

com.android.builder.internal.aapt.v2.Aapt2Exception: Android resource linking failed

 

하루 전까지만 해도 잘 되던 빌드가 위와 같은 에러가 뜨면서 진행되지를 않는다.

처음보는 에러에 검색을 해봤지만.. 유니티에서 문제가 된 사람은 거의 없던건지, 잘 못찾았던건지 안드로이드 스튜디오에서 작업한 사람의 문의글로 가득한 페이지를 보면서 이것저것 따라해보기 시작했다.

 


시도 1) 안드로이드 스튜디오 업데이트

 

2018 버전으로 작업한 프로젝트이기에 유니티에서 제공하는 SDK를 사용하지 않고 안드로이드 스튜디오에서 받은 SDK를 사용중이라 시도를 해봤다.

 

안드로이드 스튜디오 몇몇 버전이 AAPT2라는게 기본적으로 사용되어진다고 해서 최신버전으로 바꾸거나 AAPT2를 비활성화 하라는 내용인데, Unity에 대한 설명이 아니라서 일단 안드로이드 스튜디오를 최신버전으로 변경후 빌드를 시도해봤다.

 

결과 : 실패


시도 2) .android .gralde 폴더 제거

 

이 시도는 스택오버플로우에서 보게된것인데, 위의 폴더를 제거하면 빌드가 된다고하는 내용이었다.

※ 위의 폴더들은 C:\Users\UserName 폴더 안에 존재한다.

 

.android, .gradle 폴더 삭제후 빌드를 시도해봤다.

 

결과 : 실패


시도 3) mainTemplate.gradle 설정

 

이 시도는 gradle 버전을 수정하여 해결하는 방법이다.

 

유니티 Build setting - Publishing setting에 Main gralde template를 체크해주면 플러그인 폴더에 mainTemplate 파일이 생성된다.

 

해당 파일을 열면 dependencies 항목에 다음과 같이 적혀있다.

dependencies {
        classpath 'com.android.tools.build:gradle:3.4.0'
**BUILD_SCRIPT_DEPS**}
}

필자의 경우엔 위와 같이 gradle 버전이 3.4.0으로 되어있었는데, 해당 부분을 3.3.2로 변경후 저장, 빌드를 시도해봤다.

 

결과 : 성공

 


급하게 해결해야 했던 상황이라, 왜 저게 되는건지 자세한 사항은 파악하지 못했지만.. 일단 무사히 빌드가 되는것에 안도하고 위기를 넘기게 되었다.

 

자세한 사항은 추후 파악 후 서술할 예정.

반응형

+ Recent posts