반응형

몫 double quotient = System.Math.Truncate(c); 

나머지 double remainder = a % b;

 

float 형으로 반환

반올림 Mathf.Round(3.5f) : 4f

올림 Mathf.Ceil(3.5f : 4f

내림 Mathf.Floor(3.5f) : 3f

 

int 형으로 반환

반올림 Mathf.RoundToInt(3.5f) : 4

올림 Mathf.CeilToInt(3.5f) : 4

내림 Mathf.FloorToInt(3.5f) : 3

 

<string to int 스트링에서 인트로 변환>

answer

Int = int.Parse(answer);

반응형

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

[Unity] 화면보호기 끄기  (0) 2023.01.30
[Unity] 중첩 코루틴 Coroutine  (0) 2023.01.30
[Unity] UI Blur Shader  (0) 2023.01.05
[Unity] VS Code Settings Sync  (0) 2022.12.26
[Unity] 암호화 복호화  (0) 2022.11.29
반응형
Screen.sleepTimeout = SleepTimeout.NeverSleep;

 

반응형

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

[Unity] float 반올림, 올림, 내림  (0) 2023.01.30
[Unity] 중첩 코루틴 Coroutine  (0) 2023.01.30
[Unity] UI Blur Shader  (0) 2023.01.05
[Unity] VS Code Settings Sync  (0) 2022.12.26
[Unity] 암호화 복호화  (0) 2022.11.29
반응형

중첩 코루틴

코루틴 내부에서 또다른 코루틴을 호출한다. 해당 코루틴이 완료될 때까지 기다리게 된다. 의존성이 있는 여러 작업을 수행하는데 유리하게 사용 될 수 있다.

 

void Start () {
	StartCoroutine (TestRoutine());
}

IEnumerator TestRoutine() {
	Debug.Log ("Run TestRoutine");
	yield return StartCoroutine (OtherRoutine ());
	Debug.Log ("Finish TestRoutine");
}

IEnumerator OtherRoutine() {
	Debug.Log ("Run OtherRoutine #1");
	yield return new WaitForSeconds (1.0f);
	Debug.Log ("Run OtherRoutine #2");
	yield return new WaitForSeconds (1.0f);
	Debug.Log ("Run OtherRoutine #3");
	yield return new WaitForSeconds (1.0f);
	Debug.Log ("Finish OtherRoutine");
}
반응형

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

[Unity] float 반올림, 올림, 내림  (0) 2023.01.30
[Unity] 화면보호기 끄기  (0) 2023.01.30
[Unity] UI Blur Shader  (0) 2023.01.05
[Unity] VS Code Settings Sync  (0) 2022.12.26
[Unity] 암호화 복호화  (0) 2022.11.29
반응형

스토어에 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
반응형

VS Code 설정 동기화하기

반응형

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

[Unity] 중첩 코루틴 Coroutine  (0) 2023.01.30
[Unity] UI Blur Shader  (0) 2023.01.05
[Unity] 암호화 복호화  (0) 2022.11.29
[Unity] mov파일 만들어 webm파일 사용하기  (0) 2022.02.04
[Unity] MQTT 통신 구현  (0) 2021.10.19
반응형
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace Study.Model
{
    public sealed class Crypto
    {
        protected RijndaelManaged myRijndael;
        private static string encryptionKey = "KEY 값";
        private static string initialisationVector = "IV값";
        // Singleton pattern used here with ensured thread safety 
        protected static readonly Crypto _instance = new Crypto();
        public static Crypto Instance
        {
            get
            {
                return _instance;
            }
        }
        public Crypto() { }

        //복호화 
        public string DecryptText(string encryptedString)
        {
            try
            {
                using (myRijndael = new RijndaelManaged())
                {
                    //key,iv 값의 인코딩방식에 따라 byte변환을 달리해야한다 
                    myRijndael.Key = HexStringToByte(encryptionKey);
                    myRijndael.IV = HexStringToByte(initialisationVector);
                    myRijndael.Mode = CipherMode.CBC;
                    myRijndael.Padding = PaddingMode.PKCS7;
                    Byte[] ourEnc = Convert.FromBase64String(encryptedString);
                    string ourDec = DecryptStringFromBytes(ourEnc, myRijndael.Key, myRijndael.IV);
                    return ourDec;
                }
            }
            catch (Exception e)
            {
                return encryptedString;
            }
        }

        //암호화 
        public string EncryptText(string plainText)
        {
            try
            {
                using (myRijndael = new RijndaelManaged())
                {
                    //key,iv 값의 인코딩방식에 따라 byte변환을 달리해야한다 
                    myRijndael.Key = HexStringToByte(encryptionKey);
                    myRijndael.IV = HexStringToByte(initialisationVector);
                    myRijndael.Mode = CipherMode.CBC;
                    myRijndael.Padding = PaddingMode.PKCS7;
                    byte[] encrypted = EncryptStringToBytes(plainText, myRijndael.Key, myRijndael.IV);
                    string encString = Convert.ToBase64String(encrypted);
                    return encString;
                }
            }
            catch (Exception e)
            {
                return plainText;
            }
        }

        //Byte를 EncryptString으로 변환 
        protected byte[] EncryptStringToBytes(string plainText, byte[] Key, byte[] IV)
        { 
            // Check arguments. 
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");
            byte[] encrypted;
            // Create an RijndaelManaged object 
            // with the specified key and IV. 
            using (RijndaelManaged rijAlg = new RijndaelManaged())
            {
                rijAlg.Key = Key; rijAlg.IV = IV;
                // Create a decrytor to perform the stream transform. 
                ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
                // Create the streams used for encryption. 
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {
                            //Write all data to the stream. 
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }
            // Return the encrypted bytes from the memory stream. 
            return encrypted;
        }

        //Byte를 복호화된 스트링으로 변환 
        protected string DecryptStringFromBytes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments. 
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");
            // Declare the string used to hold // the decrypted text. 
            string plaintext = null;
            // Create an RijndaelManaged object // with the specified key and IV. 
            using (RijndaelManaged rijAlg = new RijndaelManaged())
            {
                rijAlg.Key = Key;
                rijAlg.IV = IV;
                // Create a decrytor to perform the stream transform. 
                ICryptoTransform decryptor = rijAlg.CreateDecryptor(rijAlg.Key, rijAlg.IV);
                // Create the streams used for decryption. 
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {
                            // Read the decrypted bytes from the decrypting stream // and place them in a string. 
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }
            }
            return plaintext;
        }

        //BASE64 인코딩된 키 , IV 값 Byte변환 
        protected static byte[] Base64StringToByte(string base64String)
        {
            try
            {
                return Convert.FromBase64String(encryptionKey);
            }
            catch (Exception e)
            {
                throw;
            }
        }

        //UTF8 인코딩된 키 , IV 값 Byte변환 
        protected static byte[] Utf8StringToByte(string utf8String)
        {
            try
            {
                byte[] bytes = Encoding.UTF8.GetBytes(utf8String);
                return bytes;
            }
            catch (Exception e)
            {
                throw;
            }
        }

        //HexString KEY , IV 값 Byte변환 
        protected static byte[] HexStringToByte(string hexString)
        {
            try
            {
                int bytesCount = (hexString.Length) / 2;
                byte[] bytes = new byte[bytesCount];
                for (int x = 0; x < bytesCount; ++x)
                {
                    bytes[x] = Convert.ToByte(hexString.Substring(x * 2, 2), 16);
                }
                return bytes;
            }
            catch (Exception e)
            {
                throw;
            }
        }
    }
}
반응형

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

[Unity] UI Blur Shader  (0) 2023.01.05
[Unity] VS Code Settings Sync  (0) 2022.12.26
[Unity] mov파일 만들어 webm파일 사용하기  (0) 2022.02.04
[Unity] MQTT 통신 구현  (0) 2021.10.19
[Unity] Tetris 게임 만들기  (1) 2021.06.25
반응형

유니티에서 투명 한 애니메이션을 가져오는 방법은 png시퀀스로 가져와 애니메이터에 넣어 사용하는 방식이 간편하고 좋아서 웬만한 투명 애니메이션은 가능하면 png시퀀스로 사용해 왔었다.

하지만 영상으로 가져와서 사용할 수 있는 방법을 찾던 중 webM파일로 가져와서 간단하게 사용하는 방식이 있어 정리해본다.

 

1. 배경이 투명 한 mov 영상파일을 만든다.

2. mov파일을 webM파일로 컨버팅 한다.

3. 유니티에 webM파일을 임포트해서 VideoClip으로 가져와 VideoPlayer에 넣어 사용한다.

 

일단 인터넷에 검색하면 나오는 클라우드 컨버팅으로 이것저것 다 해봤지만 잘 되지 않았다.

내가 사용했던 되는 방식을 그대로 정리한다.

 

애팩에서 유니티에서 사용할 애니메이션클립을 제작하고 배경을 투명하게 세팅한다. 컴포지션 창 왼쪽 하단에 투명하게 해주는 아이콘이 있으니 확인해서 클릭.

 

File / Export / Add to Render Queue 클릭해서

Output Module 옆에 Lossless클릭

알파채널을 추가해서 설정해 준 후 ok클릭.

적당한 이름으로 랜더를 눌러 mov 파일을 뽑아준다.

 

1-Click Video Converter를 실행하고 좌상단 Add File을 눌러 mov파일을 불러온다.

불러온 파일 오른쪽에 아웃풋 영상을 선택할 수 잇는데 클릭. (캡쳐화면에는 AVI라는 아이콘)

종류가 많은데... General Video를 클릭하고 아래로 내리다 보면 WebM이라고 보인다. 맨 아랫쪽에 더 내리면 WebM vp9이라고도 있는데 이걸로 하면 유니티 에디터에서 지원하지 않는다는 에러가 난다. 첫번째로 보이는 WebM으로 선택.

하단에 보이는 Profile옆에 Settings를 클릭.

아래와 같이 Keep Original로 설정하고 ok를 눌러준다.

유니티로 파일을 드래그해서 가져온 후 아래처럼 설정한다.

... 비디오클립/비디오 설정
[SerializeField] VideoClip m_FindImageClip;
[SerializeField] VideoPlayer m_VideoPlayer;


... 사용할 때 ...
m_VideoPlayer.clip = m_FindImageClip;
m_VideoPlayer.Play();
반응형

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

[Unity] VS Code Settings Sync  (0) 2022.12.26
[Unity] 암호화 복호화  (0) 2022.11.29
[Unity] MQTT 통신 구현  (0) 2021.10.19
[Unity] Tetris 게임 만들기  (1) 2021.06.25
[Unity] 스도쿠게임 만들기  (0) 2021.06.15
반응형

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
반응형
테트리스의 역사

테트리스

출처 : 나무위키

1984년 소련의 프로그래머 알렉세이 파지노프(Alexey Leonidovich Pajitnov)가 만들었고 21년 6월 6일에 37주년을 맞았다. 전통 퍼즐 게임인 '펜토미노(Pentomino)'를 개량하여 만든것. 하지만 5개의 정사각형으로 조합된 도형들을 이용하던 '펜토미노'는 1984년 당시의 기술로는 게임화하기에 다소 복잡해서 4개의 사각형을 조합한 '테트로미노(Tetromino)'를 사용, 개량하면서 이름도 그리스어 접두사 'Tetra~(4개의)'와 개발자가 좋아하던 스포츠인 '테니스(Tennis)'의 끝자리를 따와서 붙였다.

 

퍼즐게임의 대표주자이자, 세계에서 8번째로 많이 팔린 게임 시리즈. 배우긴 쉽지만 마스터하는건 어려운 게임. 저작권을 관리하는 테트리스 컴퍼니가 2003년 3월 한국에 상륙하여 테트리스의 저작권을 주장하여 국내 테트리스 게임이 대거 서비스가 중단되는 테트리스 대란이 일어났다. 대부분의 회사들은 소송을 감수하고 서비스를 계속할 실익이 없다고 판단하고 서비스를 중단하거나, 돈을 주고 서비스를 계속하는 길을 선택하였다. 아직 한국에는 테트리스 게임을 따와서 배포할때 저작권을 침해하는 지에 대한 판례는 없다. 다만 한국에서도 테트리스 컴퍼니 측이 '테트리스' 명칭에 대한 상표권을 취득했기 때문에 테트리스 컴퍼니가 아닌 곳에서 '테트리스'라는 명칭을 사용해 상업적인 게임을 출시할 수는 없다고 한다. ㅠㅠ

계약을 하더라도 테트리스 컴퍼니의 동의 없이는 게임 룰을 변경할 수 없게 하고 있다. 한게임에서 계약한 서비스를 했었지만 2013년 이용자 수 부족으로 서비스 종료. 모바일 버전은 컴투스가 꾸준히 내다가 라리선스가 EA로 넘어갔으나, 2020년 4월에 서비스가 종료. 현재는 중국 산하 N3TWORK에서 모바일 라이선스를 취득해 스마트폰용 테트리스가 출시되었으며, Primetime(프라임타임)이라는 기간을 정해 테트리스 대호를 개최하고 있는데, 상금까지 걸려있으므로 자신이 고수라고 생각한다면 참가해 볼 만 하다.

 

테트로미노(Tetromino), 폴리오미노(Polyomino)에 대해

테트로미노(Tetromino)

또는 4-오미노(4-omino)는 4개의 정사각형으로 이루어진 폴리오미노이다. 5가지의 자유테트로미노, 7가지의 단면 테트로미노, 19가지의 고정 테트로미노가 있다. 단면 테트로미노 7가지는 테트리스에서 이용된다.

 

폴리오미노(Polyomino)

하버드 대학교의 솔로몬 골롬(Solomon W. Golomb) 박사가 수학 강의 중에서 처음 사용한, n개의 정사각형들이 서로 최소한 1개의 변을 공유하여 만들어지는 다각형들을 총칭하며 위 표의 형태를 말한다. n이 몇개인가에 따라 부르는 이름이 달라지는데 두개일 경우 우리가 많이 먹는 도미노피자 이름이 있는걸 알수 있다.

 

 

 

테트로미노 Prefab 제작

2DSprite 1x1사이즈를 만들고 이를 기본으로 4개의 블록을 가지는 7가지 테트로미노 프리팹을 제작한다.

회전을 고려 하여 축을 설정 한다.

 

 

그리드 설정

좌측 최하단 을 0,0으로 10x20의 그리드를 생성한다. 포토샾으로 미리 제작해서 가져와도 되지만 여기서는 유니티에서 직접 2DSprite를 사용해 레이아웃을 그렸다.

 

 

 

테트리스 컨트롤 정의 및 구현

기본적으로 각 테트로미노프리팹에 TetrisBlock.cs 스크립이 붙게 되고 아래 설명되는 코드들은 모두 이 스크립트에 들어간다.

 

1. 움직임 정의

왼쪽, 오른쪽, 아래, 한번에 내리기, 회전을 할 수 있는 기능을 넣을 예정이고 아래 키에 맵핑한다.

2. 유동범위 체크

블록이 움직일 수 있는 유동 가능한 번위를 체크한다.

유동범위체크 스크립트

public static int Height = 20;
public static int Width = 10;

// 블록들을 저장하고 체크하기 위한 10x20 그리드 데이터
private static Transform[,] grid = new Transform[Width, Height]; 

bool ValidMove()
{
	foreach (Transform children in transform) // 4씩 존재함.
	{
        int roundedX = Mathf.RoundToInt(children.transform.position.x);
        int roundedY = Mathf.RoundToInt(children.transform.position.y);

        if (roundedX < 0 || roundedX >= Width || roundedY < 0 || roundedY >= Height)
        return false; // 그리드 좌표를 벗어나면 false

        if (grid[roundedX, roundedY] != null) // 그리드에 블록이 있으면 false
        return false;
	}
	return true;
}

 

3. 아래로 움직이기

키가 눌리지 않아도 자동으로 밑으로 움직여야 하고 키가 길게 눌리면 빠르게 이동된다.

public float FallTime = 0.8f;

if (Time.time - previousTime > (Input.GetKey(KeyCode.DownArrow) ? FallTime / 14 : FallTime))
{
     moveDown();
}

void moveDown()
{
     // Down
     transform.position += new Vector3(0, -1, 0);
     if (!ValidMove())
     {
          transform.position -= new Vector3(0, -1, 0);
          AddToGrid();
          checkForLines();
          this.enabled = false;
          if (!isGameOver)
               spawTetromino.NewTetromino();
     }
     previousTime = Time.time;
}

 

4. 왼쪽, 오른쪽 이동

if ((Input.GetKey(KeyCode.LeftArrow) && Time.time - previousTimeLeft > (Input.GetKey(KeyCode.LeftArrow) ? FallTime / 8 : FallTime)))
     moveLeft();
else if ((Input.GetKey(KeyCode.RightArrow) && Time.time - previousTimeRight > (Input.GetKey(KeyCode.RightArrow) ? FallTime / 8 : FallTime)))
     moveRight();

void moveLeft()
{
     // Left
     transform.position += new Vector3(-1, 0, 0);
     if (!ValidMove())
          transform.position -= new Vector3(-1, 0, 0);
     previousTimeLeft = Time.time;
}
void moveRight()
{
     // Right
     transform.position += new Vector3(1, 0, 0);
     if (!ValidMove())
          transform.position -= new Vector3(1, 0, 0);
     previousTimeRight = Time.time;
}

 

5. 회전

if (Input.GetKeyDown(KeyCode.UpArrow))
     rotateBlock();

void rotateBlock()
{
     transform.RotateAround(transform.TransformPoint(RotationPoint), new Vector3(0, 0, 1), 90);
     if (!ValidMove())
          transform.RotateAround(transform.TransformPoint(RotationPoint), new Vector3(0, 0, 1), -90);
     // 터치커맨드 리셋 연타방지용
     touchButtons.MyCommand = Command.None;
}

 

 

 

바닥에 닿을 때 처리 할 것들

기본적으로 10x20의 그리드의 정보를 처리 할 수 있는 Array[,]를 만들어 두고 데이터를 관리한다. 해당 그리드에 블록이 있는지 없는지 체크하고 삭제하거나 입력한다.

 

1. 그리드에 해당 블록 추가

블록이 내려와 바닥에 닿거나 블록이 있어서 더이상 내려갈 수 없을 때 해당 그리드에 블록을 추가한다.

private static Transform[,] grid = new Transform[Width, Height]; // 블록들을 저장하고 체크하기 위한 10x20 그리드 데이터

void AddToGrid()
{
     foreach (Transform children in transform)
     {
          int roundedX = Mathf.RoundToInt(children.transform.position.x);
          int roundedY = Mathf.RoundToInt(children.transform.position.y);

          if (roundedY < 20)
               grid[roundedX, roundedY] = children; // 해당 좌표를 그리드에 입력한다.
          else
               gameOver();
     }
}

 

2. 가로라인 블록 체크

그리드의 행을 기준으로 블록이 가득 차있는지 체크한다.

void checkForLines()
{
     int cnt = 0;
     for (int i = Height - 1; i >= 0; i--)
     {
          if (hasLine(i)) // 채워진 라인이 있는지 확인하고 있다면 삭제한 후 아래로 내려준다.
          {
               DeleteLine(i);
               RowDown(i);
               cnt++;
          }
     }
}

bool hasLine(int i) // 해당 라인이 모두 채워졌는지 확인하여 true, false 반환
{
     for (int j = 0; j < Width; j++)
          if (grid[j, i] == null)
               return false;
     return true;
}

 

3. 라인 삭제 및 블록 아래로 이동

해당 라인에 블록이 모두 채워졌을 때 삭제하고 해당 라인 위쪽 블록들을 아래로 내려준다.

void DeleteLine(int i)
{
     for (int j = 0; j < Width; j++)
     {
          Instantiate(sparkPref, grid[j, i].gameObject.transform.position, Quaternion.identity); // 이펙트효과 추가
          Destroy(grid[j, i].gameObject);
          grid[j, i] = null;
     }
}

void RowDown(int i)
{
     for (int y = i; y < Height; y++)
          for (int j = 0; j < Width; j++)
               if (grid[j, y] != null)
               {
                   grid[j, y - 1] = grid[j, y];
                   grid[j, y] = null;
                   grid[j, y - 1].transform.position -= new Vector3(0, 1, 0);
               }
}

 

 

 

 

Spawn 생성과 관리

 

SpawnTetromino.cs파일을 생성하고 작성 후 같은 이름으로 하이어라키에 위치시킨다.

 

1. Spawn(생성)

7개의 테트로미노중 랜덤으로 하나의 테트로미노가 생성되고 다음 테트로미노도 보이게 한다.

void createTetromino()
{
     if (isFirst)
     {
          isFirst = false;
          targetSpawn = Instantiate(Tetrominoes[Random.Range(0, Tetrominoes.Length)], transform.position, Quaternion.identity);
          ListTetrominoes.Add(targetSpawn);
          nextSpawn = Instantiate(Tetrominoes[Random.Range(0, Tetrominoes.Length)], transform.position + new Vector3(4, 1f, 0), Quaternion.identity);
          nextSpawn.GetComponent<TetrisBlock>().enabled = false;
          nextSpawn.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
     } else {
          targetSpawn = nextSpawn;
          targetSpawn.transform.position = transform.position;
          targetSpawn.transform.localScale = Vector3.one;
          targetSpawn.GetComponent<TetrisBlock>().enabled = true;
          ListTetrominoes.Add(targetSpawn);
          nextSpawn = null;
          nextSpawn = Instantiate(Tetrominoes[Random.Range(0, Tetrominoes.Length)], transform.position + new Vector3(4, 1f, 0), Quaternion.identity);
          nextSpawn.GetComponent<TetrisBlock>().enabled = false;
          nextSpawn.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
          destoroyCheck();
     }
}

 

2. 삭제된 테트로미노 관리

테트로미노 안쪽 4개의 블록 오브젝트가 모두 파괴되면 나머지 오브젝트도 체크해서 삭제 처리한다.

public List<GameObject> ListTetrominoes;

void destoroyCheck()
{
     if (ListTetrominoes.Count > 0)
     {
          for (int i = 0; i < ListTetrominoes.Count; i++)
          {
               if (ListTetrominoes[i].transform.childCount == 0)
               {
                   Destroy(ListTetrominoes[i]);
                   ListTetrominoes.RemoveAt(i);
               }
          }
     }
}

 

 

전체스크립트 및 하이어라키 구조

 

GameData.cs

게임데이터를 관리하기 위한 스크립트이다. 하이어라키에 해당이름으로 빈게임오브젝트를 만들고 해당 스크립트를 얹어준다.

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

public class GameData : MonoBehaviour
{
    public int Score;
    public int PlayTime;
    public int PlayLevel;
    public int PlayCombo;
}

 

GameOver.cs

하이어라키에 게임오버 화면을 미리 만들고 해당 스크립트를 얹어준다. 

using UnityEngine.SceneManagement;
using UnityEngine;
using UnityEngine.UI;

public class GameOver : MonoBehaviour
{
    public Button Btn_Restart;
    void Start()
    {
        Btn_Restart.onClick.AddListener(Reset);
    }

    void Reset()
    {
        SceneManager.LoadScene("Main");
    }
}

 

SelfDestroy.cs

블록이 사라질 때 파티클 오브젝트를 사라지게 하기 위한 스크립트이다. 파티클 프리팹에 해당 스크립트를 붙여주면 해당 시간이 지난 후 알아서 자체적으로 디스트로이 된다.

using UnityEngine;

public class SelfDestroy : MonoBehaviour
{
    public float LifeTime = 3;
    void Start()
    {
        Invoke("destroyMe", LifeTime);
    }

    void destroyMe()
    {
        Destroy(this.gameObject);
    }
}

 

SoundController.cs

효과음을 관리하는 파일이다. 마찬가지로 하이어라키에 위치시켜준다. 각 효과음을 미리 준비하여 mp3파일 형태로 만들어두고 인스펙터에 해당 효과음을 넣어준다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SoundClip
{
    Drop,
    Explo,
    Explo4,
    GameStart,
    GameOver
}
public class SoundController : MonoBehaviour
{
    public AudioClip A_Drop, A_Explo, A_Explo4lines, A_GameStart, A_GameOver;
    public AudioSource myAudio;

    public void PlaySound(SoundClip audioType)
    {
        switch (audioType)
        {
            case SoundClip.Drop:
                myAudio.clip = A_Drop;
                break;
            case SoundClip.Explo:
                myAudio.clip = A_Explo;
                break;
            case SoundClip.Explo4:
                myAudio.clip = A_Explo4lines;
                break;
            case SoundClip.GameStart:
                myAudio.clip = A_GameStart;
                break;
            case SoundClip.GameOver:
                myAudio.clip = A_GameOver;
                break;
        }

        myAudio.Play();
    }
}

 

SpawnTetromino.cs

위에 설명한 생성관리를 해주는 스크립트이다. 마찬가지로 하이어라키에 아래와 같이 위치시켜주고 7개의 만들어둔 테트로미노 프리팹을 넣어준다.

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

public class SpawnTetromino : MonoBehaviour
{
    public GameObject[] Tetrominoes;
    public List<GameObject> ListTetrominoes;
    GameObject nextSpawn;
    GameObject targetSpawn;
    bool isFirst = true;

    //-------------
    SoundController soundController;

    void Start()
    {
        if (soundController == null)
            soundController = FindObjectOfType<SoundController>();
        Application.targetFrameRate = 60;
        ListTetrominoes = new List<GameObject>();
        soundController.PlaySound(SoundClip.GameStart);
        NewTetromino();
    }

    public void NewTetromino()
    {
        Invoke("createTetromino", 0.5f);
    }

    void createTetromino()
    {
        if (isFirst)
        {
            isFirst = false;
            targetSpawn = Instantiate(Tetrominoes[Random.Range(0, Tetrominoes.Length)], transform.position, Quaternion.identity);
            ListTetrominoes.Add(targetSpawn);

            nextSpawn = Instantiate(Tetrominoes[Random.Range(0, Tetrominoes.Length)], transform.position + new Vector3(4, 1f, 0), Quaternion.identity);
            nextSpawn.GetComponent<TetrisBlock>().enabled = false;
            nextSpawn.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);
        }
        else
        {
            targetSpawn = nextSpawn;
            targetSpawn.transform.position = transform.position;
            targetSpawn.transform.localScale = Vector3.one;
            targetSpawn.GetComponent<TetrisBlock>().enabled = true;
            ListTetrominoes.Add(targetSpawn);

            nextSpawn = null;
            nextSpawn = Instantiate(Tetrominoes[Random.Range(0, Tetrominoes.Length)], transform.position + new Vector3(4, 1f, 0), Quaternion.identity);
            nextSpawn.GetComponent<TetrisBlock>().enabled = false;
            nextSpawn.transform.localScale = new Vector3(0.5f, 0.5f, 0.5f);

            destoroyCheck();
        }
    }

    void destoroyCheck()
    {
        if (ListTetrominoes.Count > 0)
        {
            for (int i = 0; i < ListTetrominoes.Count; i++)
            {
                if (ListTetrominoes[i].transform.childCount == 0)
                {
                    Destroy(ListTetrominoes[i]);
                    ListTetrominoes.RemoveAt(i);
                }
            }
        }
    }
}

 

TetrisBlock.cs

가장 핵심이 되는 코드 이다. 위에 설명한 부분들이 모두 들어가 있으며 7개의 테트로미노 프리팹에 각각 붙여준다.

using UnityEngine;

public class TetrisBlock : MonoBehaviour
{
    public Vector3 RotationPoint;
    public float FallTime = 0.8f;
    public static int Height = 20;
    public static int Width = 10;
    private float previousTime, previousTimeLeft, previousTimeRight;
    private static Transform[,] grid = new Transform[Width, Height];

    bool isGameOver;

    // other commponent ----------
    TouchButtons touchButtons;
    GameData gameData;
    SpawnTetromino spawTetromino;
    SoundController soundController;

    // vfx ----------
    [SerializeField] GameObject sparkPref;

    void Update()
    {
        if (touchButtons == null)
            touchButtons = FindObjectOfType<TouchButtons>();
        if (gameData == null)
            gameData = FindObjectOfType<GameData>();
        if (spawTetromino == null)
            spawTetromino = FindObjectOfType<SpawnTetromino>();
        if (soundController == null)
            soundController = FindObjectOfType<SoundController>();


        //=================== 키보드일 경우

        if ((Input.GetKey(KeyCode.LeftArrow) && Time.time - previousTimeLeft > (Input.GetKey(KeyCode.LeftArrow) ? FallTime / 8 : FallTime)))
            moveLeft();
        else if ((Input.GetKey(KeyCode.RightArrow) && Time.time - previousTimeRight > (Input.GetKey(KeyCode.RightArrow) ? FallTime / 8 : FallTime)))
            moveRight();
        else if (Input.GetKeyDown(KeyCode.UpArrow))
            rotateBlock();

        if (Time.time - previousTime > (Input.GetKey(KeyCode.DownArrow) ? FallTime / 14 : FallTime))
            moveDown();

        if (Input.GetKeyDown(KeyCode.Space)) // 터치
            dropBlock();

        //================== 터치일 경우
        if (touchButtons)
        {
            if (touchButtons.MyCommand == Command.Left && Time.time - previousTimeLeft > (touchButtons.MyCommand == Command.Left ? FallTime / 8 : FallTime))
                moveLeft();
            else if (touchButtons.MyCommand == Command.Right && Time.time - previousTimeRight > (touchButtons.MyCommand == Command.Right ? FallTime / 8 : FallTime))
                moveRight();
            else if (touchButtons.MyCommand == Command.Rotate)
                rotateBlock();

            if (touchButtons.MyCommand == Command.MoveDown)
                moveDown();

            if (touchButtons.MyCommand == Command.Down)
                dropBlock();
        }
    }

    void moveLeft()
    {
        // Left
        transform.position += new Vector3(-1, 0, 0);
        if (!ValidMove())
            transform.position -= new Vector3(-1, 0, 0);
        previousTimeLeft = Time.time;
    }
    void moveRight()
    {
        // Right
        transform.position += new Vector3(1, 0, 0);
        if (!ValidMove())
            transform.position -= new Vector3(1, 0, 0);
        previousTimeRight = Time.time;
    }
    void rotateBlock()
    {
        // Rotate!
        transform.RotateAround(transform.TransformPoint(RotationPoint), new Vector3(0, 0, 1), 90);
        if (this.tag == "Mino_I" || this.tag == "Mino_S" || this.tag == "Mino_Z") // 도는 범위가 넓어서 다시 잡아줌. 90도로 돌아갔다가 다시 원래 위치로
        {
            if (this.transform.localEulerAngles.z < 0 || this.transform.localEulerAngles.z > 90)
                this.transform.localEulerAngles = Vector3.zero;
        }
        else if (this.tag == "Mino_O") // 돌릴필요 없음
            this.transform.localEulerAngles = Vector3.zero;

        if (!ValidMove())
            transform.RotateAround(transform.TransformPoint(RotationPoint), new Vector3(0, 0, 1), -90);

        // 4개의 블록의 부모가 돌아가니 그림자가 돌아가는데 해당 블록들은 항상 그대로 있게 만들어준다.
        for (int i = 0; i < transform.childCount; i++)
            transform.GetChild(i).transform.rotation = Quaternion.identity;

        // 터치커맨드 리셋 연타방지용 
        touchButtons.MyCommand = Command.None;
    }
    void moveDown()
    {
        // Down
        transform.position += new Vector3(0, -1, 0);
        if (!ValidMove())
        {
            transform.position -= new Vector3(0, -1, 0);
            AddToGrid();
            checkForLines();
            this.enabled = false;
            if (!isGameOver)
                spawTetromino.NewTetromino();
        }
        previousTime = Time.time;
    }
    void dropBlock()
    {
        touchButtons.MyCommand = Command.None;
        FallTime = 0;
    }

    /// <sammary>
    /// 10x20 그리드 전체 라인을 체크해서 가로라인이 만들어 질 경우 해당 라인을 삭제하고 아래로 내려준다. 그리고 몇줄의 라인이 만들어 졌는지 체크해서 점수를 준다.
    /// </sammary>
    void checkForLines()
    {
        int cnt = 0;
        for (int i = Height - 1; i >= 0; i--)
        {
            if (hasLine(i)) // 채워진 라인이 있는지 확인하고 있다면 삭제한 후 아래로 내려준다.
            {
                DeleteLine(i);
                RowDown(i);
                cnt++;
            }
        }
        if (cnt == 0)
        {
            soundController.PlaySound(SoundClip.Drop);
            gameData.Score += 10;
        }
        else
        {
            Debug.Log("deleted lines : " + cnt);
            // Score
            gameData.Score += cnt * 100;

            if (cnt == 4)
                soundController.PlaySound(SoundClip.Explo4);
            else
                soundController.PlaySound(SoundClip.Explo);
        }
    }

    /// <sammary>
    /// 해당 가로라인에 블록이 모두 있는지 없는지를 체크한다.
    /// </sammary>
    bool hasLine(int i)
    {
        for (int j = 0; j < Width; j++)
            if (grid[j, i] == null)
                return false;
        return true;
    }

    /// <sammary>
    /// 해당 라인을 삭제한다.
    /// </sammary>
    void DeleteLine(int i)
    {
        for (int j = 0; j < Width; j++)
        {
            Instantiate(sparkPref, grid[j, i].gameObject.transform.position, Quaternion.identity);
            Destroy(grid[j, i].gameObject);
            grid[j, i] = null;
        }
        // soundController.PlaySound(SoundClip.Explo);
    }

    /// <sammary>
    /// 해당 가로 라인이 비어있을 경우 아래로 내려준다
    /// </sammary>
    void RowDown(int i)
    {
        for (int y = i; y < Height; y++)
            for (int j = 0; j < Width; j++)
                if (grid[j, y] != null)
                {
                    grid[j, y - 1] = grid[j, y];
                    grid[j, y] = null;
                    grid[j, y - 1].transform.position -= new Vector3(0, 1, 0);
                }
    }

    /// <sammary>
    /// 10x20 그리드에 해당 블록을 채워 넣어줌 채울 수 없을 경우 게임 오버
    /// <sammary>
    void AddToGrid()
    {
        foreach (Transform children in transform)
        {
            int roundedX = Mathf.RoundToInt(children.transform.position.x);
            int roundedY = Mathf.RoundToInt(children.transform.position.y);

            if (roundedY < 20)
                grid[roundedX, roundedY] = children;
            else
                gameOver();

        }
    }

    /// <summary>
    /// 게임오버가 됐을 경우 띄우는 화면
    /// </summary>
    void gameOver()
    {
        isGameOver = true;
        Debug.Log("Game Over@@");
        GameObject.FindWithTag("GameOver").gameObject.transform.GetChild(0).gameObject.SetActive(true);
        soundController.PlaySound(SoundClip.GameOver);
    }

    /// <summary>
    /// 10x20 그리드 안쪽에서 움직이고 있는지 아닌지를 판단
    /// </summary>
    bool ValidMove()
    {
        foreach (Transform children in transform)
        {
            int roundedX = Mathf.RoundToInt(children.transform.position.x);
            int roundedY = Mathf.RoundToInt(children.transform.position.y);

            if (roundedX < 0 || roundedX >= Width || roundedY < 0 || roundedY >= Height)
                return false;

            if (grid[roundedX, roundedY] != null)
                return false;
        }
        return true;
    }
}

인스펙터에 떨어지는 속도와 없어질때 사용 할 VFX효과 Prefab이 입력되어있다.

 

TouchButtons.cs

모바일 플랫폼을 위해 터치컨트롤 스크립트이다.

using UnityEngine;


public enum Command
{
    Left,
    Right,
    MoveDown,
    Down,
    Rotate,
    None
}

public class TouchButtons : MonoBehaviour
{

    public Command MyCommand;
    [SerializeField] TouchHandler BtnLeft, BtnRight, BtnMoveDown, BtnDown, BtnRotate;

    void Start()
    {
        BtnLeft.onTouchDown.AddListener(() => SetCommand(Command.Left));
        BtnRight.onTouchDown.AddListener(() => SetCommand(Command.Right));
        BtnRotate.onTouchDown.AddListener(() => SetCommand(Command.Rotate));
        BtnMoveDown.onTouchDown.AddListener(() => SetCommand(Command.MoveDown));
        BtnDown.onTouchDown.AddListener(() => SetCommand(Command.Down));

        BtnLeft.onTouchUp.AddListener(ResetCommand);
        BtnRight.onTouchUp.AddListener(ResetCommand);
        BtnRotate.onTouchUp.AddListener(ResetCommand);
        BtnMoveDown.onTouchUp.AddListener(ResetCommand);
        BtnDown.onTouchUp.AddListener(ResetCommand);
    }

    void SetCommand(Command type)
    {
        MyCommand = type;
        Debug.Log("type : " + type);
    }
    void ResetCommand()
    {
        Debug.Log("type : " + MyCommand);
        MyCommand = Command.None;
    }
}

화면과 같이 버튼을 위치 시켜주고 인스펙터의 My Command를 기본 None으로 선택해준다.

 

TouchHandler.cs는 사용하지 않음.

 

아직 완성까지는 아니지만 어느정도 완성된 플레이 영상이다.

 

 

반응형

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

[Unity] mov파일 만들어 webm파일 사용하기  (0) 2022.02.04
[Unity] MQTT 통신 구현  (0) 2021.10.19
[Unity] 스도쿠게임 만들기  (0) 2021.06.15
[Unity] UDP 통신  (0) 2021.05.04
[Unity] Partial class  (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

+ Recent posts