유니티에서 XML 사용하기

 

게임을 제작할 때에, 여러 가지 데이터들을 저장하고 불러와야하는 경우가 많다. 게임 진행 상황을 저장하는 경우도 있을 수 있고, 맵이 랜덤으로 생성되는 게임의 경우에는 현재 생성된 맵의 데이터를 저장해뒀다가 다음에 다시 불러와야 할 수도 있다. 또는 게임의 아이템 정보들의 목록을 저장해둬야 할 수도 있다. 위와 같은 경우에 XML을 사용하면 저런 기능들을 쉽게 구현할 수 있게 된다.

 

XML은 마크업 언어를 정의하기 위한 확장성 마크업 언어라고 하는데, 여기서는 조작하기 쉬운 일종의 로컬 데이터베이스나 데이터를 저장하기 위한 파일로 사용될 것이다.

 

이번 섹션의 예시에서는 게임의 캐릭터 정보를 저장하기 위해서 XML을 사용한다고 가정하고 진행 해보겠다.

 

1. XML 파일 생성하여 저장하기

첫 번째로 알아볼 것은 XML 파일을 생성하는 것이다. 만약 게임을 시작하고 처음으로 저장을 하는 경우라면 아직 데이터를 저장하기 위한 파일이 생성되어 있지 않을 것이다. 물론 미리 XML 파일을 만들어두고 사용하는 것도 가능하지만, XML 파일을 생성하는 법에 대해서 알아두는 것도 나쁘지 않다.

 

다음이 XML을 생성하여 저장하는 코드의 전체이다 :

 

public class XmlTest : MonoBehaviour
{
    void Start()
    {
        CreateXml();
    }

    void CreateXml()
    {
        XmlDocument xmlDoc = new XmlDocument();

        // Xml을 선언한다(xml의 버전과 인코딩 방식을 정해준다.)
        xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "utf-8", "yes"));

        // 루트 노드 생성
        XmlNode root = xmlDoc.CreateNode(XmlNodeType.Element, "CharacterInfo", string.Empty);
        xmlDoc.AppendChild(root);

        // 자식 노드 생성
        XmlNode child = xmlDoc.CreateNode(XmlNodeType.Element, "Character", string.Empty);
        root.AppendChild(child);

        // 자식 노드에 들어갈 속성 생성
        XmlElement name = xmlDoc.CreateElement("Name");
        name.InnerText = "wergia";
        child.AppendChild(name);

        XmlElement lv = xmlDoc.CreateElement("Level");
        lv.InnerText = "1";
        child.AppendChild(lv);

        XmlElement exp = xmlDoc.CreateElement("Experience");
        exp.InnerText = "45";
        child.AppendChild(exp);

        xmlDoc.Save("./Assets/Resources/Character.xml");
    }
}

 

저장할 캐릭터 데이터의 내용은 캐릭터의 이름과 레벨, 경험치라고 가정하고 진행했다. 위의 코드를 실행하면 지정된 경로에 Character.xml 파일이 생성된다.

 

 

생성된 xml 파일은 다음과 같은 내용이다 :

 

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<CharacterInfo>
  <Character>
    <Name>wergia</Name>
    <Level>1</Level>
    <Experience>45</Experience>
  </Character>
</CharacterInfo>

 

작성한 대로 캐릭터의 이름, 레벨, 경험치 값이 저장되었다.

 

 

2. XML 파일에서 데이터 불러와서 사용하기

데이터를 저장하는데 성공했다면 이 다음에는 저장된 데이터를 불러와서 사용해야할 것이다. 다음의 코드가 저장된 XML 데이터를 불러오는 것이다.

 

using System.Xml;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class XmlTest : MonoBehaviour
{
    void Start()
    {
        LoadXml();
    }

    void LoadXml()
    {
        TextAsset textAsset = (TextAsset)Resources.Load("Character");
        Debug.Log(textAsset);
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(textAsset.text);

        XmlNodeList nodes = xmlDoc.SelectNodes("CharacterInfo/Character");

        foreach (XmlNode node in nodes)
        {
            Debug.Log("Name :: " + node.SelectSingleNode("Name").InnerText);
            Debug.Log("Level :: " + node.SelectSingleNode("Level").InnerText);
            Debug.Log("Exp :: " + node.SelectSingleNode("Experience").InnerText);
        }
    }

}

 

위의 코드를 실행해보면 XML에 저장해둔 캐릭터 데이터가 잘 불러와졌음을 알 수 있다.

 

 

불러온 데이터를 캐릭터 오브젝트를 생성해서 그 값을 다시 넣어준다면 캐릭터는 저장되기 직전의 캐릭터와 같은 상태를 가지게 될 것이다.

 

 

3. 이미 존재하는 XML 파일에 데이터 덮어씌워 저장하기

게임에서 이미 저장하기 기능을 사용해서 XML 파일이 생성되어 있고, 그 XML파일에 덮어 씌워 저장하는 경우도 있을 수 있다. 그럴 땐 다음 코드와 같이 이미 존재하는 XML 파일을 불러온 후에 그 안의 값을 수정하고 저장해주면 된다 :

 

using System.Xml;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class XmlTest : MonoBehaviour
{
    void Start()
    {
        SaveOverlapXml();
    }

    void SaveOverlapXml()
    {
        TextAsset textAsset = (TextAsset)Resources.Load("Character");
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.LoadXml(textAsset.text);

        XmlNodeList nodes = xmlDoc.SelectNodes("CharacterInfo/Character");
        XmlNode character = nodes[0];

        character.SelectSingleNode("Name").InnerText = "wergia";
        character.SelectSingleNode("Level").InnerText = "5";
        character.SelectSingleNode("Experience").InnerText = "180";

        xmlDoc.Save("./Assets/Resources/Character.xml");
    }

}

 

위 코드를 실행해보면 다음과 같이 XML 파일이 변경되어 있는 것을 볼 수 있다 :

 

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<CharacterInfo>
  <Character>
    <Name>wergia</Name>
    <Level>5</Level>
    <Experience>180</Experience>
  </Character>
</CharacterInfo>

 

위의 예시처럼 덮어씌워 저장하는 방법 이외에도 새로 노드를 덧붙여서 추가적인 내용을 저장한다던가, 아니면 여러 개의 저장 슬롯을 만들어서 저장하는 방법을 사용할 수 있다.

 

 

4. 취약점

저장 방식으로 사용하기 굉장히 편리한 XML이지만 이것도 굉장한 단점이 하나 있다. 이 XML을 그대로 사용하면 그냥 텍스트 편집기로도 열리기 때문에 사용자들이 쉽게 그 값을 변조할 수 있게 된다. 이것은 하나의 에디터나 다름없는 것이기 때문에 만약 제작하는 게임이 데이터가 변조되면 안되는 경우에는 심각한 문제가 될 수 있다. 만약 XML에 게임 데이터를 저장하고 불러와야 되는 경우에는 별도의 암호화/복호화 기능을 덧붙여서 기능을 만드는 것을 추천한다.

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

유니티에서 프라우드넷으로 네트워크를 구축할 때 위치동기화를 위한 추측항법 사용법

 

 

이 문서를 보기전에 필요한 지식 :

1. 프라우드넷 서버와 클라이언트 구축법.

2. 프라우드넷의 네트워크 전송 방식인 ReliableSend와 UnreliableSend의 차이점

3. 프라우드넷의 RMI, Proxy와 Stub에 대한 지식.

4. 프라우드넷을 통한 게임 네트워크에서 캐릭터 위치 동기화 하는 방법.

5. 유니티 프로젝트에서 프라우드넷을 사용하는 방법.

 

 

1. 캐릭터의 위치 동기화란

캐릭터가 움직일 수 있는 네트워크 게임에서 캐릭터의 위치란 아주 중요한 것이다. 상대방 캐릭터의 실제 위치가 내가 인식한 위치가 다르다면 문제가 될 것이다. 다음의 그림을 보자 : 

 

만약 상대방 캐릭터의 위치 동기화에 실패했다고 가정해보자. 파란색 원이 내 캐릭터이고 분홍색 원은 상대 캐릭터의 실제 위치이며 빨간 원은 위치동기화에 실패해서 상대 캐릭터가 잘못 이동한 위치라고 했을 때, 내가 보이는 대로 빨간색 적을 공격한다면 데미지가 적에게 들어가야할까? 내 입장에서 보이는대로 빨간색 상대를 공격해서 데미지가 들어간다면 나는 납득할 수 있을지 모르겠지만 상대방의 입장에서는 납득하기 어려울 것이다. 상대방이 보기에는 내가 다른 방향을 보고 공격하는데 자신한테 데미지가 들어오는 것으로 보일 것이기 때문이다.

 

이렇듯이 캐릭터의 위치 동기화는 중요하다. 여기에 자주 사용되는 방법은 자신의 캐릭터의 위치가 변경될 때마다 상대방의 클라이언트에 그 변경된 위치를 알려주는 것이다.

 

캐릭터의 위치를 다른 클라이언트에게 전송하는 Proxy

    public void SyncUnitPos(string unitName, UnityEngine.Vector3 pos, Quaternion rot)
    {
        RmiContext context = RmiContext.UnreliableSend;

        client.c2cMessageSender.SyncUnitPos(otherClientHostID, context, unitName, pos, rot);
    }

 

다른 클라이언트로부터 받은 캐릭터의 위치를 자신의 클라이언트에서 찾아서 적용하는 Stub

client.c2cMessageReceiver.SyncUnitMove = (HostID remote, RmiContext rmiContext, string unitName, UnityEngine.Vector3 pos, Quaternion rot) =>
{

Character moveCharacter = AllSpawnUnitDictionary[unitName];

moveCharacter.transform.position = pos;

moveCharacter.transfrom.rotation = rot;

};

 

위의 프록시와 스텁으로 단순하게 캐릭터의 위치를 보내고 받아서 적용하는 것이다. 이 방법은 단순하지만 중요한 단점이 있다. 그것은 바로 여러가지 상황에 따라 캐릭터의 위치를 전송받는 측에서의 캐릭터의 움직임이 끊어져 보일 수 있다는 것이다. 특히 캐릭터의 위치 동기화처럼 네트워크 전송량이 많은 것을 처리할 때는 호스트 간에 메시지를 주고받는 시간이 짧은 UnreliableSend를 사용하기 때문에 메시지의 도착의 확실성이 보장되지 않기 때문에 중간에 잃어버리는 메시지가 많다면 캐릭터의 움직임이 끊어져서 버벅거리면서 이동하는 것처럼 보이거나 텔레포트하는 것처럼 보일 확률이 높아진다.

 

 

2. 추측항법이란

이것을 해결하기 위한 방법이 바로 추측항법이다. 추측항법의 사전적 의미는 다음과 같다 :

 

가장 최근에 구한 정확한 선위를 기초로 선박의 침로와 항정에 의하여 현재의 선위를 추측 및 추정하거나 또는 출발지와 목적지의 경 · 위도를 알고 앞으로 취해야 할 침로 및 항정을 구하는 항법을 추측 항법이라고 한다. 선박이 항행 중에 가장 필요로 하는 것이 바로 선박의 현재 위치이다. 선박의 실측 위치를 얻을 수 없을 때에는, 가장 최근에 실제로 관측하여 구한 실측 위치를 기준으로 하여 그 후에 조타한 진침로와 선속 또는 기관의 회전수로 구한 항정에 의하여 선위를 결정하는 것을 선위의 추측이라 하고, 이와 같이 하여 결정된 선위를 추측위치라 한다.

 

[네이버 지식백과] 추측항법 [dead reckoning sailing, 推測航法] (선박항해용어사전, 한국해양대학교)

 

간단하게 말하면 가장 최근에 확인한 실제 위치를 기준으로 하여 현재의 속도와 방향을 더하여 현재 위치를 추정해내는 것이다. 실제로는 자신의 위치를 특정하기 어려운 항해에서 사용되는 방법인데 이것을 네트워크 게임의 개발에 응용한 것이다. 개념은 기존의 캐릭터의 위치와 방향을 가져와서 바로 적용하는 것에 더해서 캐릭터의 이동 방향과 속도를 함께 가져와서 다음 캐릭터 위치 동기화 신호가 오기전까지 받은 이동 방향과 속도를 이용해서 추정해낸 위치로 미리 캐릭터를 이동시키는 것이다.

 

우리는 이제 추측항법을 구현해야 하겠지만 다행이도 이러한 기능을 구현하는데 도움을 주는 클래스가 이미 프라우드넷에 구현이 되어있다. Nettention.Proud.PositionFollower 라는 클래스이다.

 

그리고 캐릭터의 위치를 동기화 하는 Proxy와 Stub은 다음과 같이 변경될 것이다.

 

캐릭터의 위치를 다른 클라이언트에게 전송하는 Proxy

    public void SyncUnitPos(string unitName, UnityEngine.Vector3 pos, UnityEngine.Vector3 vel, Quaternion rot)
    {
        RmiContext context = RmiContext.UnreliableSend;

        client.c2cMessageSender.SyncUnitPos(otherClientHostID, context, unitName, pos, vel, rot);
    }

 

다른 클라이언트로부터 받은 캐릭터의 위치를 자신의 클라이언트에서 찾아서 적용하는 Stub

client.c2cMessageReceiver.SyncUnitMove = (HostID remote, RmiContext rmiContext, string unitName, UnityEngine.Vector3 pos,  UnityEngine.Vector3 vel, Quaternion rot) =>
{

Character moveCharacter = AllSpawnUnitDictionary[unitName];
moveCharacter.MoveRemote(pos, vel, rot);

};

 

그냥 pos와 rot을 넣어주기만 하면 되던 이전과는 달리 처리할 것이 많으니 Character 클래스에 MoveRemote라는 함수를 만들었다.

 

Character 클래스에서 처리할 것

public class Character

{

// 이 캐릭터가 네트워크를 통해서 간접적으로 조종되는 캐릭터인가?

bool isRemote;

// 추측 항법을 도와줄 프라우드넷의 클래스이다.

Nettention.Proud.PositionFollower posFollower = new Nettention.Proud.PositionFollower();

 

// 네트워크의 stub을 받아 Follower의 위치를 설정한다

public void MoveRemote(Vector3 pos, Vector3 vel, Quaternion rot)

{

var npos = new Nettention.Proud.Vector3();
npos.x = pos.x;
npos.y = pos.y;
npos.z = pos.z;
      
var nvel = new Nettention.Proud.Vector3();
nvel.x = velocity.x;
nvel.y = velocity.y;
nvel.z = velocity.z;

 

posFollower.SetTarget(npos, nvel);

transform.rotation = rot;

}

 

// Follower에 따라서 캐릭터를 이동시킨다

public void FollowRemoteMove()

{

posfollower.FrameMove(Time.deltaTime);

var pos = new Nettention.Proud.Vector3();

var vel = new Nettention.Proud.Vector3();

posfollower.GetFollower(ref pos, ref vel);
unit.transform.position = new Vector3((float)pos.x, (float)pos.y, (float)pos.z);
unit.transform.rotation = Quaternion.Lerp(unit.transform.rotation, destRot, Time.deltaTime * 100);

}

 

void Update()

{

if (isRemote) FollowRemoteMove();

}

}

 

이 방법을 사용할 때는 자신의 캐릭터와 상대방의 캐릭터, 즉 직접 조종하는 캐릭터와 네트워크를 통해서 조종되는 캐릭터를 명확하게 구분해야 한다.

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

유니티 에디터의 글씨가 빨간색으로 나오는 버그

 

유니티 엔진을 사용하다보면 다음과 같이 UI의 글자들이 빨간색으로 나오는 버그가 발생한다. 빨간색이 주는 강렬함 때문에 이 현상을 목격한 개발자는 이게 아주 심각한 버그가 아닌가 하고 당황에 빠지게 되는데 이 상황에서는 절대로 당황해서는 안된다.

 

뭔가 심각한 버그가 아닌가 하고 작업을 롤백한다던지, 무언가 다른걸 시도하는 작업자들이 있는데 그렇게 할 경우, 진행했던 며칠간의 작업을 날린다던지 하는 문제가 발생할 수 있다. 하지만 이 버그는 결코 심각한 버그는 아니다. 단지 간단한 GUI 버그일 뿐이다.

 

그냥 무시하고 작업을 진행하다 보면 어느 샌가 정상으로 돌아온 유니티 엔진을 볼 수 있다. 저 빨간색 글씨들이 너무 거슬린다면 침착하게 현재 진행중인 작업을 저장하고 난 뒤에 유니티를 재시작하는 방법도 있다.

 

다음과 같은 상황을 맞이하게 된다면 절대 당황하지 말고 작업을 저장하라.

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Animation Event 사용법

 

 

새로운 버전의 글!

 

이 버전의 글은 약간의 오류를 포함하고 있으며 이를 수정한 최신 버전의 글을 링크를 통해서 보실 수 있습니다.

 

 

개요(Overview)

게임을 제작할 때, 애니메이션이 실행되는 도중에 적절한 타이밍에 맞춰서 무언가가 실행되어야 하는 경우가 종종 발생한다. 예를 들자면 대표적으로는 캐릭터가 걷거나 뛸 때 발소리가 나거나 먼지가 일어나야 한다던지 캐릭터가 무기를 휘두를 때 맞는 타이밍에 맞춰서 데미지가 들어가야 한다던지 하는 경우가 있을 수 있다. 이러한 문제를 해결하는 하드한 방법으로는 계산해낸 타이밍에 맞춰서 해당 함수를 실행시킨다던지, 콜라이더를 원하는 위치에 붙여서 그 콜라이더가 충돌했을때 처리하는 방법이 있지만, 이러한 방법들은 매우 비효율적이다.

 

계산해낸 타이밍에 맞춰서 원하는 함수를 실행하는 방법의 경우에는 공격 속도나 이동 속도에 맞춰서 모션의 속도가 변하는 유동적인 상황에 대처하기가 매우 어렵고, 특히 콜라이더를 사용하는 방법은 겨우 발소리나 발움직임에 맞춰 먼지 이펙트를 만드는데 쓰기에는 매우 비용이 비싸다. 공격 애니메이션에 맞춰서 데미지를 주는 경우에는 무기나 데미지를 주는 부위에 콜라이더를 붙여서 사용하는 것을 고려해볼만 하지만, 이러한 방법은 주로 공격의 명중이 확정되지 않은 게임, 즉 논타겟(None-Target) 방식의 게임에 적합한 방법이며, 공격의 명중이 확정되어있는 게임의 경우에는 역시 사용하기가 곤란하다.

 

 

 

애니메이션 이벤트(Animation event)

그래서 존재하는 것이 바로 유니티의 애니메이션 이벤트(Animation event)라는 기능이다. 이 기능은 애니메이션의 실행 도중, 원하는 시점에 원하는 함수를 호출할 수 있게 해준다. 이 문장 하나만으로도 개요에서 설명한 상황에 전부 대처가 가능할 것임을 알 수 있다.

 

이 애니메이션 이벤트를 사용하는 방법은 2가지가 있는데 FBX 모델과 함께 임포트된 애니메이션에서 사용하는 방법과 애니메이션 클립(Animation clip)에서 사용하는 방법이다.

 

 

방법 1 : FBX 모델과 함께 임포트된 애니메이션에서 애니메이션 이벤트 사용하기

 

 

프로젝트 창(Project view)에서 임포트한 FBX파일을 선택하면 Inspector 창에 그 FBX파일의 정보들이 보일 것이다. 그 중에서 상단의 Animations라는 탭을 선택하면 그 FBX에 포함된 애니메이션의 정보를 볼 수 있다. 우리가 필요로 하는 애니메이션 이벤트 기능은 그 중에서 Events라는 곳 안에서 사용할 수 있다. 그 항목을 열면 다음과 같은 내용을 볼 수 있다 :

 

 

노란색으로 표시된 버튼을 누르면 다음과 같이 애니메이션이 재생되는 도중에 실행될 이벤트가 새로 생성된다 :

 

 

위의 이미지에서 파란색으로 표시된 것을 드래그하면 이 이벤트가 실행될 시점을 조절할 수 있으며 그 아래의 입력 필드의 내용들은 다음과 같다 :

 

 필드 이름 내용
 Function 이벤트 시점에 실행될 함수의 이름
 Float  함수에 float형 매개변수가 있을 때 들어갈 값
 Int  함수에 int형 매개변수가 있을 때 들어갈 값
 String  함수에 string형 매개변수가 있을 때 들어갈 값
 Object  실행할 함수가 들어있는 스크립트

 

이번 예시로는 공격 애니메이션의 타이밍에 맞춰 데미지를 주는 것으로 진행 해보겠다. 먼저 적에게 데미지를 주는 작업을 할 스크립트를 생성한다.

 

예제이기 때문에 자세한 과정에 대한 코드를 생략하고 AttackComponent 스크립트는 다음과 같은 함수를 가지고 있다.

 

using UnityEngine;

public class AttackComponent : MonoBehaviour
{
    public void AttackDamage()
    {
        // 적에게 데미지를 주는 처리
        Debug.Log("데미지 60!");
    }
}

 

공격 애니메이션에서 적에게 무기가 맞는 시점이 위의 Events 이미지에서 0:050라고 가정하자. 다음과 같이 이전에 생성한 이벤트를 0:050 지점으로 옮기고 Object에 생성한 AttackComponent 스크립트를 넣어주고 Function에는 AttackDamage 함수 이름을 입력해주면 된다.

 

 

다음과 같은 과정을 끝내고 나면 애니메이션이 실행되고 0:050 지점을 지날 때마다 유니티 에디터에서 "데미지 60!"이라는 로그가 출력될 것이다.

 

 

 

방법 2 : 애니메이션 클립에서 애니메이션 이벤트 사용하기

 

 

 

 

애니메이션 클립은 3D 모델 없이 애니메이션에 대한 정보만을 가지고 있는 것을 이야기한다. 물론 임포트한 FBX 모델 파일에서 애니메이션만 꺼내와서 사용하는 것도 가능하다. 이러한 애니메이션 클립에서 애니메이션 이벤트를 사용하는 방법은 이전에 소개한 FBX 모델에 포함된 애니메이션에서 사용하는 방법과 상당히 비슷하다.

 

프로젝트 창에서 애니메이션클립을 선택하고 애니메이션 탭을 열면 다음과 같은 화면을 볼 수 있다.(만약 애니메이션 탭이 보이지 않는다면 상단의 Windows 메뉴에서 Animation을 찾아서 열거나 Ctrl+6 단축키를 이용해서 열 수 있다)

 

 

애니메이션 탭에서도 FBX 파일 애니메이션의 이벤트에서 봤던 것과 같은 모양의 버튼을 볼 수 있는데 이것을 통해서 이벤트를 생성할 수 있고 드래그를 통해서 이벤트의 시점을 이동시킬 수도 있다.

 

 

그리고 이 이벤트를 선택하면 Inspector 창에서 다음과 같은 내용을 볼 수 있다 :

 

 

이 이후에는 방법 1에서 진행한 것과 같이 진행하면 원하는 결과를 얻을 수 있게 된다.

 

 

 

마무리...

 

이 애니메이션 이벤트를 사용할 때, 프레임이 씹힐 경우에 애니메이션 이벤트가 같이 스킵되는 경우가 발생한다는 이야기도 있다. 다른 이야기로는 이 문제는 레거시(Legacy) 애니메이션을 사용할 때 주로 발생하며, 메카님에서는 개발자의 실수로 트랜지션 구간을 잘못 설정했을 때 발생하는 문제이며 일반적으로는 발생하지 않는 문제라고도 한다. 이런 문제가 발생하면 애니메이션의 트랜지션에서 블랜드되는 과정을 잘 확인해보는 것이 우선일 것 같다.

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

유니티 애니메이션 속도 조절 방법

 

 

게임을 제작할 때 개발자는 애니메이션의 속도에 관여해야하는 경우가 자주 발생한다. 대표적인 예시를 들자면 캐릭터의 공격 속도 수치가 변할 수 있는 게임에서 캐릭터의 공격 속도와 공격 애니메이션의 속도를 맞추기 위해 관여하는 것이다. 이러한 두 속도를 세심하게 맞추는데 실패한다면 캐릭터의 공격 애니메이션이 끝나지 않았는데 캐릭터가 이동을 한다던지, 공격 애니메이션과 데미지가 들어가는 타이밍이 다르다던지 하는 상당히 부자연스러운 상황이 발생하게 될 것이다. 이러한 상황은 게임의 타격감이라는 요소를 떨어뜨리는 심각한 문제가 될 수도 있다.

 

그렇게 때문에 적절한 순간에 애니메이션의 속도를 조절하는 방법을 알아둘 필요가 있다. 유니티에서는 애니메이션의 속도를 조절하는 방법을 여러가지 지원하는데 하나씩 알아보도록 하자.

 

 

방법 1 : Animator에서 선택한 State의 Speed를 Inspector 창에서 애니메이션 속도 직접 변경하기

 

 

위의 그림에서 표시된 Speed 값을 직접 변경하는 것으로 애니메이션의 속도를 변경할 수 있다. 하지만 이 방법은 게임 내에서 애니메이션의 속도가 동적으로 변경되지 않을 것이라는 보장이 있을 때에만 사용이 가능하다.

 

 

방법 2 : Animator를 소유한 스크립트에서 코드를 통해 애니메이션 속도 변경하기

 

다음은 스크립트에서 코드로 애니메이션의 속도를 변경하는 예제이다.

 

using UnityEngine;

public class AnimationSpeedExample : MonoBehaviour
{
    public float animSpeed = 1.0f;
    public Animator animator;
   
    // Update is called once per frame
    void Update ()
    {
        animator.speed = animSpeed;
    }
}

 

위의 코드를 이용하면 animSpeed라는 변수의 값이 변경될 때마다 Animator 내에 있는 애니메이션들의 속도가 자동으로 변하게 될 것이다. 이 방법은 자유롭게 게임이 실행되는 와중에 애니메이션의 속도를 변경할 수 있지만, animator.speed를 통해서 애니메이션의 속도를 조절하면 해당 animator에 속하는 모든 애니메이션의 속도가 변한다는 단점이 있다. 예를 들자면, 공격 모션의 속도를 빠르게 하기 위해 speed를 1에서 2로 바꾸었는데, 공격 모션 뿐만 아니라, 이동 모션, 대기 모션 등 모든 모션의 속도가 2배 빨라지는 현상이 나타날 수 있다는 것이다. 그렇기 때문에 이 방법은 애니메이션이 블랜드되는 상황에서는 사용하기 부적합하며, 속도를 빠르게 한 애니메이션의 출력이 끝난 이후에는 다음 애니메이션의 정상적인 출력을 위해서 속도 값은 원상복귀시켜 주어야 한다는 단점이 존재한다.

 

방법 3 : Animator의 Parameter와 Multiplier를 이용한 애니메이션 속도 변경하기

 

공격 속도가 증가했는데 공격 애니메이션 이외의 애니메이션의 속도가 빨라지는 것은 부적절하며, 뛰어가면서 총을 쏜다던가 하는 방법의 애니메이션 블랜드에서는 모든 애니메이션의 속도가 변경되면 좋지 않다는 것을 앞선 방법에서 이야기했다. 그렇다면 우리가 알아야 할 방법은 속도의 변경이 필요하지 않은 애니메이션은 놔두고, 필요한 몇몇의 애니메이션의 속도만 조절하는 것이다.

 

이 방법을 위해서 필요한 것은 Animator의 Parameter와 Multiplier라는 것이다.

 

Animator의 parameter

 

 

Animator에서 State를 선택했을 때, Inspector 창에서 찾을 수 있는 Multiplier

 

이 방법을 적용하는 순서는 다음과 같다.

 

1. Animator의 Parameter에서 +를 눌러서 float형의 "AttackSpeed"라는 parameter를 만들어 준다.

 

2. 속도를 조절하고자 하는 State를 선택하고 Multiplier의 Parameter를 체크하고 추가한 AttackSpeed를 선택한다.

 

3. 속도 변경을 위해 animator의 "AttackSpeed" parameter를 변경하는 코드를 작성한다.

 

using UnityEngine;

public class AnimationSpeedExample : MonoBehaviour
{
    public float animSpeed = 1.0f;
    public Animator animator;
   
    // Update is called once per frame
    void Update ()
    {
        animator.SetFloat("AttackSpeed", animSpeed);
    }
}

 

위의 과정을 끝내고나면 Animator.SetFloat()을 통해 원하는 parameter의 값을 조정해 주는 것만으로 원하는 애니메이션의 속도를 조절할 수 있게 된다.

 

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

AssetBundleManifest 제대로 불러오는 방법

AssetBundleManifest 오브젝트 불러오기 실패와 유니티 문서의 문제점

요 며칠간 패치 시스템을 만들면서 아주 많이 고민해야 했던 문제가 있었는데 그것이 바로 AssetBundleManifest 오브젝트를 불러오는 방법이었다. 여러가지 방법을 이용해서 에셋 번들의 매니페스트 파일을 불러오는 것을 시도했으나 번번히 AssetBundleManifest 객체는 Null 값을 뱉으며 실패하는 경험을 했다. 이를 해결하기 위해서 구글링해서 발견한 코드도 적용해보고, AssetBundleManager의 코드도 참고해 봤지만 문제는 해결될 기미가 보이지 않았다.

 

제일 처음 기본적으로 참고한 유니티 5.6 문서에서는 AssetBundleManifest 객체를 불러오기 위해서 다음과 같은 코드를 사용하라고 섹션 6에서 언급하고 있다 :

 

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

출처: http://wergia.tistory.com/32 [베르의 프로그래밍 노트]

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

출처: http://wergia.tistory.com/32 [베르의 프로그래밍 노트]

 

경험해본 바에 의하면 저 코드를 이용해서 매니페스트 파일을 불러올 수 있는 것은 사실이다. 하지만 매니페스트 파일을 사용하기 위해 처음으로 유니티 5.6 문서를 본 개발자는 저 manifsetFilePath에 대해서 정확하게 어떤 경로를 의미하는지에 대한 난감함을 느끼게 될 것이다. 작성자 역시 같은 난감함을 느꼈고 이 manifestFilePath를 알아내기 위해 여러가지 시도를 해야봐야 했다.

 

 

시도 1 : 매니페스트 파일을 불러오고자 하는 에셋 번들

 

AssetBundle assetBundle = AssetBundle.LoadFromFile(assetBundleDirectory + "/character.unity3d");
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

 

이 방법은 에셋 번들을 불러오는데는 성공하였지만 이후의 매니페스트 파일을 불러오는데는 실패해서 manifest가 null 값을 가지고 있었다.

 

 

시도 2 : 매니페스트 파일을 불러오고자 하는 에셋 번들의 매니페스트 파일

 

AssetBundle assetBundle = AssetBundle.LoadFromFile(assetBundleDirectory + "/character.manifest");
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

 

 

이 방법은 파일 불러오기 자체에서부터 실패했다.

 

 

시도 3 : 매니페스트 파일이 포함되어 있는 폴더의 경로

 

AssetBundle assetBundle = AssetBundle.LoadFromFile(assetBundleDirectory);
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

 

이 방법 역시 시도 2와 마찬가지로 불러오기 자체를 실패했다.

 

 

위의 세 가지 방법 이 외에도 LoadAsset을 하는 방법을 바꾸어 보기도 했고 LoadFromFile 대신 LoadFromMemory나 비동기 함수를 사용하는 방법도 사용해 봤었고, 에셋 번들을 빌드한 이후에 나온 매니페스트 파일을 각 에셋 번들에 포함시킨 후에 재빌드하는 방법도 사용해 봤다. 간단히 결론만 말하면 그 모든 방법은 실패했다.

 

그리고 마지막 방법으로 시도해본 것이 AssetBundles.unity3d라는 파일을 호출하는 것이었다. 유니티에서 에셋 번들을 빌드하면 에디터에서 지정한 에셋 번들 이름을 가진 에셋 번들들과 그 매니페스트 파일이 생성되고, 그 외에 직접 지정한 이름이 아닌 AssetBundles라는 파일과 AssetBundles.manifest라는 파일이 생성되는데, 유니티 5.6 문서에는 이 파일의 역할이 무엇인지 명시되어 있지 않았다.

 

그렇게 마지막으로 시도한 코드는 다음과 같았다 :

 

AssetBundle assetBundle = AssetBundle.LoadFromFile(assetBundleDirectory + "/AssetBundles.unity3d");
AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

 

 

이 마지막 코드를 사용한 이후에야 매니페스트 파일을 불러올 수 있게 되었다.

 

유니티 문서에서는 AssetBundleManifest를 호출하는 방법은 정확히 알려주었으나, 어떤 파일을 불러와야 되는지는 제대로 알려주지 않은 것이다. 구글링한 내용들에서도 "AssetBundles" 파일에 대한 언급은 없었기 때문에 정확한 해결법을 찾는데 더 어려웠던 것 같다.

 


 

1. ".unity3d"라는 확장자는 웹 서버를 통해 다운받은 에셋 번들을 로컬 저장소에 저장할 때 직접 지정한 확장자이다.

2. 에셋 번들을 빌드하면 확장자 없이 에셋 번들 이름만 적힌 파일이 나온다.

 

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

로컬 저장소의 에셋 번들로부터 씬을 불러오는 방법

유니티(Unity)에서 씬(Scene) 역시 에셋 번들(Asset Bundle)에 포함 될 수 있다. 그 기본적인 방법은 유니티 5.6 문서의 섹션 7에 나와있다. 하지만 그 방법은 에셋 번들 매니저(Asset Bundle Manager)를 이용해야 하는 방법이고 너무 대략적인 내용이라 따라 실행하는 데에만 많은 시행 착오를 겪게 된다. 이 문서에서는 그에 비해 비교적 간단하고 즉시 활용할 수 있는 코드를 보여주고자 한다.

 

다음의 내용이 알려주고자 하는 코드의 전체이다 :

using System.IO;
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class AssetLoader : MonoBehaviour

{

[SerializeField]

string[] assetBundleNames;

 

[SerializeField]

string assetBundleDirectory = "Assets/AssetBundles";

 

void Start()

{

LoadSceneFromAssetBundle("불러오고자 하는 씬의 이름", false);

}

 

public void LoadSceneFromAssetBundle(string sceneName, bool isAdditive)

{

// 저장한 에셋 번들로부터 에셋 불러오기

// gamescene.unity는 작성자가 로컬 저장소에 저장한 게임 씬들이 들어있는 에셋 번들이다.

AssetBundle assetBundle = AssetBundle.LoadFromFile(Path.Combine(assetBundleDirectory + "/", "gamescene.unity3d"));

// 에셋 번들 내에 존재하는 씬의 경로를 모두 가져오기

string[] scenes = assetBundle.GetAllScenePaths();

string loadScenePath = null;

foreach (string sname in scenes)

{

if (sname.Contains(sceneName))

{

loadScenePath = sname;

}

}

 

if (loadScenePath == null) return;

 

LoadSceneMode loadMode;

 

if (isAdditive) loadMode = LoadSceneMode.Additive;

else loadMode = LoadSceneMode.Single;

 

SceneManager.LoadScene(loadScenePath, loadMode);

}

}

 

위의 코드를 활용하면 에셋 번들에 포함된 씬을 불러오는 것이 가능해진다. 다만, 게임 씬 에셋 번들이 다른 에셋 번들에 대한 종속성(Dependencies)를 가지고 있다면 씬을 불러오기 이전에 종속성을 가지고 있는 에셋 번들들을 먼저 불러와야만 한다. 만약 그렇게 하지 않으면, 불러오지 못한 오브젝트들은 게임 씬에서 missing으로 처리되어 빈 오브젝트로 나타나게 될 것이다.

 

예시로 보여준 코드에서는 게임 씬 에셋 번들을 불러오고 곧바로 에셋 번들 내에 존재하는 씬의 경로를 모두 탐색해서 씬을 불러왔지만 다른 방법으로는 게임이 시작되었을 때 게임 씬 에셋 번들을 불러와서 게임 씬들의 목록을 구성한 뒤에 필요할 때마다 활용하는 방식으로 사용할 수도 있다.

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

UnityWebRequest를 이용해서 원격 서버에서 받아온 에셋 번들을 로컬 저장소에 저장하는 방법

유니티 5.6 문서에서는 알려주지 않는 로컬 저장소 저장 방법

유니티 5.6 문서에서는 웹 서버에서 에셋 번들을 받아올 때 WWW.LoadFromCacheOrDownload 대신에 UnityWebRequest와 DownloadHandlerAssetBundle을 사용할 것을 권장하고 있다. 아래의 코드가 유니티 5.6 문서에서 보여주는 웹 서버에서 에셋 번들을 받아와서 메모리에 로드하는 예제이다.

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class UnityWebRequestExample : MonoBehaviour
{
    IEnumerator InstantiateObject()
    {
        string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
        UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
        yield return request.Send();

        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);

        GameObject cube = bundle.LoadAsset<GameObject>("Cube");
        GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");

        Instantiate(cube);
        Instantiate(sprite);
    }
}


출처: http://wergia.tistory.com/32 [베르의 프로그래밍 노트]
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class UnityWebRequestExample : MonoBehaviour
{
    IEnumerator InstantiateObject()
    {
        string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
        UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
        yield return request.Send();

        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);

        GameObject cube = bundle.LoadAsset<GameObject>("Cube");
        GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");

        Instantiate(cube);
        Instantiate(sprite);
    }
}


출처: http://wergia.tistory.com/32 [베르의 프로그래밍 노트]
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class UnityWebRequestExample : MonoBehaviour
{
    IEnumerator InstantiateObject()
    {
        string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
        UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
        yield return request.Send();

        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);

        GameObject cube = bundle.LoadAsset<GameObject>("Cube");
        GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");

        Instantiate(cube);
        Instantiate(sprite);
    }
}


출처: http://wergia.tistory.com/32 [베르의 프로그래밍 노트]
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class UnityWebRequestExample : MonoBehaviour
{
    IEnumerator InstantiateObject()
    {
        string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
        UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
        yield return request.Send();

        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);

        GameObject cube = bundle.LoadAsset<GameObject>("Cube");
        GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");

        Instantiate(cube);
        Instantiate(sprite);
    }
}


출처: http://wergia.tistory.com/32 [베르의 프로그래밍 노트]
using UnityEngine;
using UnityEngine.Networking;

public class UnityWebRequestExample : MonoBehaviour
{
    IEnumerator InstantiateObject()
    {
        string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
        UnityWebRequest request = UnityWebRequest.GetAssetBundle(uri, 0);
        yield return request.Send();

        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);

        GameObject cube = bundle.LoadAsset<GameObject>("Cube");
        GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");

        Instantiate(cube);
        Instantiate(sprite);
    }
}
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class UnityWebRequestExample : MonoBehaviour
{
    IEnumerator InstantiateObject()
    {
        string uri = "file:///" + Application.dataPath + "/AssetBundles/" + assetBundleName;
        UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.GetAssetBundle(uri, 0);
        yield return request.Send();

        AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(request);

        GameObject cube = bundle.LoadAsset<GameObject>("Cube");
        GameObject sprite = bundle.LoadAsset<GameObject>("Sprite");

        Instantiate(cube);
        Instantiate(sprite);
    }
}


출처: http://wergia.tistory.com/32 [베르의 프로그래밍 노트]

서버에서 받아온 에셋 번들을 메모리에 넣어두고 사용하고자 하는 목적이 아니라 받아온 에셋 번들을 클라이언트의 로컬 저장소에 저장하고 패치하는 시스템을 만들고자할 때에는 부적절한 예제이며, 로컬 저장소에 저장하는 방법은 유니티 5.6의 문서에서는 알려주지 않는다.

 

 

에셋 번들을 로컬 저장소에 저장하는 방법

서버에서 받아온 에셋 번들을 로컬 저장소에 저장하려면 받아온 데이터를 파일 입출력을 해야했는데, 그러기 위해서는 우선 받아온 에셋 번들의 데이터, byte[] data를 찾아내고 거기에 엑세스할 수 있어야 했다. 그래서 첫 번째로 시도한 방법이 위의 예제에서 UnitWebRequest.GetAssetBundle()로 받아온  UnityWebRequest request에서 request.downloadHandler.data를 통해서 접근하는 것이었다.

using System.IO;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class AssetLoader : MonoBehaviour
{
public string[] assetBundleNames;

IEnumerator AccessFailMethod()
{

        string uri = "http://127.0.0.1/character";


UnityWebRequest request = UnityWebRequest.GetAssetBundle(uri);

yield return request.Send();
string assetBundleDirectory = "Assets/AssetBundles";

if (!Directory.Exists(assetBundleDirectory))
{
Directory.CreateDirectory(assetBundleDirectory);
}

FileStream fs = new FileStream(assetBundleDirectory + "/" + "character.unity3d", System.IO.FileMode.Create);
fs.Write(request.downloadHandler.data, 0, (int)request.downloadedBytes);
fs.Close();
}
}

하지만 위의 방법은 에셋 번들에 대한 원시 데이터 접근은 지원하지 않는다는 예외를 발생시키고 실패한다. 즉, GetAssetBundle로 웹 서버에서 불러온 데이터는 데이터에 대한 직접 접근을 허용하지 않으니 파일 입출력을 통해서 로컬 저장소에 저장할 수 없다는 것이다.

 

그렇기 때문에 로컬 저장소에 저장하기 위해서는 웹 서버에서 에셋 번들을 받아올 때 다른 방법을 취해야 했다. 아래의 예제가 다른 방식으로 웹 서버에서 에셋 번들을 받아와서 로컬 저장소에 저장하는 예제이다 :

using System.IO;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class AssetLoader : MonoBehaviour
{

// 서버에서 받아오고자 하는 에셋 번들의 이름 목록

// 지금은 간단한 배열 형태를 사용하고 있지만 이후에는

// xml이나 json을 사용하여 현재 가지고 있는 에셋 번들의 버전을 함께 넣어주고

// 서버의 에셋 번들 버전 정보를 비교해서 받아오는 것이 좋다.
public string[] assetBundleNames;

IEnumerator SaveAssetBundleOnDisk()
{

// 에셋 번들을 받아오고자하는 서버의 주소

// 지금은 주소와 에셋 번들 이름을 함께 묶어 두었지만

// 주소 + 에셋 번들 이름 형태를 띄는 것이 좋다.
string uri = "http://127.0.0.1/character";

 

// 웹 서버에 요청을 생성한다.
UnityWebRequest request = UnityWebRequest.Get(uri);
yield return request.Send();

 

// 에셋 번들을 저장할 경로

string assetBundleDirectory = "Assets/AssetBundles";
// 에셋 번들을 저장할 경로의 폴더가 존재하지 않는다면 생성시킨다.
if (!Directory.Exists(assetBundleDirectory))
{
Directory.CreateDirectory(assetBundleDirectory);
}

 

// 파일 입출력을 통해 받아온 에셋을 저장하는 과정
FileStream fs = new FileStream(assetBundleDirectory + "/" + "character.unity3d", System.IO.FileMode.Create);
fs.Write(request.downloadHandler.data, 0, (int)request.downloadedBytes);
fs.Close();
}
}

위 예제에서 보다시피 UnityWebRequest.Get() API를 사용했을 경우 받아온 에셋 번들의 원시 데이터에 대한 접근이 가능해진다. 이로 인해서 파일 입출력을 통한 원격 서버에서 받아온 에셋 번들의 로컬 저장소 저장에 성공하게 되었다.

 

 

파일 입출력을 통한 저장 방법

받아온 에셋 번들을 파일 입출력을 통해 로컬 저장소에 저장하는 방법은 여러가지가 있다. 적당하다고 생각되는 방법을 골라서 사용하면 될 것이다.

// 파일 저장 방법 1
FileStream fs = new FileStream(assetBundleDirectory + "/" + "character.unity3d", System.IO.FileMode.Create);
fs.Write(request.downloadHandler.data, 0, (int)request.downloadedBytes);
fs.Close();

// 파일 저장 방법 2
File.WriteAllBytes(assetBundleDirectory + "/" + "character.unity3d", request.downloadHandler.data);
 

// 파일 저장 방법 3
for (ulong i = 0; i < request.downloadedBytes; i++)
{
fs.WriteByte(request.downloadHandler.data[i]);
// 저장 진척도 표시

}

 

 

로컬 저장소에 저장한 에셋 번들을 불러와서 사용하는 방법

위의 과정을 통해 원격 서버에서 받아온 에셋 번들을 로컬 저장소에 저장하는데 성공했다. 이 다음에 해야할 작업은 저장한 에셋 번들을 불러와서 사용하는 것이다. 그 작업은 유니티 5.6 문서에 나오는 기본 예제를 구현하는 것으로 충분히 가능하다.

using System.IO;
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;

public class AssetLoader : MonoBehaviour
{
IEnumerator LoadAssetFromLocalDisk()
{
string assetBundleDirectory = "Assets/AssetBundles";
// 저장한 에셋 번들로부터 에셋 불러오기
var myLoadedAssetBundle = AssetBundle.LoadFromFile(Path.Combine(assetBundleDirectory + "/", "character.unity3d"));
if (myLoadedAssetBundle == null)
{
Debug.Log("Failed to load AssetBundle!");
yield break;
}
else
Debug.Log("Successed to load AssetBundle!");

var prefab = myLoadedAssetBundle.LoadAsset<GameObject>("P_C0001");
Instantiate(prefab, Vector3.zero, Quaternion.identity);
}
}

 

 

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

+ Recent posts