[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 33회차 미션 시작합니다.

04. 배틀로얄 - 11, 12 번을 진행합니다.


저번 시간에 이어 Save, Add, Remove, Copy 등의 구현을 시작합니다.




아래 진행하는 코드 중 ArrayHelper라는 클래스는 제공되는 프로젝트에 포함되어 있는 코드를 사용합니다.
Array 기능을 좀더 쉽게 사용할 수 있는 기능을 가진 함수 모음 클래스라고 생각하면 됩니다.
크게 중요하지 않은 내용이므로 제공된 소스를 사용만 하면 됩니다. Tool용 ArrayHelper 입니다.



public class EffectData : DataBase
{
    // 저번시간코드들..
    
    public void SaveData() {
        using (XmlTextWriter xml = new XmlTextWriter(xmlFilePath + xmlFileName, System.Text.Encoding.Unicode) {
            xml.WriteStartDocument();
            xml.WriteStartElement(EFFECT); // EFFECT Key
            xml.WriteElementString("length", GetDataCount().ToString());
            for (int i = 0; i < this.names.Length; i++) {
                EffectClip clip = this.effectClips[i];
                xml.WriteStartElement(CLIP);  // CLIP Key
                xml.WriteElementString("id", i.ToString());
                xml.WriteElementString("name", this.names[i]);
                xml.WriteElementString("effectType", clip.effectType.ToString());
                xml.WriteElementString("effectPath", clip.effectPath);
                xml.WriteElementString("effectName", clip.effectName);
                xml.WriteEndElement(); // CLIP End
            }
            xml.WriteEndElement(); // EFFECT End
            xml.WriteEndDocument();
        }
    }
   
    public override int AddData(string newName) {
        if (this.names == null) {
            this.names = new string[] { name };
            this.effectClips = new EffectClip[] { new EffectClip() };
        }
        else {
            this.names = ArrayHelper.Add(name, this.names);
            this.effectClips = ArrayHelper.Add(new EffectClip(), this.effectClips);
        }
        return GetDataCount();
    }
    
    public override void RemoveData(int index) {
        this.names = ArrayHelper.Remove(index, this.names);
        if (this.names.Length == 0) this.names = null;
        this.effectClips = ArrayHelper.Remove(index, this.effectClips);
    }
    
    public void ClearData() { // Bonus function
        foreach (EffectClip clip in this.effectClips) {
            clip.ReleaseEffect();
        }
        this.effectClips = null;
        this.names = null;
    }
    
    public EffectClip GetCopy(int index) {
        if (index < 0 || index >= this.effectClips.Length) return null;
        EffectClip original = this.effectClips[index];
        EffectClip clip = new EffectClip();
        clip.effectFullPath = original.effectFullPath;
        clip.effectName = original.effectName;
        clip.effectType = original.effectType;
        clip.effectPath = original.effectPath;
        clip.realId = this.effectClips.Length;
        return clip;
    }
    
    /// <summary>
    /// 원하는 인덱스를 프리로딩해서 찾아준다
    /// </summary>
    public EffectClip GetClip(int index) {
        if (index < 0 || index >= this.effectClips.Length) return null;
        effectClips[index].PreLoad();
        return effectClips[index];
    }
    
    public override void Copy(int index) {
        this.names = ArrayHelper.Add(this.names[index], this.names);
        this.effectClips = ArrayHelper.Add(GetCopy(index), this.effectClips);
    }
}


여기까지가 EffectData 에 대한 처리 마무리였고 이제부터 본격적인 Tool 개발에 들어갑니다 ^^~





공통 툴 레이어 작성입니다.

본격적인 EffectTool 제작 전에, 공통 툴을 만들어두면 다른 툴을 만들때에도 재사용 가능하기 때문에 제작해두는 것이 좋습니다.




EditorHelper 클래스입니다.

ArrayHelper와 비슷하게 Tool 개발을 용이하게 해주는 함수들의 모음이며 프로젝트에 포함되어 배포되어 있습니다.

다음의 2가지 함수는 구현되어 있고, 나머지 함수들을 추가 구현합니다.

+ GetPath() - Resource를 전달하면 Resource의 경로를 리턴해주는 함수
+ CreateEnumStructure() - Enum 객체를 만들어주는 함수

 


public class EditorHelper
{
    public static string GetPath(UnityEngine.Object p_clip) {}
    public static void CreateEnumStructure(string enumName, StringBuilder data) {}
    
    // Tool UI 상단
    public static void EditorToolTopLayer(BaseData data, ref int selection, ref UnityObject source, int uiWidth) {
        EditorGUILayout.BeginHorizontal();
        {
            if (GUILayout.Button("ADD", GUILayout.Width(uiWidth))) {
                data.AddData("New Data");
                selection = data.GetDataCount() - 1; // 최종 Item 선택
                source = null;
            }
            if (GUILayout.Button("COPY", GUILayout.Width(uiWidth))) {
                data.Copy(selection);
                source = null;
                selection = data.GetDataCount() - 1;
            }
            if (data.GetDataCount() > 1) {
                if (GUILayout.Button("REMOVE", GUILayout.Width(uiWidth))) {
                    source = null;
                    data.RemoveData(selection);
                }
            }
            
            if (selection > data.GetDataCount() - 1) { // out of range
                selection = data.GetDataCount() - 1;
            }
        }
        EditorGUILayout.EndHorizontal();
    }
    
    // Tool UI 리스트
    public static void EditorToolListLayer(ref Vector2 ScrollPosition, BaseData data, ref int selection, ref UnityObject source, int uiWidth) {
        EditorGUILayout.BeginVertical(GUILayout.Width(uiWidth);
        {
            EditorGUILayout.Separator();
            EditorGUILayout.BeginVertical("box");
            {
                ScrollPosition = EditorGUILayout.BeginScrollView(ScrollPosition);
                {
                    if (data.GetDataCount() > 0) {
                        int lastSelection = selection;
                        selection = GUILayout.SelectionGrid(selection, data.GetNameList(true), 1);
                        if (lastSelection != selection) { // 선택이 바뀌었으면
                            source = null;
                        }
                    }
                }
                EditorGUILayout.EndScrollView();
            }
            EditorGUILayout.EndVertical();
        }
        EditorGUILayout.EndVertical();
    }
}


다음시간에 공통 툴 레이어 완성 및 본격적인 EffectTool 만들기를 시작합니다 ^^~




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 32회차 미션 시작합니다.

04. 배틀로얄 - 09, 10 번을 진행합니다.


저번에 이어서 이번에는 EffectData 클래스를 구현합니다.




코드 내용중 clipPath는 위에서 보듯이 Unity Project에서 "9.ResourcesData/Resources/Prefabs/Effects" 폴더를 가리킵니다.




마찬가지로 코드 내용중 dataPath는 위에서 보이는 Unity Project에서 "9.ResourcesData/Resources/Data" 폴더를 가리킵니다.



using System.Xml; // XML 사용.
using System.IO; // 읽기 쓰기.
/// <summary>
/// 이팩트 클립 리스트와 이팩트 파일 이름과 경로를 가지고 있으며 파일을 읽고 쓰는 기능을 가지고 있다.
/// </summary>
public class EffectData : BaseData
{
    // Array는 실수로 무한정 커질 수 있는 문제를 가지고 있어서 한정된 자원이라는 의미와 크기 명확성을 위해 배열을 사용.
    public EffectClip[] effectClips = new EffectClip[0];
    
    public string clipPath = "Effects/";
    private string xmlFilePath = ""; // Path, File 분리이유는 경로만 바꾸어 파일관리, 버전관리가 용이하며, 특정 Asset 폴더로 관리하는 등 유연하기 때문.
    private string xmlFileName = "effectData.xml";
    private string dataPath = "Data/effectData";
    //XML 구분자.
    private const string EFFECT = "effect"; // 저장 KEY
    private const string CLIP = "clip"; // 저장 KEY
    
    private EffectData() { }
    
    // 읽어오고 저장하고, 데이터를 삭제하고, 특정 클립을 얻어오고, 복사하는 기능.
    public void LoadData() {
        Debug.Log($"xmlFilePath = {Application.dataPath} + {dataDirectory}");
        this.xmlFilePath = Application.dataPath + dataDirectory; // Application.dataPath = Unity Assets Folder
        TextAsset asset = (TextAsset)ResourceManager.Load(dataPath);
        if (asset == null || asset.text == null) { // 하나도 없는 경우
            this.AddData("new Effect");
            return;
        }
        
        using (XmlTextReader reader = new XmlTextReader(new StringReader(asset.text))) {
            int currentID = 0;
            while (reader.Read()) {
                if (reader.IsStartElement()) {
                    switch (reader.Name) {
                        case "length":
                            int length = int.Parse(reader.ReadString());
                            this.names = new string[length];
                            this.effectClips = new EffectClip[length];
                            break;
                        case "id":
                            currentID = int.Parse(reader.ReadString());
                            this.effectClips[currentID] = new EffectClip();
                            this.effectClips[currentID].realId = currentID;
                            break;
                        case "name":
                            this.names[currentID] = reader.ReadString();
                            break;
                        case "effectType":
                            this.effectClips[currentID].effectType = (EffectType)Enum.Parse(typeof(EffectType), reader.ReadString());
                            break;
                        case "effectName":
                            this.effectClips[currentID].effectName = reader.ReadString();
                            break;
                        case "effectPath":
                            this.effectClips[currentID].effectPath = reader.ReadString();
                            break;
                    }
                }
            }
        }
    }
}


초반 개발시에는 데이터 파일 저장 포맷이 지속적으로 바뀌게 될텐데 이때부터 json을 사용해도 되지만 json을 사용하게 되는 경우 무언가 잘못되어 꼬이게 되는 경우 저장 구조가 복잡하여 해당 문제점을 찾기가 어렵다는 단점이 있습니다.
그래서 초반에는 xml과 같은 명확하고 찾기 쉬운 데이터 파일 구조를 사용하다가 어느 정도 안정화되면 json으로 변경하는 것도 좋은 방법입니다.

사실 실무에서는 자체 개발된 데이터 파일 구조를 많이 사용한다고 합니다.




위와 같이 $를 붙여서 사용하면 좀더 편하게 문자열 구조를 사용할 수 있습니다.
지원되지 않았을 때는 아래 문장처럼 +를 마구마구 써서 연결해야 했던 불편함이 컸었지요.

C#은 정말 개발자 편의성을 정말 많이 고려한 것 같습니다.

 


그런데 왜 1.2x를 없앤거죠?? 1x 1.5x 2x만 나오는... 갑자기 오늘부터 지원을 안하네요.. 라고 하고 있는데 잠시 그랬던거 같네요 ^^;;;
다시 불러와서 재생하니 정상적으로 나오네요.. 서버 작업중이신건가 ㅎㅎ
헉.. 그런가보네요.. 갑자기 저화질로 나오고 서버 일시오류라고 뜨며 멈추고 튕기기도 하네요 ㅠ.,ㅜ;; 무어지.. 뜨합..
어여 안정화시켜주세요..




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 31회차 미션 시작합니다.

04. 배틀로얄 - 07, 08 번을 진행합니다.





일단 Assets/1.Scripts 폴더에 대한 간단한 설명입니다.

+ Common - 공통적인 스크립트들
+ GameData - GameAsset에 쓰일 데이터 스크립트들
+ Helper - 에디터, 툴 Helper 스크립트들
+ Tool - 툴 스크립트들
+ Util - 시간계산, 위치계산 등의 유틸 스크립트들

+ Manager 폴더를 만들고, C# 스크립트 ResourceManager를 추가합니다.


using UnityEngine;
using UnityObject = UnityEngine.Object; // Object를 어디것인지 명확히 하여 사용.
/// <summary>
/// Resources.Load를 래핑하는 클래스
/// 나중엔 어셋번들로 변경됨.
/// </summary>
public class ResourceManager
{
    // ResourceManager 클래스를 Singletone 패턴으로 구현해도 되지만, 간단한 클래스는 그냥 함수를 static으로 해서 지원해도 편하다.
    public static UnityObject Load(string path) {
        // 지금은 리소스 로드지만 추후엔 어셋 로드로 변경됨.
        return Resources.Load(path);
    }
    
    public static GameObject LoadAndInstantiate(string path) {
        UnityObject source = Load(path);
        if (source == null) return null;
        return GameObject.Instantiate(source) as GameObject;
    }
}





이팩트 툴 제작을 위한 이팩트 데이터 모델을 먼저 제작합니다.
여기 내용들은 게임 장르와 기획에 따라 엄청나게 바뀌게 됩니다. 이 툴을 사용하게 되는 분들은 기획자나 밸런서분들이 될 것입니다. 그래서 요구사항도 많아지게 될 것입니다.

불필요한듯하고 굉장히 손도 많이 가는 툴을 왜 만드느냐고 의문을 가지는 개발자들이 많은 듯하네요..
저는 무조건 찬성입니다. 무조건 Tool을 개발해야 합니다. 게임뿐만이 아니라 어떠한 프로그램을 만들더라도 마찬가지라고 생각합니다. 이러한 AddOn Tool이든 Helper Tool이든 마찬가지라고 생각합니다.

Tool을 만들다 보면 3번의 고비가 온다고 합니다. ㅋㅋㅋㅋㅋㅋㅋㅋ.
사운드 툴 만들 때, 캐릭터 만들 때, 그리고 애니메이션 만들 때라고 합니다. ㅎㅎ 잘 넘기고 이겨내면 멋진 게임을 개발해 낼 수가 있게 되는 것이겠지요..


<Effect Clip>
+ Prefab - 터지는 효과, 피격 효과, 터지는 Mesh 등
+ 생명주기 - 몇초후에 사라져라 또는 화면 밖으로 넘어가면 사라져라 등
+ 위치 - 캐릭터의 손에 붙는 이팩트인가, 피격이팩트.
+ 사운드 등등..


<Effect Data>
+ Effect Clip들의 모음





EffectClip과 EffectData 스크립트를 추가합니다.

Effect Tool은 그래도 간단한 편입니다. Sound Tool로 가면 많이 복잡해진다고 합니다 ^^; 기대도 되고 긴장도 되네요.



/// <summary>
/// 이팩트 프리팹과 경로와 타입 등의 속성 데이터를 가지고 있게 되며
/// 프리팹 PreLoading 기능을 갖고 있고 - 풀링을 위한 기능이기도 합니다.
/// 이팩트 인스턴스 기능도 갖고 있으며 - 풀링과 연계해서 사용하기도 합니다.
/// </summary>
public class EffectClip
{
    public int realId = 0; // 추후 속성은 같지만 다른 이팩트 클립이 있을 수 있어서 분별용.
    
    public EffectType effectType = EffectType.NORMAL;
    public GameObject effectPrefab = null;
    public string effectName = string.Empty;
    public string effectPath = string.Empty;
    public string effectFullPath = string.Empty; // effectPath + effectName
    
    public EffectClip() { }
    
    public void PreLoad() {
        this.effectFullPath = effectPath + effectName;
        if (this.effectFullPath != string.mpty && this.effectPrefab == null) { // prefab 로딩이 되어있다면 다시 할 필요 없으므로
            this.effectPrefab = ResourceManager.Load(effectFullPath) as GameObject;
            //this.effectPrefab = (GameObject)ResourceManager.Load(effectFullPath); // 강제형변환을 앞에 넣어도 되지만 중요하지 않은 구문이 앞으로 오기 때문에 "as" 구문이 유용한 경우가 많다.
        }
    }
    
    public void ReleaseEffect() {
        if (this.effectPrefab != null)
            this.effectPrefab = null; // Destroy하지 않아도 GabageCollector가 처리할 것이다.
    }

    /// <summary>
    /// 원하는 위치에 내가 원하는 이팩트를 인스턴스합니다.
    /// </summary>
    public GameObject Instantiate(Vector3 Pos) {
        if (this.PreLoad() != null) {
            GameObject effect = GameObject.Instantiate(effectPrefab, Pos, Quaternion.identity);
            return effect;
        }
        
        return null;
    }
}





EffectType에서 "Ctrl + Click"을 하면 정의로 이동을 하게 됩니다.
GlobalDefine 스크립트는 위와 같은 내용을 가지고 있습니다.

 

걱정도 많으시고 받아들이는 사람에 대한 큰 배려심이기도 하셔서 반복적인 걱정 얘기를 많이하셔서 아직까지는 내용이 짧습니다. ㅎㅎ 강의 진행하시면서 개발 시간때문에라도 많이 빨라지지 않을까 싶습니다.

 

일단 화면도 잘 보이고.. VisualStudio 검정 배경에 글씨들도 잘 보입니다.

온라인 강의라 놓친 부분들은 다시 볼 수 있기 때문에 너무 걱정 안하시고 좋은 얘기와 내용 많이 해주시면 좋겠습니다.




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts