MainCamera에는 AudioListener가 기본적으로 추가되어있고 설정할 것은 없는데 카메라가 여러대일경우 하나만 남겨놓고 지워야 한다.
Resouces/Sounds폴더에서 WeaponSFX 패키지를 임포트하고 프로젝트뷰의 06.Sounds 폴더로 드래그&드롭해 위치를 옮긴다. 이패키지는 Rifle과 Shotgun 두 가지의 총소리와 폭발음이 포함돼 있다. Rifle폴더에 있는 p_m4_1사운드파일을 사용한다. 임포트한 오디오 클립은 Force To Mono옵션으 체크해서 가볍게 하고 사이즈도 줄일수 있다.
오디오 임포트 옵션 - Load Type
Decompress On Load : 로드시 압축을 해제하므로 사이즈가 큰 파일은 오버헤드를 발생시킨다. 작은 사이즈의 오디오에 적합하고 압축후에는 CPU자원을 덜 소비한다.
Compressed in Memory : 압축된 상태로 메모리에 상주한다. 큰 사이즈의 오디오에 적합하다.
Streaming : HDD에서 부터 스트리밍 하듯이 재생한다. 메모리가 필요없다.
오디오 임포트 옵션 - Compression Format
PCM : 비압축
ADPCM : 압축율 3.5배로 노이즈가 포함돼어 있는 음원에 적합
Voris / MP3 : 70%정도의 압축률
총소리 구현
p_mp_1.wav을 선택해 인스펙터에서 다음과 같이 설정한다. 총소리는 발사할 때마다 총소리를 발생시키므로 Decompress On Load로 Compression Format은 ADPCM정도로 한다.
Bullet프리팹을 선택해 AudioSoruce컴포넌트를 추가한다. 속성은
Audio Clip : p_mp_1.wav , 발생시킬 음원 파일
Play On Awake : 체크, 해당 컴포넌트가 활성화될 때 자동 재생 여부
Min Distance : 5 , 볼륨 100% 값으로 음원이 들리는 영역범위
Max Distance : 10 , 음원이 들리는 최대범위
하이라키의 Bullet을 수정했기 때문에 Override>Apply All버튼을 클릭해 원본 Bullet프리팹에 저장한다. 다른 방법은 Audio Source 컴포넌트에서 우클릭후 팝업된 메뉴에서 Added Component>Apply to Prefab "Bullet"을 선택한는 것이다.
이렇게 Bullet Prefab에서 구현한 총소리는 충돌하자 마자 삭제되므로 사운드가 끊어지는 현상이 발생한다. 따라서 스크립트로 처리해 보겠다. Bullet Prefab에서 AudioSource 컴포넌트를 제거한다.
하이라키뷰의 Player를 선택하고 AudioSource컴포넌트를 추가한다. 수정할 필요는 없다. 다음과 같이 스크립트를 추가하고 스크립트에 오디오클립을 연결해준다.
FireCtrl 스크립 코드
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
[RequireComponent(typeof (AudioSource))] //삭제되는것을 방지하는 어트리뷰트
public class FireCtrl : MonoBehaviour
{
public GameObject bullet; //총알프리팹
public Transform firePos; // 총알 발사 좌표
public AudioClip fireStx; //총소리에 사용할 음원
private new AudioSource audio; //오디오 소스 컴포넌트를 저장할 변수
// Update is called once per frame
private void Start() {
audio = GetComponent<AudioSource>();
}
void Update()
{ //마우스 왼쪽 버튼을 클릭했을때 Fire 함수 호출
if(Input.GetMouseButtonDown(0)) {
fire();
}
}
void fire() {
Instantiate(bullet,firePos.position, firePos.rotation); //Bullet프리팹을 동적으로 발생
audio.PlayOneShot(fireStx, 1.0f); //총소리발생
}
}
여러개의 드럼통의 같은 텍스처로 적용되여 있다. 시작과 동시에 다양하게 바꿔보자. 텍스처의 적용은 Mesh Rendere 컴포넌트에 연결된 Material에서 지정한다. 여기에 사용한 Barrel모델의 Mesh Renderer컴포넌트는 Barrel 하위에 있는 Barrel에 적용되어 있다. 부모 오브젝트는 빈 오브젝트이다.
하위의 Mesh Renderer를 연결할 변수를 선언하고 연결후 사용할 수도 있지만 BarrelCtrl스크립트에서 동적으로연결해 보자. 추가된 코드는 다음과 같다.
public Texture[] textures; //무작위로 적용할 텍스쳐배열
private new MeshRenderer renderer; //하위 MeshRenderer를 저장할 변수, new를 사용하는데 Component.renderer로 정의된 멤버 변수로서 new키워드를 사용해야 한다는데 잘 이해가 안 간다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BarrelCtrl : MonoBehaviour {
public GameObject expEffect; //폭발효과를 연결할 참조
public Texture[] textures; //무작위로 적용할 텍스쳐배열
public float radius = 10.0f; // 검사할 주변 반경을 설정하였고
private new MeshRenderer renderer; //하위 MeshRenderer를 저장할 변수
private Transform tr;//컴포넌트를 저장할 변수
private Rigidbody rb;
private int hitCount = 0; //총알맞은 회수를 누적시킬 변수
void Start() {
tr = GetComponent<Transform>();
rb = GetComponent<Rigidbody>();
renderer= GetComponentInChildren<MeshRenderer>(); //자식컴포넌트 추출
int idx = Random.Range(0, textures.Length);//난수발생
renderer.material.mainTexture= textures[idx]; //텍스처지정
}
private void OnCollisionEnter(Collision col) {
if (col.collider.CompareTag("BULLET")) {
if (++hitCount == 3) {
ExpBarrel();
}
}
}
void ExpBarrel() { //드럼통을 폭발시킨다
// 파티클효과을 참조
GameObject exp = Instantiate(expEffect, tr.position, Quaternion.identity);
Destroy(exp, 5.0f); //3초후 파티클 효과 제거
//rb.mass = 1.0f; //mass를 20에서 1로 가볍게 한다.
//rb.AddForce(Vector3.up * 1500.0f); //위쪽으로 날라가게 힘을 준다
IndirectDamage(tr.position); //파괴력을 전달
Destroy(gameObject, 3.0f); //3초후 드럼통 제거
}
void IndirectDamage(Vector3 pos) {
// 주변의 드럼통 추출
Collider[] cols = Physics.OverlapSphere(pos, radius, 1 << 3); //주변객체를 검색해서 리턴
foreach (var col in cols) { //차례대로 반복
rb = col.GetComponent<Rigidbody>();
rb.mass = 1.0f; //무게를 가볍게하고
rb.constraints = RigidbodyConstraints.None; // 움직임과 회전의 제한을 푼다
rb.AddExplosionForce(1500.0f, pos, radius, 1200.0f); //반경안에서 폭발력을 생성
}
}
}
OverlapSphereNonAlloc
Physics.OverlapSphere함수는 실행시 Sphere범위에 검출될 개수가 명확지 않을때만 사용해야한다. 동적으로 메모리를 만들기 때문에 메모리 Garbage가 발생하기 때문이다. 따라서 Sphere범위에 검출될 개수가 명확할 때는 Garbage가 발생하지 않는 Physics.OverlapSphereNoneAlloc함수를 사용하기를 권장한다. 이 함수는 결괏값을 저장할 정적 배열을 미리 선언하여 실행정에 배열의 크기를 변경할 수 없다. 우리는 배럴을 20개 배치했기 때문에 최대 검출치가 20개 이므로 이 함수를 사용할수 있는 것이다.
Models/Barrel 폴더를 보면 Barrel이 2개 있다. 화살표가 달려있는 확장자 FBX인 원본 3D로 작업한다.
선택해보면 프로젝트뷰 아래 확장자가 표시된다.
Barrel을 하이라키로 끌어다 놓고 Transform을 Reset하면 캐릭터에 비해 큰 Barrel이 보인다. 캐럭터 크기에 맞게 스케일을 조정해보자
Scale Factor
외부에서 임포트한 3D모델은 반드시 Transform의 Scale속성을 수정하지 말고 FBX Import setting의 Scale Factor속성을 수정한다. 프로젝트 뷰의 Barrel 3D모델을 선택하면 인스펙터 뷰의 3D모델의 여러정보(FBX Import Settigns)가 표시된다. 첫번째(Model)맵을 선택하고 Scale Factor를 0.4로 설정하면 현재 Barrel모델의 크기를 40% 크기로 조정할 수 있다. 스케일을 변경하고 하단에 있는 [Apply] 버튼을 클릭해 저장한다. (나의 경우 화면에 Apply 팝업창이 떳다)
이제 캐릭터와 배럴의 크기가 비슷해졌고, 배럴의 하이라키속 transform.Scale의 속성은 (1,1,1) 그대로이다.
하이라키뷰의 Barrel에 충돌처리를 위해 Capsule Collider와 Rigidbody컴포넌트를 추가한다. 잘보면 영역을 나타내는 구역이 작다.
Capsule Collider의 속성은 다음과 같이 수정해서 맞춘다.
Rigidbody 컴포넌트는 질량 mass는 20으로 하고 Constraints는 위치와 회전에 제한을 거는 속성으로 Freeze Rotation x와 z축을 체크한다. x축 z축으로 회전을 금지한다. 넘어지지 않게한다.
게임 Play 후 총알을 발사해 보면 배럴이 살짝 뒤로 움직인다.
이제 Barrel에 충돌한 총알을 제거하고 스파크 효과를 만든다. Wall에 추가한 RemoveBullet스크립트를 재활용한다. Barrel에 RemoveBullet 스크립트를 추가하고 SparksEffect를 연결한다.
이제 실행해보면 배럴에 스파크 효과가 발생하고 Bullet이 없어진다.
배럴에 총알이 3번 충돌하면 배럴이 폭발하게 해보겠다. BarrelCtrl이라는 스크립트를 생성하고 다음과 같이 작성하고 하이라키 뷰의 Barrel에 추가한다.
BarrelCtrl의 ExpEffect속성에 폭발 효과를 내는 파티클을 연결한다. 03. Prefabs/ EffectExamples/ FireExplosionEffects/ Prefabs 폴더에 있는 SmallExplosionEffect를 연결한다. .새로 연결하면 Bullet의 Tag가 변경되는 이상한 증상이 있어 다시 만들어주고 연결해줬다. 폴더내 없는 분들은 Unity Particle Pack에서
import 한후 새로만들어진 폴더에서 기존의 Prefabs폴더로 옮긴다.
SmallExplosionEffect는 프리팹을 선택하고 차일드를 포함하여 모든 Looping 속성을 모두 언체크한다.
실행해보면 잘된다.
하이라키뷰의 간략화
Barrel을 03.Prefabs폴더로 이동시켜 프리팹으로 변환하다.
이 프리팹을 이용해 20개정도 배럴을 배치하려고 하는데 그러면 하이라키뷰가 지저분해진다.
따라서 _Stage라는 EmptyObject를 만드고 하위에 복제하면 간단히 정리된다.
하이라키뷰에 빈게임오브젝트를 하나 만들고 이름을 _STAGE로 저장한다. _를 붙이는 이유는 하이라키뷰의 맨위로 보내기 위해서다.
이제 Barrel 프리팹을 _STAGE로 옮긴후 Ctrl-D를 눌러 20개 복사하고 위치를 바궈도 되지만 스크립트를 이용해 자동으로 배치해 보겠다.
StageCtrl 스크립트를 하나 만들어 _Stage에 적용시킨다.
x,z좌표를 랜덤으로 발생해서 Barrel을 배치한면서 Parent를 _Stage 의 transform으로 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StageCtrl : MonoBehaviour
{
public GameObject barrel;
void Start()
{
for(int i = 0; i < 20; i++) {
float x = (float) Random.Range(-24f, 24f);
float z = (float) Random.Range(-24f, 24f);
Instantiate(barrel,new Vector3(x,5f,z), Quaternion.identity).transform.SetParent(transform);
}
}
// Update is called once per frame
void Update()
{
}
}
왼쪽이 실행전 오른쪽이 실행후이다.동적으로 생성된다.
사실 배럴의 좌표는 Random이므로 겹칠수도 있다. 따라서 49x49개의 좌표를 리스트로 만들어 놓고 랜던으로 하나씩 뽑으면서 배치하고 배치한건 리스트에서 지우주면 겹치치않게 만들수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class StageCtrl : MonoBehaviour
{
public GameObject barrel;
void Start()
{
List<Vector3> posList = new List<Vector3>();
for (int ix= -24; ix < 24; ix++) {
for (int iz = -24; iz < 24; iz++) {
posList.Add(new Vector3(ix, 0f, iz));
}
}
for(int i = 0; i < 20; i++) {
int randomIndex = Random.Range(0,posList.Count-1);
Instantiate(barrel, posList[randomIndex], Quaternion.identity).transform.SetParent(transform);
posList.RemoveAt(randomIndex);
}
}
}
Unity에서는 오일러 각과 쿼터니언을 모두 사용하여 회전과 방향을 나타낼 수 있습니다. 표현은 둘 다 동일하지만 용도와 제한 사항은 서로 다릅니다.
보통 씬에서는 방향을 오일러 각으로 표시하는 트랜스폼 컴포넌트를 사용하여 오브젝트를 회전합니다. 그러나 Unity는 회전과 방향을 내부적으로 쿼터니언으로 저장하므로 짐벌 락으로 이어질 수도 있는 더 복잡한 모션에 유용할 수 있습니다.
오일러 각
트랜스폼 좌표에서 Unity는 벡터 프로퍼티Transform.eulerAnglesX, Y, Z를 사용하여 회전을 표시합니다. 노멀 벡터와는 달리 이 값은 X, Y, Z 축에 대한 실제 회전 각도(단위: 도)를 나타냅니다.
오일러 각 회전은 3개의 축을 중심으로 3개의 개별 회전을 수행합니다. Unity는 Z축, X축, Y축을 중심으로 오일러 회전을 순차적으로 수행합니다. 이 회전 방법은 외부 회전입니다. 회전하는 동안 원래 좌표계가 변경되지 않습니다.
게임 오브젝트를 회전하려면 각 축이 트랜스폼 컴포넌트로 회전할 각의 크기를 입력할 수 있습니다. 스크립트로 게임 오브젝트를 회전하려면Transform.eulerAngles를 사용합니다. 오일러 각으로 변환하여 계산하고 회전하려면 짐벌 락 문제가 발생할 위험이 있습니다.
짐벌 락
3D 공간에 있는 오브젝트가 자유도를 잃고 2차원 내에서만 회전할 수 있는 경우를 짐벌 락이라고 합니다. 두 축이 평행하면 오일러 각으로 짐벌 락이 발생할 수 있습니다. 스크립트에서 회전 값을 오일러 각으로 변환하지 않으면 쿼터니언을 사용하여 짐벌 락을 방지해야 합니다.
짐벌 락 문제가 있는 경우Transform.RotateAround를 사용하면 오일러 각을 피할 수 있습니다. 각 축에Quaternion.AngleAxis를 사용하여 함께 곱할 수도 있습니다(쿼터니언 곱셈은 각 회전에 차례로 적용됩니다).
쿼터니언
쿼터니언은 3D 공간에서 공간 방향과 회전의 고유한 표현을 위한 수학적 표기법을 제공합니다. 쿼터니언은 4개의 숫자를 사용하여 3D에서 단위 축을 중심으로 회전 방향과 각도를 인코딩합니다. 이 4개의 값은 각이나 각의 크기가 아닌 복소수입니다. 자세한 내용은쿼터니언의 수학을 참조하십시오.
쿼터니언 회전은 계산에 있어 효율적이고 안정적이기 때문에 Unity는 회전 값을 쿼터니언으로 변환하여 저장합니다. 단일 쿼터니언은 축에 대해 360도보다 큰 회전을 나타낼 수 없기 때문에 에디터에서는 회전을 쿼터니언으로 표시하지 않습니다.
Quaternion 클래스를 사용하면 쿼터니언을 직접 사용할 수 있습니다. 회전에 스크립트를 사용하는 경우 Quaternion 클래스와 함수를 사용하여 회전 값을 만들고 변경할 수 있습니다. 회전에 오일러 각으로 값을 적용할 수 있지만 문제를 방지하기 위해 쿼터니언으로 저장해야 합니다.
오일러 각과 쿼터니언 간의 전환
다음 스크립트를 사용하여 쿼터니언과 오일러 각 간을 변환하고 원하는 방식으로 회전을 보고 편집할 수 있습니다.
Unity는 Quaternion 클래스를 사용하여 게임 오브젝트의 3차원 방향을 저장하고, 이를 통해 한 방향에서 다른 방향으로의 상대 회전을 설명합니다.
이 페이지는 Quaternion 클래스의 개요, 그리고 이 클래스를 사용하는 스크립팅의 일반적인 용도에 대해 설명합니다. Quaternion 클래스의 모든 멤버에 대한 전체 레퍼런스는Quaternion 스크립트 레퍼런스를 참조하십시오.
오일러 각(게임 오브젝트의 회전을 위해 인스펙터에 표시되는 X, Y, Z 값)과 Unity가 게임 오브젝트의 실제 회전을 저장하는 데 사용하는 기본 쿼터니언 값 간의 차이를 이해하고 있어야 합니다. 이 항목에 대한 기본 정보는Unity의 회전 및 방향을 참조하십시오.
스크립트에서 회전 처리를 다루는 경우 Quaternion 클래스와 이 클래스의 함수를 사용하여 회전 값을 만들고 수정해야 합니다. 오일러 각을 사용할 수 있는 상황도 있지만, 다음 사항을 염두에 둬야 합니다. - 오일러 각을 처리하는 Quaternion 클래스 함수를 사용해야 합니다. - 회전의 오일러 값을 검색 및 수정하고 다시 적용하면 의도하지 않은 부작용이 발생할 수 있습니다(아래 참조).
쿼터니언 직접 생성 및 조정
Unity Quaternion 클래스의 여러 함수를 통해 오일러 각을 전혀 사용하지 않고도 회전을 만들고 조정할 수 있으며, 대부분의 일반적인 경우 이러한 함수들을 사용해야 합니다. 각각의 함수는 코드 샘플이 있는 스크립트 레퍼런스에 연결됩니다.
일부의 경우 스크립트에서 오일러 각을 사용하는 것이 더 좋습니다. 이 경우 각을 변수로 유지하고 회전에 오일러 각으로적용하는 데만 사용해야 하고, 궁극적으로 쿼터니언으로 저장되어야 합니다. 오일러 각을 쿼터니언에서검색해서 가져올 수 있지만, 검색해서 가져온 후 수정하고 다시 적용하면 문제가 발생할 수 있습니다.
아래에는 흔히 발생하는잘못된 방법에 대한 몇 개의 예제가 있습니다. 설명을 위해 X축을 중심으로 게임 오브젝트를 초당 10도씩 회전시키려는 가상의 예제를 사용합니다. 다음 2코드는 잘못된방법입니다.
// rotation scripting mistake #1
// the mistake here is that we are modifying the x value of a quaternion
// this value does not represent an angle, and does not produce desired results
void Update ()
{
var rot = transform.rotation;
rot.x += Time.deltaTime * 10;
transform.rotation = rot;
}
// rotation scripting mistake #2
// Read, modify, then write the Euler values from a Quaternion.
// Because these values are calculated from a Quaternion,
// each new rotation might return very different Euler angles, which might suffer from gimbal lock.
void Update ()
{
var angles = transform.rotation.eulerAngles;
angles.x += Time.deltaTime * 10;
transform.rotation = Quaternion.Euler(angles);
}
그리고 다음은 스크립트에서 오일러 각을올바르게사용하는 예입니다.
// Rotation scripting with Euler angles correctly.
// Store the Euler angle in a class variable, and only use it to
// apply it as an Euler angle, but never rely on reading the Euler back.
float x;
void Update ()
{
x += Time.deltaTime * 10;
transform.rotation = Quaternion.Euler(x,0,0);
}
오일러각과 쿼터니언각의 차이
새로운 씬을 하나 만들고 Models>Player폴더의 Player를 끌어다 놓습니다. (Prefab은 이미 다른 스크립트가 들어 있어 안됩니다.) 이 강좌가 처음이 신분들은 아무 게임오브젝트를 만들어 놓습니다.
아래 스크립트를 끌어다 적용합니다.
using UnityEngine;
public class ExampleScript : MonoBehaviour {
float rotationSpeed = 45;
Vector3 currentEulerAngles;
Quaternion currentRotation;
float x;
float y;
float z;
void Update() {
if (Input.GetKeyDown(KeyCode.Alpha1)) x = 1 - x;
if (Input.GetKeyDown(KeyCode.Alpha2)) y = 1 - y;
if (Input.GetKeyDown(KeyCode.Alpha3)) z = 1 - z;
if(Input.GetKeyDown(KeyCode.Alpha0)) {
x = 0; y = 0; z = 0;
currentEulerAngles = Vector3.zero;
}
//modifying the Vector3, based on input multiplied by speed and time
currentEulerAngles += new Vector3(x, y, z) * Time.deltaTime * rotationSpeed;
//moving the value of the Vector3 into Quanternion.eulerAngle format
currentRotation.eulerAngles = currentEulerAngles;
//apply the Quaternion.eulerAngles change to the gameObject
transform.rotation = currentRotation;
}
void OnGUI() {
GUIStyle style = new GUIStyle();
style.fontSize = 24;
// Use eulerAngles to show the euler angles of the quaternion stored in Transform.Rotation
GUI.Label(new Rect(10, 0, 0, 0), "Rotating on X:" + x + " Y:" + y + " Z:" + z, style);
//outputs the Quanternion.eulerAngles value
GUI.Label(new Rect(10, 25, 0, 0), "CurrentEulerAngles: " + currentEulerAngles, style);
//outputs the transform.eulerAngles of the GameObject
GUI.Label(new Rect(10, 50, 0, 0), "GameObject World Euler Angles: " + transform.eulerAngles, style);
}
}
1,2,3을 누르면 해당되는 x,y,z축으로 무한히 회전합니다.
0을 누르면 초기화 됩니다.
Play시켜보면 오일러나 GameObject World Euler Angle 둘 다 (0,0,0)입니다.
처음 1만 눌러 90도 전에 다시 1을 눌러 세워 보겠습니다. 아직 2개의 값은 비슷합니다.
다시1을 눌러 90도가 넘은면 1을 눌러 세워 봅니다. World Euler Angles.x가 줄어들고 y,z가 180됩니다.
일단 1,2,3을 토글하면서 오일러값과 쿼터니언의 값 변화를 보세요 쿼터니언값의 변화는 상식적이지 않습니다. 연속적인 회전이 안됩니다.
0을 눌러 초기화 합니다
우선 1만 눌러 적당히 90을 만들어 아래로 눕게 합니다. 그다음 2,나 3을 눌러보면 둘다 Y축으로 돌아가는데 하나는 반대로 돌아갑니다.
이게 짐벌록입니다. x축을 90돌면서 차일드인 Z축이 같이 돌아 Y축과 겹쳐졌기 때문입니다.
다른 축으로도 실험해보왔지만 일어나지 않습니다. 이건 유니티의 x,y,z계산 우선순위때문인듯 합니다.
재미있는건 1을 눌러 90도쯤 변화시켜 눕힌후 2를 눌러 y축을 회전시킨후 3을 누르면 각도는 열심히 변하는데 실제 게임오브젝트는 멈춰있습니다 y축과 z축의 방향이 서로 반대라 합쳐서 0이 되기 때문입니다. 꼭해보시기 바랍니다. 희안합니다.
사진으로 간단하게 설명하겠다.
우선 다음 사진과 같이 x, y ,z 축을 가진 오브젝트가 있다고 해보자.
저 화살표가 가리키는 방향을 계속 유의하자.이 오브젝트의 x(빨간축) 축을 90도 회전시켜보겠다.
x축(빨간축)으로 90도 회전시킨 모습이다. 화살표를 보면 오른쪽 방향을 가리키고 있다.
여기까지는 문제가 없다.
자, 그다음 Y축(초록색)으로 90도 회전시켜보겠다.
Y축으로90도회전시키니다음과같은모습이 된다.
Z축과X축이한축으로합쳐지면서한축에대한계산이불가능해집니다.
이러한현상을바로 '짐벌락(Gimbal-lock)' 이라고 한다.
이러한 짐벌락 현상이 생기는 이유는 오일러 앵글이 자체적으로 설정되어있는 순서로 해당 축들을 개별적으로 계산하기 때문이다.
Unity에서는 오일러 앵글이 X, Y, Z 순서로 계산된다.
세 개의 축을 동시에 계산하지 않고 각 축을 독립적으로 판단하기에 다음과 같이 어쩌다가 겹쳐버리는 현상이 발생하는 것이다. 이렇게 축이 겹쳐버리면 한 축에 대해서는 계산이 불가능하기에, 정확한 각도 계산이 불가능하다.
Unity로 2D 게임을 제작할 때는 오일러 각으로도 각도 구현에 문제가 없지만, 모든 각도를 통제해줘야 하는 3D 게임 같은 경우에는 오일러 앵글만으로는 구현에 한계가 있다.
오일러 각의 이러한 문제를 해결하기 위해 나온 것이 바로쿼터니언(Quaternion)이다.
쿼터니언(Quaternion)
쿼터니언은 각 축을 한꺼번에 계산하기 때문에 짐벌락 문제가 발생하지 않는다.
Euler angle 과는다르게 쿼터니언은4개의 성분(x, y, z, w)으로이루어져 있다.
해당 성분은 벡터(x, y, z)와 스칼라(w)를 의미한다.
쿼터니언은 내부가 수학적으로 복잡하게 구현되어있어 이를 제대로 이해하지 못한다면 자유자재로 다루기는 상당히 까다롭다.
RPG류 게임에서 주인공이 휘두르는 검에 표현되는 궤적이나 총알을 발사했을때 총알이 날아가는 궤적을 만들어 보겠다.
유니티는 이런 효과를 할수 있는 Trail Rendere를 제공한다.
Trail Renderer
Bullet Prefab을 끌어다 씬에 놓는다.
하이러키 뷰에서 Bullet을 선택하고 메뉴에서 Component>Effects>Trail Renderer를 선택해 Trail Renderer를 추가한다. Bullet의 Z축을 잡고 앞으로 드래그하면 동적으로 생성된 메시를 볼수 있다. 이때 씬뷰의 렌더링모드를 Wireframe으로 변경하면 동적으로 만들어진 메시의 형태를 볼 수 있다.
동적으로 생성된 메시는 위와 같이 노란색이 아니라 분홍색이다. 아직 머터리얼이 없어서 그렇다.
총기가 밑에 CreateEmpty를 만든후 이름을 FirePos로 변경한고 자식으로 한다.
여기에 MyGizmo라는 Script를 하나 넣어준다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyGizmo : MonoBehaviour
{
public Color _color = Color.yellow;
public float _radius = 0.1f;
private void OnDrawGizmos() {
Gizmos.color = _color;
Gizmos.DrawSphere(transform.position, _radius);
}
}
총끝에 노란공이 보인다. 너무크다
인스펙터뷰에의 My Gizmo Script변수 Radius를 0.02 Color를 클릭해 A를50으로 바꾼다.
다음같이 예쁘게 바뀐다. 이 기즈모는 씬뷰에서만 보이고 프리뷰에서는 안보인다.
그런데 노란공은 총구와 위치가 잘 않맞는다.
FirePos의 위치를 총구쪽으로 이동시켜준다. 그런데 총이 비스듬하게 되어 있어 되는듯하지만 맞추기 어렵다.
GamePlay를 누른후 포즈를 누른다.
그럼 다음같이 총구를 맞추기 쉬운 자세로 드래그및 로테이션을 할수 있다.. 이상태에서 기즈모를 하이리체서 선택해 끌어다 맞춘다. 잘되었으면 그냥 나오면 힘들게 맞춘게 틀어진다. 따라서 현재의 파라메터를 기록해야한다.
Transfor 오른쪽 삼점을 누르면 Copy>Component 할수 있다 플레이를 멈춘후 Paste해 준다. 잘 달라붙는다
이렇게 기즈모 스크립트를 작성하지 않아도 유니티에서 좌상 육각형을 눌러보면 아이콘을 고를 수 있다. 대신 이건 사이즈나 투명도를 조정할 수는 없다.
그리고 하이라키뷰의 Player를 선택하고 육각형 Icon을 눌러 위쪽으 가로로긴 원 Lable을 선택하면 게임오브젝트의 이름이 씬뷰에만 표시된다.
FireCtrl
BulletCtrl도 원래대로한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BulletCtrl : MonoBehaviour
{
public float damage = 20.0f;
public float force = 1500.0f;
private Rigidbody rb;
void Start() {
rb = GetComponent<Rigidbody>();
rb.AddForce(transform.forward * force);
}
}
이전에 만든 AddForceCtrl.cs을 제거하고 프로젝트뷰에서 아예 지워버리고 마우스좌클릭을 하면 총알을 발사하기 위해 FireCtrl.cs 스크립트을 다시 작성해서 Player에 적용할 것이다. 총알은 게임중 동적으로 생성할것이므로 하이라키의 Bullet은 지운다. 지워도 아까 프리팹으로 만들어 놨기때문에 괜찮다.
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class FireCtrl : MonoBehaviour
{
public GameObject bullet;
public Transform firePos;
// Update is called once per frame
void Update()
{
if(Input.GetMouseButtonDown(0)) {
fire();
}
}
void fire() {
Instantiate(bullet,firePos.position, firePos.rotation);
}
}
총알이 Wall에 충돌했을 때 Wall의 관점에서 생각해 보면 Collider 컴포넌트를 가진 어떤 게임오브젝트가 날아와서 충돌이 일어났는지 알 필요가 있다. 물론 충돌한 오브젝트의 이름으로 판단할 수도 있지만 인스턴트의 경우 번호가 붙으므로 프리팹에 Tag를 지정하고 Tag명식별하는 법이 있다.
Tag는 기본적으로는 Untagged이지만 디폴트로 생성된 이름을 선택할 수도 있고 Tag 콤보박스를 클릭한다음 Add Tag로 만들수 있다. 대소문자를 식별한다.
Tags의 +를 누르면 이름을 추가할 수 있다.
Inspector의 루트로 빠져나오면 Tab에 Bullet이 추가되어 있고 선택하면 Untagged에서 Bullet로 변경된다.
총알이 벽에 충돌할때 발생되는 이벤트함수가 호출되는지 확인해보자 Wall prefab에 스크립트를 추가한후 RemoveBullet라고 이름바꾼다. 이러면 하이라키의 4개의 wall에 다 적용된다.
코드를 보면 총알이 벽에 부딪치면 다음같은 이벤트가 발생되고 Collision 객체가 전달된다.
이 Collision은 충돌 객체및 Tag이름, 충돌위치, 충돌속도가 들어 있다
코드는 이중 tag를 식별해 충돌된 Bullet 오브젝트를 제거한다. private void OnCollisionEnter(Collision coll) { if(coll.collider.tag == "Bullet") { Destroy(coll.gameObject); } }
collision.CompareTag()
if(coll.collider.tag == "Bullet") 와 같이 게임오브젝트의 문자속성을 가져오는 코드는 실행시 문자열의 복사본을 생성한다. 따라서 가지비컬렉션이 발생하지 않는 coll.collider.comapreTag("bullet")를 추천한다.