300x250
300x250

 

Unity 프로젝트에 에셋을 임포트 할 때, Built-In 환경에서 작업된 에셋들이 URP 환경에서는 쉐이더가 깨지는 문제를 종종 겪게 됩니다. 이 글에서는 이러한 문제를 해결하는 방법을 정리하였습니다.

 

에셋 임포트 사진

 

문제 상황

에셋을 임포트한 후, 프로젝트 내에서 쉐이더가 깨진 모습을 볼 수 있습니다. 이는 주로 핑크빛 에러로 나타나며, 이는 쉐이더가 올바르게 작동하지 않는다는 것을 의미합니다.

에셋 쉐이더 깨진 사진1
에셋 쉐이더 깨진 사진2

 

에셋 쉐이더 깨진 사진3

 

수동으로 쉐이더 교체하기

각 프리팹의 깨진 쉐이더를 URP 쉐이더로 변경하는 방법입니다.

  1. 깨진 프리팹의 쉐이더를 클릭합니다.
  2. 기존의 Standard 쉐이더를 URP의 Lit 쉐이더로 변경합니다.

수동 쉐이더 교체 1

 

수동 쉐이더 교체 2

 

수동 쉐이더 교체 3

 

쉐이더 변경 후에도 텍스처가 빠져 있는 경우가 발생할 수 있습니다. 이 경우, 텍스처를 다시 적용해 줍니다.

수동 쉐이더 교체 4
수동 쉐이더 교체 5

 

수동 쉐이더 교체 6

 

 

자동으로 쉐이더 교체하기

모든 프리팹에 대해 위 작업을 수동으로 하기에는 무리가 있습니다. 이를 해결하기 위해 일본분이 정리해주신 자동으로 쉐이더를 변환하는 방법을 소개합니다.

 

[Unity]3Dオブジェクトがピンク色になった時の解決方法 #URP - Qiita

 

[Unity]3Dオブジェクトがピンク色になった時の解決方法 - Qiita

概要3DオブジェクトのAssetがピンク色になってしまう事、よくあると思います。ここで説明するのは、URP環境のアプリを作ろうとしている方を対象にしています。2022年10月2日 操作説明をわ

qiita.com

 

  1. Window 메뉴에서 Rendering을 선택하고, Render Pipeline Converter를 엽니다.
  2. Built-In to URP를 선택합니다.
  3. Material Upgrade를 선택한 후, Initalize Converters를 누르고, Initalize And Convert를 클릭합니다.

 

 

자동으로 쉐이더 교체하기 1

 

자동으로 쉐이더 교체하기 2

.

자동으로 쉐이더 교체하기 3

 

자동으로 쉐이더 교체하기 4

 

이 과정을 통해 Unity가 다시 임포팅을 시작하며, 모든 프리팹의 쉐이더가 URP 쉐이더로 자동 변환됩니다. 변환 후에는 정상적으로 프리팹이 보이게 됩니다.

적용 모습 1
적용 모습 2

 

 

 

 

 

References

[Unity]3Dオブジェクトがピンク色になった時の解決方法 #URP - Qiita

300x250
300x250

Win32Exception: ApplicationName='powershell', CommandLine='-ExecutionPolicy Bypass -File "C:/Program Files/Unity/Hub/Editor/2022.3.8f1/Editor/Data/PlaybackEngines/AndroidPlayer/Tools\RunElevatedCommand.ps1" -ArgumentList Ignored "C:\Program Files\Unity\Hub\Editor\2022.3.8f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\cmdline-tools\6.0\bin\sdkmanager.bat" "C:\Program Files\Unity\Hub\Editor\2022.3.8f1\Editor\Data\PlaybackEngines\AndroidPlayer\OpenJDK" ""platforms;android-33"" "I:\SampleProject\Temp\AndroidSDKTool"', CurrentDirectory='', Native error= 지정된 파일을 찾을 수 없습니다.

System.Diagnostics.Process.StartWithCreateProcess (System.Diagnostics.ProcessStartInfo startInfo) (at <f7bfd758b02a4936b0078adb4cb60396>:0)
Systehttp://m.Diagnostics.Process.Start () (at <f7bfd758b02a4936b0078adb4cb60396>:0)
(wrapper remoting-invoke-with-check) Systehttp://m.Diagnostics.Process.Start()
UnityEditor.Android.AndroidSDKTools.RunAndroidSdkToolElevatedWindowsInternal (System.String elevatedCommandPath, System.String toolName, System.String javaHome, System.String arguments, System.String errorMsg, System.String toolsdir) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.Android.AndroidSDKTools.RunAndroidSdkToolElevatedWindows (System.String toolName, System.String javaHome, System.String arguments, System.String errorMsg) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.Android.AndroidSDKTools.InstallPlatform (System.Int32 apiLevel) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.Android.PostProcessor.Tasks.CheckAndroidSDK+SDKPlatformDetector.Update (UnityEditor.Android.AndroidSDKTools sdkTools, System.Version minVersion, UnityEditor.Android.PostProcessor.ProgressHandler onProgress) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.Android.PostProcessor.Tasks.CheckAndroidSDK.EnsureSDKComponentVersion (System.Version minVersion, UnityEditor.Android.PostProcessor.Tasks.CheckAndroidSDK+SDKComponentDetector detector) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.Android.PostProcessor.Tasks.CheckAndroidSDK.EnsureSDKComponentVersion (System.Int32 minVersion, UnityEditor.Android.PostProcessor.Tasks.CheckAndroidSDK+SDKComponentDetector detector) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.Android.PostProcessor.Tasks.CheckAndroidSDK.Execute (UnityEditor.Android.PostProcessor.PostProcessorContext context) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.Android.PostProcessor.PostProcessRunner.RunAllTasks (UnityEditor.Android.PostProcessor.PostProcessorContext context) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.Android.PostProcessAndroidPlayer.PrepareForBuild (UnityEditor.BuildPlayerOptions buildPlayerOptions) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.Android.AndroidBuildPostprocessor.PrepareForBuild (UnityEditor.BuildPlayerOptions buildOptions) (at <2a152c15da574a70b6653d9f1dbcd106>:0)
UnityEditor.PostprocessBuildPlayer.PrepareForBuild (UnityEditor.BuildPlayerOptions buildOptions) (at <35c0e5f206594d2fa707969117964d70>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)


Error building Player: Win32Exception: ApplicationName='powershell', CommandLine='-ExecutionPolicy Bypass -File "C:/Program Files/Unity/Hub/Editor/2022.3.8f1/Editor/Data/PlaybackEngines/AndroidPlayer/Tools\RunElevatedCommand.ps1" -ArgumentList Ignored "C:\Program Files\Unity\Hub\Editor\2022.3.8f1\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\cmdline-tools\6.0\bin\sdkmanager.bat" "C:\Program Files\Unity\Hub\Editor\2022.3.8f1\Editor\Data\PlaybackEngines\AndroidPlayer\OpenJDK" ""platforms;android-33"" "I:\SampleProject\Temp\AndroidSDKTool"', CurrentDirectory='', Native error= 지정된 파일을 찾을 수 없습니다.


Build completed with a result of 'Failed' in 8 seconds (7966 ms)
2 errors
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

 

빌드를 하려고 했는데 아래와 같은 오류가 뜨면서 빌드가 안됬습니다.

 

오류가 길게 나오지만 "Android SDK is missing required plaform API" 이것만 보면 비교적 쉽게 접근할 수 있습니다.

 

타겟 API Level을 안드로이드 13.0 (API level 33)으로 세팅했는데

 

"C:\Program Files\Unity\Hub\Editor\에디터 버전\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\platforms" 위 경로에 android-33이 없어서였습니다. 저는 해결을 한 후라서 스크린샷에는 있는 거입니다.

 

유니티 안드로이드 빌드 (API Level 29 ~ 30) (tistory.com)

 

해결법은 안드로이드 스튜디오를 설치한 후에 셋팅즈에 들어가서 안드로이드 API Level33을 지원하는 SDK를 설치해주면 

 

"C:\Users\유저명\AppData\Local\Android\Sdk\platforms" 해당 경로에

 

android-33이 설치가 될 탠데 이 폴더를 통째로 아까 "C:\Program Files\Unity\Hub\Editor\에디터 버전\Editor\Data\PlaybackEngines\AndroidPlayer\SDK\platforms"  경로에 복사를 해주고 에디터를 재실행하면 빌드가 정상적으로 됩니다.

 

 

 

 

 

References

[Error/해결] 유니티 안드로이드 build 오류 (android sdk is missing required platform api, FAILURE: Build failed with an exception, Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details) (velog.io)

유니티 안드로이드 빌드 (API Level 29 ~ 30) (tistory.com)

 

300x250
300x250

2.1 네트워크 연결 구분

네트워크 연결은 LAN, MAN, WAN으로 구분됩니다.

LAN(근거리 통신망)

  • 비교적 소규모의 네트워크로, 특정 지리적 영역 내의 네트워크를 의미합니다.
  • 스위치와 같은 비교적 간단한 장비로 구성되며, 일반적으로 이더넷과 같은 기술을 사용합니다.

MAN(메트로폴리탄 지역 네트워크)

  • 도시와 같은 지역적으로 확장된 네트워크를 가리킵니다.
  • 일반적으로 도시 내의 여러 LAN을 연결하는 데 사용됩니다.
  • 보다 넓은 지리적 영역을 커버하기 위해 WAN과 LAN의 중간에 위치합니다.

WAN(광역 네트워크)

  • 멀리 떨어진 지역 간의 네트워크를 연결하는 데 사용됩니다.
  • LAN이나 MAN보다 훨씬 더 큰 지역을 커버합니다.
  • 주로 통신 사업자로부터 회선을 임대하여 사용하며, 인터넷에 접속하거나 원격지와 통신할 때 사용됩니다.
  • 네트워크 사용에 따라 비용이 부과되며, 일반적으로 자신이 소유한 땅이나 건물이 아닌 곳을 통해 통신해야 할 때 사용됩니다.

 

2.2 네트워크 회선

네트워크 회선은 정보를 전송하는 데 사용되는 전송 매체로, 다양한 유형이 있습니다. 이들은 네트워크 연결의 핵심을 이루며, 정보의 안정적인 전달을 보장합니다. 네트워크 회선에는 인터넷 회선, 전용 회선, 인터넷 전용 회선, VPN, 그리고 DWDM 등이 있습니다. 이들은 각각의 용도와 특징에 따라 다르게 사용됩니다. 이제 각각의 유형을 자세히 설명해 보겠습니다.

인터넷 회선

  • 종류 : 광랜, FTTH, 동축 케이블 인터넷, xDSL(ADSL, VDSL 등)
  • 인터넷 접속을 위해 통신사업자와 연결하는 회선
  • 인터넷 전용 회선과는 전송기술이 약간 다릅니다.
  • 가입자와 통신사업자 간에 직접 연결되는 구조가 아니라 전송 선로 공유 기술을 사용
  • 아파트 광랜은 내부 선로가 직접 연결되는 구조여서 인터넷 전용 회선처럼 보이지만 아파트에서 통신사업자까지 연결한 회선을 아파트 가입자가 공유하는 구조
  • 전송 선로를 공유하므로 일반 인터넷 회선의 속도는 전송 가능한 최대 속도이고 그 속도를 보장하지 않습니다.(주변 사용량에 따라 속도가 느려질 수 있습니다)
  • 기존 전화선을 사용하거나 특정 구간부터 다른 사용자와 공유

전용 회선

  • 가입자와 통신사업자 간에 대역폭을 보장해 주는 서비스를 대부분 전용 회선이라고 부릅니다.
  • 가입자와 통신사업자 간에는 전용 케이블로 연결되어 있고 통신 사업자 내부에서 TDM 같은 기술로 마치 직접 연결한 것처럼 통신 품질을 보장해 줍니다.
  • 본사-지사 연결에 주로 사용
  • 저속 : 음성 전송 기술 기반
    • 음성 전송 기술은 64kbps 단위로 분할되며, 작은 단위를 묶어 회선 접속 속도를 높이는 방식으로 발전
    • 이더넷 기반의 광 전송 기술이 발전하면서 음성 전송 기술의 사용 빈도가 줄었습니다. 이는 이더넷 기술의 신뢰성과 속도가 향상되었기 때문입니다.
    • 전문 전송이나 대외 연결에는 여전히 저속 회선이 사용되는 경우가 많으며, 이를 위해 원격지 전송 기술을 지원하는 라우터가 필요합니다.
  • 고속 : 메트로 이더넷
    • 대부분 광케이블 기반의 이더넷을 사용
    • 가입자와 통신사업자 간의 접속 기술은 이더넷을 사용
    • 통신사업자 내부에서는 이런 개별 가입자를 묶어 통신할 수 있는 다른 고속 통신 기술 사용
    • 두 연결이 다른 이유는 통신사업자는 여러 가입자를 구분해야 하고 가입자 트래픽을 고속으로 전송하는 것이 중요하기 때문입니다.
  • LLCF : 한쪽 링크가 다운 시에 상대 링크도 중지시키는 기능입니다. 이는 네트워크의 안정성을 높이고 데이터 전송의 신뢰성을 보장합니다. 만약 한쪽 링크에 문제가 발생하면, LLCF는 이를 감지하고 상대 쪽 링크도 중지시켜 데이터 전송이 중단되지 않도록 합니다.

인터넷 전용 회선

  • 통신 대역폭을 보장하여 가입자와 통신사업자 간의 안정적인 연결을 제공합니다.
  • 이 구조는 가입자가 통신사업자와 직접 연결되고, 이 연결이 다시 인터넷과 연결되는 형태를 가지고 있습니다. 
  • 이는 일반 가정에서 사용되는 접속 기술과는 달리 다른 가입자와의 경쟁 없이 연결 품질을 보장합니다.

VPN

  • 물리적으로는 전용선이 아니지만 가상으로 직접 연결한 것처럼 보안과 프라이버시를 보장하는 네트워크 기술입니다.
  • 이 기술은 인터넷을 통해 안전하고 암호화된 터널을 만들어 사용자들이 원격에서 안전하게 네트워크에 접속할 수 있도록 합니다. 이는 개인 정보와 데이터를 보호하고 외부 공격으로부터 보안을 유지하는 데 중요한 역할을 합니다.
  • 원격으로 작업하는 직원, 회사 간 연결, 온라인 보안 및 개인 정보 보호에 널리 사용되며, 네트워크 트래픽을 안전하게 암호화하여 데이터 유출을 방지합니다.
  • 통신사업자 VPN : 통신사가 제공하는 네트워크를 이용하여 여러 가입자가 연결되는 VPN 서비스.
  • 가입자 VPN : 개인이나 기업이 직접 설정하여 사용하는 인터넷 연결을 보호하는 VPN.

DWDM

  • 먼 거리를 통신할 떄 케이블 포설 비용이 매우 많이 들고 관리가 어려운 문제를 극복하기 위해 개발되었습니다.
  • WDM 기술이 나오기 전에는 하나의 광케이블에 하나의 통신만 가능했습니다.
  • 통신사업자는 많은 가입자를 구분하고 높은 대역폭의 통신을 제공해야 하므로 여러 개의 케이블을 포설해야 했고 WDM과 DWDM 기술은 하나의 광케이블에 다른 파장의 빛을 통해 여러 채널을 만드는 동시에 많은 데이터를 전송할 수 있습니다.

 

 

2.3 네트워크 구성 요소

네트워크 인터페이스 카드(NIC)

  • 흔히 랜 카드라고 부르는 부품입니다.
  • 컴퓨터를 네트워크에 연결하기 위한 하드웨어 장치
  • 주요 역할 
    • 직렬화 : 전기적 신호를 데이터 신호 형태로 또는 데이터 신호 형태를 전기적 신호 형태로 변환해줍니다.
    • MAC 주소 : 받은 패킷의 도착지 주소가 자신의 MAC 주소가 아니면 폐기하고 맞다면 시스템 내부에서 처리할 수 있도록 전달
    • 흐름 제어 : 패킷 기반 네트워크에선 다양한 통신이 하나의 채널을 이용하므로 이미 통신 중인 데이터 처리 때문에 새로운 데이터를 못 받을 수 있는데 이때 데이터 유실 방지를 위해 데이터를 받지 못할 때 상대방에게 통신 중지를 요청합니다.

케이블

  • 신뢰도 높은 통신이 필요한 경우에 사용합니다.
  1. 동축 케이블:
    • 내부에 중심 도선이 있고, 그 주위에 절연체와 외부 도선이 있는 케이블입니다.
    • 주로 라디오 주파수 통신이나 케이블 TV와 같은 높은 주파수 신호 전달에 사용됩니다.
    • 신뢰성이 높고 높은 대역폭을 제공하여 데이터 전송이 안정적입니다.
  2. 트위스티드 페어 케이블:
    • 두 개의 동일한 선을 서로 꼬아 만든 케이블입니다.
    • 주로 이더넷 네트워크나 전화 회선에서 사용되며, 외부 잡음에 대한 방어력이 뛰어납니다.
    • 데이터 전송 거리가 짧고 비용이 저렴하며, 다양한 네트워크 환경에서 널리 사용됩니다.
  3. 광케이블:
    • 광섬유를 사용하여 빛을 전달하는 케이블입니다.
    • 데이터를 광신호로 변환하여 전송하므로 전기 신호에 비해 훨씬 빠르고 멀리 전달할 수 있습니다.
    • 전기 잡음에 면역이 있고, 보안성과 신뢰성이 높아서 네트워크 통신에서 많이 사용됩니다.

 

커넥터

  • 케이블의 끝부분으로 네트워크 장비나 네트워크 카드에 연결되는 부분입니다.
  • 트위스티드 페어 케이블에서는 RJ-45 커넥터를 사용하지만 광 케이블은 다양한 커넥터가 있습니다.

허브

  •  케이블과 동일한 1계층에서 동작하는 장비입니다.
  • 멀어질수록 감쇄되는 전기 신호를 재생성하여 여러 대의 장비를 연결하는 목적으로 사용됩니다.
  • 그러나 허브는 들어온 신호를 모든 포트로 내보내기 때문에 네트워크 성능이 저하되고, 무한 순환 패킷 등 다양한 문제를 일으키므로 현재 거의 사용되지 않습니다.

스위치

  • 허브와 마찬가지로 여러 장비를 연결하고 통신을 중재하는 역할을 합니다.
  •  2계층에서 동작하며, MAC 주소를 이해하고 목적지 MAC 주소를 기반으로 통신을 중재합니다.
  • 허브와는 다르게 스위치는 목적지 MAC 주소의 위치를 파악하여 목적지가 연결된 포트로만 전기 신호를 전송합니다.
  • 허브의 역할과 통신을 중재하는 기능을 모두 포함하므로 스위칭 허브로도 불립니다.

라우터

  • 네트워크 통신 필요성 증대 : 네트워크 규모가 커지고 먼 지역과의 통신 요구가 증가함에 따라 라우터의 필요성이 대두되었습니다.
  • 3계층 동작 : OSI 모델의 3계층에서 동작하며, 네트워크 간 통신을 위해 프로토콜을 변환합니다.
  • 불필요한 패킷 관리 :  브로드캐스트와 멀티캐스트를 제어하여 원격지로 쓸데없는 패킷이 전송되지 않도록 합니다.
  • 주소 관리 : 불분명한 주소로 통신을 시도할 경우 이를 버리고, 정확한 방향으로 패킷을 지정된 경로로 전송합니다.
  • 경로 지정 : 최적의 경로를 설정하여 패킷을 전달하며, 네트워크 상에서 패킷의 포워딩을 관리합니다.
  • 라우터보다는 일반 사용자가 더 많이 접하는 유사한 장비는 L3 스위치와 공유기입니다.

로드 밸런서

  • 네트워크에서 트래픽을 관리하는 장치로, 일반적으로 OSI 7계층 중 4계층에서 작동합니다.
  • 애플리케이션 계층의 특징을 이해하고 동작하는 ADC(애플리케이션 전달 컨트롤러)로도 알려져 있습니다.
  • 또한, 로드 밸런서의 한 종류인 L4 스위치는 여러 포트를 가지면서 로드 밸런싱 역할을 수행합니다.
  • 클라이언트 요청을 받아들이고 목적지 서버로 트래픽을 분산합니다. 이 과정에서 4계층 포트 주소를 확인하고 동시에 IP 주소를 변경하여 서버 그룹에 속한 서버 중 가장 적절한 서버로 트래픽을 보냅니다.
  • 주로 웹 서비스에서 활용되며, 웹 서버를 효율적으로 관리하고 성능을 최적화하기 위해 사용됩니다.
  • 서비스 헬스체크와 대용량 세션 처리 기능 등 다양한 기능을 제공하여 안정적인 서비스 운영을 지원합니다.

보안 장비(방화벽/IPS)

  • 정보를 제어하고 공격을 방어하는 데 초점이 맞추어져 있습니다.
  • 네트워크 장비와 달리 정보 전달보다는 제어와 방어에 집중합니다.
  • 다양한 보안 장비는 방어 목적과 설치 위치에 따라 사용됩니다.
  • 가장 유명한 보안 장비로는 방화벽이 있으며, OSI 7계층 중 4계층에서 동작합니다.
  • 방화벽은 패킷의 3, 4계층 정보를 확인하고 정책과 비교하여 패킷을 차단하거나 전달합니다.

공유기

  • 가정이나 작은 회사에서 사용되는 대부분의 공유기는 2계층 스위치, 3계층 라우터, 4계층 NAT, 그리고 간단한 방화벽 기능을 통합한 장비입니다.
  • 내부적으로는 스위치, 무선 AP, 그리고 라우터로 구성되어 있으며, 여러 기능을 한 곳에 통합하여 사용자 편의성을 제공합니다.

모뎀

  • 짧은 거리와 먼 거리 통신 기술 간의 변환을 담당하는 장비로, 이더넷으로 연결된 LAN과 WAN 포트를 모두 포함합니다.
  • LAN 및 WAN 포트는 이더넷이므로, 100m 이상의 먼 거리 통신을 위해 모뎀을 사용하여 다른 통신 기술로 변환해주어야 합니다.

 

 

References

- <도서> IT 엔지니어를 위한 네트워크 입문 - 예스24 (yes24.com)

300x250
300x250

1.1 네트워크 구성도 살펴보기

 

1.1.1 홈 네트워크
홈 네트워크를 이루는 주요 구성 요소인 모뎀, 공유기, 그리고 단말 간의 물리적 연결은 네트워크의 핵심입니다. 아래에서 각 요소에 대해 더 자세히 알아보겠습니다.

 

  • 물리적 단말기
    • 모뎀 : 인터넷 서비스 공급자(ISP)와 집을 연결하는 장치로, 외부의 디지털 신호를 이해할 수 있는 형태로 변환합니다.
    • 공유기 : 모뎀에서 나온 인터넷 신호를 받아 무선(Wi-Fi)이나 유선(이더넷)으로 여러 기기에 나누어 주는 역할을 합니다.
    • 단말 : 컴퓨터, 노트북, 스마트폰 등 네트워크에 연결되는 모든 기기를 말하며, 공유기를 통해 모두 함께 통신합니다.
  • 유선 연결과 무선 연결
    • 유선 연결 : 이더넷 케이블을 사용하여 기기들을 직접 연결하는 방식으로, 랜 카드를 통해 물리적인 케이블을 이용해 데이터를 전송합니다.
    • 무선 연결 : 무선 랜 카드는 Wi-Fi, 블루투스, NFC 등 다양한 무선 통신 기술을 지원합니다. 이를 통해 기기들은 서로 통신하고 데이터를 교환할 수 있습니다. 

 

 

1.1.2 데이터 센터 네트워크


데이터 센터 네트워크는 기업이나 대규모 서비스 제공 업체가 안정적이고 효율적인 대용량 서비스를 제공하기 위해 구성하는 핵심 부분입니다.

 

  • 이중화 기술의 활용 : 데이터 센터는 안정성과 가용성을 보장하기 위해 다양한 이중화 기술을 적극적으로 도입합니다. 예를 들어, 서버, 네트워크 장비, 전원 공급장치 등에서 이중화 기술을 사용하여 장애 발생 시에도 서비스 중단을 최소화합니다.
  • 고속 이더넷 기술 : 대용량 데이터를 신속하게 처리하기 위해 10G, 25G, 40G, 100G, 400G와 같은 고속 이더넷 기술을 데이터 센터에서 사용합니다. 이로써 대규모 트래픽을 효과적으로 처리하고 빠른 데이터 전송 속도를 제공할 수 있습니다.
  • 스케일 아웃과 스파인-리프 구조 : 기존에는 3계층 구성이 일반적이었으나, 최근에는 스케일 아웃 기반의 애플리케이션과 서비스가 등장하면서 2계층 구성인 스파인-리프 구조가 더 많이 사용되고 있습니다. 이 구조는 서버 간 통신이 늘어나는 현대의 트래픽 경향을 지원하기 위한 제안입니다. 스파인-리프 구조는 효율적인 데이터 전송 경로를 제공하여 높은 확장성과 성능을 보장합니다.

 

 

1.2 프로토콜

통신 시 규약으로 사용되는 프로토콜의 개념을 소개합니다.

프로토콜 정의 : 네트워크 통신 시 사용되는 규약에 대한 개념 설명

 

1.3 OSI 7계층과 TCP/IP

OSI 7계층과 TCP/IP 프로토콜 스택의 관계를 비교하여 설명합니다.



OSI 7 계층 : 2계층과 3계층으로 세분화하여 이해
TCP/IP 프로토콜 스택 : OSI 7 계층을 더 실용적인 관점에서 묶어 설명

 

1.4 OSI 7계층별 이해하기

각 계층에 대한 상세한 내용과 주요 프로토콜을 설명합니다.

1계층(피지컬 계층) 

  • 대표적 프로토콜 : RS-232, RS-449, V.35 등
  • 주요 장비 소개 : 허브, 리피터, 케이블, 커넥터, 트랜시버, 탭
  • 역할 설명 : 물리적 연결과 관련된 정보를 정의하며, 주로 전기 신호를 전달하는 데 초점을 둡니다.
    주소의 개념이 없어 모든 포트에 동일한 전기 신호를 전송하지만 주소 정보가 없기 때문에 논리적인 주소 할당이 이루어지지 않습니다.

2계층(데이터 링크 계층) 

  • 대표적 프로토콜 : IEEE 802.2, FDDI 등
  • 주요 장비 소개 : 스위치, 브릿지, 네트워크 카드
  • 역할 설명 : 주소 체계 도입으로 출발지와 도착지 주소를 확인하고 데이터 처리를 수행합니다.
    데이터 통신 시 주소 정보를 이용해 정확한 주소로 통신하며, 전기 신호를 정확히 전달하기보다는 주소 정보를 중점으로 처리합니다. 플로 컨트롤을 통해 데이터를 안정적으로 전송하고, 에러를 탐지하거나 고치는 역할을 수행합니다.

3계층(네트워크 계층) 

  • 대표적 프로토콜 : ARP, IPv4, IPv6, NAT, IPSec, VRRP 등
  • 주요 장비 소개 : 라우터, L3 스위치
  • 역할 설명 : IP 주소와 같은 논리적인 주소를 정의하며, 네트워크 주소 부분과 호스트 주소 부분으로 나뉩니다.
    네트워크 주소 정보를 이용하여 자신이 속한 네트워크와 원격지 네트워크를 구분하고, 라우터를 통해 최적의 경로를 찾아 패킷을 전송합니다.


4계층(트랜스포트 계층) 

트랜스포트 계층의 주요 역할은 네트워크 통신 중에 발생할 수 있는 오류를 발견하고 수정하는 것입니다. 이 계층에서는 데이터 패킷을 안전하고 효율적으로 전송하기 위해 여러 기술과 프로토콜을 사용합니다.

  • 대표적 프로토콜 : TCP, UDP, SCTP, DCCP, AH, AEP 등
  • 주요 장비로는 로드 밸런서와 방화벽이 있으며, 이들은 네트워크 트래픽을 관리하고 보안을 유지하는 데 도움을 줍니다.
  • TCP와 UDP는 가장 일반적으로 사용되는 프로토콜입니다. TCP는 신뢰성 있는 연결을 제공하는 반면, UDP는 더 빠른 전송을 가능하게 하지만 신뢰성이 떨어집니다.
  • 시퀀스 번호와 ACK 번호의 사용은 데이터 패킷의 순서를 보장하고, 전송된 데이터가 정확히 수신되었는지 확인하는 데 중요합니다.
  • 데이터를 보낼 때 포트 번호를 사용하여 어떤 애플리케이션으로 데이터를 보낼지 결정합니다.


5계층(세션 계층) 

세션 계층은 네트워크 연결의 시작과 끝을 관리하며, 데이터 전송 중 발생할 수 있는 문제를 해결하는 역할을 합니다.

  • 대표적 프로토콜 : L2TP, PPTP, NFS, RPC, RTCP, SIP, SSH 등
  • 주요 역할 : 이 계층의 주요 역할은 네트워크 세션을 만들고 유지하는 것입니다. 연결이 끊어졌을 때 에러를 복구하고 데이터를 재전송하는 기능도 포함됩니다.


6계층(프레젠테이션 계층) 

프레젠테이션 계층은 서로 다른 시스템 간에 데이터를 주고받을 때, 그 데이터가 이해할 수 있는 공통의 형식으로 변환되도록 돕습니다. 이 계층에서는 데이터를 알맞은 형식으로 '번역'하여, 다양한 애플리케이션과 시스템 간의 호환성을 보장합니다.

  • 대표적 프로토콜 : TLS, AFP, SSH 등
  • 주요 기능 : 표현 방식이 다른 시스템 간의 통신을 돕기 위해 하나의 통일된 구문 형식으로 변환합니다.
    MIME 인코딩, 암호화, 압축, 코드 변환 등의 동작을 수행하여 사용자 시스템의 응용 계층에서 데이터 형식의 차이를 다룹니다.


7계층(애플리케이션 계층) :

애플리케이션 계층은 사용자와 직접 상호작용하는 소프트웨어 애플리케이션을 위한 프로토콜과 서비스를 제공합니다. 이 계층은 사용자가 네트워크를 통해 데이터를 송수신할 수 있게 하는 인터페이스 역할을 합니다.

  • 대표적 프로토콜 :  FTP(파일 전송), SMTP(이메일 전송), HTTP(웹 페이지 접근), TELNET(원격 제어) 등
  • 주요 장비 소개 : ADC(Application Delivery Controller), NGFW(차세대 방화벽), WAF(Web Application Firewall) 등이 있으며, 이들은 애플리케이션의 성능을 개선하고 보안을 강화하는 데 도움을 줍니다.
  • 주요 기능 : 애사용자의 요청에 따라 애플리케이션 프로세스를 정의하고 서비스를 수행합니다. 사용자 인터페이스(UI) 부분이나 사용자의 입력과 출력을 처리하는 부분을 담당합니다.

 

1.5 인캡슐레이션과 디캡슐레이션

패킷 네트워크에서 데이터의 전송과 수신에 관한 과정을 인캡슐레이션과 디캡슐레이션을 통해 설명합니다.

인캡슐레이션 

 포장 과정 이라고 생각할 수 있습니다. 마치 물건을 배송하기 위해 상자에 포장하고, 배송지 주소와 같은 정보를 붙이는 과정과 비슷합니다.

  • 데이터 준비 : 사용자 또는 애플리케이션에서 보내려는 원본 데이터가 준비됩니다.
  • 단계별 추가 : 이 데이터는 네트워크를 통해 전송되기 전에 여러 계층을 거치면서 필요한 정보(헤더)를 추가받습니다. 각 계층은 자신만의 헤더를 추가하는데, 이 헤더에는 목적지 주소, 데이터 형식, 크기, 보안 관련 정보 등이 포함될 수 있습니다.
  • 최종 포장 : 모든 필요한 헤더 정보가 추가되면, 데이터 패킷이 완성됩니다. 이 패킷은 이제 네트워크를 통해 올바른 목적지로 전송될 준비가 됩니다.


디캡슐레이션 : 포장 해제 과정으로 이해할 수 있습니다. 배송된 물건이 목적지에 도착하여 상자를 열고 내용물을 꺼내는 과정과 유사합니다.

  • 데이터 수신 : 네트워크를 통해 전송된 데이터 패킷이 목적지에 도착합니다.
  • 단계별 제거 : 데이터 패킷은 네트워크의 다른 쪽 끝에서 각 계층을 거치면서 각 계층에 해당하는 헤더 정보를 제거합니다. 이 과정을 통해 각 계층은 패킷에서 자신에게 필요한 정보를 해석하고, 최종적으로 원본 데이터에 가까워집니다.
  • 원본 데이터 복원 : 모든 헤더 정보가 제거되면, 최종적으로 원본 데이터가 복원됩니다. 이제 이 데이터는 수신자 또는 목적지 애플리케이션에서 사용할 수 있게 됩니다.

이러한 과정은 데이터가 네트워크를 통해 안전하고 효율적으로 전송되게 하며, 데이터의 무결성과 신뢰성을 보장하는 데 중요합니다. 인캡슐레이션은 데이터를 올바르게 포장하여 전송하는 반면, 디캡슐레이션은 받은 데이터를 정확하게 해석하고 사용할 수 있도록 합니다.

 

 

References

- <도서> IT 엔지니어를 위한 네트워크 입문 - 예스24 (yes24.com)

 

300x250
300x250

0. 컴퓨터 구조를 알아야 하는 이유

1. 효율적인 코드 작성: 컴퓨터 구조를 알면 코드를 더 효율적으로 작성할 수 있습니다. 이는 빠르고 효율적인 프로그램을 만드는데 도움이 됩니다.

2. 문제 해결 능력 향상: 컴퓨터 구조를 이해하면 버그를 찾고 해결하는 능력이 향상됩니다. 코드가 하드웨어와 어떻게 상호 작용하는지 이해하면 오류를 예방하고 빠르게 고칠 수 있습니다.

3. 리소스 효율성: 시스템 리소스를 효율적으로 활용할 수 있습니다. 이는 메모리나 프로세서 등의 자원을 효과적으로 사용하여 성능을 향상할 수 있습니다.

4. 최신 기술 적용: 컴퓨터 구조를 알면 최신 기술을 적용하는데 도움이 됩니다. 빠르게 변하는 기술 트렌드에 대응할 수 있죠.

 

int[] myArray = { 1, 2, 3, 4, 5 };
int length = myArray.Length; // 배열의 길이를 변수에 저장

// 비효율적인 메모리 액세스
for (int i = 0; i < myArray.Length; i++)
{
    int value = myArray[i];
    // 작업 수행
}

// 효율적인 메모리 액세스
for (int i = 0; i < length; i++) // 변수를 사용하여 배열의 길이에 직접 접근하지 않음
{
    int element = myArray[i];
    // 작업 수행
}

 

간단한 예시 코드 입니다. 여기서 주목할 점은 루프에서 배열의 길이에 접근할 때 myArray.Length를 매번 호출하는 것이 아니라, 한 번 변수에 저장하여 사용하는 것입니다. 이렇게 하면 루프 내에서 배열의 길이에 대한 반복적인 메모리 액세스가 감소하게 되어 효율성이 향상됩니다. 

 

이렇게 컴퓨터 구조를 고려하여 코드를 작성하게 되는 것이 컴퓨터 구조를 알아야 하는 이유 중 하나입니다.

 

 

1. 컴퓨터의 핵심 구성요소 4가지.

 

1-1. CPU (Central Processing Unit - 중앙 처리 장치)

역할: 명령어를 해석하고 실행하여 컴퓨터의 모든 연산과 작업을 제어하는 중심 처리 장치입니다.
기능: 산술 논리 연산, 제어 흐름 관리, 메모리 액세스 등을 수행하여 프로그램의 실행을 담당합니다.

 

1-2. 주기억장치 (RAM - Random Access Memory)

- 역할: 현재 실행 중인 프로그램 및 데이터를 일시적으로 저장하는 공간으로, CPU가 빠르게 액세스 할 수 있는 메모리입니다.
- 기능: 프로그램의 실행에 필요한 데이터 및 명령을 저장하며, 전원이 꺼지면 내용이 소멸하는 휘발성 메모리입니다.

 

1-3. 보조기억장치

- 역할: 데이터와 프로그램을 장기적으로 저장하는 데 사용되며, 전원이 꺼져도 정보를 보존합니다.
- 종류: 하드 디스크 드라이브 (HDD),  SSD (Solid State Drive), USB 등이 있습니다.
- 기능: 운영 체제, 소프트웨어, 파일 등을 저장하여 필요할 때 불러와 사용합니다.

 

1-4. 입출력장치:

- 역할: 컴퓨터와 외부 세계 간의 데이터 송수신을 담당하는 장치입니다.
- 종류: 키보드, 마우스, 모니터, 프린터, 스캐너, 네트워크 카드 등 다양한 장치가 포함됩니다.
- 기능: 사용자 입력을 받고 결과를 표시하며, 외부 데이터를 컴퓨터에 입력하거나 컴퓨터의 결과를 외부에 출력합니다.

 

 

2. 메인보드와 시스템 버스

 

컴퓨터의 핵심 구성요소로는 CPU, 주기억장치, 보조기억장치, 입출력장치가 있습니다. 그러나 이들은 직접적으로 서로 통신할 수 없습니다. 따라서 이 구성요소들 간의 상호 작용을 중계하고 제어하기 위해 메인보드와 시스템 버스가 필요합니다.

 

2-1. 메인보드 (Mainboard)
역할: 컴퓨터의 모든 구성 요소를 연결하고 제어하는 중앙 플랫폼입니다.
구성: CPU, RAM, 그래픽 카드, 저장 장치 및 다양한 입출력 장치를 연결하는 슬롯과 포트를 가지고 있습니다.
기능: 전기 신호를 전달하고 데이터를 교환하여 컴퓨터의 모든 부품이 원활하게 동작할 수 있도록 합니다.

 

 

2.2 시스템 버스 (System Bus)
역할: 메인보드 상의 다양한 구성 요소들 간에 데이터와 제어 신호를 전송하는 통로입니다.
구성: 데이터 버스, 주소 버스, 제어 버스로 구성되어 있습니다.
데이터 버스: 데이터 전송을 위한 경로로, CPU와 주기억장치 및 입출력 장치 간에 데이터를 전송합니다.
주소 버스: 주소 정보를 전송하여 주기억장치나 입출력 장치를 식별합니다.
제어 버스: 데이터와 주소를 제어하고 명령어를 전송하는 데 사용됩니다.
기능: CPU와 주기억장치 간의 데이터 전송, 입출력 장치와의 통신, 그리고 주변 장치들 간의 상호 작용을 가능하게 합니다.

 

 

메인보드는 컴퓨터의 중심 부품으로써 다른 모든 하드웨어를 연결하고 통합하는 역할을 합니다. 시스템 버스는 이러한 구성 요소들 간의 통신을 가능하게 하여 컴퓨터의 기능을 조정하고 제어합니다.

 

 

 

References

- <도서> 혼자 공부하는 컴퓨터 구조+운영체제 ( 혼자 공부하는 컴퓨터 구조+운영체제 - 예스24 (yes24.com) )

300x250
300x250

0. 개요

이전 글에서 분량 문제로 추가하지 못한 Insert, Delete, Update 기능을 추가하겠습니다.

 

1. 사전 작업

[Unity] RestApi 서버 이용하여 유저정보 관리 #1 (MySQL) :: 별빛상자 (tistory.com)

 

[Unity] RestApi 서버 이용하여 유저정보 관리 #1 (MySQL)

0. 개요 이전에 Nodejs로 RestApi 서버를 실습한 적이 있습니다. 이거를 그때 당시엔 데이터베이스 데이터를 웹브라우저 상에서만 확인했었는데 유니티 클라이언트에서 데이터를 받는 방법도 정리

starlightbox.tistory.com

여기까지 되었다면 UI를 수정해주겠습니다.

 

1_1 UserCellView 수정

유저 데이터에 내용을 변경하면 좌상단에 체크 오브젝트를 표시해서 누를 경우 업데이트 내용이 서버에 반영되게 구현을 해볼 예정입니다.

우상단에 X 표시를 누를 경우엔 서버에 유저에 데이터 삭제 요청을 보낼 예정입니다.

 

1_2 신규유저 추가 버튼

우상단에 + 버튼을 추가하여 신규 유저를 추가할 수 있는 버튼을 만들었습니다. 해당버튼을 누르게 되면 신규유저 추가 화면이 나올 예정입니다.

 

신규유저 추가

 

 

2. UI 기능 작업

 

Packets.cs

using System.Collections.Generic;

public class Packets 
{
    public class user
    {
        public string user_id;
        public string user_password;
        public string user_name;
    }
    
    public class req_get_users
    {

    }
    public class res_get_users
    {
        public int status_code;
        public List<user> users;
    }

    
    public class req_insert_user
    {
        public string user_id;
        public string user_password;
        public string user_name;
    }

    public class res_insert_user
    {
        public int status_code;
    }
    
    public class req_delete_user
    {
        public string user_id;
    }

    public class res_delete_user
    {
        public int status_code;
    }
    
    public class req_update_user
    {
        public string user_id;
        public string user_password;
        public string user_name;
    }

    public class res_update_user
    {
        public int status_code;
    }

}

 

HTTPManager에 서버의 정의해둔 Api를 호출하는 부분을 추가하겠습니다.

 

HTTPManager.cs

using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

public class HTTPManager : MonoBehaviour
{
    public static HTTPManager instance;

    public UnityAction<List<Packets.user>> onGetUsers;

    public void RequestUsers()
    {
        StartCoroutine(RequestUsersImpl());
    }
    
    public void RequestInsertUser(Packets.req_insert_user packet)
    {
        StartCoroutine(RequestInsertUserImpl(packet));
    }
    
    public void RequestDeleteUser(Packets.req_delete_user packet)
    {
        StartCoroutine(RequestDeleteUserImpl(packet));
    }
    
    public void RequestUpdateUser(Packets.req_update_user packet)
    {
        StartCoroutine(RequestUpdateUserImpl(packet));
    }

    private string _host = "http://localhost";
    private int _port = 3000;
    
    private void Awake()
    {
        instance = this;
    }

    private IEnumerator RequestInsertUserImpl(Packets.req_insert_user packet)
    {
        var url = string.Format("{0}:{1}{2}", _host, _port, "/users/insertUser");

        Debug.Log(url);
        var www = new UnityWebRequest(url, "POST");
        // 객체를 => 문자열 직렬화
        var json = JsonConvert.SerializeObject(packet);
        // 문자열 => byte 배열로 직렬화
        var rawdata = Encoding.UTF8.GetBytes(json);

        www.uploadHandler = new UploadHandlerRaw(rawdata);  // 요청
        www.downloadHandler = new DownloadHandlerBuffer();  // 응답
        www.SetRequestHeader("Content-Type", "application/json");

        yield return www.SendWebRequest();

        if(www.result == UnityWebRequest.Result.Success)
        {
            Debug.LogFormat("---->{0}", www.downloadHandler.text);
            var res_signup = JsonConvert.DeserializeObject<Packets.res_insert_user>(www.downloadHandler.text);    //역직렬화 
            Debug.LogFormat("status : {0}", res_signup.status_code);
        }
        else
        {
            Debug.LogFormat("에러");
        }
    }
    
    private IEnumerator RequestDeleteUserImpl(Packets.req_delete_user packet)
    {
        var url = string.Format("{0}:{1}{2}", _host, _port, "/users/deleteUser");

        Debug.Log(url);
        var www = new UnityWebRequest(url, "POST");
        // 객체를 => 문자열 직렬화
        var json = JsonConvert.SerializeObject(packet);
        // 문자열 => byte 배열로 직렬화
        var rawdata = Encoding.UTF8.GetBytes(json);

        www.uploadHandler = new UploadHandlerRaw(rawdata);  // 요청
        www.downloadHandler = new DownloadHandlerBuffer();  // 응답
        www.SetRequestHeader("Content-Type", "application/json");

        yield return www.SendWebRequest();

        if(www.result == UnityWebRequest.Result.Success)
        {
            Debug.LogFormat("---->{0}", www.downloadHandler.text);
            var res_signup = JsonConvert.DeserializeObject<Packets.res_insert_user>(www.downloadHandler.text);    //역직렬화 
            Debug.LogFormat("status : {0}", res_signup.status_code);
        }
        else
        {
            Debug.LogFormat("에러");
        }
    }
    
    private IEnumerator RequestUpdateUserImpl(Packets.req_update_user packet)
    {
        var url = string.Format("{0}:{1}{2}", _host, _port, "/users/updateUser");

        Debug.Log(url);
        var www = new UnityWebRequest(url, "POST");
        // 객체를 => 문자열 직렬화
        var json = JsonConvert.SerializeObject(packet);
        // 문자열 => byte 배열로 직렬화
        var rawdata = Encoding.UTF8.GetBytes(json);

        www.uploadHandler = new UploadHandlerRaw(rawdata);  // 요청
        www.downloadHandler = new DownloadHandlerBuffer();  // 응답
        www.SetRequestHeader("Content-Type", "application/json");

        yield return www.SendWebRequest();

        if(www.result == UnityWebRequest.Result.Success)
        {
            Debug.LogFormat("---->{0}", www.downloadHandler.text);
            var res_signup = JsonConvert.DeserializeObject<Packets.res_insert_user>(www.downloadHandler.text);    //역직렬화 
            Debug.LogFormat("status : {0}", res_signup.status_code);
        }
        else
        {
            Debug.LogFormat("에러");
        }
    }
    
    private IEnumerator RequestUsersImpl()
    {
        var url = string.Format("{0}:{1}{2}", _host, _port, "/users/getUsers");
        Debug.Log(url);
        var www = new UnityWebRequest(url, "GET");
        www.downloadHandler = new DownloadHandlerBuffer();  //응답 
        yield return www.SendWebRequest();
        if (www.result == UnityWebRequest.Result.Success)
        {
            var res_get_users = JsonConvert.DeserializeObject<Packets.res_get_users>(www.downloadHandler.text);
            if (res_get_users.status_code == 200)
            {
                onGetUsers(res_get_users.users);
            }
        }
        else
        {
            Debug.Log("에러");
        }
    }
}

 

각각 Insert, Delete, Update에 대한 호출 부분을 추가하였습니다.

 

UserCellView.cs

using UnityEngine;
using UnityEngine.UI;

public class UserCellView : MonoBehaviour
{
    public Text textUserId;
    public InputField  inputFieldPassword;
    public InputField inputFieldUserName;

    public GameObject submitBtnGo;

    public void Init(Packets.user user)
    {
        _userId = user.user_id;
        _password = user.user_password;
        _userName = user.user_name;

        textUserId.text = _userId;
        inputFieldPassword.text = _password;
        inputFieldUserName.text = _userName;
        
        submitBtnGo.SetActive(false);
    }

    public void CheckUpdateInfo()
    {
        if (_isUpdate)
            return;

        // 유저 데이터를 변경한 경우
        if (inputFieldPassword.text != _password || inputFieldUserName.text != _userName)
        {
            _isUpdate = true;
            submitBtnGo.SetActive(true);
        }
    }

    public void OnClickUpdate()
    {
        if (inputFieldPassword.text == "")
            return;
        if (inputFieldUserName.text == "")
            return;
        
        Packets.req_update_user packet = new Packets.req_update_user
        {
            user_id = _userId,
            user_password = inputFieldPassword.text,
            user_name =  inputFieldUserName.text
        };
        
        HTTPManager.instance.RequestUpdateUser(packet);
        TestMain.instance.Refresh();
    }
    
    public void OnClickDelete()
    {
        Packets.req_delete_user packet = new Packets.req_delete_user
        {
            user_id = _userId
        };
        
        HTTPManager.instance.RequestDeleteUser(packet);
        TestMain.instance.Refresh();
    }

    private string _userId;
    private string _password;
    private string _userName;
    private bool _isUpdate;
}

 

 

TestMain.cs

using UnityEngine;
using UnityEngine.UI;

public class TestMain : MonoBehaviour
{
    public static TestMain instance;
    
    public GameObject userCellviewPrefab;
    public GameObject insertUserGo;
    public RectTransform content;
    
    public InputField inputFieldUserId;
    public InputField inputFieldPassword;
    public InputField inputFieldUserName;

    public void OnClickShowInsertUser()
    {
        inputFieldUserId.text = "";
        inputFieldPassword.text = "";
        inputFieldUserName.text = "";
        
        insertUserGo.SetActive(true);
    }
    public void OnClickHideInsertUser()
    {
        insertUserGo.SetActive(false);
    }
    
    public void OnClickInsertUser()
    {
        if (inputFieldUserId.text == "")
            return;
        if (inputFieldPassword.text == "")
            return;
        if (inputFieldUserName.text == "")
            return;
        
        Packets.req_insert_user packet = new Packets.req_insert_user
        {
            user_id = inputFieldUserId.text,
            user_password = inputFieldPassword.text,
            user_name =  inputFieldUserName.text
        };
        HTTPManager.instance.RequestInsertUser(packet);

        Refresh();
        OnClickHideInsertUser();
    }

    public void Refresh()
    {
        foreach (Transform child in content)
        {
            Destroy(child.gameObject) ;
        }
        HTTPManager.instance.RequestUsers();
    }

    private void Start()
    {
        HTTPManager.instance.onGetUsers = (users) => 
        {
            foreach (var user in users)
            {
                var go = Instantiate<GameObject>(userCellviewPrefab, content);
                var cellview = go.GetComponent<UserCellView>();
                cellview.Init(user);
            }
        };
        HTTPManager.instance.RequestUsers();
    }
    
    private void Awake()
    {
        instance = this;
    }

}

 

3. 완성

 

 

 

 

감사합니다.

300x250
300x250

0. 개요

이전에 Nodejs로 RestApi 서버를 실습한 적이 있습니다. 이거를 그때 당시엔 데이터베이스 데이터를 웹브라우저 상에서만 확인했었는데 유니티 클라이언트에서 데이터를 받는 방법도 정리를 해두면 좋을 거 같아 정리를 하게 되었습니다.

 

 

1. 서버 세팅

 

기본 구조는 

[Node.Js] 서버 열고 DB연동해보기 (MySQL) :: 별빛상자 (tistory.com)

 

[Node.Js] 서버 열고 DB연동해보기 (MySQL)

저는 Visual Studio Code로 작업했어요. 1. Express 서버 열기 1-1. Visual Studio Code 실행 1-2. 작업 폴더 설정 1-3. 'Open Folder' 클릭 1-4. App.js 파일 생성 1-5. Express 서버 설정 커맨드창에 아래 명령어 입력 npm init

starlightbox.tistory.com

이걸 그대로 가져올 예정이나 여기선 유저정보 리스트만 볼 수 있기에 간단하게 기능을 추가하여 구현해보려고 합니다.

 

기존 기능

1. 유저리스트 정보 확인

 

추가 기능

1. 유저 추가

2. 유저 삭제

3. 유저 정보 변경

 

기존엔 SELECT만 사용 하고 있으니 INSERT, DELETE, UPDATE까지 사용해 볼 겸 이렇게 3가지 기능정도만 추가하면 좋을 거 같네요.

 

 

1_1. userDBC.js 수정

const mysql = require('mysql2');

// Create the connection pool. The pool-specific settings are the defaults
const pool = mysql.createPool
({
  host: 'localhost',
  user: 'user명',
  database: 'db명',
  password: '비밀번호',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

const getUsers = async ()=>
{
    const promisePool = pool.promise();
    const [rows] = await promisePool.query('SELECT * FROM users;');
    console.log(rows);
    return rows;
};

const insertUser = async (values)=>{
    const promisePool = pool.promise();
    const [rows] = await promisePool.query('INSERT INTO users (user_id, user_password, user_name) values (?, ?, ?)', values);
    return rows;
};

const deleteUser = async (userId) => {
    const promisePool = pool.promise();
    const [rows] = await promisePool.query('DELETE FROM users WHERE user_id = ?', [userId]);
    return rows;
};
const updateUser = async (userId, updatedValues) => {
    const promisePool = pool.promise();
    const [rows] = await promisePool.query('UPDATE users SET user_password = ?, user_name = ? WHERE user_id = ?', [...updatedValues, userId]);
    return rows;
};


module.exports = 
{
    getUsers, insertUser, deleteUser, updateUser
};

 

 

 

1_2. userRouter.js 수정

const express = require('express');
const userDBC = require('./usersDBC');
const router = express.Router();

router.get('/getUsers', async (req, res)=>{
    let res_get_users = {
        status_code : 500,
        users : []
    };

    try{
        const rows = await userDBC.getUsers();
        res_get_users.status_code = 200;
        if(rows.length > 0){
            rows.forEach((user)=>{
                res_get_users.users.push({
                    user_name : user.user_name,
                    user_id : user.user_id,
                    user_password : user.user_password  
                });
            });
        }else{
            console.log('사용자 없음');
        }
    }catch(error)
    {
        console.log(error.message);
    }finally
    {
        //응답 
        res.json(res_get_users);
    }
});

router.post('/insertUser', async(req, res)=>{
    const res_signup = {
        status_code : 500
    };

    try{
        const {user_id, user_password, user_name} =  req.body;
        const rows = await userDBC.insertUser([user_id, user_password, user_name]);
        if(rows.affectedRows > 0){
            // 정상작동
            res_signup.status_code = 200;
        }else{
            // ERROR
            res_signup.status_code = 201;
        }
    }catch(err)
    {
        // ERROR
        console.log(err.message);
    }finally{
        res.json(res_signup);
    }
});

router.post('/deleteUser', async(req, res)=>{
    const res_delete_user = {
        status_code : 500
    };

    try{
        const {user_id} =  req.body;
        const rows = await userDBC.deleteUser([user_id]);
        if(rows.affectedRows > 0){
            // 정상작동
            res_delete_user.status_code = 200;
        }else{
            // ERROR
            res_delete_user.status_code = 201;
        }
    }catch(err)
    {
        // ERROR
        console.log(err.message);
    }finally{
        res.json(res_delete_user);
    }
});

router.post('/updateUser', async(req, res)=>{
    const res_update_user = {
        status_code : 500
    };

    try{
        const {user_id, user_password, user_name} =  req.body;
        const rows = await userDBC.updateUser(user_id, [user_password, user_name]);
        if(rows.affectedRows > 0){
            // 정상작동
            res_update_user.status_code = 200;
        }else{
            // ERROR
            res_update_user.status_code = 201;
        }
    }catch(err)
    {
        // ERROR
        console.log(err.message);
    }finally{
        res.json(res_update_user);
    }
});

module.exports = router;

 

 

서버 작업은 여기 까지면 될 것 같습니다.

이제 유니티로 넘어 갑니다.

 

2. 유니티 세팅

 

2_1 기본 UI 구성

UserCellView 오브젝트

저는 먼저 user_id를 표현해 줄 Text 오브젝트와 password, user_name 수정을 할 수 있게 할거기 때문에 InputField 오브젝트 2개를 구성하였습니다. 

 

전체 화면

그럼 이런 식으로 서버에 있는 데이터들을 읽어와서 UserCellView 오브젝트를 생성하며 데이터를 표현해줄 것입니다.

 

2_2 UI 기능 구현

먼저 서버에 있는 유저 데이터를 가져오는 코드를 작업해보겠습니다.

 

Packets.cs

using System.Collections.Generic;

public class Packets 
{
    public class user
    {
        public string user_id;
        public string user_password;
        public string user_name;
    }
    
    public class res_get_users
    {
        public int status_code;
        public List<user> users;
    }
}

 

HTTPManager.cs

using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

public class HTTPManager : MonoBehaviour
{
    public static HTTPManager instance;

    public UnityAction<List<Packets.user>> onGetUsers;

    public void RequestUsers()
    {
        StartCoroutine(RequestUsersImpl());
    }


    private string _host = "http://localhost";
    private int _port = 3000;
    
    private void Awake()
    {
        instance = this;
    }    
    
    
    private IEnumerator RequestUsersImpl()
    {
        var url = string.Format("{0}:{1}{2}", _host, _port, "/users/getUsers");
        Debug.Log(url);
        var www = new UnityWebRequest(url, "GET");
        www.downloadHandler = new DownloadHandlerBuffer();  //응답 
        yield return www.SendWebRequest();
        if (www.result == UnityWebRequest.Result.Success)
        {
            var res_get_users = JsonConvert.DeserializeObject<Packets.res_get_users>(www.downloadHandler.text);
            if (res_get_users.status_code == 200)
            {
                onGetUsers(res_get_users.users);
            }
        }
        else
        {
            Debug.Log("에러");
        }
    }

}

 

 

TestMain.cs

using UnityEngine;
using UnityEngine.UI;

public class TestMain : MonoBehaviour
{
    public static TestMain instance;
    
    public GameObject userCellviewPrefab;
    public RectTransform content;
    
    public InputField inputFieldUserId;
    public InputField inputFieldPassword;
    public InputField inputFieldUserName;

    private void Awake()
    {
        instance = this;
    }
    
    private void Start()
    {
        HTTPManager.instance.onGetUsers = (users) => 
        {
            foreach (var user in users)
            {
                var go = Instantiate<GameObject>(userCellviewPrefab, content);
                var cellview = go.GetComponent<UserCellView>();
                cellview.Init(user);
            }
        };
        HTTPManager.instance.RequestUsers();
    }  
    
}

 

 

UserCellView.cs

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

public class UserCellView : MonoBehaviour
{
    public Text textUserId;
    public InputField  inputFieldPassword;
    public InputField inputFieldUserName;

    public void Init(Packets.user user)
    {
        _userId = user.user_id;
        _password = user.user_password;
        _userName = user.user_name;

        textUserId.text = _userId;
        inputFieldPassword.text = _password;
        inputFieldUserName.text = _userName;
    }

    private string _userId;
    private string _password;
    private string _userName;
}

 

 

 

 

이렇게 서버에서 주는 데이터를 유니티에서 확인할 수 있었습니다.

 

글이 생각보다 너무 길어지는 관계로 Insert, Update, Delete 관련 내용은 다음 글에서 다루겠습니다. 감사합니다.

300x250
300x250

오랜만에 서버 관련 코드를 복습해보려고 했는데 시작부터 원활하지 않네요.

 

 

connect ECONNREFUSED ::1:3306 구글링을 해보니 mysql 커넥션 부분에서 host를 127.0.0.1로 수정한 방법이 있길래 적용해 봤습니다.

 

const pool = mysql.createPool
({
  host: 'localhost',
  user: '계정명',
  database: 'database이름',
  password: '비밀번호',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

 

 

이 부분을 

 

const pool = mysql.createPool
({
  host: '127.0.0.1',
  user: '계정명',
  database: 'database이름',
  password: '비밀번호',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

 

이렇게요.

 

 

아 저는 이렇게 해도 해결이 안 되네요. 다른 방법을 찾아봐야 했습니다.

 

e conn refused

 

e가 무슨 뜻인진 모르겠지만 conn은 커넥션일 거 같고 refused는 거절이니 커넥션이 거절됬다인데 다른 글을 찾아보니 데이터베이스와 접속이 문제일 경우가 있을 수 있다 하여 확인해 봤습니다.

 

MySql workbench 를 열고 테스트 커넥션을 해보니 역시나

 

 

접속이 안되네요.

 

 

서비스가 중지가 되어 있으니 당연히 접속이 안되던 오류였습니다.

 

 

서비스를 실행시키고 다시 코드를 실행해 보니

 

 

다행히 잘 되네요. 간단한 오류여서 다행이었습니다.

 

 

References

[nodejs] node에서 mySQL 연결(Error: connect ECONNREFUSED ::1:3306) (tistory.com)

Carl's Tech Blog (tistory.com)

300x250

'[개인공부] > Node.Js' 카테고리의 다른 글

[Node.Js] 서버 열고 DB연동해보기 (MySQL)  (0) 2023.02.26
[Node.Js] 설치 (18.14.2 LTS)  (0) 2023.02.26
300x250

1. 디자인 패턴

디자인 패턴은 소프트웨어 개발에서 발생하는 일반적인 문제에 대한 해결책을 제공하는 일종의 Well-established pattern으로, 디자인에서 자주 발생하는 문제에 대한 해결책을 제공하는 일종의 템플릿 또는 모범 사례입니다.

 

2. 디자인 패턴 왜 알아야 할까요?

프로그램 개발에서 디자인 패턴을 알아야 하는 이유는 여러 가지가 있습니다. 다양한 측면에서 개발 프로세스를 개선하고 코드의 유지보수성과 확장성을 향상하는 데 도움을 줍니다.

 

1. 재사용성 증가: 디자인 패턴을 사용하면 이미 검증된 솔루션을 사용하여 코드를 작성할 수 있습니다. 이는 개발자가 비슷한 문제에 계속해서 새로운 해결책을 개발할 필요 없이 기존의 패턴을 활용하여 시간을 절약하고 코드의 재사용성을 증가시킬 수 있습니다.


2. 유지보수성 향상: 디자인 패턴을 사용하면 코드의 일관성과 가독성이 향상되어 유지보수가 더 쉬워집니다. 다른 개발자나 여러 프로젝트 간에도 일관된 디자인이 있어 코드를 이해하고 수정하기가 더 쉬워집니다.


3. 확장성 강화: 디자인 패턴은 시스템이 나중에 변경되거나 확장될 때 유연성을 제공합니다. 새로운 요구사항이나 기능이 추가될 때, 디자인 패턴을 통해 기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있습니다.


4. 코드의 가독성 증가: 디자인 패턴은 일반적으로 잘 알려진 개발 관행을 따르기 때문에 코드의 가독성을 향상시킵니다. 이는 코드를 이해하고 유지보수하기가 더 쉽게 만들어줍니다.


5. 품질 향상: 디자인 패턴은 소프트웨어 개발에서 품질을 향상시킬 수 있는 다양한 원칙을 포함하고 있습니다. 예를 들어, 객체 지향 디자인 원칙을 따르는 패턴은 모듈화, 낮은 결합도, 높은 응집도 등과 같은 품질 개선을 지원합니다.


6. 커뮤니케이션 향상: 디자인 패턴은 개발자들 간의 의사 소통을 촉진합니다. 일반적으로 알려진 패턴을 사용하면 다른 개발자들도 빠르게 코드를 이해할 수 있으며, 이는 협업을 더 효과적으로 만듭니다.

 

이러한 이유로, 디자인 패턴은 개발 프로세스에서 중요한 역할을 하며 효율적이고 유지보수 가능한 소프트웨어를 만들기 위해 반드시 알아두어야 하는 개념 중 하나입니다.

 

3. 디자인 패턴 종류

디자인 패턴은 크게 세 가지 카테고리로 나눌 수 있습니다.

 

1. 생성 패턴 (Creational Patterns): 객체의 생성 메커니즘을 다루는 패턴입니다. 이 패턴은 객체를 생성, 합성 및 표현하는 방법을 결정하는데 도움을 줍니다. 


2. 구조 패턴 (Structural Patterns): 클래스와 객체를 조합하여 더 큰 구조를 만드는 패턴입니다. 이러한 패턴은 상속, 객체 컴포지션을 사용하여 시스템을 더 유연하게 만듭니다. 

3. 행위 패턴 (Behavioral Patterns): 객체 간의 알고리즘 및 역할을 나누는 패턴입니다. 이 패턴은 객체 간의 통신 및 역할 분배에 중점을 둡니다. 

디자인 패턴은 주로 GoF(Gang of Four)에서 제안한 23가지 디자인 패턴이 널리 알려져 있습니다. 이러한 패턴은 "Design Patterns: Elements of Reusable Object-Oriented Software"라는 책에서 소개되었습니다. 이러한 디자인 패턴을 이해하고 적용함으로써 코드를 더 구조적이고 유연하게 만들 수 있습니다.

 

생성 패턴 구조 패턴 행동 패턴
싱글톤 (Singleton) 어댑터 (Adapter) 책임 연쇄 (Chain-of-Responsibility)
팩토리 메소드 (Factory Method) 브릿지 (Bridge) 커맨드 (Command)
추상 팩토리 (Abstract Factory) 컴포짓 (Composite) 인터프리터 (Interpreter)
빌더 (Builder) 데코레이터 (Decorator) 이터레이터 (Iterator)
프로토타입 (Prototype) 퍼사드 (Facade) 중재자 (Mediator)
  플라이웨이트 (Flyweight) 메멘토 (Memento)
  프록시 (Proxy) 옵저버 (Observer)
    상태 (State)
    전략 (Strategy)
    템플릿 메소드 (Template Method)
    비지터 (Visitor)

 

References

[디자인 패턴] 총 정리 (2) - 디자인 패턴이란? + 디자인 패턴 종류 (tistory.com)

300x250
300x250

Unity 2022 LTS 버전이 나왔더라고요.

기존 프로젝트가 Unity 2021.3.5f1 버전을 사용 중이어서 아직 초창기라 버전을 올려줬습니다.

 

 

Unity is running with Administrator privileges, which is not supported. Unity executes scripts and binary libraries in your project that may originate from third party sources and potentially be harmful to your computer. Unity may also execute scripts and binary libraries that are still under development and not yet fully tested. Running Unity with Administrator privileges may lead to catastrophic consequences, including but not limited to accidental data loss, change of global system settings or even bricking your device.

근데 버전을 올린 후로 실행할 때마다 해당메시지가 계속 떠서 찾아본 결과 UAC(사용자 계정 컨트롤 설정 변경)으로 해결할 수 있더라고요.

 

설정 후 재부팅

 

 

이후 UAC 설정을 원래대로 바꿔도 정상 작동 확인 했습니다.

 

참고문헌

How do I run 2022.2.1f1 as a standard user? : r/Unity3D (reddit.com)

 

300x250
300x250

0. 실행환경

Unity 2021.3.5f1

 

 

씬에선 Content Size Fitter 컴포넌트가 정상 작동해서 Content 사이즈가 잘 늘어났으나 실행 시 정상적으로 작동 안 하는 이슈 발생

 

오브젝트를 껐다 켰을경우 정상 작동 확인 후 검색해 보니 유니티에서 간헐적으로 발생하는 오류라고 합니다. 이때 임시로

 

LayoutRebuilder.ForceRebuildLayoutImmediate(transform as RectTransform);

문제가 되는 오브젝트를 강제로 다시 재빌드 해주면 되는데요. 저는 이걸 스크립트화 해가지고 문제가 되는 오브젝트에 넣어 줘서 해결했습니다.

 

 

using UnityEngine;
using UnityEngine.UI;

public class UIRebuildLayout : MonoBehaviour
{
    private void Start()
    {
        LayoutRebuilder.ForceRebuildLayoutImmediate(transform as RectTransform);
    }
}

 

참고문헌

[Unity] 유니티 콘텐트사이즈피터 버그 증상 임시? 해결책 Get layoutgroup and contentSizeFitter to update immediately (tistory.com)

300x250
300x250

0. 구현 이유
에셋스토어에서 가져온 캐릭터를 사용하려 했는데, 캐릭터에 포함된 불필요한 스크립트들을 제거해야 할 상황이었습니다. 그래서 자식 오브젝트들을 일일이 확인하며 스크립트를 제거하려 했으나, 이 작업이 상당한 시간을 요구하는 것을 알게 되었습니다. 결국 제 시간을 절약하기 위해 자체적으로 스크립트 제거 기능을 개발하게 되었습니다.

Unity 에디터 확장을 사용하여 게임 오브젝트에 붙은 여러 스크립트를 한 번에 제거하는 기능을 구현하는 방법에 대해 알아보겠습니다.


1. 에디터 확장 스크립트 작성
먼저, Unity 프로젝트에서 스크립트를 생성해야 합니다. 프로젝트 안에 Editor 폴더를 만들고, 그 안에 ScriptRemoverWindow.cs라는 이름으로 스크립트 파일을 생성합니다.

 

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

public class ScriptRemoverWindow : EditorWindow
{
    private int Count = 10;
    private List<string> scriptNames = new List<string>();

    // 에디터 메뉴에서 "SLG/Remove Scripts by Name" 옵션을 추가합니다.
    [MenuItem("slg/Remove Scripts by Name")]
    private static void ShowWindow()
    {
        // "Script Remover"라는 윈도우를 엽니다.
        GetWindow(typeof(ScriptRemoverWindow), false, "Script Remover");
    }

    private void OnGUI()
    {
        GUILayout.Label("Enter the script names to remove:");
        
        // 입력 필드를 생성하여 스크립트 이름을 입력받습니다.
        for (int i = 0; i < Count; i++)
        {
            scriptNames.Add("");
        }
        
        for (int i = 0; i < Count; i++)
        { 
            scriptNames[i] = EditorGUILayout.TextField("Script " + (i + 1), scriptNames[i]);
        }

        // "Remove Scripts" 버튼을 생성하고 클릭 시 스크립트 제거 함수를 호출합니다.
        if (GUILayout.Button("Remove Scripts"))
        {
            RemoveComponentsByNames(scriptNames);
        }
    }

    private static void RemoveComponentsByNames(List<string> targetComponentNames)
    {
        // 선택된 게임 오브젝트에 대해 스크립트 제거 함수를 호출합니다.
        if (Selection.gameObjects != null)
        {
            foreach (GameObject go in Selection.gameObjects)
            {
                RemoveComponentsRecursively(go.transform, targetComponentNames);
                
                Component[] components = go.GetComponents<Component>();

                foreach (Component component in components)
                {
                    if (component != null && targetComponentNames.Contains(component.GetType().Name))
                    {
                        // 즉시 객체를 제거하고, 실행 취소 기록에 남깁니다.
                        Undo.DestroyObjectImmediate(component);
                    }
                }
            }
        }
    }

    private static void RemoveComponentsRecursively(Transform parent, List<string> targetComponentNames)
    {
        // 하위 자식들에 대해 재귀적으로 컴포넌트 제거 함수를 호출합니다.
        for (int i = parent.childCount - 1; i >= 0; i--)
        {
            Transform child = parent.GetChild(i);
            RemoveComponentsRecursively(child, targetComponentNames);

            // 하위 자식 오브젝트의 컴포넌트를 순회하며 제거 대상인 경우 제거합니다.
            Component[] components = child.GetComponents<Component>();

            foreach (Component component in components)
            {
                if (component != null && targetComponentNames.Contains(component.GetType().Name))
                {
                    // 즉시 객체를 제거하고, 실행 취소 기록에 남깁니다.
                    Undo.DestroyObjectImmediate(component);
                }
            }
        }
    }
}

이 스크립트는 "slg" 메뉴에 "Remove Scripts by Name" 옵션을 추가하고, 해당 옵션을 선택하면 "Script Remover"라는 윈도우가 열립니다. 여기에서 제거할 스크립트 이름을 입력하고 "Remove Scripts" 버튼을 클릭하면 선택된 게임 오브젝트에서 해당 스크립트를 제거합니다.

 

 

 

2. 사용 방법

상단 메뉴 바에서 "slg" 메뉴를 찾아 "Remove Scripts by Name" 옵션을 선택합니다.

 

"Script Remover" 윈도우에서 제거하고 싶은 스크립트의 이름을 입력합니다. 필요한 만큼 필드를 추가하여 여러 개의 스크립트 이름을 입력할 수 있습니다.

 

 

Before

 


After




감사합니다.

300x250
300x250

0. Mobile Notifications 구현해야 하는 이유

 

게임 앱에서 푸시 알림을 사용하는 이유는 다양합니다. 이러한 알림은 사용자 경험을 개선하고 게임의 참여도를 높일 수 있는 강력한 도구입니다. 


재참여 유도: 게임을 잊지 않고 계속해서 플레이하도록 유도할 수 있습니다. 푸시 알림을 통해 사용자에게 게임에 대한 업데이트, 이벤트, 할인, 새로운 콘텐츠 등을 알릴 수 있습니다. 이렇게 하면 사용자들이 게임에 다시 참여하고 새로운 콘텐츠나 기능을 경험하도록 유도할 수 있습니다.

이벤트 및 경쟁 참여 유도: 게임 내 이벤트, 경쟁, 토너먼트 등에 사용자를 참여시킬 수 있습니다. 푸시 알림을 통해 이러한 이벤트에 대한 알림과 참여 방법, 보상 등을 전달할 수 있습니다. 사용자는 이러한 알림을 통해 게임에서의 목표를 인식하고 참여 동기를 얻을 수 있습니다.

소셜 상호작용 장려: 게임에서의 소셜 상호작용은 매우 중요합니다. 푸시 알림을 통해 사용자에게 친구 요청, 선물, 도움 요청 등을 알릴 수 있습니다. 또한 다른 플레이어와의 상호작용을 촉진하기 위해 이벤트에 대한 초대, 공유, 리더보드 업데이트 등을 알릴 수도 있습니다.

개인화된 경험 제공: 푸시 알림은 사용자에게 개인화된 경험을 제공할 수 있는 효과적인 방법입니다. 사용자의 선호도, 플레이 스타일, 진행 상황 등을 고려하여 맞춤형 알림을 전송할 수 있습니다. 이렇게 하면 사용자는 게임에서 더 많은 관심과 참여를 보일 수 있습니다.

중요한 업데이트 및 경고 전달: 푸시 알림을 사용하면 게임 내 중요한 업데이트, 경고 또는 이슈에 대해 사용자에게 신속하게 알릴 수 있습니다. 예를 들어, 서버 다운타임, 보안 경고, 결제 확인 등을 사용자에게 알릴 수 있습니다.

푸시 알림은 게임 애플리케이션의 사용자 참여와 유지에 매우 유용한 도구입니다. 그러나 알림의 빈도와 타이밍은 사용자 경험에 큰 영향을 줄 수 있으므로 신중하게 계획하고 구성해야 합니다. 사용자의 플레이 스타일과 선호도를 고려하여 푸시 알림을 사용하는 것이 좋습니다.

 

유니티에서 Mobile Notifications에 관련된 패키지를 제공하고 있기때문에 쉽게 구현할 수 있었습니다.

 

 

 

1. 프로젝트 생성

안드로이드 셋팅

 

2. 패키지 추가

3. 샘플도 임폴트 Import 해줍니다.

4.

샘플 생성확인 후 NotificationsManager.cs 스크립트 작성

using NotificationSamples;
using System;
using UnityEngine;

public class NotificationsManager : GameNotificationsManager
{
    // Public
    ////////////////////////////////////////////////////////////////////////////////////////

    public const string channel1 = "channel1";
    public const string channel2 = "channel2";
    public const string channel3 = "channel3";

    // 알람푸쉬 권한
    public void Init(bool isAlarmAccept = true, bool isNightAlarmAccept = true)
    {
        _isAlarmAccept = isAlarmAccept;
        _isNightAlarmAccept = isNightAlarmAccept;
    }

    private void SendNotification(string title, string body, DateTime deliveryTime, int? badgeNumber = null,
                                 bool reschedule = false, string channelId = null,
                                 string smallIcon = null, string largeIcon = null)
    {
        // 알람푸쉬 권한 체크
        if (!_isAlarmAccept)
            return;
        
        // 야간푸쉬 권한 체크
        if(!_isNightAlarmAccept)
        {
            bool isNight = CheckNightTime(deliveryTime);
            if (isNight)
                return;
        }

        IGameNotification notification = instance.CreateNotification();
        if (notification == null)
        {
            return;
        }

        notification.Title = title;
        notification.Body = body;
        notification.Group = !string.IsNullOrEmpty(channelId) ? channelId : channel1;
        notification.DeliveryTime = deliveryTime;
        notification.SmallIcon = smallIcon;
        notification.LargeIcon = largeIcon;

        if (badgeNumber != null)
        {
            notification.BadgeNumber = badgeNumber;
        }

        PendingNotification notificationToDisplay = instance.ScheduleNotification(notification);
        notificationToDisplay.Reschedule = reschedule;
    }

    // Private
    ////////////////////////////////////////////////////////////////////////////////////////
    private GameNotificationsManager instance;

    private float _tenSecond = 10f;
    private float _twentySecond = 20f;
    private bool _isAlarmAccept = true;
    private bool _isNightAlarmAccept = true;

    private int nightStartHour = 22;
    private int nightEndHour = 6;

    private void Awake()
    {
        instance = this;
    }

    // Start is called before the first frame update
    void Start()
    {
        var c1 = new GameNotificationChannel(channel1, "channel1", "channel1 notifications");
        var c2 = new GameNotificationChannel(channel2, "channel2", "channel2 notifications");
        var c3 = new GameNotificationChannel(channel3, "channel3", "channel3 notifications");


        instance.Initialize(c1, c2, c3);

        instance.Platform.CancelAllScheduledNotifications();
    }

    private void OnApplicationPause(bool pause)
    {
        if (pause)
        {
            RefreshNotifications();
        }
        else
        {
            //resume
            if (instance != null && instance.Platform != null)
            {
                instance.Platform.CancelAllScheduledNotifications();
            }

        }
    }

    private void OnApplicationQuit()
    {
        RefreshNotifications();
    }

    private void RefreshNotifications()
    {
        if (instance == null || instance.Platform == null)
        {
            return;
        }

        CancelAllNotifications();

        // 알람 허용자 체크
        if(_isAlarmAccept)
        {
            var title = "TITLE TEXT";
            SendNotification(title, "tenSecond", DateTime.Now.AddSeconds(_tenSecond));
            SendNotification(title, "twentySecond", DateTime.Now.AddSeconds(_twentySecond));
        }

    }

    private bool CheckNightTime(DateTime checkTime)
    {
        bool isNight = false;

        int hour = int.Parse(checkTime.ToString("HH"));

        if (hour < nightEndHour || hour >= nightStartHour)
            isNight = true;

        return isNight;
    }

}

 

5.  스크립트 넣어주고 빌드

 

 

 

 

 

참고문헌

Introduction | Mobile Notifications | 1.3.2 (unity3d.com)

 

300x250
300x250

1. 제목

- 백준 15903 카드 합체 놀이

- BOJ 15903 카드 합체 놀이

 

문제 링크 : 15903번: 카드 합체 놀이 (acmicpc.net)

 

15903번: 카드 합체 놀이

첫 번째 줄에 카드의 개수를 나타내는 수 n(2 ≤ n ≤ 1,000)과 카드 합체를 몇 번 하는지를 나타내는 수 m(0 ≤ m ≤ 15×n)이 주어진다. 두 번째 줄에 맨 처음 카드의 상태를 나타내는 n개의 자연수 a1,

www.acmicpc.net


2. 풀이 과정

 

이 카드 합체를 총 m번 하면 놀이가 끝난다. m번의 합체를 모두 끝낸 뒤, n장의 카드에 쓰여있는 수를 모두 더한 값이 이 놀이의 점수가 된다. 이 점수를 가장 작게 만드는 것이 놀이의 목표이다.

 

이문제에서 핵심 문장이라고 생각합니다. N개의 카드들 중 2개를 선정해서 합체를 하면 2개의 카드가 모두 같은 숫자가 된다는 규칙이 있기 때문에 가장 작은 숫자의 카드끼리 합체를 하는 게 유리합니다. 그렇기에 이문제는 카드들을 오름차순으로 정렬하고 가장 작은 숫자의 카드 2장을  합치 고를 M번 반복하는 방법으로 풀었습니다. 정렬방법으로는 우선순위큐를 이용하여 큐에 넣을 때 카드들이 정렬되게 했습니다.

 

1. 우선순위큐에 카드를 넣는다.
2. 가장 작은 숫자의 카드 2장을 골라 우선순위큐에서 빼고 합친다음 다시 우선순위큐에 넣는다


3. 코드

// 1. 우선순위큐에 카드를 넣는다.
// 2. 가장 작은 숫자의 카드 2장을 골라 우선순위큐에서 빼고 합친다음 다시 우선순위큐에 넣는다

#include <iostream>
#include <queue>
#include <functional>

using namespace std;

int main()
{
    int n, m;
    priority_queue <long long, vector<long long>, greater<long long>> cards;

    cin >> n >> m;

    for (int i = 0; i < n; i++)
    {
        long long card;
        cin >> card;

        // 1. 우선순위큐에 카드를 넣는다.
        cards.push(card);
    }

    for (int i = 0; i < m; i++)
    {
        // 2. 가장 작은 숫자의 카드 2장을 골라 우선순위큐에서 빼고 합친다음 다시 우선순위큐에 넣는다
        long long card1 = cards.top();
        cards.pop();
        long long card2 = cards.top();
        cards.pop();

        long long mergeCard = card1 + card2;
        cards.push(mergeCard);
        cards.push(mergeCard);
    }

    long long result = 0;
    for (int i = 0; i < n; i++)
    {
        result += cards.top();
        cards.pop();
    }

    cout << result << endl;

    return 0;
}
300x250

'<C++ 백준 BOJ> > 그리디' 카테고리의 다른 글

[백준 BOJ1946] 신입 사원 (C++)  (0) 2023.07.09
[백준 BOJ13305] 주유소 (C++)  (0) 2023.07.02
[백준 BOJ1789] 수들의 합 (C++)  (0) 2023.07.02
[백준 BOJ1026] 보물 (C++)  (0) 2023.07.02
[백준 BOJ2217] 로프 (C++)  (0) 2023.07.02
300x250

1. 제목

- 백준 1946 신입사원

- BOJ 1946 신입사원

 

문제 링크 : 1946번: 신입 사원 (acmicpc.net)

 

1946번: 신입 사원

첫째 줄에는 테스트 케이스의 개수 T(1 ≤ T ≤ 20)가 주어진다. 각 테스트 케이스의 첫째 줄에 지원자의 숫자 N(1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N개 줄에는 각각의 지원자의 서류심사 성

www.acmicpc.net


2. 풀이 과정

 

서류심사 성적으로 이미 한번 정렬을 했기 때문에 N번째 지원자보다 서류심사 성적이 높은 지원자들 중 면접 점수가 가장 높은지 체크하는 것

 

1. 서류심사 성적으로 먼저 지원자를 정렬한다.
2. minScore2 값에 N번째 지원자 중 가장 면접 점수가 높은 점수를 저장
3. N번째 지원자가 N번째 지원자 중 가장 면접 점수가 높다면 합격


3. 코드

// 서류심사 성적으로 이미 한번 정렬을 했기 때문에 N번째 지원자보다 서류심사 성적이 높은 지원자들 중 면접 점수가 가장 높은지 체크하는 것
// 
// 1. 서류심사 성적으로 먼저 지원자를 정렬한다.
// 2. minScore2 값에 N번째 지원자 중 가장 면접 점수가 높은 점수를 저장
// 3. N번째 지원자가 N번째 지원자 중 가장 면접 점수가 높다면 합격
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;


struct Employee
{
	int score1;
	int score2;
};
bool Compare(Employee e1, Employee e2)
{
	return e1.score1 < e2.score1;
}

int main()
{
	int T;
	scanf("%d", &T);
	vector <Employee> employees;
	vector <int> result;

	for (int t = 0; t < T; t++)
	{
		int N;
		scanf("%d", &N);
		for (int n = 0; n < N; n++)
		{
			int score1, score2;
			scanf("%d %d", &score1, &score2);
			employees.push_back({ score1, score2 });
		}

		// 1. 서류심사 성적으로 먼저 지원자를 정렬한다.
		sort(employees.begin(), employees.end(), Compare);
		int minScore2 = employees[0].score2;
		int passer = 1;

		for (int i = 1; i < N; i++)
		{
			// 2. minScore2 값에 N번째 지원자 중 가장 면접 점수가 높은 점수를 저장
			// 3. N번째 지원자가 N번째 지원자 중 가장 면접 점수가 높다면 합격
			if (employees[i].score2 < minScore2)
				passer++;
			minScore2 = min(minScore2, employees[i].score2);
		}
		result.push_back(passer);
		employees.clear();
	}

	for (int t = 0; t < T; t++)
		printf("%d \n", result[t]);

	return 0;
}
300x250
300x250

1. 제목

- 백준 13305 주유소

- BOJ 13305 주유소

 

문제 링크 : 13305번: 주유소 (acmicpc.net)

 

13305번: 주유소

표준 입력으로 다음 정보가 주어진다. 첫 번째 줄에는 도시의 개수를 나타내는 정수 N(2 ≤ N ≤ 100,000)이 주어진다. 다음 줄에는 인접한 두 도시를 연결하는 도로의 길이가 제일 왼쪽 도로부터 N-1

www.acmicpc.net


2. 풀이 과정

 

1. 현재 위치에서 다음 위치까지 가는 데 필요한 기름이 없다면 기름을 충전
2. 기름을 충전할 때 배열에 현재 나라보다 기름 가격이 싼 곳이 있다면 최소한의 기름만 충전
3. 기름을 충전할 때 배열에 현재 나라보다 기름 가격이 싼 곳까지 이동에 필요한 기름을 충전

 


3. 코드

// 1. 현재 위치에서 다음 위치까지 가는 데 필요한 기름이 없다면 기름을 충전
// 2. 기름을 충전할 때 배열에 현재 나라보다 기름 가격이 싼 곳이 있다면 최소한의 기름만 충전
// 3. 기름을 충전할 때 배열에 현재 나라보다 기름 가격이 싼 곳까지 이동에 필요한 기름을 충전

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct City
{
	// 기름 가격
	long long oilPrice;
	// 다음 지역까지 거리
	long long nextDistances;
};

vector<City> cities(100001);
int N;

int main()
{
	cin >> N;

	long long oil = 0;
	long long result = 0;

	for (int index = 0; index < N - 1; index++)
		cin >> cities[index].nextDistances;

	for (int index = 0; index < N - 1; index++)
		cin >> cities[index].oilPrice;

	for (int index = 0; index < N - 1; index++)
	{
		// 1. 현재 위치에서 다음 위치까지 가는 데 필요한 기름이 없다면 기름을 충전
		if (oil < cities[index].nextDistances)
		{
			// 2. 기름을 충전할 때 배열에 현재 나라보다 기름 가격이 싼 곳이 있다면 최소한의 기름만 충전
			if (cities[index+1].oilPrice < cities[index].oilPrice)
			{
				oil += cities[index].nextDistances;
				result += oil * cities[index].oilPrice;
			}
			else
			{
				// 3. 기름을 충전할 때 배열에 현재 나라보다 기름 가격이 싼 곳까지 이동에 필요한 기름을 충전
				long long remainingDistance = 0;
				for (int j = index; j < N - 1; j++)
				{
					if (cities[j].oilPrice < cities[index].oilPrice)
						break;
					remainingDistance += cities[j].nextDistances;
				}

				oil += remainingDistance;
				result += oil * cities[index].oilPrice;
			}
		}

		oil -= cities[index].nextDistances;
	}

	cout << result << endl;
	return 0;
}
300x250
300x250

1. 제목

- 백준 1789 수들의 합

- BOJ 1789 수들의 합

 

문제 링크 : 1789번: 수들의 합 (acmicpc.net)

 

1789번: 수들의 합

첫째 줄에 자연수 S(1 ≤ S ≤ 4,294,967,295)가 주어진다.

www.acmicpc.net


2. 풀이 과정

 

1 ~ N 개의 자연수의 합 S
1. 1부터 1씩 증가시키며 누적합 계산
2. 누적합이 S를 넘어섰다면 이전 인덱스가 최댓값

 

2번 조건문에서 실수를 했는데

if (sum >= S)
	break;

처음에는 while문을 빠져나오는 조건을 이렇게 설정을 했습니다. 누적합이 S이상 이기만 해도 빠져나와야 한다고 생각을 한 게 문제였습니다.

이렇게 하게 되면 예시로 3이 주어졌을 때

1 + 2로 

빠져나오는 조건에 충족하게 되며 이전 인덱스를 참조하게 되면 1을 리턴하며 오답을 반환하게 됩니다. 그렇기에 무조건 누적합이 S를 초과했을 때 while문을 빠져나오게 조건문을 수정하였습니다.

 


3. 코드

// 1 ~ N 개의 자연수의 합 S
// 1. 1부터 1씩 증가시키며 누적합 계산
// 2. 누적합이 S를 넘어섰다면 이전 인덱스가 최댓값

#include <iostream>

using namespace std;

int main()
{
	long long S;
	cin >> S;

	long long index = 1;
	long long sum = 0;
	while (true)
	{
		// 1. 1부터 1씩 증가시키며 누적합 계산
		sum += index;

		// 2. 누적합이 S를 넘어섰다면 이전 인덱스가 최댓값
		if (sum > S)
			break;

		index++;

	}

	cout << index - 1;

	return 0;
}
 
 
300x250

'<C++ 백준 BOJ> > 그리디' 카테고리의 다른 글

[백준 BOJ1946] 신입 사원 (C++)  (0) 2023.07.09
[백준 BOJ13305] 주유소 (C++)  (0) 2023.07.02
[백준 BOJ1026] 보물 (C++)  (0) 2023.07.02
[백준 BOJ2217] 로프 (C++)  (0) 2023.07.02
[백준 BOJ1931] 회의실 배정 (C++)  (0) 2022.10.10
300x250

1. 제목

- 백준 1026 보물

- BOJ 1026 보물

 

문제 링크 : 1026번: 보물 (acmicpc.net)

 

1026번: 보물

첫째 줄에 N이 주어진다. 둘째 줄에는 A에 있는 N개의 수가 순서대로 주어지고, 셋째 줄에는 B에 있는 수가 순서대로 주어진다. N은 50보다 작거나 같은 자연수이고, A와 B의 각 원소는 100보다 작거

www.acmicpc.net


2. 풀이 과정

 

배열 A와 배열 B를 곱해서 최소의 수를 만들기 위해선 가장 높은 수 와 가장 낮은 수를 곱하면 됩니다.
1. 배열 A는 오름차순 정렬, 배열 B는 내림차순 정렬
2. 각 정렬된 배열 A와 배열 B를 곱한 후 더해줍니다.


3. 코드

// 배열 A와 배열 B를 곱해서 최소의 수를 만들기 위해선 가장 높은 수 와 가장 낮은 수를 곱하면 됩니다.
// 1. 배열 A는 오름차순 정렬, 배열 B는 내림차순 정렬
// 2. 각 정렬된 배열 A와 배열 B를 곱한 후 더해줍니다.

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

// 내림차순
bool Compare(int num1, int num2)
{
	if (num1 > num2)
		return true;
	return false;
}

int main()
{
	int N;

	cin >> N;
	vector<int> A(N), B(N);

	for (int index = 0; index < N; index++)
		cin >> A[index];

	for (int index = 0; index < N; index++)
		cin >> B[index];

	// 1. 배열 A는 오름차순 정렬, 배열 B는 내림차순 정렬
	sort(A.begin(), A.end());
	sort(B.begin(), B.end(), Compare);

	int result = 0;

	// 2. 각 정렬된 배열 A와 배열 B를 곱한 후 더해줍니다.
	for (int index = 0; index < N; index++)
		result += A[index] * B[index];


	cout << result << endl;

	return 0;
}
300x250

'<C++ 백준 BOJ> > 그리디' 카테고리의 다른 글

[백준 BOJ13305] 주유소 (C++)  (0) 2023.07.02
[백준 BOJ1789] 수들의 합 (C++)  (0) 2023.07.02
[백준 BOJ2217] 로프 (C++)  (0) 2023.07.02
[백준 BOJ1931] 회의실 배정 (C++)  (0) 2022.10.10
[백준 BOJ11399] ATM (C++)  (0) 2022.10.10
300x250

1. 제목

- 백준 2217 로프

- BOJ 2217 로프

문제 링크 : 2217번: 로프 (acmicpc.net)

 
 

2217번: 로프

N(1 ≤ N ≤ 100,000)개의 로프가 있다. 이 로프를 이용하여 이런 저런 물체를 들어올릴 수 있다. 각각의 로프는 그 굵기나 길이가 다르기 때문에 들 수 있는 물체의 중량이 서로 다를 수도 있다. 하

www.acmicpc.net

 


2. 풀이 과정

 

1. 로프 내림차순으로 정렬
2. 가장 튼튼한 로프부터 추가하며 최대 중량 계산
3. 최대 중량 = max(로프 수 * 고른 로프 중 가장 약한 로프 중량, 최대 중량).

 


3. 코드

// 1. 로프 내림차순으로 정렬
// 2. 가장 튼튼한 로프부터 추가하며 최대 중량 계산
// 3. 최대 중량 = max(로프 수 * 고른 로프 중 가장 약한 로프 중량, 최대 중량)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

bool Compare(int num1, int num2)
{
	if (num1 > num2)
		return true;
	return false;
}

int main()
{
	int N;
	cin >> N;

	vector<int> v(N);

	for (int index = 0; index < N; index++)
		cin >> v[index];

	// 1. 로프 내림차순으로 정렬
	sort(v.begin(), v.end(), Compare);

	int maxWeight = 0;

	// 2. 가장 튼튼한 로프부터 추가하며 최대 중량 계산
	for (int index = 0; index < N; index++)
	{
		// 3. 최대 중량 = max(로프 수 * 고른 로프 중 가장 약한 로프 중량, 최대 중량)
		maxWeight = max(maxWeight, v[index] * (index+1));
	}

	cout << maxWeight << endl;

	return 0;
}
300x250

'<C++ 백준 BOJ> > 그리디' 카테고리의 다른 글

[백준 BOJ1789] 수들의 합 (C++)  (0) 2023.07.02
[백준 BOJ1026] 보물 (C++)  (0) 2023.07.02
[백준 BOJ1931] 회의실 배정 (C++)  (0) 2022.10.10
[백준 BOJ11399] ATM (C++)  (0) 2022.10.10
[백준 BOJ2839] 설탕배달 (C++)  (0) 2022.10.10
300x250

Lerp 함수는 보간(Interpolation) 기법 중 하나인 선형 보간(Linear Interpolation)을 수행합니다. 이는 두 지점 사이의 값을 일직선 상에 있는 다른 값으로 변환하는 것을 의미합니다. 이를 좀 더 쉽게 이해하기 위해서는, 두 점을 잇는 직선상에 있는 다른 지점들을 구하는 것과 같다고 생각할 수 있습니다.

즉 위 사진처럼 A와 B사이의 좌표를 구한다고 생각하면 편합니다.

이런 식으로 무수하게 많은 좌표가 있을 텐데 Lerp 함수를 사용하면 이각각의 좌표를 쉽게 구할 수 있습니다.


Lerp 함수는 다음과 같은 형태로 사용됩니다.

public static Vector3 Lerp(Vector3 a, Vector3 b, float t);


여기서 a와 b는 각각 선형 보간을 수행할 두 지점이며, t는 a와 b 사이에서 내분되는 비율을 나타내는 값입니다. t 값은 0과 1 사이의 값을 가지며, t가 0이면 결과는 a가 되고, t가 1이면 결과는 b가 됩니다. 그리고 0과 1 사이의 값이 아닌 t 값을 사용하는 경우, Lerp 함수는 a와 b 사이의 값을 내분한 값으로 계산합니다.

예를 들어, A가 0이고 B가 1인 경우, t가 0.5인 Lerp 함수를 호출하면 다음과 같은 결과가 나옵니다.


Vector3.Lerp(a, b, 0.5f); // 결과는 (0.5)


이는 a와 b를 잇는 직선상에서 t = 0.5 지점에 해당하는 값을 반환하는 것입니다.

Lerp 함수는 보간 기법을 적용하기 때문에, 물체의 이동, 회전, 크기 변화 등을 자연스럽게 만들 수 있습니다. 또한, 보간 기법은 게임에서 많이 사용되는 기법 중 하나이므로, 게임 개발자라면 반드시 알고 있어야 하는 개념 중 하나입니다.

 

 

이걸 사용해서 0부터 10000까지 10초 동안 올라가는 코드를 작성해 보겠습니다.

    private IEnumerator LerpTestImpl()
    {
        float delta = 0;
        float duration = 10f;
        int startValue = 0;
        int endValue = 10000;

        while (delta <= duration)
        {
            float t = delta / duration;

            float currentValue = Mathf.Lerp(startValue, endValue, t);
            imgSliderFront.fillAmount = t;
            textPer.text = string.Format("{0}%", t * 100f);
            text.text = currentValue.ToString();

            delta += Time.deltaTime;
            yield return null;
        }
    }

 

 

이렇게 하면 10초 동안 각초마다 정해진 구간만큼 똑같이 이동을 하는데 상황에 따라선 처음엔 빠르게 올라가다가 후반부에 느리게 올라갔으면 하는 연출이 필요할 때가 있습니다. 이럴 때는 

Easing 함수 치트 시트 (easings.net)

 

Easing Functions Cheat Sheet

Easing functions specify the speed of animation to make the movement more natural. Real objects don’t just move at a constant speed, and do not start and stop in an instant. This page helps you choose the right easing function.

easings.net

이 사이트 같은 곳을 참고하셔서 t값에 수식을 적어주시면 됩니다. 수학적인 부분이라 왜 저런 수식이 나왔는지는 설명이 어렵습니다.

 

 

    private IEnumerator LerpTestImpl()
    {
        float delta = 0;
        float duration = 10f;
        int startValue = 0;
        int endValue = 10000;

        while (delta <= duration)
        {
            float t = delta / duration;
            t = Mathf.Sin((t * Mathf.PI) / 2);

            float currentValue = Mathf.Lerp(startValue, endValue, t);
            imgSliderFront.fillAmount = t;
            textPer.text = string.Format("{0}%", t * 100f);
            text.text = currentValue.ToString();

            delta += Time.deltaTime;
            yield return null;
        }
    }

이렇게 하면 빠르게 올라가다가 끝에 가까워질수록 느리게 올라가는 연출을 간단하게 할 수 있습니다.

 

 

그런데 이렇게 끝내면 아래와 같이 9999와 같이 딱 떨어지지 않는 경우가 발생할 수 있습니다. 왜냐하면 Lerp함수와 타임값을 사용해서 중간 좌표를 구해주는 식으로 구현을 했는데 이렇게 하면

이러한 좌표를 구해버릴 수도 있는 겁니다. 그래서 끝시간에 도달했을 때 끝값을 고정해줘야 합니다. 이것을 끝보정이라고 합니다.

    private IEnumerator LerpTestImpl()
    {
        float delta = 0;
        float duration = 10f;
        int startValue = 0;
        int endValue = 10000;

        while (delta <= duration)
        {
            float t = delta / duration;
            t = Mathf.Sin((t * Mathf.PI) / 2);

            float currentValue = Mathf.Lerp(startValue, endValue, t);
            imgSliderFront.fillAmount = t;
            textPer.text = string.Format("{0}%", t * 100f);
            text.text = currentValue.ToString();

            delta += Time.deltaTime;
            yield return null;
        }
        
        // 10초가 지났으니 끝보정
        imgSliderFront.fillAmount = 1;
        textPer.text = string.Format("{0}%", 100f);
        text.text = "10000";
    }

 

이러한식으로 마지막 부분에는 도착지에 해당하는 좌표를 넣어서 세팅을 해주면 정상적으로 작동하는 것을 볼 수 있습니다.

 

 

참고자료 :

(403) [유니티] Lerp를 프로처럼 사용하는 방법 - YouTube

https://easings.net/ko

300x250
300x250