<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Lede's Archive</title>
    <link>https://lede-archive.tistory.com/</link>
    <description>개발 일지, 아이디어 등 Lede의 기록을 수집하고 기록하는 공간</description>
    <language>ko</language>
    <pubDate>Sun, 16 Aug 2026 11:37:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Lede_</managingEditor>
    <image>
      <title>Lede's Archive</title>
      <url>https://tistory1.daumcdn.net/tistory/4634538/attach/a1232daa26cf4d57abdf7fe9bd65ddb0</url>
      <link>https://lede-archive.tistory.com</link>
    </image>
    <item>
      <title>[Unreal Engine] Error MSB4236 : 지정된 'Microsoft.NET.SDK.WorkloadAutoImportPropsLocator' SDK를 찾을 수 없습니다.</title>
      <link>https://lede-archive.tistory.com/40</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Solution&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진 프로젝트를 Rider에서 컴파일 할 때 다음과 같은 에러가 발생할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Error MSB4236 : 지정된 'Microsoft.NET.SDK.WorkloadAutoImportPropsLocator' SDK를 찾을 수 없습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1856&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8NXu7/btsDNGO8LZO/xaZsTlOKg2kBuHHfWZemKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8NXu7/btsDNGO8LZO/xaZsTlOKg2kBuHHfWZemKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8NXu7/btsDNGO8LZO/xaZsTlOKg2kBuHHfWZemKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8NXu7%2FbtsDNGO8LZO%2FxaZsTlOKg2kBuHHfWZemKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1856&quot; height=&quot;206&quot; data-origin-width=&quot;1856&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. File &amp;rarr; Settings &amp;rarr; Build, Execution, Deployment &amp;rarr; Toolset and Build 탭으로 이동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NSSsQ/btsDJgKVFXQ/6pkxfqHRtNKZwstuXOIK50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NSSsQ/btsDJgKVFXQ/6pkxfqHRtNKZwstuXOIK50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NSSsQ/btsDJgKVFXQ/6pkxfqHRtNKZwstuXOIK50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNSSsQ%2FbtsDJgKVFXQ%2F6pkxfqHRtNKZwstuXOIK50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;997&quot; height=&quot;744&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;744&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;2. Toolset &amp;rarr; MSBuild global properties &amp;rarr; Edit... 버튼을 클릭하여 설정 메뉴를 오픈한 후 우측의 +버튼을 눌러 다음과 같이 Property를 추가한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;MSBuildEnableWorkloadResolver = False&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yvVTP/btsDJ0niuwS/nzkwBhf0kDZkHf8kYABD20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yvVTP/btsDJ0niuwS/nzkwBhf0kDZkHf8kYABD20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yvVTP/btsDJ0niuwS/nzkwBhf0kDZkHf8kYABD20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyvVTP%2FbtsDJ0niuwS%2FnzkwBhf0kDZkHf8kYABD20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;998&quot; height=&quot;747&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;747&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Unreal Engine/팁</category>
      <category>build</category>
      <category>build error</category>
      <category>compile</category>
      <category>compile error</category>
      <category>Error MSB4236</category>
      <category>MSB4236</category>
      <category>Unreal</category>
      <category>unreal engine</category>
      <category>언리얼</category>
      <category>언리얼 엔진</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/40</guid>
      <comments>https://lede-archive.tistory.com/40#entry40comment</comments>
      <pubDate>Mon, 22 Jan 2024 02:56:14 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal Engine C++] C++에서 블루프린트 액세스 하는 방법</title>
      <link>https://lede-archive.tistory.com/39</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Solution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FClassFinder를 사용하여 블루프린트 클래스를 불러올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GameMode 클래스에서는 다음과 같은 방법으로 캐릭터 Pawn을 불러온다.&lt;/p&gt;
&lt;pre id=&quot;code_1699787496029&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// set default pawn class to our Blueprinted character
static ConstructorHelpers::FClassFinder&amp;lt;APawn&amp;gt; 
     PlayerPawnBPClass(TEXT(&quot;/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter&quot;));
if (PlayerPawnBPClass.Class != NULL)
{
	DefaultPawnClass = PlayerPawnBPClass.Class;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 DefaultPawnClass 필드는 GameModeBase.h 헤더에 다음과 같이 선언되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1699787601281&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public:
	/** The default pawn class used by players. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Classes)
	TSubclassOf&amp;lt;APawn&amp;gt; DefaultPawnClass;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Detail&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.unrealengine.com/5.3/en-US/API/Runtime/CoreUObject/UObject/ConstructorHelpers/FClassFinder/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;FClassFinder 언리얼 엔진 문서&lt;/a&gt;에서 자세한 사항을 확인할 수 있다.&lt;/p&gt;</description>
      <category>Unreal Engine/팁</category>
      <category>unreal engine</category>
      <category>Unreal Engine 5</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/39</guid>
      <comments>https://lede-archive.tistory.com/39#entry39comment</comments>
      <pubDate>Sun, 12 Nov 2023 20:16:41 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal Engine] Assertion failed: Index == TypeIndex 이슈 해결</title>
      <link>https://lede-archive.tistory.com/38</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Solution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진 5.3에서 Editor Startup Map과 Game Default Map을 변경하고 엔진을 재시작 한다면 다음과 같은 이슈가 발생할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Assertion&amp;nbsp;failed:&amp;nbsp;Index&amp;nbsp;==&amp;nbsp;TypeIndex&amp;nbsp;[File:D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Public\Misc\TVariant.h]&amp;nbsp;[Line:&amp;nbsp;118]&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 Config 폴더의 DefaultEngine.ini 파일에서 변경한 맵을 초기화하여 엔진을 실행할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;[Your Project Path]/Config/DefaultEngine.ini&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 맵 경로를 공백으로 변경한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;[/Script/EngineSettings.GameMapsSettings] &lt;br /&gt;GameDefaultMap=&lt;br /&gt;EditorStartupMap=&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Detail&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 9월 25일에 올라온 &lt;a href=&quot;https://forums.unrealengine.com/t/ue-5-3-assertion-failed-index-typeindex/1294524/10&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;개발자 커뮤니티 글&lt;/a&gt;에 따르면 메인 브랜치에서 해결 될 예정이라고 한다.&lt;/p&gt;</description>
      <category>Unreal Engine/팁</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/38</guid>
      <comments>https://lede-archive.tistory.com/38#entry38comment</comments>
      <pubDate>Sat, 11 Nov 2023 08:51:47 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal Engine C++]  트랜스폼을 로컬에서 월드 스페이스로 변환하는 방법</title>
      <link>https://lede-archive.tistory.com/37</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Solution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 액터에 종속된 로컬 Position, Rotation 등을 다음과 같이 변환할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Position&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;GetTransform().TransformPosition(FVector&amp;amp; Position)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rotation&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;GetTransform().TransformRotation(FQuat&amp;amp; Rotation)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Detail&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FTransform &lt;a href=&quot;https://docs.unrealengine.com/4.26/en-US/API/Runtime/Core/Math/FTransform/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;언리얼 엔진 문서&lt;/a&gt;에서 자세한 설명을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Unreal Engine/팁</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/37</guid>
      <comments>https://lede-archive.tistory.com/37#entry37comment</comments>
      <pubDate>Tue, 7 Nov 2023 00:56:35 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal Engine C++] FVector 정규화 부동소수점 문제 완화 방법</title>
      <link>https://lede-archive.tistory.com/36</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Sloution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;FVector&lt;/code&gt;의 &lt;code&gt;Normalize()&lt;/code&gt; 메소드 대신 &lt;code&gt;GetSafeNormal()&lt;/code&gt; 메소드를 사용하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;보다 정밀하게 값을 계산하기 때문에 부동소수점 문제를 완화할 수 있으며, 메소드를 호출한 벡터 인스턴스 자체를 정규화 하는 대신 정규화된 새로운 벡터를 생성하여 반환하기 때문에 비교적 디버깅에도 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Detail&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부동소수점이 무엇이고 어떤 구조를 가지며 어떤 한계점이 있는지는 정리가 잘 된 글을 공유하는것으로 대체한다. &lt;a href=&quot;https://dataonair.or.kr/db-tech-reference/d-lounge/expert-column/?mod=document&amp;amp;uid=52381&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;를 클릭하여 해당 게시글로 이동할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FVector의 메소드와 관련해서는 &lt;a href=&quot;https://docs.unrealengine.com/4.27/en-US/API/Runtime/Core/Math/FVector/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;언리얼 엔진 문서&lt;/a&gt;에 정리되어 있으며 다음과 같이 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://docs.unrealengine.com/4.27/en-US/API/Runtime/Core/Math/FVector/Normalize/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Normalize()&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 허용 오차보다 크면 메소드를 호출한 인스턴스를 정규화 하고, 그렇지 않으면 변경되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;a href=&quot;https://docs.unrealengine.com/4.27/en-US/API/Runtime/Core/Math/FVector/GetSafeNormal/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GetSafeNormal()&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터의 정규화된 복사본을 가져오며, 길이를 기준으로 안전하게 정규화 할 수 있는지 확인한다. 벡터 길이가 너무 작아서 안전하게 정규화할 수 없는 경우 0 벡터를 반환한다.&lt;/p&gt;</description>
      <category>Unreal Engine/팁</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/36</guid>
      <comments>https://lede-archive.tistory.com/36#entry36comment</comments>
      <pubDate>Tue, 7 Nov 2023 00:34:45 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal Engine] 프로젝트에서 게임 서버 &amp;amp; 클라이언트 실행하기</title>
      <link>https://lede-archive.tistory.com/35</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Solution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 10 or 11 및 언리얼 엔진 5.3 기준으로 커맨드 창에 다음과 같이 입력하여 실행 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  서버 실행&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;UnrealEditor.exe 경로&quot; &quot;프로젝트.uproject 경로&quot; &quot;시작 월드.umap 경로&quot; -server -log&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 환경에서는 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;D:\Epic&amp;nbsp;Games\UE_5.3\Engine\Binaries\Win64\UnrealEditor.exe&quot;&amp;nbsp;&quot;D:\Projects\Unreal\MultiplayerSample1\MultiplayerSample1.uproject&quot;&amp;nbsp;/Game/ThirdPerson/Maps/ThirdPersonMap.umap&amp;nbsp;-server&amp;nbsp;-log&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; ️ 클라이언트 실행&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;UnrealEditor.exe 경로&quot; &quot;프로젝트.uproject 경로&quot; &quot;서버 연결 필요 시 서버 IP&quot; -game -log&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자의 환경에서는 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;D:\Epic&amp;nbsp;Games\UE_5.3\Engine\Binaries\Win64\UnrealEditor.exe&quot;&amp;nbsp;&quot;D:\Projects\Unreal\MultiplayerSample1\MultiplayerSample1.uproject&quot;&amp;nbsp;172.30.1.54&amp;nbsp;-game&amp;nbsp;-log&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Detail&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.unrealengine.com/5.3/ko/command-line-arguments-in-unreal-engine/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;언리얼 엔진 5.3 문서&lt;/a&gt;에서는 다음과 같이 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 커맨드라인 구문&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;lt;EXECUTABLE&amp;gt; [URL_PARAMETERS] [ARGUMENTS]&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 항목의 설명은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;EXECUTABLE은 실행 파일의 이름을 나타내며 그 예는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ex)&amp;nbsp; UnrealEditor.exe, MyGame.exe&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;URL_PARAMETERS는 선택적인 URL 파라메터이며 그 예는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ex) MyMap, /Game/Maps/BonusMaps/BonusMap.umap?game=MyGameMode&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;ARGUMENT는 추가적이고 선택적인 커맨드라인 플래그 또는 키 값 쌍을 포함하며 그 예는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ex) -log, -game, -windowed, -ResX=400 ResY=620&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 바탕으로 몇가지 예제를 통해 작성 방법을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 플래그에 대한 설명은 &lt;a href=&quot;https://docs.unrealengine.com/5.3/ko/command-line-arguments-in-unreal-engine/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;언리얼 엔진 5.3 / 명령줄 실행 인자 문서&lt;/a&gt;에서 직접 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. MyGame 프로젝트를 실행하되 Windows에서 MyGameMode 게임모드 전체 화면으로 BonusMap을 실행한다면 그 커맨드라인은 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;UnrealEditor.exe MyGame.uproject /Game/Maps/BonusMaps/BonusMap.umap?game=MyGameMode -game -fullscreen&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. MyGame 이라는 프로젝트가 있고, 이 프로젝트에 대해 /Game/Maps/BonusMap/ 에 위치한 BonusMap.umap 맵으로 서버를 실행시키고, 그 이후에 로컬 환경에서 클라이언트를 실행시키고 서버에 접속하여야 한다면 그 커맨드라인은 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;UnrealEditor.exe MyGame.uproject /Game/Maps/BonusMaps/BonusMap.umap -server -port=7777 -log&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;UnrealEditor.exe MyGame.uproject 127.0.0.1:7777 -game -log&lt;/blockquote&gt;</description>
      <category>Unreal Engine/팁</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/35</guid>
      <comments>https://lede-archive.tistory.com/35#entry35comment</comments>
      <pubDate>Tue, 7 Nov 2023 00:08:13 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal Engine C++] 코드가 실행 중인 위치가 서버인지 판단하는 방법</title>
      <link>https://lede-archive.tistory.com/34</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Sloution&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진에서 지원하는 &lt;code&gt;HasAuthority()&lt;/code&gt; 함수를 사용하자.&lt;br /&gt;현재 위치가 서버라면 &lt;code&gt;true&lt;/code&gt;를 그렇지 않다면 &lt;code&gt;false&lt;/code&gt;를 반환 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Detail&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.unrealengine.com/5.3/en-US/API/Runtime/Engine/GameFramework/AActor/HasAuthority/&quot;&gt;언리얼 엔진 문서&lt;/a&gt;에서는 해당 액터에게 네트워크 어소리티(권한, Authority)가 있는지의 여부를 반환한다고 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 - 클라이언트 모델에서 서버와 클라이언트 간의 통신에서는 보안과 신뢰성을 유지하기 위해 어소리티가 필요하다. 그렇기에 서버에 어소리티가 존재하며, 어소리티의 존재 여부를 확인하는것 만으로 서버인지 판단할 수 있는 기준이 된다.&amp;nbsp; 즉, 서버와 클라이언트에서 어떤 값이 다르다면 그 값은 어소리티를 가진 서버의 값을 따라가게 된다.&lt;/p&gt;</description>
      <category>Unreal Engine/팁</category>
      <category>unreal engine</category>
      <category>Unreal Engine 5</category>
      <category>Unreal Engine C++</category>
      <category>언리얼 엔진</category>
      <category>언리얼 엔진 5</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/34</guid>
      <comments>https://lede-archive.tistory.com/34#entry34comment</comments>
      <pubDate>Mon, 6 Nov 2023 13:54:57 +0900</pubDate>
    </item>
    <item>
      <title>[Minecraft Plugin Tutorial] 2. 간단한 슈팅 게임 제작</title>
      <link>https://lede-archive.tistory.com/33</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하기에 앞서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 개발 환경을 설정하지 못했다면 &lt;a href=&quot;https://lede-archive.tistory.com/32&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 포스트&lt;/a&gt;를 참고해 주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 다음 항목을 학습하는 것을 목표로 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 메세지와 플레이어 메시지를 전송하는 방법&lt;/li&gt;
&lt;li&gt;커맨드를 제작하고 등록하는 방법&lt;/li&gt;
&lt;li&gt;이벤트 리스너를 제작하고 등록하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 목표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어가 화살을 쏘아 상대 플레이어를 맞추거나 처치했을 때 스코어가 오르는 게임을 제작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 게임을 구현하기 위해 다음과 같은 항목을 필요로 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임 시작/종료 기능&lt;/li&gt;
&lt;li&gt;플레이어의 스코어 정보를 저장하는 기능&lt;/li&gt;
&lt;li&gt;조건 만족 시 플레이어의 스코어를 변경하는 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/iuEGmmwjEBA&quot; width=&quot;560&quot; height=&quot;315&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/Lede-dev/ShootingGame&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에서 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 게임 관리자 제작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 게임의 진행을 관리할 수 있는 관리자 클래스를 제작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관리자 클래스에서는 게임의 진행 상태 및 플레이어의 스코어 정보를 관리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 관리자는 단 하나의 인스턴스만 생성되기를 바라기 때문에 싱글톤 클래스로 제작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1681478418254&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GameController {

    // 인스턴스를 단 하나만 생성하기 위해 Holder클래스를 제작합니다.
    private static class GameControllerHolder {
        private static final GameController INSTANCE = new GameController();
    }

    // Holder클래스에서 생성된 인스턴스를 반환합니다.
    public static GameController getInstance() {
        return GameControllerHolder.INSTANCE;
    }

    // 게임의 진행 상태를 저장합니다.
    private boolean isPlaying = false;
    
    // 플레이어의 스코어 정보를 저장합니다.
    private Map&amp;lt;String, Integer&amp;gt; scores = Maps.newHashMap();

    // 직접적으로 객체를 생성할 수 없도록 private으로 처리합니다.
    private GameController() {}

    // 게임을 시작합니다.
    public void startGame() {
        if (!isPlaying) { // 게임이 실행중이지 않을 때
            isPlaying = true; // 게임을 시작 상태로 변경합니다.
            scores = Maps.newHashMap(); // 플레이어의 스코어 정보를 초기화 합니다.
        }
    }

    // 게임을 종료합니다.
    public void stopGame() {
        if (isPlaying) { // 게임이 실행중일 때
            isPlaying = false; // 게임을 종료 상태로 변경합니다.
        }
    }

    // 플레이어의 스코어를 설정합니다.
    public void setScore(@NotNull Player player, int score) {
        if (isPlaying) { // 게임이 실행중일 때
            // 플레이어의 UUID를 가져옵니다.
            final String uuid = player.getUniqueId().toString();

            // 플레이어의 스코어를 변경합니다.
            scores.put(uuid, score);
        }
    }

    // 플레이어의 스코어 정보를 반환합니다.
    public int getScore(@NotNull Player player) {
    	// 플레이어의 UUID를 가져옵니다.
        final String uuid = player.getUniqueId().toString();
        
        // 플레이어의 스코어 정보를 조회하고
        // 스코어가 존재한다면 가져온 스코어를, 그렇지 않다면 0을 반환합니다.
        return scores.getOrDefault(uuid, 0);
    }

    // 모든 플레이어의 스코어를 반환합니다.
    public Map&amp;lt;String, Integer&amp;gt; getScores() {
        return Maps.newHashMap(scores);
    }

    // 게임이 진행중이면 true, 그렇지 않다면 false를 반환합니다.
    public boolean isPlaying() {
        return isPlaying;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;관련 코드는 &lt;a href=&quot;https://github.com/Lede-dev/ShootingGame/blob/master/src/main/java/com/molruexception/shootinggame/GameController.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에서도 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 게임 커맨드 제작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 게임을 시작하고 종료할 수 있는 커맨드를 제작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드의 자동 완성 기능을 제작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CommandExecutor 인터페이스를 구현하여 커맨드 입력 시 작동할 기능을 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TabCompleter 인터페이스를 구현하여 커맨드 자동 완성 기능을 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 커맨드 클래스를 제작합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1681478977703&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CommandExecutor: onCommand() 메소드를 구현하여 커맨드의 작동 방식을 구현합니다.
// TabCompleter: onTabComplete() 메소드를 구현하여 커맨드의 자동 완성을 구현합니다.
public class GameCommand implements CommandExecutor, TabCompleter {

    @Override
    public boolean onCommand(
    	@NotNull CommandSender sender, @NotNull Command command, 
        @NotNull String label, @NotNull String[] args
    ) {
    
        // 입력한 커맨드 인자의 길이가 0이라면 종료합니다.
        // /shoot &amp;lt;- 인자의 길이가 0인 커맨드 입니다.
        // /shoot start &amp;lt;- 인자의 길이가 1인 커맨드 입니다.
        if (args.length == 0) {
            return false;
        }

        // 가장 처음으로 입력받은 인자가 무엇인지 확인합니다.
        switch (args[0]) {
            // /shoot start 커맨드를 입력하였을 때 실행됩니다.
            case &quot;start&quot; -&amp;gt; {
            	// GameController 객체를 가져옵니다.
                GameController.getInstance().startGame();
                
                // Bukkit.broadcast() 메소드를 사용하여 모든 유저에게 메시지를 전송합니다.
                // 또한, Paper 에서는 문자열 처리를 String 대신 Component를 사용하는것을 권장합니다.
                Bukkit.broadcast(Component.text(&quot;Start Game.&quot;));
            }
            // /shoot stop 커맨드를 입력하였을 때 실행됩니다.
            case &quot;stop&quot; -&amp;gt; {
            	// GameController 객체를 가져옵니다.
                GameController.getInstance().stopGame();
                
                // 모든 유저에게 메세지를 전송합니다.
                Bukkit.broadcast(Component.text(&quot;Stop Game.&quot;));
            }
        }

        return false;
    }

    @Override
    public @Nullable List&amp;lt;String&amp;gt; onTabComplete(
    	@NotNull CommandSender sender, @NotNull Command command, 
        @NotNull String label, @NotNull String[] args
    ) {
        // 입력한 커맨드 인자의 길이가 1이 아니라면 종료합니다.
        if (args.length != 1) {
            return null;
        }

        // 첫번째 인자의 자동완성을 &quot;start&quot; 와 &quot;stop&quot; 으로 설정합니다.
        return Lists.newArrayList(&quot;start&quot;, &quot;stop&quot;);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;관련 코드는 &lt;a href=&quot;https://github.com/Lede-dev/ShootingGame/blob/master/src/main/java/com/molruexception/shootinggame/GameCommand.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에서도 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로, plugin.yml 파일에 커맨드 정보를 입력합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1681479060819&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: ShootingGame
version: '${version}'
main: com.molruexception.shootinggame.ShootingGame
api-version: 1.19

# 등록할 커맨드 목록
commands:
  # 등록할 커맨드 이름 /shoot &amp;lt;arg 1&amp;gt;
  shoot:
    # 커맨드를 사용할 때 필요한 퍼미션
    permission: game.command.shoot&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;관련 코드는 &lt;a href=&quot;https://github.com/Lede-dev/ShootingGame/blob/master/src/main/resources/plugin.yml&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에서도 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 제작한 커맨드를 메인 클래스의 onEnable() 메소드에서 등록합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1681479228344&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class ShootingGame extends JavaPlugin {

    @Override
    public void onEnable() {
        // plugin.yml에 작성한 커맨드를 불러옵니다.
        final PluginCommand shootCommand = getCommand(&quot;shoot&quot;);
        
        // 커맨드를 정상적으로 불러왔다면
        if (shootCommand != null) {
            // 커맨드 객체를 생성합니다.
            GameCommand command = new GameCommand();
            
            // 불러온 커맨드의 실행 객체를 위에서 생성한 커맨드 객체로 설정합니다.
            shootCommand.setExecutor(command);
            
            // 불러온 커맨드의 자동완성 객체를 위에서 생성한 커맨드 객체로 설정합니다.
            shootCommand.setTabCompleter(command);
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;관련 코드는 &lt;a href=&quot;https://github.com/Lede-dev/ShootingGame/blob/master/src/main/java/com/molruexception/shootinggame/ShootingGame.java#L13-L18&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에서도 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 스코어 리스너 제작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 리스너 클래스를 제작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스너 클래스는 Listener 인터페이스를 구현하여 이벤트 리스너를 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1681479692133&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Listener 인터페이스를 구현하여 이벤트 리스너를 추가합니다.
public class GameListener implements Listener {

    // 화살 적중시 스코어를 5점으로 설정합니다.
    private static final int ARROW_HIT_SCORE = 5;
    
    // 적 처치시 스코어를 20점으로 설정합니다.
    private static final int PLAYER_KILL_SCORE = 20;

    // EventHandler 어노테이션을 부착하여 이벤트를 수신합니다.
    @EventHandler
    public void onArrowHit(ProjectileHitEvent event) { // ProjecttileHitEvent에서 발사체 피격을 감지합니다.
        // 게임이 실행중인지 확인하고 실행중이지 않다면 종료합니다.
        final GameController controller = GameController.getInstance();
        if (!controller.isPlaying()) {
            return;
        }

        // 투사체를 피격한 엔티티가 플레이어가 아니라면 종료합니다.
        if (!(event.getHitEntity() instanceof Player)) {
            return;
        }

        // 투사체가 화살이 아니라면 종료합니다.
        if (!(event.getEntity() instanceof Arrow arrow)) {
            return;
        }

        // 화살을 날린 주체가 플레이어가 아니라면 종료합니다.
        if (!(arrow.getShooter() instanceof Player shooter)) {
            return;
        }

        // 화살을 날린 플레이어의 스코어를 증가시킵니다.
        final int score = controller.getScore(shooter) + ARROW_HIT_SCORE;
        controller.setScore(shooter, score);

        // Player#sendMessage() 메소드를 사용하여 플레이어에게 메시지를 전송합니다.
        // Paper 에서는 String 대신 Component 사용을 권장합니다.
        shooter.sendMessage(Component.text(String.format(
                &quot;적을 공격하여 %d 포인트를 획득하였습니다. 현재 스코어: %d&quot;,
                ARROW_HIT_SCORE, score
        )));
    }

    // EntityDeathEvent 에서 엔티티의 사망을 감지합니다.
    @EventHandler
    public void onDeath(EntityDeathEvent event) {
    
        // 게임이 실행중인지 확인하고 실행중이지 않다면 종료합니다.
        final GameController controller = GameController.getInstance();
        if (!controller.isPlaying()) {
            return;
        }

        // 사망한 엔티티가 플레이어라면 victim 으로 캐스팅하고
        // 그렇지 않다면 종료합니다.
        if (!(event.getEntity() instanceof Player victim)) {
            return;
        }

        // 사망한 플레이어를 누가 죽였는지 불러옵니다.
        final Player killer = victim.getKiller();
        
        // 공격한 플레이어를 불러올 수 있다면
        if (killer != null) {
        
            // 공격한 플레이어의 스코어를 증가시킵니다.
            final int score = controller.getScore(killer) + PLAYER_KILL_SCORE;
            controller.setScore(killer, score);

            // Player#sendMessage() 메소드를 사용하여 메시지를 전송합니다.
            // Paper 에서는 String 대신 Component 사용을 권장합니다.
            killer.sendMessage(Component.text(String.format(
                    &quot;적을 처치하여 %d 포인트를 획득하였습니다. 현재 스코어: %d&quot;,
                    PLAYER_KILL_SCORE, score
            )));
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;관련 코드는 &lt;a href=&quot;https://github.com/Lede-dev/ShootingGame/blob/master/src/main/java/com/molruexception/shootinggame/GameListener.java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에서도 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로, 제작한 리스너를 메인 클래스의 onEnable() 메소드에서 등록합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1681479926317&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class ShootingGame extends JavaPlugin {

    @Override
    public void onEnable() {
        // Register Command
        final PluginCommand shootCommand = getCommand(&quot;shoot&quot;);
        if (shootCommand != null) {
            GameCommand command = new GameCommand();
            shootCommand.setExecutor(command);
            shootCommand.setTabCompleter(command);
        }

        // 플러그인 매니저를 불러옵니다.
        final PluginManager pm = Bukkit.getPluginManager();
        
        // 이벤트 리스너 객체를 생성하여 등록합니다.
        pm.registerEvents(new GameListener(), this);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;관련 코드는 &lt;a href=&quot;https://github.com/Lede-dev/ShootingGame/blob/master/src/main/java/com/molruexception/shootinggame/ShootingGame.java#L20-L22&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브&lt;/a&gt;에서도 확인할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해가 되지 않는 부분이나 궁금하신 점이 있다면 댓글로 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>튜토리얼/마인크래프트 플러그인</category>
      <category>마인크래프트 플러그인</category>
      <category>마인크래프트 플러그인 강좌</category>
      <category>마인크래프트 플러그인 튜토리얼</category>
      <category>플러그인 강좌</category>
      <category>플러그인 개발 환경 설정</category>
      <category>플러그인 튜토리얼</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/33</guid>
      <comments>https://lede-archive.tistory.com/33#entry33comment</comments>
      <pubDate>Fri, 14 Apr 2023 22:55:00 +0900</pubDate>
    </item>
    <item>
      <title>[Minecraft Plugin Tutorial] 1. 플러그인 개발환경 설정</title>
      <link>https://lede-archive.tistory.com/32</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;시작하기에 앞서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 플러그인 개발은 현 시점(2023-04-14) 기준 가장 최신버전인 마인크래프트 1.19.4 버전으로 진행합니다.&lt;br /&gt;- 코어 파일은 Spigot fork 인 Paper코어를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. JDK 다운로드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.oracle.com/kr/java/technologies/downloads/#jdk17-windows&quot;&gt;다운로드 페이지&lt;/a&gt;에 방문하여 자신의 환경에 맞는 파일을 다운로드하여 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 JDK 17.0.6 Windows x64 Installer를 사용하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jdk_download.png&quot; data-origin-width=&quot;1419&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FvWyv/btsahyMSGWj/qB0r1PUbGppqUnwBkTMdQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FvWyv/btsahyMSGWj/qB0r1PUbGppqUnwBkTMdQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FvWyv/btsahyMSGWj/qB0r1PUbGppqUnwBkTMdQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFvWyv%2FbtsahyMSGWj%2FqB0r1PUbGppqUnwBkTMdQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1419&quot; height=&quot;550&quot; data-filename=&quot;jdk_download.png&quot; data-origin-width=&quot;1419&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. IntelliJ 다운로드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.jetbrains.com/ko-kr/idea/download/&quot;&gt;다운로드 페이지&lt;/a&gt;에 방문하여 IntelliJ IDE를 다운로드하여 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이선스를 보유하고 있지 않다면 Community Edition을 사용하여도 무방합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ide_download.png&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRLyPY/btsaeTEO0MO/58tFWb7QSDH6x7UuZW4oI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRLyPY/btsaeTEO0MO/58tFWb7QSDH6x7UuZW4oI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRLyPY/btsaeTEO0MO/58tFWb7QSDH6x7UuZW4oI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRLyPY%2FbtsaeTEO0MO%2F58tFWb7QSDH6x7UuZW4oI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1342&quot; height=&quot;598&quot; data-filename=&quot;ide_download.png&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. IntelliJ Minecraft Plugin 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ 설치가 모두 마무리 되었다면 다음 절차를 따라 IntelliJ에 플러그인을 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. IntelliJ 최초 실행 화면에서 Plugins 탭으로 이동하거나 툴바의 File -&amp;gt; Settings -&amp;gt; Plugin 탭으로 이동하여 플러그인 설치 페이지로 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 검색창에 minecraft를 검색하고 Minecraft Development 플러그인을 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 설치가 완료되었다면 IntelliJ를 재시작 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;plugin_download.png&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b08WRg/btsaje1srEz/x2o00i7VbkwBhML4jniTiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b08WRg/btsaje1srEz/x2o00i7VbkwBhML4jniTiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b08WRg/btsaje1srEz/x2o00i7VbkwBhML4jniTiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb08WRg%2Fbtsaje1srEz%2Fx2o00i7VbkwBhML4jniTiK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1018&quot; height=&quot;772&quot; data-filename=&quot;plugin_download.png&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 프로젝트 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Minecraft Development 플러그인의 설치가 완료되었다면 다음 절차를 따라 프로젝트를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. IntelliJ 최초 실행 화면의 Projects 탭의 New Project 버튼을 클릭하거나 툴바의 File -&amp;gt; New -&amp;gt; Project 탭으로 이동하여 프로젝트 생성 페이지로 이동합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;new_project.png&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PChLy/btsagPO2Pty/WQ5bKAFQezI8pUnuuBfOp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PChLy/btsagPO2Pty/WQ5bKAFQezI8pUnuuBfOp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PChLy/btsagPO2Pty/WQ5bKAFQezI8pUnuuBfOp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPChLy%2FbtsagPO2Pty%2FWQ5bKAFQezI8pUnuuBfOp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1018&quot; height=&quot;772&quot; data-filename=&quot;new_project.png&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-1. 빨간색 박스와 같이 좌측의 Generators 목록에서 Minecraft 항목을 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-2. 초록색 박스와 같이 프로젝트의 이름과 생성될 폴더를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-3. 파란색 박스와 같이 Plugin -&amp;gt; Bukkit -&amp;gt; Paper 코어를 선택하고 버전을 1.19.4로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-4. 노란색 박스와 같이 Build System을 Gradle로 설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;create_project.png&quot; data-origin-width=&quot;1279&quot; data-origin-height=&quot;892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3zTkj/btsafP9Aqph/n9JCxXYQhKzra5Qzur66Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3zTkj/btsafP9Aqph/n9JCxXYQhKzra5Qzur66Ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3zTkj/btsafP9Aqph/n9JCxXYQhKzra5Qzur66Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3zTkj%2FbtsafP9Aqph%2Fn9JCxXYQhKzra5Qzur66Ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1279&quot; height=&quot;892&quot; data-filename=&quot;create_project.png&quot; data-origin-width=&quot;1279&quot; data-origin-height=&quot;892&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 프로젝트의 생성이 모두 완료될 때까지 기다립니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;init_project.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EKU5L/btsaeXNWm11/SKWydtcz6sa0bBOctGgQoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EKU5L/btsaeXNWm11/SKWydtcz6sa0bBOctGgQoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EKU5L/btsaeXNWm11/SKWydtcz6sa0bBOctGgQoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEKU5L%2FbtsaeXNWm11%2FSKWydtcz6sa0bBOctGgQoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; data-filename=&quot;init_project.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 플러그인 빌드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인의 메인 클래스를 확인해보면 다음과 같이 onEnable() 과 onDisable() 메소드를 확인할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;onEnable() 메소드는 플러그인이 활성화 될 때 호출됩니다.&lt;br /&gt;onDisable() 메소드는 플러그인이 비활성화 될 때 호출됩니다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;main_class.png&quot; data-origin-width=&quot;955&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTELFa/btsagPVRMLI/75antkvaPZQM6i2f0a22P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTELFa/btsagPVRMLI/75antkvaPZQM6i2f0a22P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTELFa/btsagPVRMLI/75antkvaPZQM6i2f0a22P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTELFa%2FbtsagPVRMLI%2F75antkvaPZQM6i2f0a22P0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;955&quot; height=&quot;504&quot; data-filename=&quot;main_class.png&quot; data-origin-width=&quot;955&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onEnable() 과 onDisable() 메소드에 메시지 출력을 추가하여 플러그인 활성화/비활성화를 로깅합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;add_log.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4ozDw/btsaeXHcryi/5DA46DiUEeUaXqhrS7LGOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4ozDw/btsaeXHcryi/5DA46DiUEeUaXqhrS7LGOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4ozDw/btsaeXHcryi/5DA46DiUEeUaXqhrS7LGOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4ozDw%2FbtsaeXHcryi%2F5DA46DiUEeUaXqhrS7LGOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;948&quot; height=&quot;511&quot; data-filename=&quot;add_log.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public final class SampleProject extends JavaPlugin {

    @Override
    public void onEnable() {
        // Plugin startup logic
        System.out.println(&quot;Plugin Enabled.&quot;);
    }

    @Override
    public void onDisable() {
        // Plugin shutdown logic
        System.out.println(&quot;Plugin Disabled.&quot;);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle 파일로 이동하여 다음과 같이 플러그인 빌드 경로를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드 경로는 자신의 환경에 맞춰 임의로 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;set_build_dir.png&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;927&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TQbzy/btsaeXAtVDX/9w02fCHF5kkYqtrgXaQLOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TQbzy/btsaeXAtVDX/9w02fCHF5kkYqtrgXaQLOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TQbzy/btsaeXAtVDX/9w02fCHF5kkYqtrgXaQLOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTQbzy%2FbtsaeXAtVDX%2F9w02fCHF5kkYqtrgXaQLOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1100&quot; height=&quot;927&quot; data-filename=&quot;set_build_dir.png&quot; data-origin-width=&quot;1100&quot; data-origin-height=&quot;927&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;jar {
    destinationDirectory.set(file(&quot;D:\\ProjectTest\\1.19.2\\plugins&quot;))
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측의 패널에서 Gradle 버튼을 클릭하고 Tasks -&amp;gt; build -&amp;gt; jar 를 클릭하여 플러그인을 빌드합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;build_button.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BUXW8/btsakcJg2tW/OJwQZ4vUZKYuAekmmfKze0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BUXW8/btsakcJg2tW/OJwQZ4vUZKYuAekmmfKze0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BUXW8/btsakcJg2tW/OJwQZ4vUZKYuAekmmfKze0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBUXW8%2FbtsakcJg2tW%2FOJwQZ4vUZKYuAekmmfKze0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1080&quot; data-filename=&quot;build_button.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 로그가 출력되면 설정한 빌드 경로에서 빌드된 플러그인을 확인할 수 있습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;build_complete.png&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gyd6k/btsahKz5qHg/VnmxpFszmnnRh7xvCLMIo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gyd6k/btsahKz5qHg/VnmxpFszmnnRh7xvCLMIo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gyd6k/btsahKz5qHg/VnmxpFszmnnRh7xvCLMIo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGyd6k%2FbtsahKz5qHg%2FVnmxpFszmnnRh7xvCLMIo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1070&quot; height=&quot;278&quot; data-filename=&quot;build_complete.png&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;278&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;build_dir.png&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1ptLP/btsafpKdJ4h/7JdJt3XRzwNcznHpyqDJGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1ptLP/btsafpKdJ4h/7JdJt3XRzwNcznHpyqDJGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1ptLP/btsafpKdJ4h/7JdJt3XRzwNcznHpyqDJGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1ptLP%2FbtsafpKdJ4h%2F7JdJt3XRzwNcznHpyqDJGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1125&quot; height=&quot;712&quot; data-filename=&quot;build_dir.png&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 플러그인 실행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 실행/종료 하여 다음과 같이 로그를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;log.png&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sy7Gt/btsaiaZxH33/5TUoYhjxeJ2zKgCgN3k5KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sy7Gt/btsaiaZxH33/5TUoYhjxeJ2zKgCgN3k5KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sy7Gt/btsaiaZxH33/5TUoYhjxeJ2zKgCgN3k5KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsy7Gt%2FbtsaiaZxH33%2F5TUoYhjxeJ2zKgCgN3k5KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1115&quot; height=&quot;628&quot; data-filename=&quot;log.png&quot; data-origin-width=&quot;1115&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경 설정에 어려움이 있거나 궁금하신점이 있다면 댓글로 남겨주세요.&lt;/p&gt;</description>
      <category>튜토리얼/마인크래프트 플러그인</category>
      <category>마인크래프트 플러그인</category>
      <category>마인크래프트 플러그인 강좌</category>
      <category>마인크래프트 플러그인 튜토리얼</category>
      <category>플러그인 강좌</category>
      <category>플러그인 개발 환경 설정</category>
      <category>플러그인 튜토리얼</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/32</guid>
      <comments>https://lede-archive.tistory.com/32#entry32comment</comments>
      <pubDate>Fri, 14 Apr 2023 18:57:09 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 싱글톤(Singleton) 패턴</title>
      <link>https://lede-archive.tistory.com/31</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;싱글톤(Singleton) 패턴이란?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템에 단 하나의 클래스 인스턴스만 존재하도록 하여 인스턴스에 대한 단일 액세스 지점을 제공하고 이에 따라 충돌 및 불일치를 방지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;싱글톤 패턴의 장점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;글로벌 액세스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스에 하나의 인스턴스만 존재하고 시스템의 모든 객체가 싱글톤 클래스의 동일한 인스턴스에 액세스 할 수 있기 때문에, 일관된 동작 및 데이터 공유가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리소스 소비 감소&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 전체에서 싱글톤 클래스의 인스턴스가 하나만 생성되기 때문에 사용하는 리소스의 양이 줄어든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;싱글톤 패턴의 단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전역 상태 (Global State)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;싱글톤 클래스의 인스턴스는 시스템의 모든 개체에서 액세스 및 수정이 가능하기 때문에 잠재적으로 의도하지 않은 동작과 버그가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;긴밀한 결합&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;싱글톤 패턴은 싱글톤 인스턴스의 구현에 의존하기 때문에 싱글톤 인스턴스가 자주 사용되는 시스템의 경우 개체간의 긴밀한 결합으로 이어질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 예제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 게임의 구성 요소를 불러오는 시스템을 예로 들어 설명한 싱글톤 패턴의 예제이다&lt;br /&gt;먼저 싱글톤 패턴의 클래스를 정의한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public class GameConfig {

    /**
        멀티스레드 환경에서 단 하나의 객체만 생성됨을 보장하기 위해
        Initialization on demand holder 방식으로 객체 초기화
    */    
    private static class GameConfigHolder {
        public static final GameConfig INSTANCE = new GameConfig();
    }

    // JVM 에서 GameConfigHolder의 static field 를 초기화
    public static GameConfig getInstance() {
        return GameConfigHolder.INSTANCE; 
    }

    private int maxPlayer = 0;

    private GameConfig() {} // 다른 객체에서 인스턴스를 직접적으로 생성할 수 없다

    public int getMaxPlayer() {
        return maxPlayer;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 GameConfig 의 maxPlayer 값을 출력하는 예제이다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    GameConfig config = GameConfig.getInstance();
    System.out.printf(&quot;Max Player: %d&quot;, config.getMaxPlayer()); 
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>공부합시다/DesignPattern</category>
      <category>Design Pattern</category>
      <category>Java</category>
      <category>Singleton</category>
      <category>singletonPattern</category>
      <category>디자인 패턴</category>
      <category>싱글톤</category>
      <category>싱글톤 패턴</category>
      <category>자바</category>
      <author>Lede_</author>
      <guid isPermaLink="true">https://lede-archive.tistory.com/31</guid>
      <comments>https://lede-archive.tistory.com/31#entry31comment</comments>
      <pubDate>Sun, 9 Apr 2023 08:16:43 +0900</pubDate>
    </item>
  </channel>
</rss>