Game Programming
Shader Portfolio (2023)
- PC - One - Ongoing - Unity
I believe that shaders are one of the most impactful aspect in creating the world within a game. In particular, shaders play a crucial role in expressing the world I want to portray and unifying various environments into a cohesive graphic concept.

Having majored in graphic design and engaged in both designer and programmer, I became interested in the Shader. I have been studying Unity CG and HLSL. And I am also implementing as Shader Graph from shader I had studied.

I plan to continuously update and add more projects in the future.
Code Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//> parts of SSS shader
{
    //> diffuse
    float nDotl = dot(i.normalWS, lightDir);
    float3 diffuse = saturate(nDotl);
 
    //> specular
    float3 refl = reflect(lightDir, normalWS);
    float rDotv = saturate(dot(refl, -viewDir));
    float3 specular = pow(rDotv, 100);
    specular *= specularIntensity.rgb;
 
    //> SSS
    float3 h = normalize(lightDir + normalWS * _Distortion);
    float vDoth = pow(saturate(dot(viewDir, -h)), _SSSPower + 0.000001* _SSSScale;
    float3 backLight = _Attenuation * (vDoth + _Ambient) * thickness * _SSSColor.rgb;
}
 
//> parts of Rim light shader
{
    float3 normalWS = normalize(i.normalWS);
    float3 viewDirWS = normalize(i.viewDirWS);
    float nDotv = dot(normalWS, viewDirWS);
    float rim = saturate(nDotv);
 
    rim = pow(1 - rim, _RimPower);
 
    float holo = pow(frac(normalWS.y * 3 - _Time.y), 15);
    float alpha = (holo + rim) * abs(sin(_Time.y));
 
    return half4(_RimColor.rgb, alpha);
}
 
//> Parts of Icicle shader 
{
    //> ice offset
    float2 appendXZ = (o.normalWS.xz * _IcicleMaskTile) + 0.5;
    float4 offsetMask = tex2Dlod(_IceOffset, float4(appendXZ, 00));
                
    //> yMask and vertex Offset
    float yMask = saturate(o.normalWS.y * -1 * _IceSlider);
    float yMaskTop = saturate(o.normalWS.y * 3* remap(_IceSlider, float2(01), float2(00.02));
    float3 vertexOffset = v.normalOS * (yMask * offsetMask.rgb * _IceLength + yMaskTop);
    o.yMask = saturate(yMask * 8);
                
    //> Set                
    o.positionCS = TransformObjectToHClip(v.positionOS + vertexOffset);
    float3 positionWS = TransformObjectToWorld(v.positionOS + vertexOffset);
    o.viewDir = GetWorldSpaceViewDir(positionWS);
}
 
//> Parts of Ramp shader
{
    float3 lightDir = normalize(_MainLightPosition.xyz);
    float3 viewDir = normalize(i.viewDir);
    float nDotl = saturate(dot(normalWS, lightDir));
    float nDotv = saturate(dot(normalWS, viewDir));
    half4 ramp = SAMPLE_TEXTURE2D(_RampTex, sampler_RampTex, float2(nDotl, nDotv));
}
 
//> Parts of Water Lake shader
{
    //> Water Color
    float depthFade = Depthfade(i.positionNDC.xy, i.scrPos, _DepthDistance);
    float4 waterColor = lerp(_ShallowColor, _DeepColor, depthFade);
 
    //> Distortion
    float2 uvD = Movement(i.uv, _DistortionSpeed, _DistortionScale);
    float noiseD = GradientNoise(uvD, 20);
    float3 normalTS = NormalFromHeight_Tangent(noiseD, i.positionWS, i.tbn);
    float3 distortion = normalTS * _DistortionStrength + float3(i.positionNDC.xy, 0);
    distortion = SampleSceneColor(distortion);
 
    //> Foam
    float  foamCutoff = Depthfade(i.positionNDC.xy, i.scrPos, _FoamAmount) * _FoamCutoff;
    float2 uvF = Movement(i.uv, _FoamSpeed, _FoamScale);
    float4 noiseF = GradientNoise(uvF, 1);
    float4 foamTerm = step(foamCutoff, noiseF) * _FoamColor.a;
    float4 foam = lerp(waterColor, _FoamColor, foamTerm);
 
    //> Shadow
    float4 shadowcoord = TransformWorldToShadowCoord(i.positionWS);
    Light mainLight = GetMainLight(shadowcoord);
    float3 attenuation = mainLight.color * mainLight.distanceAttenuation * mainLight.shadowAttenuation;
    attenuation = attenuation * 0.6 + 0.4;
 
    //> Reflect
    float2 uv = float2(i.scrPos.xy / i.scrPos.w);
    uv = uv * _ReflectionTilingOffset.xy + _ReflectionTilingOffset.zw;
    float4 refl = SAMPLE_TEXTURE2D(_ReflectionTex, sampler_ReflectionTex, uv);
}
 
//> Parts of Outline Post Processing shader
{
    float  cameraOutlineDepthTexture  = tex2D(_CustomOutlineDepthTexture,i.uv).r;
    float4 cameraColorTexture         = tex2D(_CameraOpaqueTexture,i.uv);
    float4 cameraColorObstructTexture = tex2D(_CustomColorObstructTexture,i.uv);
    float  cameraDepthObstructTexture = tex2D(_CustomDepthObstructTexture,i.uv).r;
    float3 cameraWorldNormalTexture   = sampleWorldNormal(i.uv); 
        
    float3 worldPos = ComputeWorldSpacePosition(i.uv, cameraOutlineDepthTexture, UNITY_MATRIX_I_VP);//World Pos texture
    float3 V = GetWorldSpaceNormalizeViewDir(worldPos);
    float2 screenSpaceUV = i.uv;
    float fresnel = saturate(dot(V,cameraWorldNormalTexture));
}
 
//> RenderFeature
public class ScreenSpaceOutlines: ScriptableRendererFeature
{
    class DrawOpaqueDepthPass : ScriptableRenderPass
    {
        readonly int               srcDepthID;
        readonly int               dstDepthID;
        readonly List<ShaderTagId> shaderTagIdList;
 
        RTHandle srcDepthRT;
        RTHandle dstDepthRT;
        RendererListParams rendererListParams;
        RendererList       rendererList;
        DrawingSettings    depthDrawingSettings;
        FilteringSettings  depthFilteringSettings;
 
        public DrawOpaqueDepthPass (LayerMask outlineMask)
        {
            srcDepthID = Shader.PropertyToID("_DrawOutlineDepth");
            dstDepthID = Shader.PropertyToID("_CustomOutlineDepthTexture");
            shaderTagIdList = new List<ShaderTagId>
            {
                new ShaderTagId("UniversalForward"),
                new ShaderTagId("UniversalForwardOnly"),
                new ShaderTagId("LightweightForward"),
                new ShaderTagId("SRPDefaultUnlit")
            };
 
            depthFilteringSettings = new FilteringSettings(RenderQueueRange.opaque, outlineMask);
        }
 
        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            cmd.GetTemporaryRT(srcDepthID, Screen.width, Screen.height, 32, FilterMode.Point, RenderTextureFormat.Depth);
            srcDepthRT = RTHandles.Alloc(new RenderTargetIdentifier(srcDepthID));
 
            RenderTextureDescriptor destDepthDescriptor = cameraTextureDescriptor;
            destDepthDescriptor.depthBufferBits = 32;
            destDepthDescriptor.colorFormat = RenderTextureFormat.RFloat;
            cmd.GetTemporaryRT(dstDepthID, destDepthDescriptor);
            dstDepthRT = RTHandles.Alloc(new RenderTargetIdentifier(dstDepthID));
 
            ConfigureTarget(srcDepthRT);
            ConfigureClear(ClearFlag.All, Camera.main.backgroundColor);
        }
 
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get();
 
            using (new ProfilingScope(cmd, new ProfilingSampler("Draw_CustomOutlineDepthTexture")))
            {
                depthDrawingSettings = CreateDrawingSettings(shaderTagIdList, ref renderingData, SortingCriteria.CommonTransparent);// renderingData.cameraData.defaultOpaqueSortFlags);
                rendererListParams = new RendererListParams(renderingData.cullResults, depthDrawingSettings, depthFilteringSettings);
                rendererList = context.CreateRendererList(ref rendererListParams);
                
                cmd.DrawRendererList(rendererList);
                cmd.Blit(srcDepthRT.nameID, dstDepthRT.nameID);
            }
            context.ExecuteCommandBuffer(cmd);
 
            cmd.Clear();
            CommandBufferPool.Release(cmd);
        }
 
        public override void OnCameraCleanup(CommandBuffer cmd)
        {
            cmd.ReleaseTemporaryRT(srcDepthID);
            cmd.ReleaseTemporaryRT(dstDepthID);
            dstDepthRT.Release();
            srcDepthRT.Release();
        }
    }
 
    // ...
    // ...
    // ScriptableRenderPass and Setting of ScriptableRendererFeature
}
cs

World DoMeowNation (2021 - 2022)
- Android - Two - 52 Weeks - Unity
I stepped out of the environment of solo development and took charge of balancing design and programming. I addressed the aspect ratio differences between mobile phones and tablets and attempted to create a resource self-atlas by developing an Atlas Maker. (Although I discovered that Unity's Sprite Atlas could handle this, I only confirmed its functionality and did not use it in the actual project.)

For external data, I carried out additional feature development using VSTO in Excel, allowing for easier translation by linking external texts with IDs and applying language changes immediately upon selecting the country.

The game was developed in event units, and saving was done at the event unit level to optimize performance and prevent lag in situations where frequent saving is required due to the nature of conquest simulations.

Using Linq, I utilized posts in the in-game social networking system as a database to search and display information.
Code Sample & Additional Video
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/// <summary>
/// When the game starts, use this method to adjust the resolution.
/// </summary>
public class CanvasResolution : MonoBehaviour
    int[] GetRatio()
    {
        int rwidth = Screen.width;
        int rheight = Screen.height;
 
        int max, min;
        if (rwidth < rheight)
        {
            max = rwidth;
            min = rheight;
        }
        else
        {
            max = rheight;
            min = rwidth;
        }
 
        int temp = 0;
        while (max % min != 0)
        {
            temp = max % min;
            max = min;
            min = temp;
        }
 
        int gcd = min;
 
        int war = rwidth / gcd;
        int har = rheight / gcd;
 
        return new int[] { war, har };
    }
}
/// <summary>
/// Searching data structures in query form using Linq.
/// </summary>
using System.Linq;
 
public class NewsEvent
{
    NewsInfo News(int type, ref int beforeNewsID)
    {
        int beforeID = beforeNewsID;
        var id = (from news in TableData.newsData
                  where news.type == type
                  && news.id != beforeID
                  && EventData.conditionEvent.CheckCondition(news)
                  select news.id).OrderBy(x => System.Guid.NewGuid()).First();
        beforeNewsID = id;
        GameData.Save(ref _data, SAVE_DATA.NEWS_EVENT_SAVE);
        return new NewsInfo(false, id);
    }
}
 
public class TableData
{
    void Awake()
    {
        LoadTables();
    }
 
    static void LoadTables()
    {
        tableDic = new Dictionary<TABLE, Table>();
        TextAsset asset = (TextAsset)Resources.Load("Data/tableName");
        TextReader reader = new StringReader(asset.text);
        string tableName = string.Empty;
 
        while ((tableName = reader.ReadLine()) != null)
        {
            //> The Enum file created in Excel is automatically created as a TABLE class.
            TABLE table = (TABLE)Enum.Parse(typeof(TABLE), tableName);
            tableDic.Add(table, LoadTable(tableName));
        }
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/// This is for the txt export button.
public partial class Ribbon_Txt
{
    /// <summary>
    /// When the button is pressed, it outputs the text in a custom parsing form.
    /// </summary>
    private void btnExport_Click(object sender, RibbonControlEventArgs e)
    {
        try
        {
            Excel.Workbook wb = Globals.ThisAddIn.GetActiveWorkbook();
 
            foreach(Excel.Worksheet ws in wb.Sheets)
            {
                Excel.Range range = ws.UsedRange;
 
                if (range.Rows.Count < 2)
                {
                    if (source.MessageBox.GetResultOK("Only column definitions exist in the rows of the sheet."))
                        return;
                }
 
                List<string> varType = new List<string>();
                List<string> varName = new List<string>();
 
                for (int c = (int)COLUMN.ID; c <= range.Columns.Count; c++)
                {
                    string colValue = ws.Cells[RowIndex: (int)ROW.COLUMN, ColumnIndex: c].value;
                    switch (colValue.First())
                    {
                        case 'n': varType.Add("int"); break;
                        case 's': varType.Add("string"); break;
                        case 'f': varType.Add("float"); break;
                    }
 
                    //> 'd' is designer's Memo
                    if (colValue.First() != 'd')
                        varName.Add(colValue.Substring(1));
                }
 
                List<List<string>> result = new List<List<string>>();
                for (int r = (int)ROW.DATA_START; r <= range.Rows.Count; r++)
                {
                    if (ws.Cells[RowIndex: r, ColumnIndex: (int)COLUMN.ID].value == null)
                    {
                        //> ID is Null
                        continue;
                    }
                    else
                    {
                        //> Not numeric
                        int rowResult = 0;
                        object rowObj = ws.Cells[RowIndex: r, ColumnIndex: (int)COLUMN.ID].value;
                        string rowValue = rowObj.ToString();
                        if (!int.TryParse(rowValue, out rowResult))
                            continue;
                    }
 
                    List<string> data = new List<string>();
                    for (int c = (int)COLUMN.ID; c <= range.Columns.Count; c++)
                    {
                        //> 'd' is designer's Memo
                        string colValue = ws.Cells[RowIndex: (int)ROW.COLUMN, ColumnIndex: c].value;
                        if (colValue.First() == 'd')
                            continue;
 
                        //> Columns value
                        if(range[RowIndex: r, ColumnIndex: c].value == null)
                        {
                            data.Add("0");
                        }
                        else
                        {
                            object obj = range[RowIndex: r, ColumnIndex: c].value;
 
                            if (obj.ToString() == "n/a")
                                data.Add("0");
                            else
                                data.Add(string.Format("{0}", obj));
                        }
                    }
 
                    result.Add(data);
                }
 
                string[] lines = new string[result.Count + 1];
                lines[0= string.Join("@#$", varName);
                for (int i = 1; i < lines.Length; i++)
                {
                    lines[i] = string.Join("@#$", result[i - 1]);
                }
 
                string path = wb.Path + @"/" + (ws.Name).ToLower() + "Data.txt";
                File.WriteAllLines(path, lines);
            }
        }
        catch (Exception ex)
        {
            source.MessageBox.GetResultOK(ex.Message);
        }
    }
}
 
/// This is for the enum export button.
public partial class Ribbon_Export
{
    private DataSet ds;
    /// <summary>
    /// When the button is pressed, an Enum file for development is created and output.
    /// </summary>
    private void btnExport_Click(object sender, RibbonControlEventArgs e)
    {       
        Excel.Workbook wb = Globals.ThisAddIn.Get_Active_Workbook();
        TableName table = new TableName();
        EnumData data = new EnumData();
        string tablePath = wb.Path + "/tableName.txt";
        string enumPath = wb.Path + "/enumData.cs";
    
        try
        {
            foreach (Excel.Worksheet ws in wb.Sheets)
            {
                string fileName = ws.Name.ToLower() + "Data";
                ds = Globals.ThisAddIn.Get_DataSet_From_Current_Sheet(ws.Name);
                
                if (File.Exists(tablePath))
                {
                    if (!table.Exist(tablePath, fileName))
                        table.Append(tablePath, fileName);
                }
                else
                    table.Create(tablePath, fileName);
 
                if (File.Exists(enumPath))
                {
                    if(!data.Check_TABLE_Refresh(enumPath, fileName))
                    {
                        data.Add_TABLE(enumPath, fileName);
                    }
 
                    if (data.Check_COLUMN_NAME_Refresh(enumPath, fileName))
                    {
                        data.Refresh_COLUMN_NAME(enumPath, fileName, ds);
                    }
                    else
                    {
                        data.Add_COLUMN_NAME(enumPath, fileName, ds);
                    }
                }
                else
                {
                    data.Create(enumPath, fileName, ds);
                }
            }
        
            MessageBox.Show("Export complete""Complete", MessageBoxButtons.OK);
        }
        catch(System.Exception ex)
        {
            MessageBox.Show("Export failure""Error", MessageBoxButtons.OK);
        }
    }
}

Archero UI Study (2020)
- Android Test Only - One - 5 Weeks - Unity
I believe that UI is the most important aspect in mobile games. PCs have keyboards and mice, while console gaming systems have gamepads. However, since mobile phones rely solely on touch screens and have small displays, UI becomes the most crucial element for players. The UI of Archers' Legend, inspired by Clash Royale, was a system I wanted to implement based on this understanding. It features smooth scrolling on the left and right screens, giving the feeling of tension and responsiveness. The UI objects' transforms were actively utilized in all aspects of the game.
Code Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
public class DragManager
{
    void Start()
    {
        _curTypeList = new List<DragContent>();
        _shopPage    = new DragContentShop();
        _equipPage   = new DragContentEquip();
        _mainPage    = new DragContentMain();
        _skillPage   = new DragContentSkill();
        _questPage   = new DragContentQuest();
 
        //> Default Page
        _curTypeList.Add(_mainPage);
    }
 
    void Update()
    {
        //> Behavior that must always be maintained is the responsibility of DoUpdate.
        _curDragContents.ForEach(type => type.DoUpdate());
 
        if (_dragging)
        {
            _power = GetDragPower();
        }
        else
        {
            //> It should return to its original position when not in the dragged state.
            _curDragContents.ForEach(type => type.ReturnToPosition(ref _power, _smoothness, _pageNumber));
        }
    }
 
    Vector2 GetDragPower()
    {
        _destPos = _curPos - GetInputPosition_Vector2();
        _curPos = GetInputPosition();
        Vector2 pow = new Vector2();
 
        if (pow.x <= -_powMax.x)
            pow.x = -_powMax.x;
        else if (pow.x >= _powMax.x)
            pow.x = _powMax.x;
        else
            pow.x = _destPos.x;
 
        if (pow.y <= -_powMax.y)
            pow.y = -_powMax.y;
        else if (pow.y >= _powMax.y)
            pow.y = _powMax.y;
        else
            pow.y = _destPos.y;
 
        return pow;
    }
 
    public void ReachedToPage(int pageNumber)
    {
        _curDragContents.Clear();
 
        switch(pageNumber)
        {
            case 0:
                _curDragContents.Add(_skillPage);
                break;
            case 1:
                _curDragContents.Add(_trainingPage);
                break;
            case 2:
                _curDragContents.Add(_snsPage);
                break;
            case 3:
                _curDragContents.Add(_newsPage);
                break;
            case 4:
                _curDragContents.Add(_shopPage);
                break;
        }
 
        _curTypeList.ForEach(type => type.Enter());
    }
}
/// <summary>
/// All of DragContent classes implement by inheriting from this base class.
/// </summary>
public class DragContent
{
    protected CanvasWindow   windowUI;
    protected Vector2        dragMin;
    protected Vector2        dragMax;
    protected Vector2        lerp;
    protected float          lerpSpeed;
    protected float          moveSpeed;
    protected float          distancePage;
 
    protected Vector2        t_anchoredPositionForX;
    protected Vector2        t_anchoredPositionForY;
 
    private bool             _destination;
 
    protected void InitializeBase()
    {
        windowUI = CanvasWindow.Instance;
        distancePage = windowUI.distancePage;
    }
 
    public void Speed(float moveSpeed) => this.moveSpeed = moveSpeed;
    public virtual void Enter() { _destination = false; }
    public virtual void DoUpdate() { }
    public virtual void Clamp() { }
    public virtual void ReturnToPosition(ref Vector2 power, Vector2 smoothness) { }
    public virtual void ReturnToTopPosition() { }
    public virtual void ReturnToBottomPosition() {}
    public virtual void Move(Vector2 destPos) { }
    public virtual void BeginDrag() {}
    public virtual void EndDrag() { }
}

HEX SRPG (2020)
- PC Test Only - One - 9 Weeks - Unity
This project was developed to create an SRPG and implement its systems. I used HexTile to create the map and implemented A* algorithm for pathfinding. For unit and enemy movement, I created an advanced FSM using generalization and inheritance. To move away from excessive transitions in Unity animations, I created a Model class controlled by scripts. The GameManager observed units and managed phases and turns.
Code Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
public class MapManager : WeakSingletonMonoBehavior<MapManager>
{
    List<Path> _openList = new List<Path>();
    List<Path> _closedList new List<Path>();
    /// <summary>
    /// Created in such a way that the user requests the path to the manager.
    /// </summary>
    public List<Hex> Get_Path(Hex start, Hex dest)
    {
        if (start.Equals(dest))
            return null;
 
        _openList.Clear();
        _closedList.Clear();
        List<Hex> rtnHex = new List<Hex>();
 
        int h = Get_Distance(start, dest);
        Path p = new Path(null, start, 0, h);
 
        _closedList.Add(p);
 
        Path result = Recursive_Find_Path(p, dest);
        if (result == null)
            return null;
 
        while(result.parent != null)
        {
            rtnHex.Insert(0, result.curHex);
            result = result.parent;
        }
 
        return rtnHex;
    }
 
    int Get_Distance(Hex h1, Hex h2)
    {
        Point pnt1 = h1.mapPnt;
        Point pnt2 = h2.mapPnt;
        return (Mathf.Abs(pnt1.x - pnt2.x+ Mathf.Abs(pnt1.y - pnt2.y+ Mathf.Abs(pnt1.z - pnt2.z)) / 2;
    }
 
    Path Recursive_Find_Path(Path path, Hex dest)
    {
 
        if (path.curHex.mapPnt == dest.mapPnt)
        {
            return path; 
        }
 
        List<Hex> neighbors = Get_Neighbors(dest, path.curHex);
        foreach (Hex h in neighbors)
        {
            Path newP = new Path(path, h , path.G + 1, Get_Distance(h, dest));
            Add_To_OpenList(newP);
        }
 
        Path bestP = _openList[0];
        if (_openList.Count > 1)
        {   
            foreach (Path p in _openList)
            {
                if (p.F < bestP.F)
                {
                    bestP = p;
                }
            }
            _openList.Remove(bestP);
            _closedList.Add(bestP);
        }
 
        if (_openList.Count == 0)
        {
            return null;
        }
 
        return Recursive_Find_Path(bestP, dest);
    }
 
    void Add_To_OpenList(Path p)
    {
        foreach(Path inP in _closedList)
        {
            if(p.curHex.mapPnt == inP.curHex.mapPnt)
            {
                return;
            }
        }
 
        foreach(Path inP in _openList)
        {
            if(p.curHex.mapPnt == inP.curHex.mapPnt)
            {
                if (p.F < inP.F)
                {
                    _openList.Remove(inP);
                    _openList.Add(p);
                    return;
                }
                else
                    return;
            }
        }
        _openList.Add(p);
    }
    
    List<Hex> Get_Neighbors(Hex dest, Hex curPos)
    {
        List<Hex> rtn = new List<Hex>();
        Point cur = curPos.mapPnt;
        foreach(Point p in _dirs)
        {
            Point tap = p + cur;
            if(tap.x + tap.y + tap.z == 0)
            {
                Hex neighborHex = Get_Hex(tap.x, tap.y, tap.z);
                
                if (neighborHex.Equals(dest))
                {
                    rtn.Add(neighborHex);
                    return rtn;
                }
                else
                {
                    if (!neighborHex.existUnit && neighborHex.walkable)
                        rtn.Add(neighborHex);
                }
            }
        }
        return rtn;
    }
 
    Hex Get_Hex(int xint yint z)
    {
        return _map[x + mapSizeX][y + mapSizeY][z + mapSizeZ];
    }
}
 
//> Can use transitionless animation by attaching the Model component to the inspector.
[RequireComponent(typeof(Animator))]
public class Model : MonoBehaviour
{
    private Animator _animator;
    private int      _stateHash;
    private bool     _loop;
 
    void Start()
    {
        _animator = GetComponent<Animator>();
    }
 
    void Update()
    {
        if (!_loop)
            return;
 
        if (End(_stateHash))
            _animator.Play(_stateHash, 00);
    }
 
    /// <summary>
    /// Provides various methods related to animation play.
    /// </summary>
    public void Play(int stateHash, bool loop, float speed = 1f)
    {   
        _animator.Play(stateHash, 00);
        _animator.speed = speed;
        _stateHash = stateHash;
        _loop = loop;
    }
 
    public void CrossFade(int stateHash, bool loop, float normalizedTransitionDuration, float speed = 1f)
    {
        _animator.CrossFade(stateHash, normalizedTransitionDuration);
        _animator.speed = speed;
        _stateHash = stateHash;
        _loop = loop;
    }
 
    public bool IsPlaying(int statehash)
    {
        if (Equal(statehash) && _animator.GetCurrentAnimatorStateInfo(0).normalizedTime < 0.99f)
            return true;
 
        return false;
    }
 
    public bool Equal(int stateHash)
    {
        if(_animator.GetCurrentAnimatorStateInfo(0).fullPathHash == stateHash)
            return true;
 
        return false;
    }
 
    public int GetCurrentStateHash()
    {
        return _animator.GetCurrentAnimatorStateInfo(0).fullPathHash;
    }
    
    public bool GetBetweenTime(int stateHash, int index, float begin = 0, float end = 0.9f)
    {
        if (!Equal(stateHash))
            return false;
 
        if (_animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= begin &&
            _animator.GetCurrentAnimatorStateInfo(0).normalizedTime <= end)
        {
            return true;
        }
 
        return false;
    }
 
    public bool End(int stateHash)
    {
        if (!Equal(stateHash))
            return false;
 
        return End();
    }
 
    public bool End()
    {
        if (_animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 0.99f)
            return true;
 
        return false;
    }
}
 
//> The abstraction supports 3 states per FSM State.
public abstract class FSM_State<T>
{
    public abstract void EnterState(T fsm);
    public abstract void UpdateState(T fsm);
    public abstract void ExitState(T fsm);
}
 
//> Implement the states of Enter, Update, and Exit through this StateMachine.
public class StateMachine<T>
{
    private T owner;
    private FSM_State<T> previousState;
    private FSM_State<T> currentState;
 
    public void InitState(T owner, FSM_State<T> state)
    {
        this.owner = owner;
        ChangeState(state);
    }
 
    public void ChangeState(FSM_State<T> state)
    {
        if (state == currentState)
            return;
 
        previousState = currentState;
 
        if (currentState != null)
            currentState.ExitState(owner);
 
        currentState = state;
 
        if (currentState != null)
            currentState.EnterState(owner);
    }
 
    public void DoUpdate()
    {
        currentState.UpdateState(owner);
    }
 
    public void RevertState()
    {
        if (previousState != null)
            ChangeState(previousState);
    }
 
    public bool CheckCurrentState(FSM_State<T> state)
    {
        return (currentState == state);
    }
}
 

Plus Minus (2019)
- Android - One - 10 Weeks - Unity
This game was created with the aim of making a puzzle game that is simple in rules but requires continuous thinking. The goal is to create the target number by adding same-colored tiles and subtracting different-colored tiles, according to the quest provided. The game is designed with a stage system, and the main focus is on generating revenue through advertisements.
▶ Code Link to github

AOS Clicker (2018)
- Android Test Only - One - 4 Weeks - Unity
This game was created for small group lectures. At the time, mobile clicker games were trending, so I chose the clicker theme to engage the participants' interest. I used object pooling algorithms to increase reusability and used StringBuilder to reduce string garbage memory and optimize performance. For the number calculation algorithm, I used Int Array and adopted the operator format for convenience.
Code Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/// <summary>
/// Stack-based pooling, providing both Pop() and Push() using an interface.
/// </summary>
public interface IPool
{
    delegatePush Push { get; set; }
    void SetInfo();
}
 
public class GameObjectPool<T> where T : IPool
{
    private int _account;
    private GameObject _prefab;
    private Transform _poolTarget;
    private Stack<GameObject> _pool;
 
    public GameObjectPool(int account, GameObject prefab, Transform poolTarget)
    {
        _account = account;
        _prefab = prefab;
        _poolTarget = poolTarget;
        _pool = new Stack<GameObject>();
 
        Allocate(_account);
    }
 
    void Allocate(int count)
    {
        for(int i = 0; i < count; i++)
        {
            GameObject go = UnityEngine.MonoBehaviour.Instantiate(_prefab) as GameObject;            
            go.transform.SetParent(_poolTarget);
            go.transform.localPosition = Vector3.zero;            
            go.GetComponent<T>().Push += Push;
            go.GetComponent<T>().SetInfo();
            go.SetActive(false);
 
            _pool.Push(go);
        }
    }
 
    public T Pop()
    {
        if(_pool.Count < 1)
            Allocate(_account);
 
        GameObject go = _pool.Pop();
        go.SetActive(true);
        return go.GetComponent<T>();
    }
 
    public void Push(GameObject go)
    {
        go.SetActive(false);
        _pool.Push(go);
    }
}
/// <summary>
/// Create a Util class and use it throughout the game.
/// </summary>
public static class Util
{
    /// <summary>
    /// Optimize string memory by declaring a StringBuilder ahead of time.
    /// </summary>
    private static StringBuilder sb = new StringBuilder();
 
    public static string Append(params object[] data)
    {
        for(int i = 0; i < data.Length; i++)
        {
            sb.Append(data[i]);
        }
 
        return sb.ToString();
    }
}
/// <summary>
/// Implementation of int array in operator format to respond to infinitely high numbers.
/// </summary>
[System.Serializable]
public struct IntArray
{
    public IntArray(IntArray arr)
    {
        number = arr.number;
    }
 
    public int[] number;
 
    public static IntArray operator +(IntArray a, IntArray b)
    {
        int length = a.number.Length > b.number.Length ? a.number.Length : b.number.Length;
 
        for (int i = 0; i < length; i++)
                {
            a.number[i] += b.number[i];
 
            if (a.number[i] < 0)
            {
                //> over int.maxValue
                a.number[i] *= -1;
                a.number[i] = int.MaxValue - 2000000000;
                a.number[i + 1+= 2;
            }
            else if (a.number[i] > 1999999999)
            {
                //> minus 2,000,000,000
                a.number[i] -= 2000000000;
                a.number[i + 1+= 2;
            }
            else if (a.number[i] > 999999999)
            {
                //> minus 1,000,000,000
                a.number[i] -= 1000000000;
                a.number[i + 1]++;
            }
        }
 
        return new IntArray(a);
    }
 
    public static IntArray operator -(IntArray a, IntArray b)
    {
        int length = a.number.Length > b.number.Length ? a.number.Length : b.number.Length;
 
        for (int i = (length - 1); i >= 0; i--)
        {
            a.number[i] -= b.number[i];
 
            if (a.number[i] < 0)
            {
                a.number[i + 1]--;
                a.number[i] += 1000000000;
            }
        }
 
        return new IntArray(a);
    }
}

Age of Survival (2017)
- Android - One - 16 Weeks - Unity
The concept of the game is a top-view shooting adventure where the protagonist survives in an apocalypse. To create stages, I developed a MapTool editor and added FileIO to import external design data, laying the foundation for game development. Although the custom map tool may lack optimization and algorithms compared to the built-in grid system in the Unity engine, creating the map tool provided a great opportunity to learn and implement the functionalities of the grid system editor class. Additionally, this map tool pixelates the final images into two layers: the floor layer and the ceiling layer, for performance and file size optimization.
Code Sample & Additional Video

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public class MapManagerMake : Editor
{
    private static string     _mapToolPath    = "/Resources/InGame/Atlases/";
    private static Vector2Int _backgroundSize = new Vector2Int(400240);
    private static Vector2Int _tileSize       = new Vector2Int(4040);
    /// <summary>
    /// When clicked "Make Backgounr png" button, all of resources convert to png with combine.
    /// </summary>
    public static void MakeAtlas_To_Background(MapManager mapManager)
    { 
        //> Make Atlas
        string resolution = mapManager.resolutionOption[mapManager.resolutionIndex];
        string[] size = resolution.Split('x');
        int width = int.Parse(size[0]);
        int height = int.Parse(size[1]);
 
        Texture2D atlas = new Texture2D(widthheight, TextureFormat.RGBA32, false);
        atlas = SetAtlas(ref atlas, ref mapManager.backgroundBlock,   _backgroundSize, false);
        atlas = SetAtlas(ref atlas, ref mapManager.backgroundTile,    _tileSize, false);
        atlas = SetAtlas(ref atlas, ref mapManager.buildingGround,    _tileSize, false);
        atlas = SetAtlas(ref atlas, ref mapManager.buildingCollision, _tileSize, false);
 
        //> Save Path
        if(Directory.Exists(Application.dataPath + _mapToolPath) == false)
        {
            Directory.CreateDirectory(Application.dataPath + _mapToolPath);
        }
 
        string fileName = GameObject.FindWithTag("Player").name;
 
        if(Directory.Exists(Application.dataPath + _mapToolPath + fileName) == false)
        {
            Directory.CreateDirectory(Application.dataPath + _mapToolPath + fileName);
        }
 
        //> Split texture each size.
        int lengthX = width / 400;
        int lengthY = height / 240;
 
        for(int i = 0; i < lengthX; i++)
        {
            for(int j = 0; j < lengthY; j++)
            {
                Texture2D tex = new Texture2D(400240, TextureFormat.RGBA32, false);
 
                for(int x = 0x < tex.widthx++)
                {
                    for(int y = 0y < tex.heighty++)
                    {
                        tex.SetPixel(xy, atlas.GetPixel(x + (i * 400),y + (j * 240)));
                    }
                }
 
                tex.filterMode = FilterMode.Point;
                tex.name = "BACKGROUND_BLOCK_" + j + "_" + i;
                FileStream fs = new FileStream(Application.dataPath + _mapToolPath + fileName + "/" + tex.name + ".png", FileMode.Create);
                BinaryWriter bw = new BinaryWriter(fs);
                bw.Write (tex.EncodeToPNG());
                bw.Close();
                fs.Close();
 
                AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
            }
        }
    }    
    /// <summary>
    /// The pixel information of the tile is created as a new texture and passed on.
    /// </summary>
    static Texture2D SetAtlas(ref Texture2D atlas, ref GameObject[][] tile, int tileWidth, int tileHeight, bool drawDefaultSprite)
    {
        //> Texture importer setting for new texture.
        for(int i = 0; i < tile.Length; i++)
        {
            for(int j = 0; j < tile[i].Length; j++)
            {
                string texPath = AssetDatabase.GetAssetPath(tile[i][j].GetComponent<SpriteRenderer>().sprite);
                TextureImporter importer = AssetImporter.GetAtPath(texPath) as TextureImporter;
                importer.textureType = TextureImporterType.Sprite;
                importer.isReadable = true;
                AssetDatabase.ImportAsset(texPath);
            }
        }
 
        //> Make sprite array for new texture.
        int width = tile.Length, height = tile[0].Length;
        Sprite[][] spriteArray = new Sprite[width][];
        for(int i = 0; i < spriteArray.Length; i++)
            spriteArray[i] = new Sprite[height];
        for (int i = 0; i < width; i++)
            for(int j = 0; j < height; j++)
                spriteArray[i][j] = tile[i][j].GetComponent<SpriteRenderer>().sprite;
 
        //> Draw pixel to the atlas.
        int textureWidthCounter = 0;
        int textureHeightCouter = 0;
        for(int i = 0; i < spriteArray.Length; i++)
        {
            for(int j = 0; j < spriteArray[i].Length; j++)
            {
                if(drawDefaultSprite == false)
                {            
                    if(spriteArray[i][j].name.Contains("Default"== true)
                    {
                        textureHeightCouter += tileHeight;
                        continue;
                    }
                }
                for(int y = 0 ; y < tileHeight; y++)
                {
                    for(int x = 0 ; x < tileWidth; x++)
                    {
                        //> Ceiling is able to have alpha value.
                        if(drawDefaultSprite == true)
                        {
                            atlas.SetPixel(x + textureWidthCounter, y + textureHeightCouter, spriteArray[i][j].texture.GetPixel(x,y));
                        }
                        //> Others aren't able to have alpha value.
                        else
                        {
                            Color color = spriteArray[i][j].texture.GetPixel(xy);
                            if (color.a <= 0.99f)
                                continue;
                            atlas.SetPixel(x + textureWidthCounter, y + textureHeightCouter, spriteArray[i][j].texture.GetPixel(xy));
                        }
                    }
                }
                atlas.Apply();
                textureHeightCouter += tileHeight;
            }
            textureWidthCounter += tileWidth;
        }
        return atlas;
    }
}

Arkanoid Hero (2016)
- Android - One - 7 Weeks - Unity
Arkanoid Hero is a game created while learning C# for the first time at Unity School. It combines the famous Arkanoid game with the concept of a hero who defeats block monsters descending to conquer the kingdom under the king's command. We added a finishing move that allows the hero to use unique skills when the gauge is filled, and enemies continuously descend at regular intervals to create tension in the game. It allowed us to become familiar with basic design patterns such as Singleton and Coroutine, C# methods, and Unity.
Code Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/// <summary>
/// Creates a singleton and destroys duplicate singletons to keep only one.
/// </summary>
public class TitleManager : MonoBehaviour
{
    static TitleManager _instance = null;
 
    public static TitleManager Instance()
    {
        return _instance;
    }
 
    void Start()
    {
        if(_instance == null)
        {
            _instance = this;
        }
        else
        {
            Destroy(this.gameObject);
        }
    }
}
/// <summary>
/// Use advertising plug-ins and compensation processing using callback methods.
/// </summary>
public class AdManager : MonoBehaviour
{
    //> This method attached to the Yes button of UI
    public void ClickYesButton()
    {
        _presentUI.SetActive(false);
 
        if(Advertisement.isReady() == true)
        {
            //> Show with default zone, pause engine and print result to debug log
            Advertisement.Show(nullnew ShowOptions
            {
                pause = true,
                resultCallback = result =>
                {
                    StartCoroutine(GetAdsGold(_rewardGoldInt));
                }
            });
        }
    }
}
/// <summary>
/// Implement a process that uses effects and actual skills using coroutines.
/// </summary>
public class SkillManager : MonoBehaviour
{
    IEnumerator SkillEffect()
    {
        GameManager.Instance().ChangeState(GAMESTATE.SKILL);
        AudioManager.Instance().PlaySFX(_killerMoveClip);
 
        _animatorSkillEffectBG.Play("SkillEffect_BG");
 
        yield return new WaitForSeconds(0.2f);
 
        _animatorSkillEffect.Play("SkillEffect");
        float anilength = _animationArray["SkillEffect"].length;
 
        yield return new WaitForSeconds(anilength + 0.3f);
 
        _animatorSkillEffect.Play("SkillEffect_Patching");
        anilength = _animationArray["SkillEffect_Patching"].length;
 
        yield return new WaitForSeconds(anilength + 0.3f);
 
        Set_All_SkillEffect(false);
 
        GameManager.Instance().ChangeState(GAMESTATE.PLAY);
        StartCoroutine(SkillProcess());
    }
}