반응형

스토어에 deprecated되어있어 개인적으로 사용하기 위해 블로깅해둠.

Shader "UI/Blur/UIBlurHQ" {
  Properties {
    _TintColor ("Tint Color", Color) = (1, 1, 1, 0.2)
    _Size ("Spacing", Range(0, 40)) = 5.0
    _Vibrancy ("Vibrancy", Range(0, 2)) = 0.2
    _MainTex ("Texture", 2D) = "white" {}

    [HideInInspector]
    _StencilComp ("Stencil Comparison", Float) = 8
    [HideInInspector]
    _Stencil ("Stencil ID", Float) = 0
    [HideInInspector]
    _StencilOp ("Stencil Operation", Float) = 0
    [HideInInspector]
    _StencilWriteMask ("Stencil Write Mask", Float) = 255
    [HideInInspector]
    _StencilReadMask ("Stencil Read Mask", Float) = 255
    [HideInInspector]
    _ColorMask ("Color Mask", Color) = (1, 1, 1, 0.2)

  }
 
  Category {
    // We must be transparent, so other objects are drawn before this one.
    Tags { 
      "Queue"="Transparent"
      "IgnoreProjector"="True" 
      "RenderType"="Opaque" 
    }

    Stencil {
      Ref [_Stencil]
      Comp [_StencilComp]
      Pass [_StencilOp] 
      ReadMask [_StencilReadMask]
      WriteMask [_StencilWriteMask]
    }

    SubShader {

      GrabPass {
        // "_GrabTexture" // Uncomment to speed-up, see documentation.
        Tags { "LightMode" = "Always" }
      }
      
      // Vertical blur
      Pass {
        Name "VERTICAL"
        Tags { "LightMode" = "Always" }

        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma fragmentoption ARB_precision_hint_fastest
        #include "UnityCG.cginc"
         
        struct appdata_t {
          float4 vertex : POSITION;
          float2 texcoord: TEXCOORD0;
        };
         
        struct v2f {
          float4 vertex : POSITION;
          float4 uvgrab : TEXCOORD0;
          float2 uv : TEXCOORD1;
        };

        float4 _MainTex_ST;
         
        v2f vert (appdata_t v) {
          v2f o;
          o.vertex = UnityObjectToClipPos(v.vertex);
          #if UNITY_UV_STARTS_AT_TOP
          float scale = -1.0;
          #else
          float scale = 1.0;
          #endif
          o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
          o.uvgrab.zw = o.vertex.zw;
          o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
          return o;
        }
         
        sampler2D _GrabTexture;
        float4 _GrabTexture_TexelSize;
        float _Size;
        sampler2D _MainTex;
         
        half4 frag( v2f i ) : COLOR {
          half4 sum = half4(0,0,0,0);
 
          #define GRABPIXEL(weight,kernely) tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x, i.uvgrab.y + _GrabTexture_TexelSize.y * kernely * _Size * 1.61, i.uvgrab.z, i.uvgrab.w))) * weight
 
          sum += GRABPIXEL(0.05, -4.0);
          sum += GRABPIXEL(0.09, -3.0);
          sum += GRABPIXEL(0.12, -2.0);
          sum += GRABPIXEL(0.15, -1.0);
          sum += GRABPIXEL(0.18,  0.0);
          sum += GRABPIXEL(0.15, +1.0);
          sum += GRABPIXEL(0.12, +2.0);
          sum += GRABPIXEL(0.09, +3.0);
          sum += GRABPIXEL(0.05, +4.0);

          half4 texcol = tex2D(_MainTex, i.uv);
          sum.a = texcol.a;
          return sum;
        }
        
        ENDCG
      }

      GrabPass {
        Tags { "LightMode" = "Always" }
      }

      // Horizontal blur
      Pass {
        Name "HORIZONTAL"
        Tags { "LightMode" = "Always" }

        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma fragmentoption ARB_precision_hint_fastest
        #include "UnityCG.cginc"

         
        struct appdata_t {
          float4 vertex : POSITION;
          float2 texcoord: TEXCOORD0;
        };
         
        struct v2f {
          float4 vertex : POSITION;
          float4 uvgrab : TEXCOORD0;
          float2 uv : TEXCOORD1;
        };

        float4 _MainTex_ST;
         
        v2f vert (appdata_t v) {
          v2f o;
          o.vertex = UnityObjectToClipPos(v.vertex);
          #if UNITY_UV_STARTS_AT_TOP
          float scale = -1.0;
          #else
          float scale = 1.0;
          #endif
          o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
          o.uvgrab.zw = o.vertex.zw;
          o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
          return o;
        }
         
        sampler2D _GrabTexture;
        float4 _GrabTexture_TexelSize;
        float _Size;
        sampler2D _MainTex;
         
        half4 frag( v2f i ) : COLOR {
          half4 sum = half4(0,0,0,0);
 
          #define GRABPIXEL(weight,kernelx) tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x + _GrabTexture_TexelSize.x * kernelx * _Size * 1.61, i.uvgrab.y, i.uvgrab.z, i.uvgrab.w))) * weight
 
          sum += GRABPIXEL(0.05, -4.0);
          sum += GRABPIXEL(0.09, -3.0);
          sum += GRABPIXEL(0.12, -2.0);
          sum += GRABPIXEL(0.15, -1.0);
          sum += GRABPIXEL(0.18,  0.0);
          sum += GRABPIXEL(0.15, +1.0);
          sum += GRABPIXEL(0.12, +2.0);
          sum += GRABPIXEL(0.09, +3.0);
          sum += GRABPIXEL(0.05, +4.0);

          half4 texcol = tex2D(_MainTex, i.uv);
          sum.a = texcol.a;
          return sum;
        }
        
        ENDCG
      }
        
      GrabPass {
        Tags { "LightMode" = "Always" }
      }
      
      // Vertical blur
      Pass {
        Name "VERTICAL"
        Tags { "LightMode" = "Always" }

        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma fragmentoption ARB_precision_hint_fastest
        #include "UnityCG.cginc"
         
        struct appdata_t {
          float4 vertex : POSITION;
          float2 texcoord: TEXCOORD0;
        };
         
        struct v2f {
          float4 vertex : POSITION;
          float4 uvgrab : TEXCOORD0;
          float2 uv : TEXCOORD1;
        };

        float4 _MainTex_ST;
         
        v2f vert (appdata_t v) {
          v2f o;
          o.vertex = UnityObjectToClipPos(v.vertex);
          #if UNITY_UV_STARTS_AT_TOP
          float scale = -1.0;
          #else
          float scale = 1.0;
          #endif
          o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
          o.uvgrab.zw = o.vertex.zw;
          o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
          return o;
        }
         
        sampler2D _GrabTexture;
        float4 _GrabTexture_TexelSize;
        float _Size;
        sampler2D _MainTex;
         
        half4 frag( v2f i ) : COLOR {
          half4 sum = half4(0,0,0,0);
 
          #define GRABPIXEL(weight,kernely) tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x, i.uvgrab.y + _GrabTexture_TexelSize.y * kernely * _Size, i.uvgrab.z, i.uvgrab.w))) * weight
 
          sum += GRABPIXEL(0.05, -4.0);
          sum += GRABPIXEL(0.09, -3.0);
          sum += GRABPIXEL(0.12, -2.0);
          sum += GRABPIXEL(0.15, -1.0);
          sum += GRABPIXEL(0.18,  0.0);
          sum += GRABPIXEL(0.15, +1.0);
          sum += GRABPIXEL(0.12, +2.0);
          sum += GRABPIXEL(0.09, +3.0);
          sum += GRABPIXEL(0.05, +4.0);

          half4 texcol = tex2D(_MainTex, i.uv);
          sum.a = texcol.a;
          return sum;
        }
        
        ENDCG
      }

      GrabPass {             
        Tags { "LightMode" = "Always" }
      }

      // Horizontal blur
      Pass {
        Name "HORIZONTAL"
        Tags { "LightMode" = "Always" }

        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma fragmentoption ARB_precision_hint_fastest
        #include "UnityCG.cginc"
         
        struct appdata_t {
          float4 vertex : POSITION;
          float2 texcoord: TEXCOORD0;
        };
         
        struct v2f {
          float4 vertex : POSITION;
          float4 uvgrab : TEXCOORD0;
          float2 uv : TEXCOORD1;
        };

        float4 _MainTex_ST;
         
        v2f vert (appdata_t v) {
          v2f o;
          o.vertex = UnityObjectToClipPos(v.vertex);
          #if UNITY_UV_STARTS_AT_TOP
          float scale = -1.0;
          #else
          float scale = 1.0;
          #endif
          o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
          o.uvgrab.zw = o.vertex.zw;
          o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
          return o;
        }
         
        sampler2D _GrabTexture;
        float4 _GrabTexture_TexelSize;
        float _Size;
        sampler2D _MainTex;
         
        half4 frag( v2f i ) : COLOR {
          half4 sum = half4(0,0,0,0);
 
          #define GRABPIXEL(weight,kernelx) tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x + _GrabTexture_TexelSize.x * kernelx * _Size, i.uvgrab.y, i.uvgrab.z, i.uvgrab.w))) * weight
 
          sum += GRABPIXEL(0.05, -4.0);
          sum += GRABPIXEL(0.09, -3.0);
          sum += GRABPIXEL(0.12, -2.0);
          sum += GRABPIXEL(0.15, -1.0);
          sum += GRABPIXEL(0.18,  0.0);
          sum += GRABPIXEL(0.15, +1.0);
          sum += GRABPIXEL(0.12, +2.0);
          sum += GRABPIXEL(0.09, +3.0);
          sum += GRABPIXEL(0.05, +4.0);

          half4 texcol = tex2D(_MainTex, i.uv);
          sum.a = texcol.a;
          return sum;
        }
        
        ENDCG
      }
      GrabPass {
        Tags { "LightMode" = "Always" }
      }
      
      // Vertical blur
      Pass {
        Name "VERTICAL"
        Tags { "LightMode" = "Always" }

        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma fragmentoption ARB_precision_hint_fastest
        #include "UnityCG.cginc"
         
        struct appdata_t {
          float4 vertex : POSITION;
          float2 texcoord: TEXCOORD0;
        };
         
        struct v2f {
          float4 vertex : POSITION;
          float4 uvgrab : TEXCOORD0;
          float2 uv : TEXCOORD1;
        };

        float4 _MainTex_ST;
         
        v2f vert (appdata_t v) {
          v2f o;
          o.vertex = UnityObjectToClipPos(v.vertex);
          #if UNITY_UV_STARTS_AT_TOP
          float scale = -1.0;
          #else
          float scale = 1.0;
          #endif
          o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
          o.uvgrab.zw = o.vertex.zw;
          o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
          return o;
        }
         
        sampler2D _GrabTexture;
        float4 _GrabTexture_TexelSize;
        float _Size;
        sampler2D _MainTex;
         
        half4 frag( v2f i ) : COLOR {
          half4 sum = half4(0,0,0,0);
 
          #define GRABPIXEL(weight,kernely) tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x, i.uvgrab.y + _GrabTexture_TexelSize.y * kernely * _Size * 0.2, i.uvgrab.z, i.uvgrab.w))) * weight
 
          sum += GRABPIXEL(0.05, -4.0);
          sum += GRABPIXEL(0.09, -3.0);
          sum += GRABPIXEL(0.12, -2.0);
          sum += GRABPIXEL(0.15, -1.0);
          sum += GRABPIXEL(0.18,  0.0);
          sum += GRABPIXEL(0.15, +1.0);
          sum += GRABPIXEL(0.12, +2.0);
          sum += GRABPIXEL(0.09, +3.0);
          sum += GRABPIXEL(0.05, +4.0);

          half4 texcol = tex2D(_MainTex, i.uv);
          sum.a = texcol.a;
          return sum;
        }
        
        ENDCG
      }

      GrabPass {             
        Tags { "LightMode" = "Always" }
      }

      // Horizontal blur
      Pass {
        Name "HORIZONTAL"
        Tags { "LightMode" = "Always" }

        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma fragmentoption ARB_precision_hint_fastest
        #include "UnityCG.cginc"
         
        struct appdata_t {
          float4 vertex : POSITION;
          float2 texcoord: TEXCOORD0;
        };
         
        struct v2f {
          float4 vertex : POSITION;
          float4 uvgrab : TEXCOORD0;
          float2 uv : TEXCOORD1;
        };

        float4 _MainTex_ST;
         
        v2f vert (appdata_t v) {
          v2f o;
          o.vertex = UnityObjectToClipPos(v.vertex);
          #if UNITY_UV_STARTS_AT_TOP
          float scale = -1.0;
          #else
          float scale = 1.0;
          #endif
          o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
          o.uvgrab.zw = o.vertex.zw;
          o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
          return o;
        }
         
        sampler2D _GrabTexture;
        float4 _GrabTexture_TexelSize;
        float _Size;
        sampler2D _MainTex;
         
        half4 frag( v2f i ) : COLOR {
          half4 sum = half4(0,0,0,0);
 
          #define GRABPIXEL(weight,kernelx) tex2Dproj(_GrabTexture, UNITY_PROJ_COORD(float4(i.uvgrab.x + _GrabTexture_TexelSize.x * kernelx * _Size * 0.2, i.uvgrab.y, i.uvgrab.z, i.uvgrab.w))) * weight
 
          sum += GRABPIXEL(0.05, -4.0);
          sum += GRABPIXEL(0.09, -3.0);
          sum += GRABPIXEL(0.12, -2.0);
          sum += GRABPIXEL(0.15, -1.0);
          sum += GRABPIXEL(0.18,  0.0);
          sum += GRABPIXEL(0.15, +1.0);
          sum += GRABPIXEL(0.12, +2.0);
          sum += GRABPIXEL(0.09, +3.0);
          sum += GRABPIXEL(0.05, +4.0);

          half4 texcol = tex2D(_MainTex, i.uv);
          sum.a = texcol.a;
           
          return sum;
        }
        
        ENDCG
      }
 
      // Distortion
      GrabPass {             
        Tags { "LightMode" = "Always" }
      }
      
      Pass {
        Tags { "LightMode" = "Always" }

        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
        // Upgrade NOTE: excluded shader from DX11 and Xbox360; has structs without semantics (struct v2f members uv)
        // #pragma exclude_renderers d3d11 xbox360
        #pragma vertex vert
        #pragma fragment frag
        #pragma fragmentoption ARB_precision_hint_fastest
        #include "UnityCG.cginc"

        struct appdata_t {
          float4 vertex : POSITION;
          float2 texcoord: TEXCOORD0;
        };
         
        struct v2f {
          float4 vertex : POSITION;
          float4 uvgrab : TEXCOORD0;
          float2 uv : TEXCOORD1;
        };
         
        float4 _MainTex_ST;

        v2f vert (appdata_t v) {
          v2f o;
          o.vertex = UnityObjectToClipPos(v.vertex);
          #if UNITY_UV_STARTS_AT_TOP
          float scale = -1.0;
          #else
          float scale = 1.0;
          #endif
          o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
          o.uvgrab.zw = o.vertex.zw;
          o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
          return o;
        }
         
        half4 _TintColor;
        float _Vibrancy;
        sampler2D _GrabTexture;
        float4 _GrabTexture_TexelSize;
        sampler2D _MainTex;
        
        half4 frag( v2f i ) : COLOR {
          half4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
          half4 texcol = tex2D(_MainTex, i.uv);
          col.r *= texcol.r;
          col.g *= texcol.g;
          col.b *= texcol.b;
          col.rgb *= 1 + _Vibrancy;
          col.a = texcol.a;
          col = lerp (col, _TintColor, _TintColor.w);
          return col;
        }
        
        ENDCG
      }
    }
  }
}

Material생성 후 해당 Shader적용 후 UI Image에 Material 적용.

반응형

'unity C#' 카테고리의 다른 글

[Unity] 화면보호기 끄기  (0) 2023.01.30
[Unity] 중첩 코루틴 Coroutine  (0) 2023.01.30
[Unity] VS Code Settings Sync  (0) 2022.12.26
[Unity] 암호화 복호화  (0) 2022.11.29
[Unity] mov파일 만들어 webm파일 사용하기  (0) 2022.02.04
반응형

https://github.com/eclipse/paho.mqtt.m2mqtt

 

GitHub - eclipse/paho.mqtt.m2mqtt

Contribute to eclipse/paho.mqtt.m2mqtt development by creating an account on GitHub.

github.com

 

요거 가져다 사용하면 문제 없이 잘 된다.

단 주의할것들은...

 

Project Settings / Player / Other Settings 에서

위에 보이는 Api Compatibility Level을 .NET 4.x로 해주고

Scipt Compilation 하단에 SSL이라고 입력해줘야 한다.

 

 

참고>

https://medium.com/@jspark141515/mqtt란-314472c246ee

 

MQTT란?

MQTT는 M2M, IOT를 위한 프로토콜로서, 최소한의 전력과 패킷량으로 통신하는 프로토콜입니다. 따라서 IOT와 모바일 어플리케이션 등의 통신에 매우 적합한 프로토콜입니다.

medium.com

https://hamait.tistory.com/390

 

MQTT 와 모스키토(Mosquitto) 란 무엇인가?

MQTT 시작하기 좋은 글 https://dzone.com/refcardz/getting-started-with-mqtt MQTT 란? (http://www.codejs.co.kr/mqtt-mq-telemetry-transport%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0/ 펌) [MQTT 프로토콜 설계..

hamait.tistory.com

http://lemonheim.blogspot.com/2017/01/mqtt-mosquitto-mac.html

 

MQTT mosquitto 서버 Mac 설치/ 테스트

lemonheim's program note

lemonheim.blogspot.com

 

반응형

'unity C#' 카테고리의 다른 글

[Unity] 암호화 복호화  (0) 2022.11.29
[Unity] mov파일 만들어 webm파일 사용하기  (0) 2022.02.04
[Unity] Tetris 게임 만들기  (1) 2021.06.25
[Unity] 스도쿠게임 만들기  (0) 2021.06.15
[Unity] UDP 통신  (0) 2021.05.04
반응형

만들어진 앱은 앱스토어 및 플레이스토어에 업로드.

앱스토어 : https://apps.apple.com/us/app/simple-sudoku-puzzle/id1567879220

 

‎Simple Sudoku Puzzle

‎Thousands of Sudoku puzzles to explore. Install to get started now! Whether you want to relax or keep your mind alive-have a good time! Choose the level you want. Play easier levels to challenge your brain, or try the expert level to get a real workout

apps.apple.com

플레이스토어 : https://play.google.com/store/apps/details?id=com.hnine.Sudoku 

 

Sudoku - Google Play 앱

언제 어디서나 스도쿠 퍼즐을 즐기십시오.

play.google.com

 

 

하나의 클래스로 처음에 작업 했다가 코드가 길어져 Partial class를 사용 하여 3개의 파일로 분리 정리함.

Partial class에 대해 궁금하다면 요기를 클릭.

 

BoardHandler.cs 작성

기본적으로 게임 View에 해당하는 전반적인 모든 것들을 연결하고 기능을 담고 있는 파일.

using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;

public enum Status
{
    PlayMode,
    MemoMode
}

public partial class BoardHandler : MonoBehaviour
{
    public static string HISTORY = "HISTORY";
    [SerializeField] HistoryManger historyManger;
    [SerializeField] UnityAdsManager adsManager;

    [Header("Theme Color")]
    [SerializeField] List<ColorSet> ListColor;
    [SerializeField] List<Image> Img_BGs;
    [SerializeField] List<Image> Img_Grids;
    [SerializeField] List<Image> ListButtons;
    [SerializeField] List<Image> ListButtonIcons;
    [SerializeField] List<Text> ListButtonsTexts;
    [SerializeField] List<Image> ListToastBGs;
    [SerializeField] List<Text> ListToastTexts;

    [Header("Game")]
    [SerializeField] Status MyStatus;
    [SerializeField] Dificulty dificulty;
    [SerializeField] int Life = 3;
    [SerializeField] int _hint = 3;
    [SerializeField] int ThemeNum = 0;
    public static bool isPlay;
    [SerializeField] float playTime;
    [SerializeField] GridLayoutGroup GLG_Holder;
    [SerializeField] VerticalLayoutGroup VLG;
    [SerializeField] HorizontalLayoutGroup HLG;
    [SerializeField] GameObject PrefabItem;
    [SerializeField] CellItem PrefabCellItem;
    int[,] solvedGrid = new int[9, 9];
    string s;
    [SerializeField] float spacing, padding, round;
    [SerializeField] List<CellItem> List_CellItem;
    int hideCount = 30;
    CellItem targetCellItem = null;

    [Space(10)]
    [Header("UI")]
    [SerializeField] Button Btn_Memo;
    [SerializeField] GameObject GO_TxtMemo, GO_TxtPlay, GO_AD;
    [SerializeField] Image Img_MemoBG;
    [SerializeField] Image[] Imgs_HolderBG;
    [SerializeField] Button[] Btns_Nums;
    [SerializeField] CanvasGroup CG_SelectDificult;
    [SerializeField] Button Btn_Normal, Btn_Medium, Btn_Hard, Btn_Expert, Btn_NewGame, Btn_Hint, Btn_Del, Btn_Theme, Btn_Pause;
    [SerializeField] CanvasGroup CG_Board;
    [SerializeField] RectTransform RT_Borad, RT_Top, RT_Bot, RT_BotUIHolder, RT_TopUIHolder;
    [SerializeField] Text Txt_Level, Txt_Mistake, Txt_PlayTime, Txt_Hint, Txt_Record;
    [SerializeField] UnityEngine.EventSystems.EventSystem eventSystem;
    [SerializeField] List<Text> ListNumCountTxt;
    float screenW, screenH;
    float boardW;
    Vector2 screenSize;

    static float calNum = Mathf.PI / 180;
    public static AnimationCurve SineInOut90 = new AnimationCurve(
        new Keyframe(0, 0, Mathf.Tan(5 * calNum), Mathf.Tan(5 * calNum)),
        new Keyframe(0.12f, 0.06f, Mathf.Tan(52 * calNum), Mathf.Tan(52 * calNum)),
        new Keyframe(0.4f, 0.72f, Mathf.Tan(50 * calNum), Mathf.Tan(50 * calNum)),
        new Keyframe(1f, 1f, Mathf.Tan(1 * calNum), Mathf.Tan(1 * calNum))
    );

    int usedHint = 0;
    public int SetHint
    {
        get
        {
            return _hint;
        }
        set
        {
            _hint = value;

            if (_hint == 0)
            {
                GO_AD.SetActive(true);
                Txt_Hint.gameObject.SetActive(false);
            }
            else
            {
                GO_AD.SetActive(false);
                Txt_Hint.gameObject.SetActive(true);
                Txt_Hint.text = _hint.ToString();
            }
        }
    }

    public int SetLife
    {
        get
        {
            return Life;
        }
        set
        {
            Life = value;
            Txt_Mistake.text = "Mistake : " + (3 - Life).ToString() + "/3";
        }
    }


    [Space(10)]
    [Header("Complete")]
    [SerializeField] CanvasGroup CG_Complete;
    [SerializeField] Button Btn_CompleteReGame;

    [Space(10)]
    [Header("Game Over")]
    [SerializeField] CanvasGroup CG_GameOver;
    [SerializeField] Button Btn_GameOverNewGame, Btn_GameOverRePlay, Btn_GameCompleteRePlay;

    [Space(10)]
    [Header("Toast Popup")]
    [SerializeField] ToastPopupHandler Toast;

    [Space(10)]
    [Header("New Game")]
    [SerializeField] NewGamePopupHandler NewGamePopup;


    void Start()
    {
        Application.targetFrameRate = 30;
        screenSize = new Vector2(Screen.width, Screen.height);
        resetAll();
        ThemeNum = PlayerPrefs.GetInt("ThemeNum");
        setTheme();
        Btn_Memo.onClick.AddListener(() =>
        {
            if (MyStatus == Status.PlayMode)
                toMemoMode();
            else if (MyStatus == Status.MemoMode)
                toPlayMode();
        });
        for (int i = 0; i < Btns_Nums.Length; i++)
        {
            int idx = i + 1;
            Btns_Nums[i].transform.GetChild(0).GetComponent<Text>().text = idx.ToString();
            Btns_Nums[i].onClick.AddListener(() => checkNumber(idx));
        }
        // Btn_Easy.onClick.AddListener(() => selectDificult(0));
        Btn_Normal.onClick.AddListener(() => selectDificult(1));
        Btn_Medium.onClick.AddListener(() => selectDificult(2));
        Btn_Hard.onClick.AddListener(() => selectDificult(3));
        Btn_Expert.onClick.AddListener(() => selectDificult(4));
        Btn_NewGame.onClick.AddListener(NewGamePopup.Open);
        Btn_CompleteReGame.onClick.AddListener(resetAll);
        Btn_GameOverNewGame.onClick.AddListener(resetAll);
        Btn_Hint.onClick.AddListener(HintOpen);
        Btn_Del.onClick.AddListener(resetCell);
        Btn_Theme.onClick.AddListener(() => changeTheme());
        Btn_Pause.onClick.AddListener(pauseCheck);

        NewGamePopup.Btn_NewGame.onClick.AddListener(NewGamePopupOpen);

        NewGamePopup.Btn_RePlay.onClick.AddListener(() => openFullAD("rePlay"));
        Btn_GameCompleteRePlay.onClick.AddListener(() => openFullAD("rePlay"));
        Btn_GameOverRePlay.onClick.AddListener(() => openFullAD("rePlay"));
    }

    // 전면광고 호출
    void openFullAD(string type = "hint")
    {
        // rePlay // hint
        adsManager.AdsShow(type);
    }

    void pauseCheck()
    {
        if (isPlay)
        {
            isPlay = false;
            Btn_Pause.gameObject.transform.GetChild(0).gameObject.SetActive(true);
            Btn_Pause.gameObject.transform.GetChild(1).gameObject.SetActive(false);
            for (int i = 0; i < List_CellItem.Count; i++)
            {
                int idx = i;
                List_CellItem[i].RT.DOLocalRotate(new Vector3(0, 90, 0), 0.25f).SetEase(Ease.InCubic).SetDelay(i * 0.005f).OnComplete(() =>
                {
                    List_CellItem[idx].Txt.gameObject.SetActive(false);
                    List_CellItem[idx].GO_Memo.SetActive(false);
                    List_CellItem[idx].RT.DOLocalRotate(new Vector3(0, 180, 0), 0.25f).SetEase(Ease.OutCubic);
                });
            }
        }
        else
        {
            isPlay = true;
            Btn_Pause.gameObject.transform.GetChild(0).gameObject.SetActive(false);
            Btn_Pause.gameObject.transform.GetChild(1).gameObject.SetActive(true);
            for (int i = 0; i < List_CellItem.Count; i++)
            {
                int idx = i;
                List_CellItem[i].RT.DOLocalRotate(new Vector3(0, 90, 0), 0.25f).SetEase(Ease.InCubic).SetDelay(i * 0.005f).OnComplete(() =>
                {
                    if (List_CellItem[idx].IsOpen)
                        List_CellItem[idx].Txt.gameObject.SetActive(true);
                    if (!List_CellItem[idx].IsOpen)
                        List_CellItem[idx].GO_Memo.SetActive(true);
                    List_CellItem[idx].RT.DOLocalRotate(new Vector3(0, 0, 0), 0.25f).SetEase(Ease.OutCubic);
                });
            }
        }
        setTheme();
    }

    public void NewGamePopupOpen()
    {
        // resetAll();
        adsManager.ShowNewGameAd();
    }

    // 레벨에 따라 주어지는 힌트갯수를 세팅함
    void setHint()
    {
        if (dificulty == Dificulty.Easy) { }
        else if (dificulty == Dificulty.Normal)
            SetHint = 3;
        else if (dificulty == Dificulty.Medium)
            SetHint = 3;
        else if (dificulty == Dificulty.Hard)
            SetHint = 5;
        else if (dificulty == Dificulty.Expert)
            SetHint = 7;
    }

    // 시도했던 게임을 다시 할 경우
    public void RePlayGame()
    {
        Debug.Log("RePlay");
        isPlay = false;
        SetLife = 3;
        setHint();
        playTime = 0;
        CG_Complete.gameObject.SetActive(false);
        CG_GameOver.gameObject.SetActive(false);
        for (int i = 0; i < List_CellItem.Count; i++)
        {
            int idx = i;
            List_CellItem[i].RT.DOLocalRotate(new Vector3(0, 90, 0), 0.25f).SetEase(Ease.InCubic).SetDelay(i * 0.005f).OnComplete(() =>
            {
                if (!List_CellItem[idx].IsOrigin == true)
                {
                    List_CellItem[idx].Txt.gameObject.SetActive(false);
                    List_CellItem[idx].IsOpen = false;
                }
                else
                {
                    List_CellItem[idx].Txt.gameObject.SetActive(true);
                }
                List_CellItem[idx].GO_Memo.SetActive(false);
                List_CellItem[idx].RT.DOLocalRotate(new Vector3(0, 0, 0), 0.25f).SetEase(Ease.OutCubic);
                isPlay = true;
                setTheme();// reset Theme
                toPlayMode();
            });
            List_CellItem[i].Txt.color = ListColor[ThemeNum].Clr_CellTextOrigin;
        }

    }

    // 새로 게임을 진행 할 경우
    public void resetAll()
    {
        // Debug.Log("ResetAll");
        toPlayMode();
        isPlay = false;
        SetLife = 3;
        SetHint = 3;
        playTime = 0;
        CG_Complete.gameObject.SetActive(false);
        CG_GameOver.gameObject.SetActive(false);
        CG_SelectDificult.gameObject.SetActive(true);
        CG_SelectDificult.alpha = 0;
        CG_SelectDificult.DOFade(1, 0.3f).SetEase(SineInOut90);
        for (int i = 0; i < List_CellItem.Count; i++)
        {
            List_CellItem[i].Btn.onClick.RemoveAllListeners();
            List_CellItem[i].List_MemoNums.Clear();
            Destroy(List_CellItem[i].RT.gameObject);
        }
        List_CellItem.Clear();
    }

    // 메모 모드로 집입 할 경우 처리
    void toMemoMode()
    {
        MyStatus = Status.MemoMode;

        // 버튼 상태 변경
        GO_TxtMemo.SetActive(false);
        GO_TxtPlay.SetActive(true);
        Img_MemoBG.color = ListColor[ThemeNum].Clr_MemoMode;

        // 모드를 구분하기 위한 이미지 컬러 조절
        for (int i = 0; i < Imgs_HolderBG.Length; i++)
            Imgs_HolderBG[i].color = ListColor[ThemeNum].Clr_MemoMode;
    }

    // 노멀(플레이모드)모드로 진입 할 경우 처리
    void toPlayMode()
    {
        MyStatus = Status.PlayMode;

        // 버튼 상태 변경
        GO_TxtMemo.SetActive(true);
        GO_TxtPlay.SetActive(false);
        Img_MemoBG.color = ListColor[ThemeNum].Clr_ButtonBG;

        // 모드를 구분하기 위한 이미지 컬러 조절
        for (int i = 0; i < Imgs_HolderBG.Length; i++)
            Imgs_HolderBG[i].color = ListColor[ThemeNum].Clr_PlayMode;
    }


    // 해당 셀을 터치하거나 마우스로 클릭했을 경우
    void clickedItem(CellItem item)
    {
        if (item.IsOpen)
        {
            for (int i = 0; i < List_CellItem.Count; i++)
            {
                if (List_CellItem[i].IsOpen && List_CellItem[i].MyNum == item.MyNum)                    // 셀의 숫자가 열려있는 경우 클릭하게 되면?,
                    List_CellItem[i].Img.color = ListColor[ThemeNum].Clr_FocusedSameNum;
                else                                                                                    // 셀의 숫자가 열려있는 경우 클릭한 셀을 제외한 나머지 셀들은,
                    List_CellItem[i].Img.color = ListColor[ThemeNum].Clr_UnFocused;
            }
        }
        else                                                                                            // 클릭한 셀이 열려있지 않은 경우(가려진 경우) 눌리게 되면?
        {
            for (int i = 0; i < List_CellItem.Count; i++)
                List_CellItem[i].Img.color = ListColor[ThemeNum].Clr_UnFocused;
        }
        item.Img.color = ListColor[ThemeNum].Clr_Focused;
        targetCellItem = item;

        // 클릭된 셀을 기준으로 가로세로로 포커싱되게 처리.
        int cnt = 0;
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                if (targetCellItem.MyPos.x == i && targetCellItem.MyPos.y == j) { }                     // 클릭된 카드는 제외하고 나머지 셀들은,
                else
                {
                    if (targetCellItem.MyNum != List_CellItem[cnt].MyNum)                               // 클릭된 셀의 숫자와 다른 나머지 셀들은,
                    {
                        if (i == targetCellItem.MyPos.x || j == targetCellItem.MyPos.y)                 // 클릭된 셀과 가로나 세로가 줄이 맞는 셀이면,
                            List_CellItem[cnt].Img.color = ListColor[ThemeNum].Clr_FocusLine;
                        else                                                                            // 클릭된 셀과 가로나 세로가 맞지 않는 셀이면,
                            List_CellItem[cnt].Img.color = ListColor[ThemeNum].Clr_UnFocused;
                    }
                }
                cnt++;
            }
        }
    }

    // 힌트를 클릭했을 때
    public void HintOpen()
    {
        if (MyStatus == Status.PlayMode)
        {
            if (targetCellItem != null && !targetCellItem.IsOpen)
            {
                if (_hint > 0)
                {
                    SetHint--;
                    usedHint++;
                    checkNumber(targetCellItem.MyNum);
                }
                else
                {
                    // Toast.Popup("No more hint");
                    openFullAD("hint");
                }
            }
            else
            {
                Toast.Popup("Select blank cell.");
            }
        }
        else
        {
            Toast.Popup("change to play mode.");
        }
    }

    // 숫자키를 입력받았을 때, 처리되는 부분
    void checkNumber(int idx)
    {
        if (MyStatus == Status.PlayMode) // 노멀모드에서 처리
        {
            if (targetCellItem != null && !targetCellItem.IsOpen)                                       // 선택한 셀이 있고 열려있지 않을 경우
            {
                if (idx != targetCellItem.MyNum)                                                        //! 입력받은 숫자와 선택한 셀의 숫자가 맞지 않은경우 (틀렸을 경우)
                {
                    // 숫자가 틀렸을 경우 생명이 하나 줄고 틀린숫자가 빨강으로 표기된다.
                    Life--;
                    if (Life == 0)
                    {
                        Txt_Mistake.text = "Mistake : " + (3 - Life).ToString() + "/3";
                        gameOver();
                    }
                    else
                    {
                        Txt_Mistake.text = "Mistake : " + (3 - Life).ToString() + "/3";
                        Toast.Popup("Wrong Number T.T");
                        targetCellItem.Txt.text = idx.ToString();
                        targetCellItem.Txt.gameObject.SetActive(true);
                        targetCellItem.Txt.DOColor(ListColor[ThemeNum].Clr_CellRed, 0.3f).SetEase(SineInOut90);
                    }
                }
                else                                                                                    //? 맞았을 경우
                {
                    // 숫자가 맞았을 경우 해당 숫자가 열린다.
                    targetCellItem.RT.eulerAngles = new Vector3(0, 180, 0);
                    targetCellItem.RT.DOLocalRotate(new Vector3(0, 90, 0), 0.25f).SetEase(Ease.InCubic).OnComplete(() =>
                    {
                        targetCellItem.IsOpen = true;
                        targetCellItem.Txt.text = idx.ToString();
                        targetCellItem.Txt.gameObject.SetActive(true);
                        targetCellItem.Txt.fontStyle = FontStyle.Bold;
                        targetCellItem.Txt.DOColor(ListColor[ThemeNum].Clr_CellText, 0.3f).SetEase(SineInOut90);
                        targetCellItem.RT.DOLocalRotate(new Vector3(0, 0, 0), 0.25f).SetEase(Ease.OutCubic);
                        setNumberCount();

                        // 열린 숫자와 같은 숫자를 표기한다.
                        clickedItem(targetCellItem);

                        // 선택한 셀에 힌트로 적었던 메모를 모두 삭제한다.
                        targetCellItem.GO_Memo.SetActive(false);

                        memoNumberReset_AfterOpenCell(idx);

                        // 오픈되지 않은 남은 셀의 갯수 체크!!
                        int cnt = 0;
                        for (int i = 0; i < List_CellItem.Count; i++)
                            if (!List_CellItem[i].IsOpen)
                                cnt++;
                        if (cnt == 0) // 셀이 모두 열렸을 경우 게임 끝~~~
                            gameComplete();
                    });
                }
            }
            else
            {
                Toast.Popup("This block is already open.\nPlease select a blank.");
            }
        }
        else if (MyStatus == Status.MemoMode)                                                           // 메모 모드에서 숫자를 입력했을 경우 처리.
        {
            if (targetCellItem != null && !targetCellItem.IsOpen)
            {
                if (targetCellItem.List_MemoNums[idx - 1].activeInHierarchy)
                    targetCellItem.List_MemoNums[idx - 1].SetActive(false);
                else
                    targetCellItem.List_MemoNums[idx - 1].SetActive(true);
            }
            else
            {
                Toast.Popup("This block is already open.\nPlease select a blank.");
            }
        }
    }

    // 클릭된 셀을 기준으로 메모해둔 숫자를 사라지도록 처리하기 위한 부분
    void memoNumberReset_AfterOpenCell(int idx)
    {
        int cnt = 0;
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                if (targetCellItem.MyPos.x == i && targetCellItem.MyPos.y == j) { }                     // 클릭된 카드는 제외하고 나머지 셀
                else
                {
                    if (targetCellItem.MyNum != List_CellItem[cnt].MyNum)                               // 클릭된 셀의 숫자와 다른 나머지 셀들은,
                        if (i == targetCellItem.MyPos.x || j == targetCellItem.MyPos.y)                 // 클릭된 셀과 가로나 세로가 줄이 맞는 셀이면, 메모해둔 숫자중에 해당 숫자는 사라지도록 처리
                            List_CellItem[cnt].List_MemoNums[idx - 1].SetActive(false);
                }
                cnt++;
            }
        }
        // 클릭된 셀의 숫자가 오픈 될 경우 같은 그룹에 있는 셀에 메모해둔 숫자가 오픈된 숫자와 같을 경우
        for (int i = 0; i < List_CellItem.Count; i++)
            if (targetCellItem.MyGroup == List_CellItem[i].MyGroup)
                List_CellItem[i].List_MemoNums[idx - 1].SetActive(false);
    }

    // 모든 셀의 숫자를 맞추고 게임이 완료되는 상황
    void gameComplete()
    {
        print("Complete!");
        isPlay = false;
        for (int i = 0; i < List_CellItem.Count; i++)
        {
            int ii = i;
            List_CellItem[ii].Img.DOColor(ListColor[ThemeNum].Clr_Focused, 1f).SetDelay(0.005f * i).SetEase(SineInOut90);
            List_CellItem[ii].RT.DOLocalRotate(new Vector3(0, 360, 0), 1f, RotateMode.FastBeyond360).SetDelay(0.005f * i).SetEase(SineInOut90).OnComplete(() =>
            {
                List_CellItem[ii].Img.DOColor(ListColor[ThemeNum].Clr_UnFocused, 1f).SetEase(SineInOut90);
            });
        }
        Invoke("viewRecord", 2.3f);
        eventSystem.enabled = false;
    }

    void viewRecord()
    {
        eventSystem.enabled = true;
        CG_Complete.gameObject.SetActive(true);
        CG_Complete.alpha = 0;
        CG_Complete.DOFade(1, 0.3f).SetEase(SineInOut90);

        string _dificult = dificulty.ToString() + "_record";
        Debug.Log("난이도 : " + _dificult);
        int _history = PlayerPrefs.GetInt(_dificult);

        if (_history > 0)
        {
            Debug.Log("my history time : " + _history);

            if (_history >= Mathf.RoundToInt(playTime))
            {
                Txt_Record.text = "<size=50>Level : " + dificulty.ToString() + "</size>" + "\n<size=90>Game Clear!!</size>\n\n<color=#00aeff>=== New Record ===</color>\n" + GetTimeStr(Mathf.RoundToInt(playTime));
                PlayerPrefs.SetInt(_dificult, Mathf.RoundToInt(playTime));
            }
            else
            {
                Txt_Record.text = "<size=50>Level : " + dificulty.ToString() + "</size>" + "\n<size=90>Game Clear!!</size>\n\n<color=orange>=== Best Record ===</color>\n" + GetTimeStr(Mathf.RoundToInt(_history)) + "\n\n<color=#00aeff>=== My Record ===</color>\n" + GetTimeStr(Mathf.RoundToInt(playTime));
            }
        }
        else
        {
            Txt_Record.text = "<size=50>Level : " + dificulty.ToString() + "</size>" + "\n<size=90>Game Clear!!</size>\n\n<color=#00aeff>=== New Record ===</color>\n" + GetTimeStr(Mathf.RoundToInt(playTime));
            PlayerPrefs.SetInt(_dificult, Mathf.RoundToInt(playTime));
        }
        historyManger.SaveHistory(dificulty.ToString(), GetTimeStr(Mathf.RoundToInt(playTime)), (3 - Life).ToString(), usedHint.ToString());
    }

    // 생명이 다 되서 게임오버가 되는 상황
    void gameOver()
    {
        isPlay = false;
        CG_GameOver.gameObject.SetActive(true);
        CG_GameOver.alpha = 0;
        CG_GameOver.DOFade(1, 0.3f).SetEase(SineInOut90);
    }

    // 선택 된 셀의 숫자를 지우는 함수.
    void resetCell()
    {
        if (targetCellItem != null && !targetCellItem.IsOpen)
        {
            targetCellItem.IsOpen = false;
            targetCellItem.Txt.gameObject.SetActive(false);
        }
        else if (targetCellItem == null)
            Toast.Popup("Select a box.");
        else if (targetCellItem != null && targetCellItem.IsOpen)
            Toast.Popup("Already open.");
    }



    // for layout size
    float tempSpacing, tempPadding, tempRound;
    void Update()
    {
        setResize();
        setKeyBoard();
        updateTime();
    }

    void updateTime()
    {
        if (isPlay)
        {
            playTime += Time.deltaTime;
            Txt_PlayTime.text = GetTimeStr(Mathf.RoundToInt(playTime));
        }
    }

    void setKeyBoard()
    {
        // 각 숫자패드가 눌릴 경우 처리. 나중에 소프트키도 만들어 동일하게 처리해야 함.
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            checkNumber(1);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            checkNumber(2);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha3))
        {
            checkNumber(3);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha4))
        {
            checkNumber(4);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha5))
        {
            checkNumber(5);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha6))
        {
            checkNumber(6);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha7))
        {
            checkNumber(7);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha8))
        {
            checkNumber(8);
        }
        else if (Input.GetKeyDown(KeyCode.Alpha9))
        {
            checkNumber(9);
        }
        else if (Input.GetKeyDown(KeyCode.Escape) || Input.GetKeyDown(KeyCode.Backspace))
        {
            resetCell();
        }
    }

    string GetTimeStr(int totalTime)
    {
        int min = (int)totalTime / 60;
        int sec = totalTime % 60;
        string str = min.ToString("00") + ":" + sec.ToString("00");
        return str;
    }
}

 

BoardHandler.Initialize.cs 작성

게임에 가장 핵심적인 부분이다. 9x9의 블럭에 들어가는 숫자를 랜덤으로 생성하고 섞어주는 역할을 하고 게임의 난이도 등도 관리한다.

알고리즘을 참고한 사이트 링크 : https://citylock77.tistory.com/86

using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;
using Random = UnityEngine.Random;

[Serializable]
public class CellItem
{
    public RectTransform RT;
    public int MyGroup;
    public FreeModifier FM;
    public Button Btn;
    public Text Txt;
    public Image Img;
    public int MyNum;
    public bool IsOpen;
    public Vector2 MyPos;
    public List<GameObject> List_MemoNums;
    public GameObject GO_Memo;
    public bool IsOrigin;
}

public enum Dificulty
{
    Easy = 0,
    Normal = 1,
    Medium = 2,
    Hard = 3,
    Expert = 4
}

public partial class BoardHandler : MonoBehaviour
{
    void selectDificult(int idx)
    {
        switch (idx)
        {
            case 0:
                dificulty = Dificulty.Easy;
                break;
            case 1:
                dificulty = Dificulty.Normal;
                break;
            case 2:
                dificulty = Dificulty.Medium;
                break;
            case 3:
                dificulty = Dificulty.Hard;
                break;
            case 4:
                dificulty = Dificulty.Expert;
                break;
        }
        resetAll();
        init();
    }

    void init()
    {
        CG_SelectDificult.gameObject.SetActive(false);
        // 9x9 그리드 데이터를 생성.
        initGrid(solvedGrid);

        // 생성한 그리드 데이터 숫자를 섞어줌.
        shuffleGrid(solvedGrid, 25);
        debugGrid(solvedGrid);

        // 생성한 그리드 데이터를 기반으로 View 생성.
        initView(solvedGrid);
        reSizeView();

        // AdsManager ad = GameObject.FindWithTag("AdsManager").gameObject.GetComponent<AdsManager>();
        // ad.AdsInit();
        setTheme();
    }

    // 처음 화면이 만들어지는 곳.
    void initView(int[,] grid)
    {
        CG_Board.alpha = 0;
        PrefabItem.SetActive(false);
        List_CellItem = new List<CellItem>();

        PrefabItem.GetComponent<Image>().color = ListColor[ThemeNum].Clr_Cell;
        PrefabItem.transform.GetChild(0).GetComponent<Text>().color = ListColor[ThemeNum].Clr_CellText;

        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                GameObject go = Instantiate(PrefabItem);
                go.transform.SetParent(PrefabItem.transform.parent);
                go.transform.localScale = Vector3.one;
                go.transform.localPosition = Vector3.zero;
                go.SetActive(true);
                Transform t_Memo = go.transform.GetChild(1);
                List<GameObject> list_MemoNums = new List<GameObject>();
                for (int k = 0; k < t_Memo.childCount; k++)
                {
                    t_Memo.GetChild(k).gameObject.SetActive(false);
                    list_MemoNums.Add(t_Memo.GetChild(k).gameObject);
                }
                CellItem item = new CellItem
                {
                    RT = go.GetComponent<RectTransform>(),
                    FM = go.GetComponent<FreeModifier>(),
                    Btn = go.GetComponent<Button>(),
                    Txt = go.transform.GetChild(0).GetComponent<Text>(),
                    Img = go.GetComponent<Image>(),
                    MyNum = grid[i, j],
                    IsOpen = true,
                    GO_Memo = t_Memo.gameObject,
                    List_MemoNums = list_MemoNums
                };

                item.Txt.text = grid[i, j].ToString();
                item.MyPos = new Vector2(i, j);
                item.Btn.onClick.AddListener(() => { clickedItem(item); });

                // 9개 영역으로 구문하기 위한 넘버링 : 숫자가 열렸을 경우 메모해 두었던 숫자도 리셋해주기 위해 구분 함.
                if (i > 2 && i < 6 && j < 3)                // 1번 그룹 9개
                    item.MyGroup = 1;
                else if (i > 2 && i < 6 && j < 3)           // 2번 그룹 9개
                    item.MyGroup = 2;
                else if (i > 5 && j < 3)                    // 3번 그룹 9개
                    item.MyGroup = 3;
                else if (i < 3 && j > 2 && j < 6)           // 4번 그룹 9개
                    item.MyGroup = 4;
                else if (i > 2 && i < 6 && j > 2 && j < 6)  // 5번 그룹 9개
                    item.MyGroup = 5;
                else if (i > 5 && j > 2 && j < 6)           // 6번 그룹 9개
                    item.MyGroup = 6;
                else if (i < 3 && j > 5)                    // 7번 그룹 9개
                    item.MyGroup = 7;
                else if (i > 2 && i < 6 && j > 5)           // 8번 그룹 9개
                    item.MyGroup = 8;
                else if (i > 5 && j > 5)                    // 9번 그룹 9개
                    item.MyGroup = 9;

                List_CellItem.Add(item);
            }
        }

        //! Hide Random Cell from Dificulty
        if (dificulty == Dificulty.Easy)
        {
            Txt_Level.text = "Easy";
            hideCount = 10;
        }
        else if (dificulty == Dificulty.Normal)
        {
            Txt_Level.text = "Normal";
            hideCount = 27;//28
        }
        else if (dificulty == Dificulty.Medium)
        {
            Txt_Level.text = "Medium";
            hideCount = 37;//38;
        }
        else if (dificulty == Dificulty.Hard)
        {
            Txt_Level.text = "Hard";
            hideCount = 45;//46;
        }
        else if (dificulty == Dificulty.Expert)
        {
            Txt_Level.text = "Expert";
            hideCount = 55;//56;
        }
        setHint();

        // 난이도에 따라 숨겨질 셀을 램덤으로 처리
        List<int> listHideNum = new List<int>();
        for (int i = 0; i < hideCount; i++)
        {
            int ran = getRandomNumber(listHideNum, List_CellItem.Count);
            listHideNum.Add(ran);
        }

        for (int i = 0; i < listHideNum.Count; i++)
        {
            for (int j = 0; j < List_CellItem.Count; j++)
            {
                if (listHideNum[i] == j)
                {
                    List_CellItem[j].IsOpen = false;
                    List_CellItem[j].Txt.gameObject.SetActive(false);
                }
            }
        }
        for (int i = 0; i < List_CellItem.Count; i++)
        {
            if (List_CellItem[i].IsOpen)
            {
                List_CellItem[i].IsOrigin = true;
                // List_CellItem[i].Txt.fontStyle = FontStyle.Bold;
                List_CellItem[i].Txt.color = ListColor[ThemeNum].Clr_CellTextOrigin;
            }
        }
        CG_Board.DOFade(1, 0.5f).SetEase(SineInOut90);
        isPlay = true;

        setNumberCount();
    }

    // 하단 숫자버튼에 남은 수 표기 및 해당 버튼의 숫자가 0일 경우 비활성화
    void setNumberCount()
    {
        List<int> counts = new List<int>() { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        for (int i = 0; i < List_CellItem.Count; i++)
        {
            if (!List_CellItem[i].IsOpen)
            {
                if (List_CellItem[i].MyNum == 1)
                    counts[0]++;
                else if (List_CellItem[i].MyNum == 2)
                    counts[1]++;
                else if (List_CellItem[i].MyNum == 3)
                    counts[2]++;
                else if (List_CellItem[i].MyNum == 4)
                    counts[3]++;
                else if (List_CellItem[i].MyNum == 5)
                    counts[4]++;
                else if (List_CellItem[i].MyNum == 6)
                    counts[5]++;
                else if (List_CellItem[i].MyNum == 7)
                    counts[6]++;
                else if (List_CellItem[i].MyNum == 8)
                    counts[7]++;
                else if (List_CellItem[i].MyNum == 9)
                    counts[8]++;
            }
        }

        for (int i = 0; i < ListNumCountTxt.Count; i++)
        {
            ListNumCountTxt[i].color = ListColor[ThemeNum].Clr_CellText;
            if (counts[i] != 0)
            {
                ListNumCountTxt[i].gameObject.transform.parent.transform.GetChild(0).gameObject.GetComponent<Text>().color = ListColor[ThemeNum].Clr_CellText;
                ListNumCountTxt[i].gameObject.transform.parent.GetComponent<Button>().interactable = true;
                ListNumCountTxt[i].text = counts[i].ToString();
            }
            else
            {
                Color _cor = ListColor[ThemeNum].Clr_CellText;
                ListNumCountTxt[i].gameObject.transform.parent.transform.GetChild(0).gameObject.GetComponent<Text>().color = new Vector4(_cor.r, _cor.g, _cor.b, 0.2f);
                ListNumCountTxt[i].gameObject.transform.parent.GetComponent<Button>().interactable = false;
                ListNumCountTxt[i].text = "";
            }
        }
    }

    // 그리드 배열이 처음 생성되는 곳. 9x9의 갯수로 숫자가 섞이지 않고 일단 생성만 된다.
    void initGrid(int[,] grid)
    {
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                grid[i, j] = (i * 3 + i / 3 + j) % 9 + 1;
            }
        }
    }

    // 디버깅용.
    void debugGrid(int[,] grid)
    {
        s = "";
        int sep = 0;

        for (int i = 0; i < 9; i++)
        {
            s += "|";
            for (int j = 0; j < 9; j++)
            {
                s += grid[i, j].ToString();

                sep = j % 3;
                if (sep == 2)
                    s += "|";
            }
            s += "\n";
        }
        Debug.Log("debugGrid : " + s);
    }

    // 전체 셀의 숫자들을 섞어준다.
    void shuffleGrid(int[,] grid, int shuffleAmount)
    {
        for (int i = 0; i < shuffleAmount; i++)
        {
            int value1 = Random.Range(1, 10);
            int value2 = Random.Range(1, 10);

            mixTwoGridCells(grid, value1, value2);
        }
        for (int i = 0; i < shuffleAmount; i++)
            shuffleGridHorizontal();
    }

    void shuffleGridHorizontal()
    {
        int ran = Random.Range(0, 8);
        int target = ran + 3;
        if (target > 8)
            target = target - 9;

        int small = 0, big = 0;
        if (ran > target)
        {
            small = target;
            big = ran;
        }
        else
        {
            small = ran;
            big = target;
        }
        Debug.Log(small + " change " + big);
        int changeNum = 0;
        for (int x = 0; x < 9; x++)
        {
            for (int y = 0; y < 9; y++)
            {
                if (y == small)
                {
                    changeNum = solvedGrid[x, y];
                }
                if (y == big)
                {
                    solvedGrid[x, small] = solvedGrid[x, y];
                    solvedGrid[x, y] = changeNum;
                }
            }
        }
    }

    void mixTwoGridCells(int[,] grid, int value1, int value2)
    {
        int x1 = 0, x2 = 0, y1 = 0, y2 = 0;

        for (int i = 0; i < 9; i += 3)
        {
            for (int j = 0; j < 9; j += 3)
            {
                for (int k = 0; k < 3; k++)
                {
                    for (int l = 0; l < 3; l++)
                    {
                        if (grid[i + k, j + l] == value1)
                        {
                            x1 = i + k;
                            y1 = j + l;
                        }

                        if (grid[i + k, j + l] == value2)
                        {
                            x2 = i + k;
                            y2 = j + l;
                        }
                    }
                }
                grid[x1, y1] = value2;
                grid[x2, y2] = value1;
            }
        }
    }

    // 겹치지 않는 랜덤 숫자를 가져온다.
    private int getRandomNumber(List<int> seleced, int max)
    {
        var range = Enumerable.Range(0, max).Where(i => !seleced.Contains(i));
        int index = Random.Range(0, max - seleced.Count);
        return range.ElementAt(index);
    }
}

 

BoardHandler.LayoutColor.cs 작성

게임의 컬러테마를 관리하는 파일.

using System;
using UnityEngine;
using UnityEngine.UI;

[Serializable]
public class ColorSet
{
    public Color Clr_BG;
    public Color Clr_Grid;
    public Color Clr_Cell, Clr_CellIsNotOpen;
    public Color Clr_CellText;
    public Color Clr_CellMemoText;
    public Color Clr_CellRed;
    public Color Clr_CellTextOrigin;
    public Color Clr_ButtonBG;
    public Color Clr_ButtonText;
    //======
    public Color Clr_Focused, Clr_UnFocused, Clr_FocusedSameNum, Clr_FocusLine, Clr_MemoMode, Clr_PlayMode;
    //======
    public Color Clr_ToastBg, Clr_ToastText;
}

public partial class BoardHandler : MonoBehaviour
{
    void changeTheme()
    {
        ThemeNum++;
        if (ThemeNum > ListColor.Count - 1)
            ThemeNum = 0;
        setTheme();
        PlayerPrefs.SetInt("ThemeNum", ThemeNum);
    }
    void setTheme()
    {
        for (int i = 0; i < Img_BGs.Count; i++)
            Img_BGs[i].color = ListColor[ThemeNum].Clr_BG;

        for (int i = 0; i < List_CellItem.Count; i++)
        {
            if (List_CellItem[i].IsOpen)
                List_CellItem[i].Img.color = ListColor[ThemeNum].Clr_Cell;
            else
                List_CellItem[i].Img.color = ListColor[ThemeNum].Clr_CellIsNotOpen;
            if (List_CellItem[i].Txt.gameObject.activeInHierarchy)
            {
                if (List_CellItem[i].IsOpen)
                {
                    if (!List_CellItem[i].IsOrigin)
                        List_CellItem[i].Txt.color = ListColor[ThemeNum].Clr_CellText;
                    else
                        List_CellItem[i].Txt.color = ListColor[ThemeNum].Clr_CellTextOrigin;
                }
                else
                    List_CellItem[i].Txt.color = ListColor[ThemeNum].Clr_CellRed;
            }

            for (int j = 0; j < List_CellItem[i].List_MemoNums.Count; j++)
                List_CellItem[i].List_MemoNums[j].GetComponent<Text>().color = ListColor[ThemeNum].Clr_CellMemoText;
        }

        for (int i = 0; i < Img_Grids.Count; i++)
            Img_Grids[i].color = ListColor[ThemeNum].Clr_Grid;

        if (MyStatus == Status.MemoMode)
            toMemoMode();
        else
            toPlayMode();

        for (int i = 0; i < ListButtons.Count; i++)
            ListButtons[i].color = ListColor[ThemeNum].Clr_ButtonBG;

        for (int i = 0; i < ListButtonIcons.Count; i++)
            ListButtonIcons[i].color = ListColor[ThemeNum].Clr_ButtonText;

        for (int i = 0; i < ListButtonsTexts.Count; i++)
            ListButtonsTexts[i].color = ListColor[ThemeNum].Clr_ButtonText;

        for (int i = 0; i < ListToastBGs.Count; i++)
            ListToastBGs[i].color = ListColor[ThemeNum].Clr_ToastBg;

        for (int i = 0; i < ListToastTexts.Count; i++)
            ListToastTexts[i].color = ListColor[ThemeNum].Clr_ToastText;

        for (int i = 0; i < PrefabCellItem.List_MemoNums.Count; i++)
            PrefabCellItem.List_MemoNums[i].GetComponent<Text>().color = ListColor[ThemeNum].Clr_CellMemoText;

        setNumberCount();
    }

    // 레이아웃의 사이즈가 달라질 경우 레이아웃을 다시 잡아줌.
    void reSizeView()
    {
        boardW = RT_Borad.rect.width;
        float width = (boardW - spacing * 8 - padding * 2) / 9;
        GLG_Holder.cellSize = new Vector2(width, width);
        GLG_Holder.spacing = new Vector2(spacing, spacing);
        GLG_Holder.padding.left = (int)padding;
        GLG_Holder.padding.right = (int)padding;

        VLG.spacing = width * 3 + spacing * 3 - 10;
        HLG.spacing = width * 3 + spacing * 3 - 10;

        screenW = GetComponent<RectTransform>().rect.width;
        screenH = GetComponent<RectTransform>().rect.height;
        float topHeight = (screenH - screenW) / 2 - 200;
        float botHeight = (screenH - screenW) / 2 + 200;
        float topPosY = screenW / 2 + RT_Top.rect.height / 2 + 200;
        float botPosY = -(screenW / 2 + RT_Bot.rect.height / 2) + 200;
        float gap = 0;
        if (topHeight < 160)
        {
            gap = topHeight - 160;
            Debug.Log(gap);
            RT_Borad.sizeDelta = new Vector2(1080 + gap, 1080 + gap);
            topHeight = 160;
            RT_BotUIHolder.localScale = new Vector3(RT_Borad.rect.width / 1080, RT_Borad.rect.width / 1080, RT_Borad.rect.width / 1080);
            RT_TopUIHolder.localScale = new Vector3(RT_Borad.rect.width / 1080, RT_Borad.rect.width / 1080, RT_Borad.rect.width / 1080);
        }
        else
        {
            RT_Borad.sizeDelta = new Vector2(1080, 1080);
            RT_BotUIHolder.localScale = Vector3.one;
        }
        RT_Top.sizeDelta = new Vector2(screenW, topHeight);
        // RT_Bot.sizeDelta = new Vector2(screenW, botHeight);
        // RT_Top.anchoredPosition = new Vector2(0, screenW / 2 + RT_Top.rect.height / 2 + 200);
        RT_Bot.anchoredPosition = new Vector2(0, -(screenW / 2 + RT_Bot.rect.height / 2) + 200);
    }

    // 레이아웃 사이즈를 자동 조절
    void setResize()
    {
        if (tempPadding != padding || tempSpacing != spacing || screenSize.x != Screen.width || screenSize.y != Screen.height)                                           // 패딩값이나 간격값이 바뀌게 되면 자동으로 레이아웃을 적용하도록 처리
        {
            tempPadding = padding;
            tempSpacing = spacing;
            reSizeView();
        }
        if (tempRound != round)
        {
            tempRound = round;
            for (int i = 0; i < List_CellItem.Count; i++)
                List_CellItem[i].FM.Radius = new Vector4(round, round, round, round);
        }
    }
}

 

HistoryManger.cs 작성

게임을 플레이하고 클리어 했던 기록을 볼 수 있도록하는 스크립트.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;

[Serializable]
public class HistoryData
{
    public string date;
    public string dificult;
    public string time;
    public string mistake;
    public string hint;
}
public class HistoryItem
{
    public GameObject Go_Me;
    public Text Txt_Date, Txt_Level, Txt_Time, Txt_Mis, Txt_Hint;
}

public class HistoryManger : MonoBehaviour
{
    [SerializeField] string myHistoryTxt;
    [SerializeField] List<HistoryData> listHistory;
    [SerializeField] Button btn_History, btn_Reset;
    [SerializeField] ToastPopupHandler toastPopup;
    [SerializeField] List<HistoryItem> List_HistoryItem;
    [SerializeField] GameObject go_Prefab;
    [SerializeField] Transform t_Content;
    [SerializeField] CanvasGroup cg_View;
    [SerializeField] RectTransform rt_View;
    [SerializeField] Button btn_Close, btn_OpenRecords;
    [SerializeField] GameObject go_NoData;

    void Start()
    {
        rt_View.localScale = new Vector3(0.9f, 0.9f, 0.9f);
        cg_View.alpha = 0;
        cg_View.blocksRaycasts = false;

        List_HistoryItem = new List<HistoryItem>();
        // PlayerPrefs.SetString(BoardHandler.HISTORY, "");
        if (PlayerPrefs.GetString(BoardHandler.HISTORY).Length > 5)
            myHistoryTxt = PlayerPrefs.GetString(BoardHandler.HISTORY);
        else
            myHistoryTxt = "";
        btn_History.onClick.AddListener(openHistory);
        btn_OpenRecords.onClick.AddListener(openHistory);
        btn_Reset.onClick.AddListener(resetHistory);


        // Debug.Log(getDateStr());
        btn_Close.onClick.AddListener(closeHistory);
    }

    void openHistory()
    {
        LoadHistory();
        viewHistory();
    }

    void resetHistory()
    {
        PlayerPrefs.SetString(BoardHandler.HISTORY, "");
        toastPopup.Popup("Deleted all history");
        if (List_HistoryItem != null)
        {
            for (int i = 0; i < List_HistoryItem.Count; i++)
            {
                Destroy(List_HistoryItem[i].Go_Me);
            }
        }
        myHistoryTxt = "";
        listHistory.Clear();
        List_HistoryItem.Clear();
    }


    public void SaveHistory(string _dificult, string _time, string _mistake, string _hint)
    {
        string _date = getDateStr();
        string newHistory = _date + ";" + _dificult + ";" + _time + ";" + _mistake + ";" + _hint;
        if (myHistoryTxt.Length > 5)
            myHistoryTxt = myHistoryTxt + "," + newHistory;
        else
            myHistoryTxt = newHistory;
        PlayerPrefs.SetString(BoardHandler.HISTORY, myHistoryTxt);
    }

    public void LoadHistory()
    {
        listHistory = new List<HistoryData>();
        string[] words = myHistoryTxt.Split(',');
        foreach (var word in words)
        {
            HistoryData item = new HistoryData();
            string[] datas = word.Split(';');
            for (int i = 0; i < datas.Length; i++)
            {
                if (i == 0)
                    item.date = datas[i];
                else if (i == 1)
                    item.dificult = datas[i];
                else if (i == 2)
                    item.time = datas[i];
                else if (i == 3)
                    item.mistake = datas[i];
                else if (i == 4)
                    item.hint = datas[i];
            }
            listHistory.Add(item);
        }
    }

    void viewHistory()
    {
        if (List_HistoryItem != null)
        {
            for (int i = 0; i < List_HistoryItem.Count; i++)
            {
                Destroy(List_HistoryItem[i].Go_Me);
            }
        }
        List_HistoryItem.Clear();

        for (int i = 0; i < listHistory.Count; i++)
        {
            GameObject go = Instantiate(go_Prefab);
            go.SetActive(true);
            go.transform.SetParent(t_Content);
            go.transform.localScale = Vector3.one;
            go.transform.localPosition = Vector3.zero;
            HistoryItem item = new HistoryItem
            {
                Txt_Date = go.transform.GetChild(0).GetComponent<Text>(),
                Txt_Level = go.transform.GetChild(1).GetComponent<Text>(),
                Txt_Time = go.transform.GetChild(2).GetComponent<Text>(),
                Txt_Mis = go.transform.GetChild(3).GetComponent<Text>(),
                Txt_Hint = go.transform.GetChild(4).GetComponent<Text>(),
                Go_Me = go
            };
            item.Txt_Date.text = listHistory[i].date;
            item.Txt_Level.text = listHistory[i].dificult;
            item.Txt_Time.text = listHistory[i].time;
            item.Txt_Mis.text = listHistory[i].mistake;
            item.Txt_Hint.text = listHistory[i].hint;
            List_HistoryItem.Add(item);
        }
        if (myHistoryTxt.Length > 0)
        {
            go_NoData.SetActive(false);
        }
        else
        {
            go_NoData.SetActive(true);
        }
        cg_View.blocksRaycasts = true;
        cg_View.DOFade(1, 0.35f).SetEase(BoardHandler.SineInOut90);
        rt_View.DOScale(1, 0.35f).SetEase(BoardHandler.SineInOut90);
    }

    void closeHistory()
    {
        cg_View.blocksRaycasts = false;
        cg_View.DOFade(0, 0.35f).SetEase(BoardHandler.SineInOut90);
        rt_View.DOScale(0.9f, 0.35f).SetEase(BoardHandler.SineInOut90);
    }

    string getDateStr()
    {
        return DateTime.Now.ToString("yyyy-MM-dd\\ HH:mm");
    }
}

 

MenuHandler.cs 작성

게임화면에서 메뉴화면을 호출하기 위한 파일.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;

public class MenuHandler : MonoBehaviour
{
    [SerializeField] RectTransform RT_Menu;
    RectTransform rt_me;
    [SerializeField] Button Btn_Open, Btn_Close, Btn_CloseX;
    [SerializeField] CanvasGroup CG_Me;
    bool isOpen;
    float height;

    void Start()
    {
        Btn_Open.onClick.AddListener(openMenu);
        Btn_Close.onClick.AddListener(closeMenu);
        Btn_CloseX.onClick.AddListener(closeMenu);
        rt_me = GetComponent<RectTransform>();
        height = RT_Menu.rect.height;
        // Debug.Log(height);
        isOpen = false;
        CG_Me.blocksRaycasts = false;
        CG_Me.alpha = 0;
        RT_Menu.anchoredPosition = new Vector2(-1080, 0);
    }

    void openMenu()
    {
        BoardHandler.isPlay = false;
        isOpen = true;
        CG_Me.blocksRaycasts = true;
        CG_Me.alpha = 0;
        CG_Me.DOFade(1, 0.2f);
        RT_Menu.DOAnchorPosX(0, 0.35f).SetEase(BoardHandler.SineInOut90);
    }
    void closeMenu()
    {
        BoardHandler.isPlay = true;
        isOpen = false;
        CG_Me.blocksRaycasts = false;
        RT_Menu.DOAnchorPosX(-1080, 0.35f).SetEase(BoardHandler.SineInOut90);
        CG_Me.DOFade(0, 0.35f);
    }
}

 

NewGamePopupHandler.cs 작성

새로운 게임을 하기위해 버튼을 누를 경우 새로운 게임을 할 수 있게 하는 팝업을 호출 하기 위한 파일.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;

public class NewGamePopupHandler : MonoBehaviour
{
    [SerializeField] CanvasGroup CG;
    public Button Btn_Cancel, Btn_RePlay, Btn_NewGame;
    void Start()
    {
        Btn_Cancel.onClick.AddListener(Close);
        Btn_NewGame.onClick.AddListener(Close);
        Btn_RePlay.onClick.AddListener(Close);
        gameObject.SetActive(false);
    }

    void Close()
    {

        CG.blocksRaycasts = false;
        CG.DOFade(0, 0.3f).SetEase(BoardHandler.SineInOut90).OnComplete(() =>
        {
            gameObject.SetActive(false);
        });
    }

    public void Open()
    {
        gameObject.SetActive(true);
        CG.blocksRaycasts = true;
        CG.DOFade(1, 0.3f).SetEase(BoardHandler.SineInOut90);
    }
}

 

ToastPopupHandler.cs 작성

게임 중에 간단한 팝업 문구를 띄우기 위한 스크립트.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;

public class ToastPopupHandler : MonoBehaviour
{
    [SerializeField] Text Txt_Msg;
    [SerializeField] CanvasGroup CG;
    [SerializeField] Button Btn_Dim;
    public void Popup(string msg)
    {
        CG.DOKill();
        CG.alpha = 0;
        CG.DOFade(1, 0.3f).SetEase(Ease.OutQuart);
        CG.blocksRaycasts = true;
        Txt_Msg.text = msg;
        CancelInvoke("Hide");
        Invoke("Hide", 2f);
    }

    void Start()
    {
        Hide();
        Btn_Dim.onClick.AddListener(() =>
        {
            CancelInvoke("Hide");
            Hide();
        });
    }

    void Hide()
    {
        CG.blocksRaycasts = false;
        CG.DOFade(0, 0.3f).SetEase(Ease.OutQuart);
    }
}

 

UnityAdsManager.cs 작성

유니티에서 제공하고 있는 광고를 띄우기 위한 스크립트.

광고에 대한 부분은 추가로 포스팅 예정.

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.Advertisements;

public class UnityAdsManager : MonoBehaviour
{
    public BoardHandler Board;
    public ToastPopupHandler Toast;
    public string bannerId = "banner";
    public string rewardedId = "rewardedVideo";
    public string videoId = "video";
    public bool testMode = true;

#if UNITY_IOS 
    public const string gameID = "IOS용 광고아이디";
#elif UNITY_ANDROID
    public const string gameID = "Android용 광고아이디"; 
#elif UNITY_EDITOR
    public const string gameID = "";
#endif

    void Start()
    {
        Advertisement.Initialize(gameID, testMode);
        ShowBanner();
    }

    public void ShowBanner()
    {

        StartCoroutine(ShowBannerWhenReady());
    }

    IEnumerator ShowBannerWhenReady()
    {
        while (!Advertisement.isInitialized)
        {
            yield return new WaitForSeconds(0.5f);
        }
        Advertisement.Banner.Show(bannerId);
    }

    static string myTarget;
    public void AdsShow(string target = "hint")
    {
        myTarget = target;
        ShowRewardedAd();
    }
    public void AdsShowNewGame()
    {

    }

    /// === New Game Ads ==========

    public void ShowNewGameAd()
    {
        if (Advertisement.IsReady(videoId))
        {
            var options = new ShowOptions { resultCallback = HandleNewGameAdShowResult };
            Advertisement.Show(videoId, options);
        }
    }

    private void HandleNewGameAdShowResult(ShowResult result)
    {
        switch (result)
        {
            case ShowResult.Finished:
                Board.resetAll();
                break;
            case ShowResult.Skipped:
                Board.resetAll();
                break;
            case ShowResult.Failed:
                Toast.Popup("Ads Load failed.");
                break;
        }
    }


    /// === Reward Ads ===========

    public void ShowRewardedAd()
    {
        if (Advertisement.IsReady(rewardedId))
        {
            // 광고가 끝난 뒤 콜백함수 "HandleShowResult" 호출 
            var options = new ShowOptions { resultCallback = HandleShowResult };
            Advertisement.Show(rewardedId, options);
        }
    }

    // 광고가 종료된 후 자동으로 호출되는 콜백 함수 
    private void HandleShowResult(ShowResult result)
    {
        switch (result)
        {
            case ShowResult.Finished:
                // 광고를 성공적으로 시청한 경우 보상 지급 
                if (myTarget == "hint")
                {
                    Board.SetHint = 1;
                    Board.Invoke("HintOpen", 0.5f);
                }
                else if (myTarget == "rePlay")
                {
                    Board.RePlayGame();
                }
                break;
            case ShowResult.Skipped:
                // 스킵 되었다면 뭔가 그런짓을 하면 보상을 줄 수 없잖아! 같은 창을 띄워야곘죠?
                Toast.Popup("Skiped Ads...");
                break;
            case ShowResult.Failed:
                Toast.Popup("Skiped Ads or Load failed");
                break;
        }
    }
}

 

신을 만들고 세팅하는 부분은 따로 시간 나면 정리 예정...

반응형

'unity C#' 카테고리의 다른 글

[Unity] MQTT 통신 구현  (0) 2021.10.19
[Unity] Tetris 게임 만들기  (1) 2021.06.25
[Unity] UDP 통신  (0) 2021.05.04
[Unity] Partial class  (0) 2021.05.04
[Unity] Sprite Atlas  (0) 2021.04.13
반응형

UDP(User Datagram Protocol)는 TCP와 같이 IP에 기반한 전송 프로토콜이다.

TCP는 송수신 전에 반드시 서버-클라이언트 연결이 전제되어야 하는 반면, UDP는 별도의 연결이 필요없다.


유니티에서 UDP를 사용하기 위해서는 System.Net.Sockets 네임스페이스 안의 UdpClient 클래스Socket 클래스를 사용한다. TCP와 달리 UDP는 별도의 UDP 서버 클래스가 없으며, 서버도 UdpClient 클래스를 사용한다.

 

 

*UDP 클라이언트 구현

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using static System.Console;



(1) UdpClient 객체 성성

UDP 통신을 위해 System.Net.Sockets 네임스페이스의 UdpClient 객체를 생성한다. 데이터를 내보낼 때 서버와 포트가 필요한데, UdpClient 생성자에서 잡아주는 경우도 있지만, 만약 하나의 UdpClient 객체로 여러 서버에 데이터를 보낼 경우는 Send() 메서드에서 서버와 포트를 지정해준다. 

UdpClient cli = new UdpClient();



(2) 데이터 송신

UdpClient 객체의 Send() 메서드를 사용하여 데이터를  IP와 포트번호에 맞게 보낸다. 네트워크 데이터 송수신은 바이트 데이터를 사용하기 때문에, 문자열을 보낼 경우 먼저 바이트로 인코딩한 후 보내게 된다.

msg = "안녕하세요";
byte[] datagram = Encoding.UTF8.GetBytes(msg);
cli.Send(datagram, datagram.Length, "127.0.0.1", 7777);
WriteLine("[Send] 127.0.0.1:7777 로 {0} 바이트 전송", datagram.Length);



(3) 데이터 수신

UDP에서 데이타를 수신할 경우는 UdpClient 객체의 Receive() 메서드를 사용한다. Receive() 메서드는 특히 수신 데이타와 함께 상대 컴퓨터의 종단점(IP주소와 포트) 정보도 같이 전달받는데, 이를 위해 IPEndPoint 객체를 ref 파라미터로 전달한다. 이를 통해 데이타가 수신되면 누가 그 데이타를 전송했는지 알 수 있다. 

IPEndPoint epRemote = new IPEndPoint(IPAddress.Any, 0);
byte[] bytes = cli.Receive(ref epRemote);
WriteLine("[Receive] {0} 로부터 {1} 바이트 수신", epRemote.ToString(), bytes.Length);

 

 

(4) UdpClient 객체 닫기

마지막으로 UdpClient 객체를 닫는다.

cli.Close();

 

 

*UDP 서버 구현

 

UDP 서버는 포트를 열고 클라이언트로부터 들어오는 데이타그램을 수신하게 된다. 즉, UDP 서버는 통상 UDP 포트를 Listening하고 있으면서 루프 안에서 계속 데이타 송수신을 처리하는 형태로 구현된다. UDP 클라이언트로부터 데이타그램을 직접 받아 처리하면 된다. UDP 서버는 UDP 클라이언트와 같이 거의 동일한 기능을 갖기 때문에 별도의 UDP 서버 클래스가 없고 UdpClient 클래스를 사용한다.

using System;
using System.Net;
using System.Net.Sockets;

 

 

(1) UdpClient 객체 성성

UDP 클라이언트로부터 데이타를 받아들이기 위해 먼저 Listening할 포트를 지정하며 UdpClient 객체를 생성한다.

// 포트 7777 에서 Listening
UdpClient srv = new UdpClient(7777);



(2) 데이터 수신

UDP 에서 데이타를 수신하기 위해 UdpClient 객체의 Receive() 메서드를 사용한다. Receive() 메서드는 특히 수신 데이타와 함께 상대 UDP 클라이언트 종단점(IP주소와 포트) 정보도 같이 전달받는데, 이를 위해 IPEndPoint 객체를 ref 파라미터로 전달한다. 데이타 수신 후, ref 파라미터를 체크하면 데이타를 보낸 UDP 클라이언트의 IP 주소와 포트를 알 수 있다.

// 클라이언트 IP를 담을 변수
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);

while(true) {
    byte[] dgram = srv.Receive(ref remoteEP);
    Console.WriteLine("[Receive] {0} 로부터 {1} 바이트 수신", remoteEP.ToString(), dgram.Length);
}

 

 

(3) 데이타 송신

UdpClient 객체의 Send() 메서드를 사용하여 데이타를 UDP 클라이언트로 전달한다. 이 때 클라이언트 IP는 위의 Receive() 메서드에서 받아온 IP 주소를 사용한다.

srv.Send(dgram, dgram.Length, remoteEP);
Console.WriteLine("[Send] {0} 로 {1} 바이트 송신", remoteEP.ToString(), dgram.Length);

 

 

*샘플 스크립트

 

udp_connector.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.UI;


public class UDPConnector : MonoBehaviour {
    UdpClient ReceivePort, SendPort;
    IPEndPoint remoteEndPoint;

    public class MyUDPEvent : UnityEvent<string> { }
    public MyUDPEvent OnReceiveMessage = new MyUDPEvent ();


    void Start () {
        OnReceiveMessage.AddListener (receiveMsg);
    }

    void Init () {
        ReceivePort = new UdpClient (Int32.Parse (IF_ReceivePort.text));
        ReceivePort.BeginReceive (OnReceive, null);
        Send ();
    }

    public void Send (string msg = "receive from Unity") {
        SendPort = new UdpClient ();

        string remoteIP = IF_SendIP.text;
        int remotePort = Int32.Parse (IF_SendPort.text);
        remoteEndPoint = new IPEndPoint (IPAddress.Parse (remoteIP), remotePort);

        byte[] data = System.Text.Encoding.UTF8.GetBytes (msg);
        SendPort.Send (data, data.Length, remoteEndPoint);
    }

    void OnReceive (IAsyncResult ar) {
        try {
            IPEndPoint ipEndPoint = null;
            byte[] data = ReceivePort.EndReceive (ar, ref ipEndPoint);
            message = System.Text.Encoding.UTF8.GetString (data);
        } catch (SocketException e) { }

        ReceivePort.BeginReceive (OnReceive, null);
    }

    void receiveMsg (string msg) {
        Txt_Message.text = Txt_Message.text + msg + "\n";
        // Debug.Log(message);
    }

    void Update () {
        OnReceiveMessage.Invoke (message);
    }

    public void ShutDown () {
        OnReceiveMessage.RemoveAllListeners ();
        if (ReceivePort != null)
            ReceivePort.Close ();
        if (SendPort != null)
            SendPort.Close ();
        ReceivePort = null;
        SendPort = null;
    }
}

 

communication_handler.cs

public class CommunicationHandler : MonoBehaviour {
    public UDPConnector UDP;

    void Start () {
        UDP.OnReceiveMessage.AddListener (ReceiveMsg);
    }

    void ReceiveMsg (string msg) {
    
    }

    public void SendMsg (string msg) {
        UDP.Send (msg);
    }

    void OnApplicationQuit () {
        UDP.ShutDown ();
    }
}

 

반응형

'unity C#' 카테고리의 다른 글

[Unity] Tetris 게임 만들기  (1) 2021.06.25
[Unity] 스도쿠게임 만들기  (0) 2021.06.15
[Unity] Partial class  (0) 2021.05.04
[Unity] Sprite Atlas  (0) 2021.04.13
[Unity] vscode .net framework C# 에러문제  (1) 2020.08.28
반응형

출처 : skuld2000.tistory.com/28

 

[Unity] 이제 Sprite Packer 는 그만. Sprite Atlas 를 사용하자.

유니티에서 아틀라스(Atlas) 는 여러 개의 스프라이트(Sprite)를 한 장의 큰 텍스쳐에 모아놓은 것이라고 볼 수 있다. 게임에는 UI와 배경, 캐릭터, 이펙트 등등에 수많은 텍스쳐 들이 사용되는데 각

skuld2000.tistory.com

사용 방법은 매우매우 간단하다고는 하지만 ...

개인적으로는 예전 방식이 더 괜찮았던 것 같다.

한꺼번에 선택하고 패킹태그만 붙이면되는데...

바뀐 방식은 일일이 하나씩 혹은 여러개 선택해서

넣어 줘야 하는 번거로움?이 좀 있다고 생각하던 중

폴더째 드래그 해서 넣어도 된다고 한다.

결론은 좋아진게 확실하니 한번 써보자.

 

📌사용방법

👉 Sprite Atlas 생성

 

👉 Sprite 옵션 설정

 

👉 Tight Packing을 할 경우에 가장자리 투명도가 있는 이미지의 여백을 무시하고 합쳐주기 때문에 문제가 생길 수가 있다. 문제가 되는 이미지는 해당 이미지 파일을 선택하여 FullRect옵션으로 바꿔주고 Apply 눌러주면 간단히 해결된다.

다른 방법으로는 Sprite Editor를 사용해 커스텀라인을 만들고 Use Sprite Mesh옵션을 켜서 해당 이미지가 그려 질 때 커스텀라인 안쪽으로만 그려주게 하는 방법도 있지만 몇장 안되는 이미지라면 큰 차이가 없어보인다. 자세한 건 링크를 확인하자.

 

👉 일단 아틀라스로 묶어놓게 되면 타겟플랫폼별 이미지 옵션을 설정할 때 한방에 처리할 수 있다.

 

👉 무엇보다 큰 장점이라고 하면 위에 보이는것 처럼 폴더째로 넣어줘도 된다는게 맘에 든다.

 

끝.

반응형

'unity C#' 카테고리의 다른 글

[Unity] UDP 통신  (0) 2021.05.04
[Unity] Partial class  (0) 2021.05.04
[Unity] vscode .net framework C# 에러문제  (1) 2020.08.28
[Unity] EventSystem설정 터치 감도 조절  (0) 2020.06.30
[Unity] Animation Curve DOTween 커스터마이징  (0) 2020.06.29
반응형

[info]: OmniSharp.Stdio.Host
        Starting OmniSharp on MacOS 10.15.6 (x64)
[info]: OmniSharp.Services.DotNetCliService
        DotNetPath set to dotnet
[info]: OmniSharp.MSBuild.Discovery.MSBuildLocator
        Located 1 MSBuild instance(s)
            1: StandAlone 16.8.0 - "/Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild/Current/Bin"
[info]: OmniSharp.MSBuild.Discovery.MSBuildLocator
        MSBUILD_EXE_PATH environment variable set to '/Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild/Current/Bin/MSBuild.exe'
[info]: OmniSharp.MSBuild.Discovery.MSBuildLocator
        Registered MSBuild instance: StandAlone 16.8.0 - "/Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild/Current/Bin"
            CscToolExe = csc.exe
            MSBuildToolsPath = /Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild/Current/Bin
            CscToolPath = /Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild/Current/Bin/Roslyn
            BypassFrameworkInstallChecks = true
            MSBuildExtensionsPath = /Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild
[info]: OmniSharp.Cake.CakeProjectSystem
        Detecting Cake files in '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS'.
[info]: OmniSharp.Cake.CakeProjectSystem
        Could not find any Cake files
[info]: OmniSharp.MSBuild.ProjectSystem
        Detecting projects in '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Owao_IOS.sln'.
[info]: OmniSharp.MSBuild.ProjectManager
        Queue project update for '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp.csproj'
[info]: OmniSharp.MSBuild.ProjectManager
        Queue project update for '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-firstpass.csproj'
[info]: OmniSharp.MSBuild.ProjectManager
        Queue project update for '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor.csproj'
[info]: OmniSharp.MSBuild.ProjectManager
        Queue project update for '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor-firstpass.csproj'
[info]: OmniSharp.Script.ScriptProjectSystem
        Detecting CSX files in '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS'.
[info]: OmniSharp.Script.ScriptProjectSystem
        Could not find any CSX files
[info]: OmniSharp.WorkspaceInitializer
        Invoking Workspace Options Provider: OmniSharp.Roslyn.CSharp.Services.CSharpFormattingWorkspaceOptionsProvider, Order: 0
[info]: OmniSharp.MSBuild.ProjectManager
        Loading project: /Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp.csproj
[info]: OmniSharp.WorkspaceInitializer
        Invoking Workspace Options Provider: OmniSharp.Roslyn.CSharp.Services.RenameWorkspaceOptionsProvider, Order: 100
[info]: OmniSharp.WorkspaceInitializer
        Invoking Workspace Options Provider: OmniSharp.Roslyn.CSharp.Services.ImplementTypeWorkspaceOptionsProvider, Order: 110
[info]: OmniSharp.WorkspaceInitializer
        Invoking Workspace Options Provider: OmniSharp.Roslyn.CSharp.Services.BlockStructureWorkspaceOptionsProvider, Order: 140
[info]: OmniSharp.WorkspaceInitializer
        Configuration finished.
[info]: OmniSharp.Stdio.Host
        Omnisharp server running using Stdio at location '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS' on host 20273.
[fail]: OmniSharp.MSBuild.ProjectLoader
        The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks
[warn]: OmniSharp.MSBuild.ProjectManager
        Failed to load project file '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp.csproj'.
/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp.csproj
/Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild/Current/Bin/Microsoft.Common.CurrentVersion.targets(1178,5): Error: The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks

[info]: OmniSharp.MSBuild.ProjectManager
        Loading project: /Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-firstpass.csproj
[fail]: OmniSharp.MSBuild.ProjectLoader
        The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks
[warn]: OmniSharp.MSBuild.ProjectManager
        Failed to load project file '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-firstpass.csproj'.
/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-firstpass.csproj
/Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild/Current/Bin/Microsoft.Common.CurrentVersion.targets(1178,5): Error: The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks

[info]: OmniSharp.MSBuild.ProjectManager
        Loading project: /Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor.csproj
[fail]: OmniSharp.MSBuild.ProjectLoader
        The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks
[warn]: OmniSharp.MSBuild.ProjectManager
        Failed to load project file '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor.csproj'.
/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor.csproj
/Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild/Current/Bin/Microsoft.Common.CurrentVersion.targets(1178,5): Error: The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks

[info]: OmniSharp.MSBuild.ProjectManager
        Loading project: /Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor-firstpass.csproj
[fail]: OmniSharp.MSBuild.ProjectLoader
        The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks
[warn]: OmniSharp.MSBuild.ProjectManager
        Failed to load project file '/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor-firstpass.csproj'.
/Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor-firstpass.csproj
/Users/jangbeom-seok/.vscode/extensions/ms-dotnettools.csharp-1.23.1/.omnisharp/1.37.0/omnisharp/.msbuild/Current/Bin/Microsoft.Common.CurrentVersion.targets(1178,5): Error: The reference assemblies for .NETFramework,Version=v4.7.1 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks

[fail]: OmniSharp.MSBuild.ProjectManager
        Attempted to update project that is not loaded: /Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp.csproj
[fail]: OmniSharp.MSBuild.ProjectManager
        Attempted to update project that is not loaded: /Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-firstpass.csproj
[fail]: OmniSharp.MSBuild.ProjectManager
        Attempted to update project that is not loaded: /Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor.csproj
[fail]: OmniSharp.MSBuild.ProjectManager
        Attempted to update project that is not loaded: /Users/jangbeom-seok/Desktop/Project/Project_Owao/Owao_IOS/Assembly-CSharp-Editor-firstpass.csproj

유니티를 새로 설치하고 이런에러가 뜨면서 코드힌트라든지 레퍼런스가 뜨지 않는 경우가 생긴다.

구글링하다보니 Mono설정문제인듯 하다.

참고 : github.com/OmniSharp/omnisharp-vscode/issues/3063

 

세팅 검색창에 mono라고 검색해 보면 Omnisharp: Use Global Mono 라는 부분이 나오는데 auto에서 always로 바꿔주면 에러가 사라진다.

재시작 해주면 정상 작동 한다.

반응형
반응형

private const float inchToCm = 2.54f; 
[SerializeField] private EventSystem eventSystem = null; 
[SerializeField] private float dragThresholdCM = 0.5f; //For drag Threshold 
private void SetDragThreshold() { 
    if (eventSystem != null) { 
        eventSystem.pixelDragThreshold = (int)(dragThresholdCM * Screen.dpi / inchToCm); 
    } 
} 

void Awake() { 
    SetDragThreshold(); 
}

Unity로 안드로이드 앱을 개발하다보면 스크롤 안에 버튼이 들어갈 경우가 종종 있다.

PC에서 테스트 할 때는 마우스로 잘 눌리던 버튼이 스마트 폰에 넣어서 테스트 해보면 간혹 잘 눌리지 않는 경우가 있다. 이럴때 EventSystem 설정을 바꿔줘야하는데 다음과 같이 세팅 하면 적당하다.

 

반응형
반응형

[진행과정]

1. 아두이노에 HC-06블루투스 모듈 설치

2. 아두이노 세팅 및 코딩

3. 유니티프로젝트 생성 및 세팅 (

4. 안드로이드 빌드 및 블루투스 연결

 

요렇게 진행합니다.

 

1. 아두이노에 HC-06모듈 및 마그네틱 센서 설치

HC-06 : 디지털인풋 2/3번/GND/5V 에 연결.

마그네틱 센서 : 디지털인풋 8번/GND에 연결.

hc-06 모듈 및 마그네틱센서 설치

참조1

 

[아두이노 - 블루투스 연동]

안녕하세요!! 이번 포스팅은 아두이노에서 블루투스 연동을 통해 앱과 통신하는 방법을 알아보겠습니다. * ...

blog.naver.com

참조2

 

[Unity]04.아두이노 사용하기 - Blutooth로 채팅하기

안녕하세요 유랑입니다. 오늘은 아두이노와 유니티 통신하는 방법에 대해서 알아보겠습니다. 통신방법은 블루투스를 사용하겠습니다^^ 1. 아두이노와 유니티로 채팅하기 유니티에서 Android 프로젝트에서 디바이스..

you-rang.tistory.com

2. 아두이노 세팅 및 코딩

먼저 아두이노사이트에 가서 아두이노를 설치해 줍니다.

아두이노 프로그램 다운로드

아두이노와 PC를 USB로 연결 한 후 아래 그림처럼 순서대로 보드와 포트를 자신에 맞는 것으로 선택해 줍니다.

아두이노 보드/포트 설정

블루투스 모듈을 슬레이브로변경하기 위해,시리얼 모니터창을 열어 아래 명령어를  입력해줍니다.

AT+ROLE=S

참고

 

HC-06 블루투스모듈 설정 변경하기! (마스터에서 슬레이브, 슬레이브에서 마스터로, HC-06자가진단하기!)

안녕하세요 메카솔루션입니다. 이번에는 HC-06의 마스터와 슬레이브를 수정하는방법과, 그리고 간단하게 ...

blog.naver.com

시리얼 모니터창에서 “AT”라고입력하여 OK가 반환되면,블루투스 모듈 연결이 잘 된 것입니다.

이후,AT+NAME이름을 입력하면 블루투스 모듈 이름을 설정할 수 있습니다.(AT+NAME000입력 시,000으로이름 설정)

그리고 AT+PIN핀번호 4자리를 입력하면 블루투스 연결 시 비밀번호를 설정할 수 있습니다.

마지막으로 AT+BAUD4 을 입력하여 OK9600이 반환되는 것을 확인합니다.(통신 속도 설정)

참고

 

[아두이노 - 블루투스 연동]

안녕하세요!! 이번 포스팅은 아두이노에서 블루투스 연동을 통해 앱과 통신하는 방법을 알아보겠습니다. * ...

blog.naver.com

모두 설정되었다면 아두이노에 코딩작업을 해줍니다.

#include<SoftwareSerial.h>

SoftwareSerial BTSerial (3, 2); //Connect HC-06. Use your (TX, RX) settings

int val;
int tempVal;

void setup () {
  Serial.begin (9600);
  BTSerial.begin (9600); // set the data rate for the BT port
  pinMode (8, INPUT_PULLUP);
}

void loop () {
  //switch value update
  val = digitalRead (8);

  // BT –> Data –> Serial
  if (BTSerial.available ()) {
    Serial.println ("BT available");
    Serial.write (BTSerial.read ());
  }

  if (val == HIGH) {
    if (val != tempVal) {
      tempVal = val;
      BTSerial.println ("off");
      // Do Something

    }
  } else {
    if (val != tempVal) {
      tempVal = val;
      BTSerial.println ("on");
      // Do Something

    }
  }
  delay (100);
}

코드를 대략 요약하자면 마그네틱센서가 붙으면 "on", 떨어지면 "off"라는 스티링을 블루투스 시리얼로 보내는 동작을 합니다.

찾아본 바로는 BTSerial.write("on"); 요렇게 하라고 되어있었는데 전혀 아무런 신호가 보내지지 않아서 저렇게 바꿔보니 잘되더군요.

코딩작업을 마쳤으면 두번째 아이콘을 눌러 아두이노에 업로드를 합니다.

 

3. 유니티프로젝트 생성 및 세팅

적당한 이름으로 새로운 프로젝트를 만들고 아래 파일을 다운받아 임포트 시켜줍니다.

ArduinoBluetooth.unitypackage
1.23MB

Build Settings / Player Settings… 를 열어 Other Settings를 다음과 같이 설정합니다. 

Bundle Identifier 는 똑같지 않아도 상관없으며ApiCompatibility Level* 이부분이 .Net4.x 로 선택해주어야 합니다.

안드로이드로 타겟플랫폼을 변경해주고

새로운 Scene을 만들고 Project탭의Assets/ArduinoBluetooth/Demo/Prefab폴더로 이동하여

__Manager파일을 Hierarchy에 끌어 넣습니다.

 

숫자 "0"번 키로 아래 GUI를 끄고 켤수 있습니다.

 

__Manager를 클릭해보면 Manager.cs라는 스크립트파일이 붙어있습니다.

Manager.cs를 열어서 deviceName 을 위에 아두이노 설정시 변경했던 블루투스 이름과 동일하게 수정해줍니다.

예)AT+NAMEHC-06

OnDoorOpenOnDoorClose위 두가지 이벤트가 있고외부 클래스에서 다음과 같이 사용할 수 있습니다.

public Manager _manager;

void Start(){
    _manager.onDoorOpen.AddListener(OnDoorOpen);
    _manager.onDoorClose.AddListener(OnDoorClose);
}

void OnDoorOpen(){
    //문이 열렸을 때
}
void OnDoorOpen(){
    //문이 닫혔을 때
}
…

 

연결 테스트영상입니다.

 

반응형
반응형

간혹 빌드가 끝나고나서 특정 이미지를 교체해야 하거나 영상을 바꿔야 되면 해당 비디오 파일 또는 이미지 파일을 교체 후 새롭게 빌드를 시켜야 하는 번거러운 작업을 해야할 경우가 생긴다. 

교체해야할 상황이 빈번하다고 판단되는 프로젝트일 경우 특정 폴더에 교체 할 소스 파일을 넣어두고 해당 소스파일만 바꾸면 새로 빌드할 필요가 없게 미리 처리해 두면 될 것이다. 

using System.Collections.Generic;
using System.IO;
using UnityEngine;

public class FileItemHandler : MonoBehaviour {
    public string ParentFolderName;
    public string TargetFolderName;
    public List<string> FilePathList;
    public List<string> CoverPathList;
    public List<Texture2D> CoverSpriteList;
    string filepath;
    void Awake () { GetPathList (); }

    void GetPathList () {
        string _path = "";

        //타켓 폴더 패스 설정
        if (Application.platform == RuntimePlatform.Android) {
            //android일 경우 //
            _path = AndroidRootPath () + "Download/FH/" + ParentFolderName + "/" + TargetFolderName;
        } else {
            //unity일 경우 //
            _path = System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal) + "/Desktop/FH/" + ParentFolderName + "/" + TargetFolderName;
        }

        DirectoryInfo Folder = new DirectoryInfo (_path);

        //각 비디오의 패스(URL) 리스트 만들기
        foreach (var file in Folder.GetFiles ()) {
            if (file.Extension != ".meta" && file.Extension != ".DS_Store") { //비디오 이외의 확장자를 가진 파일 제외시키기
                filepath = _path + "/" + file.Name;
                if (!filepath.Contains ("._")) { //파일명 에러 수정해주기
                    // filepath = filepath.Replace ("._", "");
                    if (filepath.Contains (".mp4")) //비디오 파일 add 리스트
                        FilePathList.Add (filepath);
                    else if (filepath.Contains (".jpg")) { //커버이미지 파일 add 리스트
                        CoverPathList.Add (filepath);
                        Texture2D tex = null;
                        byte[] filedata;
                        if (File.Exists (filepath)) {
                            filedata = File.ReadAllBytes (filepath);
                            tex = new Texture2D (2, 2);
                            tex.LoadImage (filedata);
                            // Sprite sp = SpriteFromTexture2D (tex);
                            CoverSpriteList.Add (tex);
                        }
                    }
                }
            }
        }
        Debug.Log(ParentFolderName + "/" + TargetFolderName + ", FileCount : " + FilePathList.Count+ ",, SpriteCount : " + CoverSpriteList.Count);
    }

    string AndroidRootPath () {
        string[] temp = (Application.persistentDataPath.Replace ("Android", "")).Split (new string[] { "//" }, System.StringSplitOptions.None);
        return (temp[0] + "/");
    }

    Sprite SpriteFromTexture2D (Texture2D texture) {
        return Sprite.Create (texture, new Rect (0.0f, 0.0f, texture.width, texture.height), new Vector2 (0.5f, 0.5f), 100.0f);
    }
}

 

 

반응형
반응형

1번 : 이미지 가장자리에 여백이 없고 Filter Mode를 Bilinear로 세팅.

2번 : 이미지 가장자리에 여백이 없고 Filter Mode를 Pointer로 세팅.

3번 : 이미지 가장자리에 4방으로 2px의 여백을 주고 Filter Mode를 Bilinear로 세팅.

4번 : 이미지 가장자리에 4방으로 2px의 여백을 주고 Filter Mode를 pointer로 세팅.

UI Canvas에서 설정 별 이미지 깨짐 현상 비교

보통 Sprite 이미지를 임포트 해서 Canvas 상에 올린 후 로테이션값을 조절해 사용하다보면 위 이미지에서 처럼 가장자리가 깨지거나 혹은 가장자리만 깨지는 현상을 경험할 수 있다. 결론부터 얘기해서 3번처럼 보이게 하기 위해서는 포토샵에서 아래처럼 이미지의 바깥쪽에 1~2px의 여백을 만들어 가져오면 해결된다. 가져온 이미지는 Filtermode를 Bilinear 또는 Trilinear로 선택해주고 Apply시켜준다.

포토샵에서 이미지 가장자리에 강제로 여백을 준다.
Filter Mode

 

 

참고 : https://gamedev.stackexchange.com/questions/139626/is-ui-anti-aliasing-broken-in-unity-5-6

 

Is UI anti-aliasing broken in Unity 5.6?

I have a project that was started in Unity 5.5. When I open it in 5.6, most 2D graphics look like they're not anti-aliased: left is 5.6, right is 5.5 What you see there are UI Images in a Canvas....

gamedev.stackexchange.com

 

반응형

+ Recent posts