A.1 삼각함수란?

직각삼감형의 각도에 따른 변 길이의 비율을 반환하는 함수 입니다. 

삼각함수로 사인, 코사인, 탄젠트라는 단어를 들어본 적이 있을 겁니다.

 

A.2 각도로 좌표 구하기 

플레이어가 적 캐릭터에게 사격을 하기위해 적과의 각도를 알면 다음과 같이 계산할수 있습니다.

float x = Mathf.Cos(라디안)
float y = Mathf.Sin(라디안)
Vector3 v = new Vector3(x,y) * shootSpeed;

유니티가 제공하는 삼각함수.

Mathf.cos, Mathf.sin Mathf.Tan

유니티가 제공하는 삼각함수는 각도를 매개변수로 사용하지 않고 라디안(호도법)을 사용합니다.

각도 = 라디안 x(180/원주율)

라디안=각도x(원주율/180)입니다.

유니티는 계산을 편하게 하기위해 Mathf.Deg2Rad, Mathf.Rad2Deg를 마련해 놨습니다.

 

라디안

아래 그림은 각도는 한바퀴를 360도라고 정의했을때 비율이다.

라디안은 반지름1인 원이 한바퀴 도는 둘레가 2πr인데 반지름이 1이름로 2π=360도 이다.

라디안은 결국 쉽게 말하면 한바퀴도는데 2π니까 1도 = 2π/360 = π/180 임.

역으로하면 360도 = 2π 니까 π=180도 뭐 그닥 와닫지는 않지만.

어려운건 아님.  각도는 바퀴고 라디안은 굴러간거리라고 생각하면 편함.

가만 생각해보면 원이 한바퀴도는건 파이=3.141592아니었나 생각되지만 그건 지름이 1일 경우이고 라디안은 반지름이1 지름이 2일 경우이다. ㅠㅠ

삼각함수도 반지름이 1일경우로 원을 그려 생각하면 엄청 쉬움

cosθ+sinθ=1 이게 원을 뜻한다.

x*x+y*y=1 도 원이지만. 

[네이버 지식백과]라디안[Radian] (물리학백과)

 

A.3 좌표에서 각도 구하기 

플레이어의 애니메이션을 선택하기 위해 플레이어의 이동각도를 알아야 할 때가 있습니다.

플레이어가 점 p1에서 p2로 이동할때 각도는 다음과 같습니다.

float dx = p2.x - p1.x;
float dy = p2.y - p1.y;
float angle = Mathf.Atan2(dy,dx)*Mathf.Rad2Deg;

Atan2는 좌표를 입력하면 각도를 구해주는 함수입니다.

 

A.4 자주쓰는 삼각함수 메서드

특정 게임 오브젝트 추적하기

Vector2 GetToVector(Vector2 pos) {
	float dx = pos.x-transform.position.x;
    float dy = pos.y-transform.position.y;
    float rad = Mathf.Atan2(dy,dx);
    //벡터만들기
    float x = Mathf.Cos(rad);
    float y = Mathf.Sin(rad);
    Vector2 v = new Vector2(x,y);  //방향
    return v;
}

특정 게임 오브젝트를 향하도록 회전시키기

GetAngle(Vector2 from, Vector2 to) {
	float dx = to.x-from.x;
    float dy = to.y-from.y;
    float rad = Mathf.Atan2(dy,dx);
    return rad * Mathf.Rad2Deg;
}

위함수를 Update()에서 호출해서 게임오브젝트을 로테이션 시킵니다.

Update(){
	float angle = GetAngle(transform.position, 이동 위치의 position);
    transform.rotation = Quaternion.Euler(0,0,angle);
}

다음 메소드는 이동속도를 회전각도로 반환합니다.

float velocityToAngle(){
	float r = 0;
    Rigidbody2D body = GetComponent<Rigidbody2D>();
    if(body !=null) {
    	Vector2 v = body.velocity;
        r = Mathf.Atan2(v.y, v.x) * Mathf.Rad2Deg;
    }
    retur r;
}

Update()에서 다음같이 사용됩니다.

Update() {
	float angle = velocityToAngle();
    transform.rotation = Quaternion.Euler(0,0,angle);
}

'유니티스크립팅 > 게임수학' 카테고리의 다른 글

벡터  (0) 2023.04.25

게임클리어 SE

Exit 스크립트의 OnTriggerEnter2D메서드에서 게임 클리어 SE를 재생합니다. 재생중인 BGM을 정지시키기 위해 StopBgm메서드도 호출합니다.

Exit.cs
0.00MB

 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);
        }
    }
}

게임오버SE

PlayerController 스크립트의 GameOver메서드에서 게임오버 SE를 재생합니다.  재생중인 BGM을 정지시키기 위해 StopBgm메서드도 호출합니다.

void GameOver()
{
~생략
    //BGM 정지
    SoundManager.soundManager.StopBgm();
    //SE재생 (게임 오버)
    SoundManager.soundManager.SEPlay(SEType.GameOver);
}

활쏘기 SE

ArrowShoot스크립트의 Attack메서드에서 게임오버 SE를 재생합니다.

ArrowShoot::Attack()

public void Attack()
{
    //화살을 가지고 있음 & 공격중이 아님
    if (ItemKeeper.hasArrows > 0 && inAttack == false)
    {
		~생략

        //SE재생(활 쏘기)
        SoundManager.soundManager.SEPlay(SEType.Shoot);
    }
}

게임 플레이

책을 따라하다 보니 좀 엉망진창이 되어 잘 플레이가 안된다. 교재에서 CH10 Assets을 다운 받아 적당히 손질했다.

Assets.zip
15.17MB

블로거는 최신 2020.3.48f를 사용중이다. 

 

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);   //씬 이동
    }

 

사이드뷰 게임에서는 씬이 바뀔때 마다 BGM을 처음부터 재생했습니다. 이번 예제에서는 씬이 바귀어도 게임이 이어지기 때문에 타이틀화면, 게임화면, 보스전등에서만 BGM이 바뀌도록 합니다.

게임 클리어, 게임오버, 활을 쏠때만 SE(사운드이펙트)를 적용합니다. 예제에서는 버튼이 눌릴때 문이열릴때 문이 닫힐대 아이템을얻을때 대미지를 받을때 적 사망,보스사망등 다양한 곳에 SE를 적용했습니다.

 

사운드 재생 오브젝트와 스크립트 만들기

하이라키에 빈오브젝트를 만들고 이름을 SoundManager라고 합니다.  SoundManager스크립트를 만들어 하이라키의 SoundManager게임오브젝트에 어태치합니다. 

AddComponent로 Audio Source를 추가하고 Play On Awake 체크해제하고 Loop는 체크합니다. BGM제어는 나중에 스크립트에서 제어합니다. Audio 클립은 비워둡니다.

 

씬이 바뀌어도 BGM이 끊어지지 않게 재생한다.

 

씬이 바뀌어도 BGM이 끊어지지 않게 재생하려면 SoundManager가 같은 게임 오브젝트로 존재해야 합니다.

열거형

public enum BGMType
{
    None,       //없음
    Title,      //타이틀
    InGame,     //게임 중
    InBoss,     //보스전
}
//SE 종류
public enum SEType
{
    GameClear,  //게임 클리어
    GameOver,   //게임 오버
    Shoot,      //활 쏘기
}

변수

public AudioClip bgmInTitle;    //타이틀 BGM
public AudioClip bgmInGame;     //게임 중 BGM
public AudioClip bgmInBoss;     //보스전 BGM

public AudioClip meGameClear;   //게임 클리어
public AudioClip meGameOver;    //게임 오버
public AudioClip seShoot;       //활 쏘기

static 변수

씬이 바뀌어도 값이 변화되지 않게 static변수를 마련합니다. 자신을 저장하는 soundManager와 재생중이 BGMType을 저장합니다.

public static SoundManager soundManager;    //첫 SoundManager를 갖는 변수
public static BGMType plyingBGM = BGMType.None;    //재생 중인 BGM

Awake()

Start()보다 앞에서 호출되는  Awake()에서 이전에 실행되었던 soundManager가 있으면 정리합니다.

게임이 실행되고 맨처음 SoundManager가 실행되면 soundManager변수는null입니다. 그때 DontDestroyOnLoad메서드를 호출해 게임 오브젝트가 파기되지 않도록 합니다. soundManager변수는 static이므로 씬이 바뀌어도 값이 유지됩니다.

DontDestroyOnLoad(gameObject)를 이용 씬이 바뀌어도  

private void Awake()
{
    //BGM 재생
    if (soundManager == null)
    {
        soundManager = this;  //static 변수에 자기 자신을 저장
        //씬이 바뀌어도 게임 오브젝트를 파기하지 않음
        DontDestroyOnLoad(gameObject);
    }
    else
    {
        Destroy(gameObject);//게임 오브젝트르 파기
    }
}

PlayBgm()

플레이할 BGM타입이 다르면 AudioSource컴포넌트를 이용해 오디오 클립의 이름을 바꿔서 Play()합니다.

public void PlayBgm(BGMType type)
{
    if (type != plyingBGM)
    {
        plyingBGM = type;
        AudioSource audio = GetComponent<AudioSource>();
        if (type == BGMType.Title)
        {
            audio.clip = bgmInTitle;    //타이틀
        }
        else if (type == BGMType.InGame)
        {
            audio.clip = bgmInGame;     //게임 중
        }
        else if (type == BGMType.InBoss)
        {
            audio.clip = bgmInBoss;     //보스전
        }
        audio.Play();
    }
}

StopBgm()

BGM을 정지합니다. 

public void StopBgm()
{
    GetComponent<AudioSource>().Stop();
    plyingBGM = BGMType.None;
}

SEPlay(SEType type)

상황에 맞는 사운드 이펙트를 재생합니다.

public void SEPlay(SEType type)
{
    if (type == SEType.GameClear)
    {
        GetComponent<AudioSource>().PlayOneShot(meGameClear);   //게임 클리어
    }
    else if (type == SEType.GameOver)
    {
        GetComponent<AudioSource>().PlayOneShot(meGameOver);   //게임 오버
    }
    else if (type == SEType.Shoot)
    {
        GetComponent<AudioSource>().PlayOneShot(seShoot);       //활 쏘기
    }
}

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);
        }
    }
}

 

+ Recent posts