<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>C# Archives - 어제와 내일의 나 그 사이의 이야기</title>
	<atom:link href="https://lycos7560.com/tag/c-2/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>생각의 흐름을 타고 다니며 만드는 블로그</description>
	<lastBuildDate>Tue, 25 Nov 2025 15:17:43 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://lycos7560.com/wp-content/uploads/2022/11/cropped-cropped-cropped-log-1-150x150-1-80x80.png</url>
	<title>C# Archives - 어제와 내일의 나 그 사이의 이야기</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>C# Switch 식(Expression)</title>
		<link>https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/</link>
					<comments>https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Tue, 11 Nov 2025 15:02:40 +0000</pubDate>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[!=]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[.NET 5]]></category>
		<category><![CDATA[.NET 6]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[>=]]></category>
		<category><![CDATA[<=]]></category>
		<category><![CDATA[==]]></category>
		<category><![CDATA[2019]]></category>
		<category><![CDATA[2020]]></category>
		<category><![CDATA[access]]></category>
		<category><![CDATA[action]]></category>
		<category><![CDATA[Admin]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Arrow Operator]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[Boolean]]></category>
		<category><![CDATA[Branch]]></category>
		<category><![CDATA[Branching]]></category>
		<category><![CDATA[C#7]]></category>
		<category><![CDATA[C#8]]></category>
		<category><![CDATA[C#9]]></category>
		<category><![CDATA[Case]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[Clean Code]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Comparison]]></category>
		<category><![CDATA[Compile]]></category>
		<category><![CDATA[Concise]]></category>
		<category><![CDATA[Condition]]></category>
		<category><![CDATA[Conditional]]></category>
		<category><![CDATA[Control]]></category>
		<category><![CDATA[Control Flow]]></category>
		<category><![CDATA[CSharp]]></category>
		<category><![CDATA[Declarative]]></category>
		<category><![CDATA[Declarative Style]]></category>
		<category><![CDATA[Default]]></category>
		<category><![CDATA[delegate]]></category>
		<category><![CDATA[Developer]]></category>
		<category><![CDATA[Developer Guide]]></category>
		<category><![CDATA[Docs]]></category>
		<category><![CDATA[Documentation]]></category>
		<category><![CDATA[dotnet]]></category>
		<category><![CDATA[Education]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[Error Handling]]></category>
		<category><![CDATA[Example]]></category>
		<category><![CDATA[Exception]]></category>
		<category><![CDATA[Execution]]></category>
		<category><![CDATA[Expression]]></category>
		<category><![CDATA[Expression Body]]></category>
		<category><![CDATA[Expression Tree]]></category>
		<category><![CDATA[False]]></category>
		<category><![CDATA[Feature]]></category>
		<category><![CDATA[Flow]]></category>
		<category><![CDATA[Framework]]></category>
		<category><![CDATA[Function Pointer]]></category>
		<category><![CDATA[Functional]]></category>
		<category><![CDATA[Functional Programming]]></category>
		<category><![CDATA[Grammar]]></category>
		<category><![CDATA[Guide]]></category>
		<category><![CDATA[History]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[Imperative]]></category>
		<category><![CDATA[Intuitive]]></category>
		<category><![CDATA[Lambda]]></category>
		<category><![CDATA[Language Evolution]]></category>
		<category><![CDATA[Language Feature]]></category>
		<category><![CDATA[Learning]]></category>
		<category><![CDATA[Library]]></category>
		<category><![CDATA[Logical Pattern]]></category>
		<category><![CDATA[Maintainable]]></category>
		<category><![CDATA[Mapping]]></category>
		<category><![CDATA[method]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Migration]]></category>
		<category><![CDATA[Modern C#]]></category>
		<category><![CDATA[Modern Programming]]></category>
		<category><![CDATA[New Feature]]></category>
		<category><![CDATA[November]]></category>
		<category><![CDATA[Null]]></category>
		<category><![CDATA[Null Check]]></category>
		<category><![CDATA[Nullable]]></category>
		<category><![CDATA[Object]]></category>
		<category><![CDATA[Object-Oriented]]></category>
		<category><![CDATA[OOP]]></category>
		<category><![CDATA[Operator]]></category>
		<category><![CDATA[Pattern Matching]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Property]]></category>
		<category><![CDATA[Property Pattern]]></category>
		<category><![CDATA[Range]]></category>
		<category><![CDATA[Readable]]></category>
		<category><![CDATA[Refactor]]></category>
		<category><![CDATA[Refactoring]]></category>
		<category><![CDATA[Relational Pattern]]></category>
		<category><![CDATA[release]]></category>
		<category><![CDATA[Return Value]]></category>
		<category><![CDATA[Role]]></category>
		<category><![CDATA[Running]]></category>
		<category><![CDATA[Runtime]]></category>
		<category><![CDATA[Sample Code]]></category>
		<category><![CDATA[SDK]]></category>
		<category><![CDATA[September]]></category>
		<category><![CDATA[Simplify]]></category>
		<category><![CDATA[Snippet]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[State]]></category>
		<category><![CDATA[Statement]]></category>
		<category><![CDATA[Stopped]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Switch Action]]></category>
		<category><![CDATA[Switch Branch]]></category>
		<category><![CDATA[Switch Case]]></category>
		<category><![CDATA[Switch Clean]]></category>
		<category><![CDATA[Switch Default]]></category>
		<category><![CDATA[Switch Delegate]]></category>
		<category><![CDATA[Switch Docs]]></category>
		<category><![CDATA[Switch Evolution]]></category>
		<category><![CDATA[Switch Example]]></category>
		<category><![CDATA[Switch Expression]]></category>
		<category><![CDATA[Switch Expression Action]]></category>
		<category><![CDATA[Switch Expression C#]]></category>
		<category><![CDATA[Switch Expression Clean]]></category>
		<category><![CDATA[Switch Expression Default]]></category>
		<category><![CDATA[Switch Expression Delegate]]></category>
		<category><![CDATA[Switch Expression Docs]]></category>
		<category><![CDATA[Switch Expression Education]]></category>
		<category><![CDATA[Switch Expression Evolution]]></category>
		<category><![CDATA[Switch Expression Example]]></category>
		<category><![CDATA[Switch Expression Guide]]></category>
		<category><![CDATA[Switch Expression History]]></category>
		<category><![CDATA[Switch Expression Lambda]]></category>
		<category><![CDATA[Switch Expression Learning]]></category>
		<category><![CDATA[Switch Expression Logical]]></category>
		<category><![CDATA[Switch Expression Migration]]></category>
		<category><![CDATA[Switch Expression Modern]]></category>
		<category><![CDATA[Switch Expression Null]]></category>
		<category><![CDATA[Switch Expression Pattern]]></category>
		<category><![CDATA[Switch Expression Property]]></category>
		<category><![CDATA[Switch Expression Relational]]></category>
		<category><![CDATA[Switch Expression Sample]]></category>
		<category><![CDATA[Switch Expression Simplify]]></category>
		<category><![CDATA[Switch Expression Snippet]]></category>
		<category><![CDATA[Switch Expression Study]]></category>
		<category><![CDATA[Switch Expression Tuple]]></category>
		<category><![CDATA[Switch Expression Tutorial]]></category>
		<category><![CDATA[Switch Expression Upgrade]]></category>
		<category><![CDATA[Switch Expression Version]]></category>
		<category><![CDATA[Switch Expression Wildcard]]></category>
		<category><![CDATA[Switch Feature]]></category>
		<category><![CDATA[Switch Flow]]></category>
		<category><![CDATA[Switch Function]]></category>
		<category><![CDATA[Switch Guide]]></category>
		<category><![CDATA[Switch Keyword]]></category>
		<category><![CDATA[Switch Lambda]]></category>
		<category><![CDATA[Switch Logical]]></category>
		<category><![CDATA[Switch Mapping]]></category>
		<category><![CDATA[Switch Modern]]></category>
		<category><![CDATA[Switch Null]]></category>
		<category><![CDATA[Switch Operator]]></category>
		<category><![CDATA[Switch Pattern]]></category>
		<category><![CDATA[Switch Property]]></category>
		<category><![CDATA[Switch Relational]]></category>
		<category><![CDATA[Switch Simplify]]></category>
		<category><![CDATA[Switch Statement]]></category>
		<category><![CDATA[Switch Syntax]]></category>
		<category><![CDATA[Switch Tuple]]></category>
		<category><![CDATA[Switch Tutorial]]></category>
		<category><![CDATA[Switch Wildcard]]></category>
		<category><![CDATA[Syntax]]></category>
		<category><![CDATA[System]]></category>
		<category><![CDATA[True]]></category>
		<category><![CDATA[tuple]]></category>
		<category><![CDATA[Tuple Pattern]]></category>
		<category><![CDATA[TUTORIAL]]></category>
		<category><![CDATA[Upgrade]]></category>
		<category><![CDATA[User]]></category>
		<category><![CDATA[Value]]></category>
		<category><![CDATA[Variable]]></category>
		<category><![CDATA[version]]></category>
		<category><![CDATA[Visual studio]]></category>
		<category><![CDATA[VS Code]]></category>
		<category><![CDATA[Wildcard]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40362</guid>

					<description><![CDATA[<p>C# Switch 식(Expression) C# 8.0(2019년 9월)부터 도입된 Switch 식(Switch Expression)은 기존의 switch-case 문을 획기적으로 줄여주는 강력한 기능 람다 표현식(=>)을 쓰는 것처럼 간결하고 직관적인 분기 처리가 가능 기존 방식 (Statement) 새로운 방식 (Expression) 강력한 활용 패턴 C# 9.0(2020년 11월)부터 Switch 식이 단순 매핑이 아니라 패턴 매칭(Pattern Matching)과 결합됨 범위 비교 (Relational Pattern) C# 9.0부터는 when 키워드 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/">C# Switch 식(Expression)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-1bf84b0a      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#c-switch-식expression" class="uagb-toc-link__trigger">C# Switch 식(Expression)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#기존-방식-statement" class="uagb-toc-link__trigger">기존 방식 (Statement)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#새로운-방식-expression" class="uagb-toc-link__trigger">새로운 방식 (Expression)</a></li></ul></li><li class="uagb-toc__list"><a href="#강력한-활용-패턴" class="uagb-toc-link__trigger">강력한 활용 패턴</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#범위-비교-relational-pattern" class="uagb-toc-link__trigger">범위 비교 (Relational Pattern)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#여러-변수-동시에-비교-tuple-pattern" class="uagb-toc-link__trigger">여러 변수 동시에 비교 (Tuple Pattern)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#객체-속성-비교-property-pattern" class="uagb-toc-link__trigger">객체 속성 비교 (Property Pattern)</a></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#리턴값이-없는-메서드action-실행" class="uagb-toc-link__trigger">리턴값이 없는 메서드(Action) 실행</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">C# Switch 식(Expression)</h2>



<p>C# 8.0(2019년 9월)부터 도입된 <strong>Switch 식(Switch Expression)</strong>은 기존의 <code><strong>switch-case</strong></code> 문을 획기적으로 줄여주는 강력한 기능</p>



<p>람다 표현식(<code>=></code>)을 쓰는 것처럼 간결하고 직관적인 분기 처리가 가능</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">기존 방식 (Statement)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public string GetGrade(int score)
{
    string grade;
    switch (score)
    {
        case 90:
            grade = "A";
            break;
        case 80:
            grade = "B";
            break;
        default:
            grade = "C";
            break;
    }
    return grade;
}</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">새로운 방식 (Expression)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public string GetGrade(int score) => score switch
{
    90 => "A",
    80 => "B",
    _  => "C"
};


또는 다른 범위 방식으로 (결과는 다름) 
score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    _     => "D"
};
</pre>



<ul class="wp-block-list">
<li><strong>위치 이동:</strong> <code>switch</code> 키워드가 변수 <strong>뒤</strong>로 이동 (<code>score switch</code>)</li>



<li><strong>화살표 사용:</strong> <code>case ... :</code> 대신 람다 화살표 <code>=></code>를 사용</li>



<li><strong>Default 처리:</strong> <code>default:</code> 대신 와일드카드 <code>_</code>를 사용</li>



<li><strong>식(Expression):</strong> 그 자체가 &#8216;값&#8217;을 반환하므로 메서드의 리턴값으로 바로 사용할 수 있음</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">강력한 활용 패턴</h2>



<p>C# 9.0(2020년 11월)부터 Switch 식이 단순 매핑이 아니라 <strong>패턴 매칭(Pattern Matching)</strong>과 결합됨</p>



<ul class="wp-block-list">
<li><strong>Switch Expression 자체는 C# 8에서 처음 도입</strong></li>



<li><strong>Pattern Matching 자체는 C# 7부터 지원</strong></li>



<li><strong>Relational Pattern, Logical Pattern 등이 C# 9에서 추가</strong></li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">범위 비교 (Relational Pattern)</h3>



<p>C# 9.0부터는 <code>when</code> 키워드 없이도 관계 연산자를 직접 사용</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">int temperature = 28;

string weather = temperature switch
{
    >= 30 => "폭염",
    >= 20 => "쾌적함",
    >= 10 => "쌀쌀함",
    _     => "추움"
};</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">여러 변수 동시에 비교 (Tuple Pattern)</h3>



<p>두 개 이상의 상태를 조합해서 분기해야 할 때, <code>if-else</code> 중첩 없이 튜플 패턴으로 깔끔하게 해결</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">string state = "Running";
bool hasError = true;

// (상태, 에러여부)를 한 번에 검사
string message = (state, hasError) switch
{
    ("Running", false) => "정상 작동 중",
    ("Running", true)  => "작동 중이나 오류 발생",
    ("Stopped", _)     => "시스템 중지됨", // _는 true/false 상관없음
    _                  => "알 수 없는 상태"
};</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">객체 속성 비교 (Property Pattern)</h3>



<p>객체 내부의 특정 프로퍼티 값에 따라 분기할 수도 있음 </p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var user = new User { Role = "Admin", IsDeleted = false };

string accessLevel = user switch
{
    { Role: "Admin", IsDeleted: false } => "관리자 권한",
    { Role: "User", IsDeleted: false }  => "일반 사용자",
    { IsDeleted: true }                 => "삭제된 계정",
    _                                   => "접근 불가" // null 포함
};

// 객체가 null인 경우도 자동으로 _</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">리턴값이 없는 메서드(Action) 실행</h2>



<p><code>Action</code> 델리게이트를 반환받아 즉시 실행</p>



<p>switch expression은 &#8220;값&#8221;을 반환하므로 델리게이트(함수 포인터)를 반환하는 것도 가능</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">int command = 1;

// 1. 각 조건에 맞는 Action(행동)을 선택하고
// 2. 마지막의 ()로 즉시 실행합니다.
(command switch
{
    1 => (Action)(() => SaveData()),
    2 => () => LoadData(),
    _ => () => Console.WriteLine("대기")
})(); 
// 마지막 ()는 "반환된 델리게이트 실행"</pre>



<p></p>
<p>The post <a href="https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/">C# Switch 식(Expression)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/c-switch-%ec%8b%9dexpression/40362/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</title>
		<link>https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/</link>
					<comments>https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Fri, 08 Aug 2025 07:45:23 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[2FA]]></category>
		<category><![CDATA[Account]]></category>
		<category><![CDATA[AccountManagement]]></category>
		<category><![CDATA[action]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[appsettings]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Backend]]></category>
		<category><![CDATA[Blazor]]></category>
		<category><![CDATA[Callback]]></category>
		<category><![CDATA[CD]]></category>
		<category><![CDATA[Challenge]]></category>
		<category><![CDATA[CI]]></category>
		<category><![CDATA[CICD]]></category>
		<category><![CDATA[Claim]]></category>
		<category><![CDATA[ClaimsPrincipal]]></category>
		<category><![CDATA[ClientSide]]></category>
		<category><![CDATA[CloudComputing]]></category>
		<category><![CDATA[CloudNative]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[Controller]]></category>
		<category><![CDATA[Cookie]]></category>
		<category><![CDATA[Core]]></category>
		<category><![CDATA[Cryptography]]></category>
		<category><![CDATA[CSharp]]></category>
		<category><![CDATA[DataBase]]></category>
		<category><![CDATA[DataTransferObject]]></category>
		<category><![CDATA[DbContext]]></category>
		<category><![CDATA[Debugging]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[DTO]]></category>
		<category><![CDATA[EmailConfirmation]]></category>
		<category><![CDATA[EntityFramework]]></category>
		<category><![CDATA[ErrorHandling]]></category>
		<category><![CDATA[Exception]]></category>
		<category><![CDATA[ExternalLogin]]></category>
		<category><![CDATA[ExternalProvider]]></category>
		<category><![CDATA[Frontend]]></category>
		<category><![CDATA[FullStack]]></category>
		<category><![CDATA[GCP]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[GitHub]]></category>
		<category><![CDATA[GitLab]]></category>
		<category><![CDATA[GoogleAuth]]></category>
		<category><![CDATA[GoogleCloud]]></category>
		<category><![CDATA[GoogleLogin]]></category>
		<category><![CDATA[HTTP302]]></category>
		<category><![CDATA[HttpGet]]></category>
		<category><![CDATA[HttpPost]]></category>
		<category><![CDATA[HttpRequest]]></category>
		<category><![CDATA[HttpResponse]]></category>
		<category><![CDATA[IDE]]></category>
		<category><![CDATA[Identity]]></category>
		<category><![CDATA[IIS]]></category>
		<category><![CDATA[JSONWebToken]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Kestrel]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Log]]></category>
		<category><![CDATA[Logging]]></category>
		<category><![CDATA[Login]]></category>
		<category><![CDATA[Logout]]></category>
		<category><![CDATA[Microservices]]></category>
		<category><![CDATA[MicrosoftIdentity]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[NoSQL]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[OAuth2.0]]></category>
		<category><![CDATA[OpenID]]></category>
		<category><![CDATA[OpenIDConnect]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[password]]></category>
		<category><![CDATA[Passwordless]]></category>
		<category><![CDATA[path]]></category>
		<category><![CDATA[pipeline]]></category>
		<category><![CDATA[Production]]></category>
		<category><![CDATA[Profile]]></category>
		<category><![CDATA[QueryString]]></category>
		<category><![CDATA[Redirect]]></category>
		<category><![CDATA[RedirectResponse]]></category>
		<category><![CDATA[Register]]></category>
		<category><![CDATA[ResetPassword]]></category>
		<category><![CDATA[RESTfulAPI]]></category>
		<category><![CDATA[Route]]></category>
		<category><![CDATA[Routing]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SecurityToken]]></category>
		<category><![CDATA[ServerSide]]></category>
		<category><![CDATA[session]]></category>
		<category><![CDATA[SignInManager]]></category>
		<category><![CDATA[SignUp]]></category>
		<category><![CDATA[SinglePageApplication]]></category>
		<category><![CDATA[SocialLogin]]></category>
		<category><![CDATA[SPA]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[stage]]></category>
		<category><![CDATA[Startup]]></category>
		<category><![CDATA[StateManagement]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Token]]></category>
		<category><![CDATA[TryCatch]]></category>
		<category><![CDATA[TwoFactor]]></category>
		<category><![CDATA[URI]]></category>
		<category><![CDATA[url]]></category>
		<category><![CDATA[UserDB]]></category>
		<category><![CDATA[UserManagement]]></category>
		<category><![CDATA[UserManager]]></category>
		<category><![CDATA[UserProfile]]></category>
		<category><![CDATA[UserStore]]></category>
		<category><![CDATA[VisualStudio]]></category>
		<category><![CDATA[VSCode]]></category>
		<category><![CDATA[WebAPI]]></category>
		<category><![CDATA[WebApp]]></category>
		<category><![CDATA[WebApplication]]></category>
		<category><![CDATA[WebDevelopment]]></category>
		<category><![CDATA[WebSecurity]]></category>
		<category><![CDATA[WebServer]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40245</guid>

					<description><![CDATA[<p>🔥 ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석 1️⃣ 사용자가 &#8220;Google로 로그인&#8221; 버튼을 클릭 Navigation.NavigateTo(..., forceLoad: true) Blazor의 내부 라우팅이 아닌, 브라우저가 직접 해당 URL(api/auth/Challenge/Google...)로 GET 요청보낸다. 2️⃣ 백엔드의 인증 시작 (AuthController &#8211; Challenge) 3️⃣ 외부 공급자 인증 (Google) 사용자는 Google 로그인 페이지로 이동 4️⃣ 백엔드의 콜백 처리 및 로그인/회원가입 (AuthController &#8211; Callback) [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/">ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</h2>


				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-cbc30617      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnet-core-identity를-활용한-구글-로그인oauth-전체-흐름-분석" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-사용자가-google로-로그인-버튼을-클릭" class="uagb-toc-link__trigger">1&#x20e3; 사용자가 &quot;Google로 로그인&quot; 버튼을 클릭</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-백엔드의-인증-시작-authcontroller-challenge" class="uagb-toc-link__trigger">2&#x20e3; 백엔드의 인증 시작 (AuthController &#8211; Challenge)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-외부-공급자-인증-google" class="uagb-toc-link__trigger">3&#x20e3; 외부 공급자 인증 (Google)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-백엔드의-콜백-처리-및-로그인회원가입-authcontroller-callback" class="uagb-toc-link__trigger">4&#x20e3; 백엔드의 콜백 처리 및 로그인/회원가입 (AuthController &#8211; Callback)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#5-완료" class="uagb-toc-link__trigger">5&#x20e3; 완료</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#6-authcontrollercs" class="uagb-toc-link__trigger">6&#x20e3; AuthController.cs</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1159" height="1920" src="https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid.jpg" alt="" class="wp-image-40248" srcset="https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid.jpg 1159w, https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid-181x300.jpg 181w, https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid-768x1272.jpg 768w, https://lycos7560.com/wp-content/uploads/2025/08/Google-Auth-Mermaid-927x1536.jpg 927w" sizes="(max-width: 1159px) 100vw, 1159px" /></figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 사용자가 &#8220;Google로 로그인&#8221; 버튼을 클릭</h3>



<p><code>Navigation.NavigateTo(..., forceLoad: true)</code></p>



<p>Blazor의 내부 라우팅이 아닌, 브라우저가 직접 해당 URL(<code>api/auth/Challenge/Google...</code>)로 <strong>GET 요청</strong>보낸다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">
private void LoginWithGoogle()
{
    var returnUrl = "/"; // 로그인 성공 후 돌아올 주소
    var googleLoginUrl = $"/api/auth/Challenge/Google?returnUrl={Uri.EscapeDataString(returnUrl)}";

    // 이 주소로 브라우저가 페이지를 새로고침하며 이동
    Navigation.NavigateTo(googleLoginUrl, forceLoad: true);
}</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 백엔드의 인증 시작 (AuthController &#8211; Challenge)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AuthController.cs
[HttpGet("Challenge/{provider}")]
public IActionResult Challenge(string provider, string? returnUrl = null)
{
    // ...
    var redirectUrl = Url.Action("Callback", "Auth", ...); // 1. Google이 인증 후 돌아올 우리 API 주소(Callback)를 지정
    var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); // 2. 인증 요청에 필요한 속성 구성

    return Challenge(properties, provider); // 3. Google 로그인 페이지로 리디렉션하는 응답 생성
}</pre>



<ul class="wp-block-list">
<li><code>AuthController.cs</code>의 <code>Challenge</code> 메서드가 실행됩니다. <br><code>provider</code> 매개변수에는 &#8220;Google&#8221;이 전달</li>



<li><code>_signInManager</code>는 ASP.NET Core Identity의 핵심 기능으로, 외부 로그인 과정을 도와줌</li>



<li><code>return Challenge(properties, provider);</code>는 브라우저에게 <strong>HTTP 302 Redirect</strong> 응답을 보냄<br>이 응답 헤더에는 사용자가 이동해야 할 <strong>Google의 로그인 페이지 주소</strong>가 포함</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 외부 공급자 인증 (Google)</h3>



<p><strong>사용자는 Google 로그인 페이지로 이동</strong></p>



<ul class="wp-block-list">
<li>사용자는 자신의 Google 계정으로 로그인하고, 우리 애플리케이션이 요청하는 정보(이메일, 프로필 등)에 대한 접근 권한을 허용</li>



<li>인증이 성공적으로 완료되면, Google은 2단계에서 백엔드가 지정했던 <strong>콜백 URL</strong>(<code>api/Auth/Callback</code>)로 사용자를 다시 리디렉션<br>이때 인증 코드 또는 토큰과 함께 사용자를 보냄</li>
</ul>



<ol start="1" class="wp-block-list"></ol>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4&#x20e3; 백엔드의 콜백 처리 및 로그인/회원가입 (AuthController &#8211; Callback)</h3>



<p><strong>Google이 사용자를 우리 백엔드의 <code>Callback</code> 엔드포인트로 돌려보냄</strong></p>



<p><code>AuthController.cs</code>의 <code>Callback</code> (라우팅 경로: <code>externalLogin</code>) 메서드가 실행</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// AuthController.cs
[HttpGet("externalLogin")]
public async Task&lt;IActionResult> Callback(...)
{
    // 1. Google이 보내준 정보로 외부 로그인 정보를 가져옴
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null) { /* 에러 처리 */ }

    // 2. 이 외부 정보(공급자, 키)로 우리 DB에 연결된 사용자가 있는지 확인하고 로그인 시도
    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, ...);

    if (result.Succeeded)
    {
        // 3a. 성공! 이미 가입하고 연동된 사용자. 로그인 처리 후 returnUrl로 리디렉션
        return LocalRedirect(returnUrl);
    }
    else
    {
        // 3b. 실패. 신규 사용자이거나, 이메일은 같지만 연동되지 않은 사용자.
        return await HandleUserCreationOrLinking(info, returnUrl);
    }
}</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><code>HandleUserCreationOrLinking</code> 메서드는 다음 두 가지 시나리오를 처리</p>



<ul class="wp-block-list">
<li><strong>시나리오 A: 이메일은 존재하지만 소셜 연동이 안 된 경우 (<code>LinkExistingUser</code>)</strong>
<ul class="wp-block-list">
<li>Google에서 받은 이메일로 우리 DB에서 사용자를 찾습니다. (<code>_userManager.FindByEmailAsync</code>)</li>



<li>기존 사용자가 있다면, 해당 계정에 이 Google 로그인 정보를 추가로 연결합니다. (<code>_userManager.AddLoginAsync</code>)</li>



<li>연결 후, 사용자를 로그인시키고 <code>returnUrl</code>로 리디렉션합니다.</li>
</ul>
</li>



<li><strong>시나리오 B: 완전히 새로운 사용자인 경우 (<code>CreateNewUserAsync</code>)</strong>
<ul class="wp-block-list">
<li>Google에서 받은 이메일과 이름으로 새로운 <code>ApplicationUser</code> 객체를 만듭니다. 이때 <code>EmailConfirmed</code>는 <code>true</code>로 설정합니다. (소셜 로그인은 이미 이메일이 인증된 것으로 간주)</li>



<li><code>_userManager.CreateAsync(user)</code>로 DB에 새 사용자를 저장합니다.</li>



<li>새로 생성된 사용자 계정에 Google 로그인 정보를 연결합니다. (<code>_userManager.AddLoginAsync</code>)</li>



<li>새 사용자를 로그인시키고 <code>returnUrl</code>로 리디렉션합니다.</li>
</ul>
</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">5&#x20e3; 완료</h3>



<p>로그인 또는 회원가입 및 연동이 모두 성공적으로 끝나면, 백엔드는 최종적으로 <code>LocalRedirect(returnUrl)</code>을 통해 사용자를 맨 처음 <code>Login.razor</code>에서 지정했던 주소(<code>/</code>)로 리디렉션합니다. </p>



<p>이제 사용자는 로그인된 상태로 메인 페이지를 보게 됩니다.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">6&#x20e3; AuthController.cs</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.IdentityModel.Tokens;
using Neco.BaseTemplate.Shared.Data;
using Neco.BaseTemplete.Data;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;

namespace Neco.BaseTemplete.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AuthController : ControllerBase
    {
        private readonly UserManager&lt;ApplicationUser> _userManager;
        private readonly SignInManager&lt;ApplicationUser> _signInManager;
        private readonly IUserStore&lt;ApplicationUser> _userStore;
        private readonly IEmailSender&lt;ApplicationUser> _emailSender;
        private readonly ILogger&lt;AuthController> _logger;

        public AuthController(
            UserManager&lt;ApplicationUser> userManager,
            SignInManager&lt;ApplicationUser> signInManager,
            IUserStore&lt;ApplicationUser> userStore,
            IEmailSender&lt;ApplicationUser> emailSender,
            ILogger&lt;AuthController> logger)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _userStore = userStore;
            _emailSender = emailSender;
            _logger = logger;
        }

        #region Basic Authentication

        /// &lt;summary>
        /// Handles user login.
        /// &lt;/summary>
        /// &lt;param name="model">Login request DTO (email, password, RememberMe option)&lt;/param>
        /// &lt;returns>The login result&lt;/returns>
        [HttpPost("login")]
        public async Task&lt;IActionResult> Login([FromBody] LoginUserRequestDTO model)
        {
            try
            {
                var result = await _signInManager.PasswordSignInAsync(
                    model.Email,
                    model.Password,
                    model.RememberMe,
                    lockoutOnFailure: true);

                if (result.Succeeded)
                {
                    _logger.LogInformation("User logged in: {Email}", model.Email);
                    return Ok(new { Success = true });
                }

                if (result.RequiresTwoFactor)
                {
                    return Ok(new { RequiresTwoFactor = true });
                }

                if (result.IsLockedOut)
                {
                    _logger.LogWarning("User account locked out: {Email}", model.Email);
                    return BadRequest("Account locked out");
                }

                return BadRequest("Invalid login attempt");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Login error for {Email}", model.Email);
                return StatusCode(500, "An error occurred");
            }
        }

        /// &lt;summary>
        /// Handles new user registration.
        /// &lt;/summary>
        /// &lt;param name="model">Registration request DTO&lt;/param>
        /// &lt;returns>The registration result&lt;/returns>
        [HttpPost("register")]
        public async Task&lt;IActionResult> Register([FromBody] CreateUserRequestDTO model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(new RegisterUserResponseDTO
                {
                    Status = RegisterUserResponseDTO.ResponseStatus.Fail_RequestInvalid,
                    Errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage)
                });
            }

            var user = CreateUser();
            await _userStore.SetUserNameAsync(user, model.Email, CancellationToken.None);
            var emailStore = GetEmailStore();
            await emailStore.SetEmailAsync(user, model.Email, CancellationToken.None);

            var result = await _userManager.CreateAsync(user, model.Password);

            if (!result.Succeeded)
            {
                _logger.LogInformation("User registration failed: {Email}", model.Email);

                // Handle duplicate email error
                if (result.Errors.Any(e => e.Code == "DuplicateUserName"))
                {
                    return BadRequest(new RegisterUserResponseDTO
                    {
                        Status = RegisterUserResponseDTO.ResponseStatus.Fail_DuplicateEmail,
                    });
                }

                // Handle general Identity errors
                return BadRequest(new RegisterUserResponseDTO
                {
                    Status = RegisterUserResponseDTO.ResponseStatus.Fail_General,
                    Errors = result.Errors.Select(e => e.Description)
                });
            }

            _logger.LogInformation("User created a new account with a password.");

            // Send email confirmation link
            try
            {
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

                // NOTE: For security, it's recommended to configure a fixed base URL
                // from your application's settings rather than relying on the request host.
                var baseUrl = model.BaseUrl ?? $"{Request.Scheme}://{Request.Host.Value}";
                var callbackUrl = $"{baseUrl}/Account/ConfirmEmail?userId={userId}&amp;code={code}";

                await _emailSender.SendConfirmationLinkAsync(user, model.Email, HtmlEncoder.Default.Encode(callbackUrl));
            }
            catch (Exception)
            {
                // Account created successfully but email sending failed.
                return Ok(new RegisterUserResponseDTO
                {
                    Status = RegisterUserResponseDTO.ResponseStatus.Success_EmailWarning,
                    Message = "The account was created, but the verification email sending failed."
                });
            }

            return Ok(new RegisterUserResponseDTO
            {
                Status = RegisterUserResponseDTO.ResponseStatus.Success_NeedConfirmation
            });
        }

        /// &lt;summary>
        /// Resends the email confirmation link.
        /// &lt;/summary>
        /// &lt;param name="model">Email confirmation request DTO&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpPost("emailConfirm")]
        public async Task&lt;IActionResult> ResendEmailConfirm([FromBody] RequestEmailConfirmDTO model)
        {
            if (!ModelState.IsValid)
            {
                // Always return a success response for security (to avoid user enumeration).
                return Ok(new ResponseEmailConfirmDTO
                {
                    Status = ResponseEmailConfirmDTO.ResponseStatus.Success,
                    Message = "The confirmation email has been sent successfully."
                });
            }

            try
            {
                var user = await _userManager.FindByEmailAsync(model.Email);

                // Even if the user doesn't exist or is already confirmed, return a success response for security.
                if (user == null || user.EmailConfirmed)
                {
                    return Ok(new ResponseEmailConfirmDTO
                    {
                        Status = ResponseEmailConfirmDTO.ResponseStatus.Success,
                        Message = "The confirmation email has been sent successfully."
                    });
                }

                // Generate and send the email confirmation link
                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));

                // NOTE: It is recommended to use a pre-configured base URL.
                var baseUrl = model.BaseUrl ?? $"{Request.Scheme}://{Request.Host.Value}";
                var callbackUrl = $"{baseUrl}/Account/ConfirmEmail?userId={userId}&amp;code={code}";

                await _emailSender.SendConfirmationLinkAsync(user, model.Email, HtmlEncoder.Default.Encode(callbackUrl));

                return Ok(new ResponseEmailConfirmDTO
                {
                    Status = ResponseEmailConfirmDTO.ResponseStatus.Success,
                    Message = "The confirmation email has been sent successfully."
                });
            }
            catch (Exception)
            {
                // Mail server or other exception occurred.
                return BadRequest(new ResponseEmailConfirmDTO
                {
                    Status = ResponseEmailConfirmDTO.ResponseStatus.Fail,
                    Message = $"An error occurred while processing your request. (Server Error)"
                });
            }
        }

        /// &lt;summary>
        /// Handles password reset requests.
        /// &lt;/summary>
        /// &lt;param name="model">Password reset request DTO&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpPost("resetPassword")]
        public async Task&lt;IActionResult> ResetPassword([FromBody] ResetPasswordRequestDTO model)
        {
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null)
            {
                return BadRequest("User not found");
            }

            var resetToken = await _userManager.GeneratePasswordResetTokenAsync(user);
            var callbackUrl = $"{model.BaseUrl}/Account/ResetPassword?email={model.Email}&amp;token={resetToken}";

            await _emailSender.SendPasswordResetLinkAsync(user, model.Email, callbackUrl);

            return Ok("Password reset link sent");
        }

        /// &lt;summary>
        /// Handles user logout.
        /// &lt;/summary>
        /// &lt;returns>The logout result&lt;/returns>
        [HttpPost("logout")]
        public async Task&lt;IActionResult> Logout()
        {
            await _signInManager.SignOutAsync();
            return Ok("Logged out successfully");
        }

        /// &lt;summary>
        /// Handles the generation of a JWT token.
        /// &lt;/summary>
        /// &lt;param name="model">Login request DTO (email, password)&lt;/param>
        /// &lt;returns>Token information&lt;/returns>
        [HttpPost("token")]
        public async Task&lt;IActionResult> GenerateToken([FromBody] LoginUserRequestDTO model)
        {
            var user = await _userManager.FindByEmailAsync(model.Email);
            if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
            {
                return Unauthorized("Invalid credentials");
            }

            var claims = new[]
            {
                new Claim(ClaimTypes.Name, user.UserName),
                new Claim(ClaimTypes.Email, user.Email)
            };

            // WARNING: Do not hard-code the key in production.
            // Move this to a secure configuration file (e.g., appsettings.json)
            // and use a strong, randomly generated key.
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("A_Very_Strong_And_Secret_Key_For_JWT_Auth"));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var token = new JwtSecurityToken(
                issuer: "yourdomain.com",
                audience: "yourdomain.com",
                claims: claims,
                expires: DateTime.Now.AddMinutes(30),
                signingCredentials: creds);

            return Ok(new { token = new JwtSecurityTokenHandler().WriteToken(token) });
        }

        #endregion

        #region External Authentication (OAuth)

        /// &lt;summary>
        /// Initiates a challenge request to an external authentication provider (e.g., Google).
        /// &lt;/summary>
        /// &lt;param name="provider">The name of the authentication provider (e.g., "Google")&lt;/param>
        /// &lt;param name="returnUrl">The URL to redirect to after successful authentication&lt;/param>
        /// &lt;returns>A challenge result to the external provider&lt;/returns>
        [HttpGet("Challenge/{provider}")]
        public IActionResult Challenge(string provider, string? returnUrl = null)
        {
            _logger.LogInformation("Starting {Provider} login", provider);

            if (string.IsNullOrWhiteSpace(provider))
            {
                return BadRequest(new { error = "Provider not specified." });
            }

            // Normalize and validate returnUrl
            returnUrl = NormalizeReturnUrl(returnUrl);

            // Set the callback URL
            var redirectUrl = Url.Action("Callback", "Auth", new { returnUrl }, Request.Scheme);

            // Configure authentication properties
            var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);

            return Challenge(properties, provider);
        }

        /// &lt;summary>
        /// Handles the callback from an external authentication provider.
        /// &lt;/summary>
        /// &lt;param name="returnUrl">The URL to redirect to after successful authentication&lt;/param>
        /// &lt;param name="remoteError">Error message from the external provider&lt;/param>
        /// &lt;returns>An appropriate response based on the authentication result&lt;/returns>
        [HttpGet("externalLogin")]
        public async Task&lt;IActionResult> Callback(string? returnUrl = null, string? remoteError = null)
        {
            try
            {
                returnUrl = NormalizeReturnUrl(returnUrl);

                if (!string.IsNullOrEmpty(remoteError))
                {
                    _logger.LogError("External provider error: {RemoteError}", remoteError);
                    return Redirect($"/login?error={Uri.EscapeDataString(remoteError)}");
                }

                var info = await _signInManager.GetExternalLoginInfoAsync();
                if (info == null)
                {
                    _logger.LogError("Could not retrieve external login info");
                    return Redirect("/login?error=external_login_failed");
                }

                // Attempt to sign in an existing user
                var result = await _signInManager.ExternalLoginSignInAsync(
                    info.LoginProvider,
                    info.ProviderKey,
                    isPersistent: false,
                    bypassTwoFactor: true);

                if (result.Succeeded)
                {
                    var user = await _userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
                    _logger.LogInformation("User {Email} successfully logged in", user?.Email);
                    return LocalRedirect(returnUrl);
                }

                if (result.IsLockedOut)
                {
                    _logger.LogWarning("Attempt to log in with a locked-out account");
                    return Redirect("/login?error=locked_out");
                }

                // New user or linking an existing user
                return await HandleUserCreationOrLinking(info, returnUrl);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred while processing external login");
                return Redirect("/login?error=server_error");
            }
        }

        #endregion

        #region Helper Methods

        /// &lt;summary>
        /// Normalizes and validates the returnUrl.
        /// &lt;/summary>
        private string NormalizeReturnUrl(string? returnUrl)
        {
            if (string.IsNullOrWhiteSpace(returnUrl))
                return "/";

            if (Uri.TryCreate(returnUrl, UriKind.Absolute, out var uri))
                returnUrl = uri.PathAndQuery;

            return Url.IsLocalUrl(returnUrl) ? returnUrl : "/";
        }

        /// &lt;summary>
        /// Handles new user creation or linking an existing user to an external login.
        /// &lt;/summary>
        private async Task&lt;IActionResult> HandleUserCreationOrLinking(ExternalLoginInfo info, string returnUrl)
        {
            var email = info.Principal.FindFirstValue(ClaimTypes.Email);
            if (string.IsNullOrEmpty(email))
            {
                _logger.LogError("Missing email information from external provider {Provider}", info.LoginProvider);
                return Redirect("/login?error=email_required");
            }

            var existingUser = await _userManager.FindByEmailAsync(email);
            return existingUser != null
                ? await LinkExistingUser(existingUser, info, returnUrl)
                : await CreateNewUserAsync(info, returnUrl);
        }

        /// &lt;summary>
        /// Links external login information to an existing user.
        /// &lt;/summary>
        private async Task&lt;IActionResult> LinkExistingUser(ApplicationUser user, ExternalLoginInfo info, string returnUrl)
        {
            var addLoginResult = await _userManager.AddLoginAsync(user, info);
            if (addLoginResult.Succeeded)
            {
                _logger.LogInformation("Successfully linked {Provider} login to existing user {Email}", info.LoginProvider, user.Email);
                await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
                return LocalRedirect(returnUrl);
            }

            _logger.LogError("Failed to link external login to existing user: {Errors}",
                string.Join(", ", addLoginResult.Errors.Select(e => e.Description)));
            return Redirect("/login?error=link_failed");
        }

        /// &lt;summary>
        /// Creates a new user based on external login information.
        /// &lt;/summary>
        private async Task&lt;IActionResult> CreateNewUserAsync(ExternalLoginInfo info, string returnUrl)
        {
            var email = info.Principal.FindFirstValue(ClaimTypes.Email);
            if (string.IsNullOrEmpty(email))
            {
                _logger.LogError("Missing email information from external provider");
                return BadRequest(new { error = "Email information was not provided." });
            }

            var userName = email.Split('@')[0];
            var user = new ApplicationUser
            {
                UserName = email,
                Email = email,
                NickName = userName,
                EmailConfirmed = true // Email is already confirmed via external provider
            };

            var createResult = await _userManager.CreateAsync(user);
            if (!createResult.Succeeded)
            {
                _logger.LogError("User creation failed: {Errors}",
                    string.Join(", ", createResult.Errors.Select(e => e.Description)));

                // Attempt to create an alternative user name on a naming conflict
                if (createResult.Errors.Any(e => e.Code == "DuplicateUserName"))
                {
                    user.UserName = $"{userName}_{Guid.NewGuid().ToString("N").Substring(0, 4)}";
                    createResult = await _userManager.CreateAsync(user);

                    if (!createResult.Succeeded)
                    {
                        return BadRequest(new
                        {
                            error = "Account creation failed",
                            details = createResult.Errors.Select(e => e.Description)
                        });
                    }
                }
                else
                {
                    return BadRequest(new
                    {
                        error = "Account creation failed",
                        details = createResult.Errors.Select(e => e.Description)
                    });
                }
            }

            // Link external login information
            var addLoginResult = await _userManager.AddLoginAsync(user, info);
            if (!addLoginResult.Succeeded)
            {
                _logger.LogError("Failed to add login information: {Errors}",
                    string.Join(", ", addLoginResult.Errors.Select(e => e.Description)));

                // Rollback: Delete the created user
                await _userManager.DeleteAsync(user);

                return BadRequest(new
                {
                    error = "Failed to link external login information",
                    details = addLoginResult.Errors.Select(e => e.Description)
                });
            }

            _logger.LogInformation("New user created and logged in successfully: {Email}", email);
            await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);

            return LocalRedirect(returnUrl);
        }

        private ApplicationUser CreateUser()
        {
            try
            {
                return Activator.CreateInstance&lt;ApplicationUser>();
            }
            catch
            {
                throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " +
                    $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor.");
            }
        }

        private IUserEmailStore&lt;ApplicationUser> GetEmailStore()
        {
            if (!_userManager.SupportsUserEmail)
            {
                throw new NotSupportedException("The default UI requires a user store with email support.");
            }
            return (IUserEmailStore&lt;ApplicationUser>)_userStore;
        }

        #endregion

        #region Profile Management

        /// &lt;summary>
        /// Handles user profile updates.
        /// &lt;/summary>
        /// &lt;param name="model">Update request DTO&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpPut("updateProfile")]
        public async Task&lt;IActionResult> UpdateProfile([FromBody] UpdateUserProfileDTO model)
        {
            var user = await _userManager.FindByIdAsync(model.Email);
            if (user == null)
            {
                return NotFound("User not found");
            }

            user.NickName = model.Nickname;
            user.PhoneNumber = model.PhoneNumber;

            var result = await _userManager.UpdateAsync(user);
            if (!result.Succeeded)
            {
                return BadRequest(result.Errors.Select(e => e.Description));
            }

            return Ok("Profile updated successfully");
        }

        /// &lt;summary>
        /// Handles user deletion.
        /// &lt;/summary>
        /// &lt;param name="userId">The ID of the user to delete&lt;/param>
        /// &lt;returns>The result of the process&lt;/returns>
        [HttpDelete("deleteUser/{userId}")]
        public async Task&lt;IActionResult> DeleteUser(string userId)
        {
            var user = await _userManager.FindByIdAsync(userId);
            if (user == null)
            {
                return NotFound("User not found");
            }

            var result = await _userManager.DeleteAsync(user);
            if (!result.Succeeded)
            {
                return BadRequest(result.Errors.Select(e => e.Description));
            }

            return Ok("User deleted successfully");
        }

        #endregion
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private void LoginWithGoogle()
{
    try
    {
        var returnUrl = "/";
        var googleLoginUrl = $"/api/auth/Challenge/Google?returnUrl={Uri.EscapeDataString(returnUrl)}";

        // Redirect in the current window instead of a popup
        // This is a placeholder for a client-side navigation method, e.g., in Blazor
        // Navigation.NavigateTo(googleLoginUrl, forceLoad: true);
    }
    catch (Exception ex)
    {
        // Error handling
        Console.WriteLine($"Google login error: {ex.Message}");
    }
}
</pre>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/">ASP.NET Core Identity를 활용한 구글 로그인(OAuth) 전체 흐름 분석</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/asp-net-core-identity%eb%a5%bc-%ed%99%9c%ec%9a%a9%ed%95%9c-%ea%b5%ac%ea%b8%80-%eb%a1%9c%ea%b7%b8%ec%9d%b8oauth-%ec%a0%84%ec%b2%b4-%ed%9d%90%eb%a6%84-%eb%b6%84%ec%84%9d/40245/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Ubuntu export 명령어 (.NET)</title>
		<link>https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/</link>
					<comments>https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Fri, 08 Aug 2025 06:21:42 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[app-config]]></category>
		<category><![CDATA[appsettings]]></category>
		<category><![CDATA[appsettings.development.json]]></category>
		<category><![CDATA[appsettings.json]]></category>
		<category><![CDATA[appsettings.production.json]]></category>
		<category><![CDATA[appsettings.staging.json]]></category>
		<category><![CDATA[asp-net-core]]></category>
		<category><![CDATA[aspnetcore]]></category>
		<category><![CDATA[aspnetcore-environment]]></category>
		<category><![CDATA[aspnetcore-urls]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[bash_profile]]></category>
		<category><![CDATA[bashrc]]></category>
		<category><![CDATA[Build]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[child-process]]></category>
		<category><![CDATA[CLI]]></category>
		<category><![CDATA[command-line]]></category>
		<category><![CDATA[config]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[connection-string-env]]></category>
		<category><![CDATA[connection-strings]]></category>
		<category><![CDATA[CSharp]]></category>
		<category><![CDATA[database-config]]></category>
		<category><![CDATA[database-connection]]></category>
		<category><![CDATA[db-connection]]></category>
		<category><![CDATA[Deployment]]></category>
		<category><![CDATA[dev]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[dotnet]]></category>
		<category><![CDATA[dotnet-config]]></category>
		<category><![CDATA[dotnet-core]]></category>
		<category><![CDATA[dotnet-environment]]></category>
		<category><![CDATA[dotnet-environment-variable]]></category>
		<category><![CDATA[dotnet-publish]]></category>
		<category><![CDATA[dotnet-run]]></category>
		<category><![CDATA[dotnet-settings]]></category>
		<category><![CDATA[env]]></category>
		<category><![CDATA[Environment]]></category>
		<category><![CDATA[environment-config]]></category>
		<category><![CDATA[environment-example]]></category>
		<category><![CDATA[environment-variable]]></category>
		<category><![CDATA[environment-variable-setup]]></category>
		<category><![CDATA[export]]></category>
		<category><![CDATA[export-command]]></category>
		<category><![CDATA[export-example]]></category>
		<category><![CDATA[export-variable]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[linux-dotnet]]></category>
		<category><![CDATA[linux-export]]></category>
		<category><![CDATA[localhost]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[path]]></category>
		<category><![CDATA[port-config]]></category>
		<category><![CDATA[printenv]]></category>
		<category><![CDATA[Process]]></category>
		<category><![CDATA[prod]]></category>
		<category><![CDATA[Production]]></category>
		<category><![CDATA[production-server]]></category>
		<category><![CDATA[publish]]></category>
		<category><![CDATA[release]]></category>
		<category><![CDATA[secrets]]></category>
		<category><![CDATA[secure-environment-variable]]></category>
		<category><![CDATA[server-config]]></category>
		<category><![CDATA[server-environment]]></category>
		<category><![CDATA[Service]]></category>
		<category><![CDATA[session]]></category>
		<category><![CDATA[shell]]></category>
		<category><![CDATA[shell-variable]]></category>
		<category><![CDATA[stage]]></category>
		<category><![CDATA[Staging]]></category>
		<category><![CDATA[staging-server]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[system-environment]]></category>
		<category><![CDATA[systemd]]></category>
		<category><![CDATA[systemd-service]]></category>
		<category><![CDATA[Terminal]]></category>
		<category><![CDATA[test-environment]]></category>
		<category><![CDATA[ubuntu-dotnet]]></category>
		<category><![CDATA[ubuntu-dotnet-deploy]]></category>
		<category><![CDATA[url-binding]]></category>
		<category><![CDATA[개발]]></category>
		<category><![CDATA[개발서버]]></category>
		<category><![CDATA[개발환경]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[닷넷]]></category>
		<category><![CDATA[닷넷코어]]></category>
		<category><![CDATA[리눅스]]></category>
		<category><![CDATA[민감정보]]></category>
		<category><![CDATA[배포]]></category>
		<category><![CDATA[배포서버]]></category>
		<category><![CDATA[배포환경]]></category>
		<category><![CDATA[서버환경]]></category>
		<category><![CDATA[설정파일]]></category>
		<category><![CDATA[쉘]]></category>
		<category><![CDATA[스테이징]]></category>
		<category><![CDATA[스테이징환경]]></category>
		<category><![CDATA[우분투]]></category>
		<category><![CDATA[운영환경]]></category>
		<category><![CDATA[터미널]]></category>
		<category><![CDATA[테스트서버]]></category>
		<category><![CDATA[테스트환경]]></category>
		<category><![CDATA[포트설정]]></category>
		<category><![CDATA[프로덕션]]></category>
		<category><![CDATA[프로덕션환경]]></category>
		<category><![CDATA[프로세스]]></category>
		<category><![CDATA[환경변수]]></category>
		<category><![CDATA[환경변수보안]]></category>
		<category><![CDATA[환경설정]]></category>
		<category><![CDATA[환경설정파일]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40241</guid>

					<description><![CDATA[<p>📄 Ubuntu export 명령어 1️⃣ 개요 export 명령어는 현재 셸 환경에 환경 변수를 등록하거나 수정하는 데 사용 등록된 변수는 현재 셸과 그 자식 프로세스에서 사용할 수 있음 2️⃣ 기본 문법 3️⃣ 특징 4️⃣ 확인 방법 💡 .NET에서의 사용 예시 1️⃣ ASP.NET Core에서 환경 지정 프로젝트 유형 권장 환경 변수 이유 ASP.NET Core 웹앱 ASPNETCORE_ENVIRONMENT 웹 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/">Ubuntu export 명령어 (.NET)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-b32ec816      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#ubuntu-export-명령어" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Ubuntu export 명령어</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-개요" class="uagb-toc-link__trigger">1&#x20e3; 개요</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-기본-문법" class="uagb-toc-link__trigger">2&#x20e3; 기본 문법</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-특징" class="uagb-toc-link__trigger">3&#x20e3; 특징</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-확인-방법" class="uagb-toc-link__trigger">4&#x20e3; 확인 방법</a></li></ul></li><li class="uagb-toc__list"><a href="#net에서의-사용-예시" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> .NET에서의 사용 예시</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-aspnet-core에서-환경-지정" class="uagb-toc-link__trigger">1&#x20e3; ASP.NET Core에서 환경 지정</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-net에서-특정-포트로-실행" class="uagb-toc-link__trigger">2&#x20e3; .NET에서 특정 포트로 실행</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-영구-설정" class="uagb-toc-link__trigger">3&#x20e3; 영구 설정</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4c4.png" alt="📄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Ubuntu export 명령어</h2>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; 개요</h3>



<p><code>export</code> 명령어는 <strong>현재 셸 환경에 환경 변수를 등록하거나 수정</strong>하는 데 사용</p>



<p>등록된 변수는 <strong>현재 셸과 그 자식 프로세스</strong>에서 사용할 수 있음</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; 기본 문법</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">export 변수명=값

또는

export 변수명</pre>



<ul class="wp-block-list">
<li><code>변수명=값</code> : 새 환경 변수를 만들거나 기존 변수 값을 변경</li>



<li><code>변수명</code> : 이미 선언된 변수를 환경 변수로 승격</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 특징</h3>



<ul class="wp-block-list">
<li>환경 변수는 <strong>프로세스 간 전달</strong> 가능 (부모 → 자식).</li>



<li><code>export</code>로 설정한 변수는 <strong>현재 세션</strong>에서만 유효.</li>



<li>영구적으로 유지하려면 <code>~/.bashrc</code> 또는 <code>~/.bash_profile</code>에 추가해야 함.</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">4&#x20e3; 확인 방법</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">echo $변수명       # 변수 값 출력
printenv 변수명    # 환경 변수 확인
env                # 전체 환경 변수 목록
</pre>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"/>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> .NET에서의 사용 예시</h2>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1&#x20e3; ASP.NET Core에서 환경 지정</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">export ASPNETCORE_ENVIRONMENT=Development
export ASPNETCORE_ENVIRONMENT=Staging
export ASPNETCORE_ENVIRONMENT=Production

export DOTNET_ENVIRONMENT=Development
export DOTNET_ENVIRONMENT=Staging // 실제 운영 환경(Production)과 거의 동일하지만, 사용자에게는 서비스되지 않는 사전 검증용 환경
export DOTNET_ENVIRONMENT=Production

dotnet run</pre>



<ul class="wp-block-list">
<li>예를 들어 <code>Development</code>로 설정하면, 개발 모드 환경 설정(<code>appsettings.Development.json</code>)을 자동으로 사용</li>
</ul>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>프로젝트 유형</th><th>권장 환경 변수</th><th>이유</th></tr></thead><tbody><tr><td>ASP.NET Core 웹앱</td><td>ASPNETCORE_ENVIRONMENT</td><td>웹 호스팅에 최적화된 공식 표준이며 <code>DOTNET_ENVIRONMENT</code>보다 <strong>우선순위가 높</strong></td></tr><tr><td>Worker Service</td><td>DOTNET_ENVIRONMENT</td><td>웹과 관련 없는 일반적인 .NET 호스팅 환경을 위한 표준 변수</td></tr><tr><td>콘솔 앱</td><td>DOTNET_ENVIRONMENT</td><td>범용적 사용</td></tr><tr><td>Blazor</td><td>ASPNETCORE_ENVIRONMENT</td><td>ASP.NET Core 호스팅 모델을 기반으로 하므로 웹 앱과 동일한 기준</td></tr></tbody></table></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2&#x20e3; .NET에서 특정 포트로 실행</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># 5005번 포트로 애플리케이션 실행
export ASPNETCORE_URLS="http://localhost:5005"

dotnet run</pre>



<ul class="wp-block-list">
<li><code>ASPNETCORE_URLS</code> 환경 변수를 이용해 실행 시 포트를 변경할 수 있음</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3&#x20e3; 영구 설정</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="bash" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""># 1. .bashrc 파일에 환경 변수 설정 명령어를 추가
echo 'export DOTNET_ENVIRONMENT=Production' >> ~/.bashrc

# 2. 수정된 .bashrc 설정을 현재 터미널 세션에 바로 적용
source ~/.bashrc

# 3. 설정 확인
echo $DOTNET_ENVIRONMENT</pre>



<p></p>
<p>The post <a href="https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/">Ubuntu export 명령어 (.NET)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/ubuntu/ubuntu-export-%eb%aa%85%eb%a0%b9%ec%96%b4-net/40241/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ASP.NET Identity 데이터베이스</title>
		<link>https://lycos7560.com/c/asp-net/asp-net-identity-%eb%8d%b0%ec%9d%b4%ed%84%b0%eb%b2%a0%ec%9d%b4%ec%8a%a4/40160/</link>
					<comments>https://lycos7560.com/c/asp-net/asp-net-identity-%eb%8d%b0%ec%9d%b4%ed%84%b0%eb%b2%a0%ec%9d%b4%ec%8a%a4/40160/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Tue, 08 Jul 2025 23:58:43 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[2단계인증]]></category>
		<category><![CDATA[Access Control]]></category>
		<category><![CDATA[Access Token]]></category>
		<category><![CDATA[Account Lockout]]></category>
		<category><![CDATA[Account Recovery]]></category>
		<category><![CDATA[Account Settings]]></category>
		<category><![CDATA[Admin Panel]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[ASP.NET Identity]]></category>
		<category><![CDATA[AspNetRoleClaims]]></category>
		<category><![CDATA[AspNetRoles]]></category>
		<category><![CDATA[AspNetUserClaims]]></category>
		<category><![CDATA[AspNetUserLogins]]></category>
		<category><![CDATA[AspNetUserRoles]]></category>
		<category><![CDATA[AspNetUsers]]></category>
		<category><![CDATA[AspNetUserTokens]]></category>
		<category><![CDATA[Authentication Filter]]></category>
		<category><![CDATA[Authentication Token]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[Authorization System]]></category>
		<category><![CDATA[Authorize Attribute]]></category>
		<category><![CDATA[Brute Force Protection]]></category>
		<category><![CDATA[C# Programming]]></category>
		<category><![CDATA[Claim Type]]></category>
		<category><![CDATA[Claim Value]]></category>
		<category><![CDATA[Claims-based Authentication]]></category>
		<category><![CDATA[Composite Key]]></category>
		<category><![CDATA[Concurrency Control]]></category>
		<category><![CDATA[Custom Claims]]></category>
		<category><![CDATA[Database Design]]></category>
		<category><![CDATA[Database Tables]]></category>
		<category><![CDATA[Dynamic Permissions]]></category>
		<category><![CDATA[EF Core]]></category>
		<category><![CDATA[Email Confirmation]]></category>
		<category><![CDATA[Email Token]]></category>
		<category><![CDATA[Entity Framework]]></category>
		<category><![CDATA[External Login]]></category>
		<category><![CDATA[Facebook Login]]></category>
		<category><![CDATA[Foreign Key]]></category>
		<category><![CDATA[Google Login]]></category>
		<category><![CDATA[Grant Permission]]></category>
		<category><![CDATA[Group Permissions]]></category>
		<category><![CDATA[Hierarchical Permissions]]></category>
		<category><![CDATA[Identity Core]]></category>
		<category><![CDATA[Identity Framework]]></category>
		<category><![CDATA[JWT Token]]></category>
		<category><![CDATA[JWT토큰]]></category>
		<category><![CDATA[Login Failure]]></category>
		<category><![CDATA[Login Provider]]></category>
		<category><![CDATA[Login System]]></category>
		<category><![CDATA[Microsoft Login]]></category>
		<category><![CDATA[Middleware]]></category>
		<category><![CDATA[Multiple Roles]]></category>
		<category><![CDATA[OAuth]]></category>
		<category><![CDATA[OpenID Connect]]></category>
		<category><![CDATA[Password Hashing]]></category>
		<category><![CDATA[Password Reset]]></category>
		<category><![CDATA[Permission Check]]></category>
		<category><![CDATA[Permission Matrix]]></category>
		<category><![CDATA[Policy-based Authorization]]></category>
		<category><![CDATA[Primary Key]]></category>
		<category><![CDATA[Profile Management]]></category>
		<category><![CDATA[Provider Key]]></category>
		<category><![CDATA[Refresh Token]]></category>
		<category><![CDATA[Relational Database]]></category>
		<category><![CDATA[Role Assignment]]></category>
		<category><![CDATA[Role Hierarchy]]></category>
		<category><![CDATA[Role-based Authorization]]></category>
		<category><![CDATA[Security Filter]]></category>
		<category><![CDATA[Security Policy]]></category>
		<category><![CDATA[Security Stamp]]></category>
		<category><![CDATA[Security System]]></category>
		<category><![CDATA[Session Management]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Team-based Permissions]]></category>
		<category><![CDATA[Token Management]]></category>
		<category><![CDATA[Two-Factor Authentication]]></category>
		<category><![CDATA[User Authentication]]></category>
		<category><![CDATA[User Dashboard]]></category>
		<category><![CDATA[User Interface]]></category>
		<category><![CDATA[User Management]]></category>
		<category><![CDATA[User Properties]]></category>
		<category><![CDATA[User Registration]]></category>
		<category><![CDATA[User Roles]]></category>
		<category><![CDATA[User Session]]></category>
		<category><![CDATA[User Token]]></category>
		<category><![CDATA[Web Security]]></category>
		<category><![CDATA[계정복구]]></category>
		<category><![CDATA[계정설정]]></category>
		<category><![CDATA[계정잠금]]></category>
		<category><![CDATA[계층적권한]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[관계형데이터베이스]]></category>
		<category><![CDATA[관리자패널]]></category>
		<category><![CDATA[구글로그인]]></category>
		<category><![CDATA[권한검사]]></category>
		<category><![CDATA[권한관리]]></category>
		<category><![CDATA[권한매트릭스]]></category>
		<category><![CDATA[권한부여]]></category>
		<category><![CDATA[권한어트리뷰트]]></category>
		<category><![CDATA[그룹권한]]></category>
		<category><![CDATA[기본키]]></category>
		<category><![CDATA[기초]]></category>
		<category><![CDATA[다중역할]]></category>
		<category><![CDATA[데이터베이스설계]]></category>
		<category><![CDATA[데이터베이스테이블]]></category>
		<category><![CDATA[동시성제어]]></category>
		<category><![CDATA[동적권한]]></category>
		<category><![CDATA[로그인시스템]]></category>
		<category><![CDATA[로그인실패]]></category>
		<category><![CDATA[로그인제공자]]></category>
		<category><![CDATA[리프레시토큰]]></category>
		<category><![CDATA[마이크로소프트로그인]]></category>
		<category><![CDATA[미들웨어]]></category>
		<category><![CDATA[보안스탬프]]></category>
		<category><![CDATA[보안시스템]]></category>
		<category><![CDATA[보안정책]]></category>
		<category><![CDATA[보안필터]]></category>
		<category><![CDATA[복합키]]></category>
		<category><![CDATA[브루트포스방어]]></category>
		<category><![CDATA[비밀번호재설정]]></category>
		<category><![CDATA[비밀번호해싱]]></category>
		<category><![CDATA[사용자관리]]></category>
		<category><![CDATA[사용자대시보드]]></category>
		<category><![CDATA[사용자세션]]></category>
		<category><![CDATA[사용자속성]]></category>
		<category><![CDATA[사용자역할]]></category>
		<category><![CDATA[사용자인증]]></category>
		<category><![CDATA[사용자인터페이스]]></category>
		<category><![CDATA[사용자토큰]]></category>
		<category><![CDATA[세션관리]]></category>
		<category><![CDATA[액세스토큰]]></category>
		<category><![CDATA[역할계층]]></category>
		<category><![CDATA[역할기반권한]]></category>
		<category><![CDATA[역할할당]]></category>
		<category><![CDATA[외래키]]></category>
		<category><![CDATA[외부로그인]]></category>
		<category><![CDATA[웹보안]]></category>
		<category><![CDATA[이메일인증]]></category>
		<category><![CDATA[이메일토큰]]></category>
		<category><![CDATA[인가시스템]]></category>
		<category><![CDATA[인증토큰]]></category>
		<category><![CDATA[인증필터]]></category>
		<category><![CDATA[접근제어]]></category>
		<category><![CDATA[정책기반권한]]></category>
		<category><![CDATA[제공자키]]></category>
		<category><![CDATA[커스텀클레임]]></category>
		<category><![CDATA[클레임값]]></category>
		<category><![CDATA[클레임기반인증]]></category>
		<category><![CDATA[클레임타입]]></category>
		<category><![CDATA[토큰관리]]></category>
		<category><![CDATA[팀기반권한]]></category>
		<category><![CDATA[페이스북로그인]]></category>
		<category><![CDATA[프로필관리]]></category>
		<category><![CDATA[회원가입]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40160</guid>

					<description><![CDATA[<p>ASP.NET Identity는 .NET 애플리케이션에서 사용자 인증과 권한 관리를 담당하는 시스템입니다. 이 시스템은 7개의 핵심 테이블로 구성되어 있으며, 각각은 특별한 역할을 수행합니다. 🏗️ 전체 구조 개요 📋 1. AspNetUsers &#8211; 사용자 기본 정보 역할: 회원가입한 사용자들의 기본 정보를 저장하는 메인 테이블 주요 필드 설명 필드명 타입 설명 예시 Id GUID 사용자 고유 식별자 a1b2c3d4-e5f6-... UserName string [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-identity-%eb%8d%b0%ec%9d%b4%ed%84%b0%eb%b2%a0%ec%9d%b4%ec%8a%a4/40160/">ASP.NET Identity 데이터베이스</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>


				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-9e961c7c      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#전체-구조-개요" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3d7.png" alt="🏗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 전체 구조 개요</a><li class="uagb-toc__list"><a href="#1-aspnetusers-사용자-기본-정보" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4cb.png" alt="📋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 1. AspNetUsers &#8211; 사용자 기본 정보</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#주요-필드-설명" class="uagb-toc-link__trigger">주요 필드 설명</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#보안-관련-필드들" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f512.png" alt="🔒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 보안 관련 필드들</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#실제-사용-예시" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 사용 예시</a></li></ul></li><li class="uagb-toc__list"><a href="#2-aspnetroles-역할-정의" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f465.png" alt="👥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 2. AspNetRoles &#8211; 역할 정의</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#주요-필드" class="uagb-toc-link__trigger">주요 필드</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#일반적인-역할-예시" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3ad.png" alt="🎭" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 일반적인 역할 예시</a></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#3-aspnetuserroles-사용자-역할-연결" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 3. AspNetUserRoles &#8211; 사용자-역할 연결</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#구조" class="uagb-toc-link__trigger">구조</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#관계-예시" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 관계 예시</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#실제-사용법" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4bb.png" alt="💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 사용법</a></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#4-claims-시스템-세부-권한-관리" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3ab.png" alt="🎫" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 4. Claims 시스템 &#8211; 세부 권한 관리</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#클레임-vs-역할-비교" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f50d.png" alt="🔍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 클레임 vs 역할 비교</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnetroleclaims-역할별-클레임" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3f7.png" alt="🏷" class="wp-smiley" style="height: 1em; max-height: 1em;" /> AspNetRoleClaims &#8211; 역할별 클레임</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#aspnetuserclaims-개인별-클레임" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f464.png" alt="👤" class="wp-smiley" style="height: 1em; max-height: 1em;" /> AspNetUserClaims &#8211; 개인별 클레임</a></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#5-aspnetuserlogins-외부-로그인-연동" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 5. AspNetUserLogins &#8211; 외부 로그인 연동</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#구조" class="uagb-toc-link__trigger">구조</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#연동-과정-예시" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f504.png" alt="🔄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 연동 과정 예시</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#실제-구현" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4bb.png" alt="💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 구현</a></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#6-aspnetusertokens-인증-토큰-관리" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f39f.png" alt="🎟" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 6. AspNetUserTokens &#8211; 인증 토큰 관리</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#구조" class="uagb-toc-link__trigger">구조</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#토큰-유형별-예시" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f510.png" alt="🔐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 토큰 유형별 예시</a></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#권한-검사-실제-활용법" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3af.png" alt="🎯" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 권한 검사 실제 활용법</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-컨트롤러에서-역할-기반-권한-검사" class="uagb-toc-link__trigger">1. 컨트롤러에서 역할 기반 권한 검사</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-클레임-기반-권한-검사" class="uagb-toc-link__trigger">2. 클레임 기반 권한 검사</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-코드에서-동적-권한-검사" class="uagb-toc-link__trigger">3. 코드에서 동적 권한 검사</a></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#실무-시나리오-예시" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3e2.png" alt="🏢" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실무 시나리오 예시</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#회사-내부-시스템-권한-설계" class="uagb-toc-link__trigger">회사 내부 시스템 권한 설계</a></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li></ul></li><li class="uagb-toc__list"><a href="#테이블-간-관계-요약" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 테이블 간 관계 요약</a><li class="uagb-toc__list"><a href="#핵심-포인트" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 핵심 포인트</a></ul></ul></ul></ul></ul></ul></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="1193" height="806" src="https://lycos7560.com/wp-content/uploads/2025/07/image-16.png" alt="" class="wp-image-40161" srcset="https://lycos7560.com/wp-content/uploads/2025/07/image-16.png 1193w, https://lycos7560.com/wp-content/uploads/2025/07/image-16-300x203.png 300w, https://lycos7560.com/wp-content/uploads/2025/07/image-16-768x519.png 768w" sizes="(max-width: 1193px) 100vw, 1193px" /><figcaption class="wp-element-caption">ASP.NET Core Identity 기본 템플릿 Database Diagram</figcaption></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p>ASP.NET Identity는 .NET 애플리케이션에서 사용자 인증과 권한 관리를 담당하는 시스템입니다. </p>



<p>이 시스템은 7개의 핵심 테이블로 구성되어 있으며, 각각은 특별한 역할을 수행합니다.</p>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3d7.png" alt="🏗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 전체 구조 개요</h2>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f464.png" alt="👤" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 사용자 (AspNetUsers)
    <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2195.png" alt="↕" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 다대다 관계
<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f465.png" alt="👥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 역할 (AspNetRoles)
    <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2195.png" alt="↕" class="wp-smiley" style="height: 1em; max-height: 1em;" />
<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4cb.png" alt="📋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 권한/정보 (Claims)
<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f510.png" alt="🔐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 로그인 정보 (Logins, Tokens)
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4cb.png" alt="📋" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 1. AspNetUsers &#8211; 사용자 기본 정보</h2>



<p><strong>역할</strong>: 회원가입한 사용자들의 기본 정보를 저장하는 메인 테이블</p>



<h3 class="wp-block-heading">주요 필드 설명</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>필드명</th><th>타입</th><th>설명</th><th>예시</th></tr></thead><tbody><tr><td><code>Id</code></td><td>GUID</td><td>사용자 고유 식별자</td><td><code>a1b2c3d4-e5f6-...</code></td></tr><tr><td><code>UserName</code></td><td>string</td><td>로그인 ID</td><td><code>john_doe</code></td></tr><tr><td><code>Email</code></td><td>string</td><td>이메일 주소</td><td><code>john@example.com</code></td></tr><tr><td><code>PasswordHash</code></td><td>string</td><td>암호화된 비밀번호</td><td><code>AQAAAAEAACcQ...</code></td></tr><tr><td><code>EmailConfirmed</code></td><td>bool</td><td>이메일 인증 완료 여부</td><td><code>true</code></td></tr><tr><td><code>TwoFactorEnabled</code></td><td>bool</td><td>2단계 인증 활성화 여부</td><td><code>false</code></td></tr><tr><td><code>LockoutEnd</code></td><td>DateTime?</td><td>계정 잠금 해제 시간</td><td><code>2024-12-31 23:59:59</code></td></tr><tr><td><code>AccessFailedCount</code></td><td>int</td><td>로그인 실패 횟수</td><td><code>3</code></td></tr></tbody></table></figure>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f512.png" alt="🔒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 보안 관련 필드들</h3>



<p><strong>SecurityStamp</strong>: 보안이 변경될 때마다 갱신되는 &#8220;도장&#8221;</p>



<ul class="wp-block-list">
<li>비밀번호 변경, 이메일 변경 시 자동 갱신</li>



<li>이전 로그인 세션들을 무효화하는 역할</li>
</ul>



<p><strong>ConcurrencyStamp</strong>: 동시 수정 방지 &#8220;버전 번호&#8221;</p>



<ul class="wp-block-list">
<li>여러 사용자가 같은 계정을 동시에 수정하는 것을 방지</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 사용 예시</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 사용자 계정 잠금 확인
if (user.LockoutEnd.HasValue &amp;&amp; user.LockoutEnd > DateTime.UtcNow)
{
    // 계정이 잠겨있음
    return "계정이 일시적으로 잠겨있습니다.";
}

// 로그인 실패 횟수 증가
user.AccessFailedCount++;
if (user.AccessFailedCount >= 5)
{
    user.LockoutEnd = DateTime.UtcNow.AddMinutes(30);
}
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f465.png" alt="👥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 2. AspNetRoles &#8211; 역할 정의</h2>



<p><strong>역할</strong>: 사용자들을 그룹으로 분류하는 역할(Role) 정보 저장</p>



<h3 class="wp-block-heading">주요 필드</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>필드명</th><th>설명</th><th>예시</th></tr></thead><tbody><tr><td><code>Id</code></td><td>역할 고유 식별자</td><td><code>role-admin-001</code></td></tr><tr><td><code>Name</code></td><td>역할 이름</td><td><code>Administrator</code></td></tr><tr><td><code>NormalizedName</code></td><td>검색용 정규화된 이름</td><td><code>ADMINISTRATOR</code></td></tr></tbody></table></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3ad.png" alt="🎭" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 일반적인 역할 예시</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 기본 역할들
var roles = new[]
{
    new IdentityRole { Name = "SuperAdmin", NormalizedName = "SUPERADMIN" },
    new IdentityRole { Name = "Admin", NormalizedName = "ADMIN" },
    new IdentityRole { Name = "Manager", NormalizedName = "MANAGER" },
    new IdentityRole { Name = "Employee", NormalizedName = "EMPLOYEE" },
    new IdentityRole { Name = "Customer", NormalizedName = "CUSTOMER" }
};
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f517.png" alt="🔗" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 3. AspNetUserRoles &#8211; 사용자-역할 연결</h2>



<p><strong>역할</strong>: 어떤 사용자가 어떤 역할을 가지는지 매핑하는 중간 테이블</p>



<h3 class="wp-block-heading">구조</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>필드명</th><th>설명</th></tr></thead><tbody><tr><td><code>UserId</code></td><td>사용자 ID (외래키)</td></tr><tr><td><code>RoleId</code></td><td>역할 ID (외래키)</td></tr></tbody></table></figure>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 관계 예시</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">사용자 "김철수" (UserId: user123)
├── Admin 역할 (RoleId: role-admin)
└── Manager 역할 (RoleId: role-manager)

사용자 "이영희" (UserId: user456)
└── Employee 역할 (RoleId: role-employee)
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4bb.png" alt="💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 사용법</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 사용자에게 역할 할당
await userManager.AddToRoleAsync(user, "Admin");

// 사용자의 모든 역할 조회
var userRoles = await userManager.GetRolesAsync(user);
// 결과: ["Admin", "Manager"]

// 특정 역할 확인
bool isAdmin = await userManager.IsInRoleAsync(user, "Admin");
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3ab.png" alt="🎫" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 4. Claims 시스템 &#8211; 세부 권한 관리</h2>



<p><strong>클레임(Claim)이란?</strong> 사용자나 역할에 대한 &#8220;주장&#8221; 또는 &#8220;속성&#8221;을 나타내는 이름-값 쌍입니다.</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f50d.png" alt="🔍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 클레임 vs 역할 비교</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>구분</th><th>역할 (Role)</th><th>클레임 (Claim)</th></tr></thead><tbody><tr><td><strong>특징</strong></td><td>그룹 단위 분류</td><td>개별 속성/권한</td></tr><tr><td><strong>예시</strong></td><td>&#8220;관리자&#8221;, &#8220;직원&#8221;</td><td>&#8220;게시물삭제권한&#8221;, &#8220;IT부서소속&#8221;</td></tr><tr><td><strong>유연성</strong></td><td>제한적</td><td>매우 유연</td></tr></tbody></table></figure>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3f7.png" alt="🏷" class="wp-smiley" style="height: 1em; max-height: 1em;" /> AspNetRoleClaims &#8211; 역할별 클레임</h3>



<p><strong>역할</strong>: 특정 역할에 속한 모든 사용자가 공통으로 가지는 권한/속성</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>필드명</th><th>설명</th><th>예시</th></tr></thead><tbody><tr><td><code>RoleId</code></td><td>역할 ID</td><td><code>role-admin</code></td></tr><tr><td><code>ClaimType</code></td><td>클레임 유형</td><td><code>Permission</code></td></tr><tr><td><code>ClaimValue</code></td><td>클레임 값</td><td><code>CanManageUsers</code></td></tr></tbody></table></figure>



<p><strong>실제 예시:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// "Admin" 역할에 클레임 추가
var claims = new[]
{
    new Claim("Permission", "CanManageUsers"),
    new Claim("Permission", "CanDeletePosts"),
    new Claim("Permission", "CanViewReports"),
    new Claim("Department", "Management")
};

foreach (var claim in claims)
{
    await roleManager.AddClaimAsync(adminRole, claim);
}
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f464.png" alt="👤" class="wp-smiley" style="height: 1em; max-height: 1em;" /> AspNetUserClaims &#8211; 개인별 클레임</h3>



<p><strong>역할</strong>: 특정 사용자에게만 부여되는 개별적인 권한/속성</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>필드명</th><th>설명</th><th>예시</th></tr></thead><tbody><tr><td><code>UserId</code></td><td>사용자 ID</td><td><code>user123</code></td></tr><tr><td><code>ClaimType</code></td><td>클레임 유형</td><td><code>SpecialPermission</code></td></tr><tr><td><code>ClaimValue</code></td><td>클레임 값</td><td><code>CanAccessBetaFeatures</code></td></tr></tbody></table></figure>



<p><strong>실제 예시:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 특정 사용자에게만 베타 기능 접근 권한 부여
var betaClaim = new Claim("Feature", "BetaAccess");
await userManager.AddClaimAsync(user, betaClaim);

// VIP 고객 표시
var vipClaim = new Claim("CustomerLevel", "VIP");
await userManager.AddClaimAsync(user, vipClaim);
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 5. AspNetUserLogins &#8211; 외부 로그인 연동</h2>



<p><strong>역할</strong>: 구글, 페이스북 등 외부 서비스를 통한 로그인 정보 저장</p>



<h3 class="wp-block-heading">구조</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>필드명</th><th>설명</th><th>예시</th></tr></thead><tbody><tr><td><code>LoginProvider</code></td><td>로그인 제공자</td><td><code>Google</code></td></tr><tr><td><code>ProviderKey</code></td><td>제공자 내 사용자 ID</td><td><code>google-user-12345</code></td></tr><tr><td><code>ProviderDisplayName</code></td><td>화면 표시명</td><td><code>구글 계정</code></td></tr><tr><td><code>UserId</code></td><td>연결된 로컬 사용자 ID</td><td><code>user123</code></td></tr></tbody></table></figure>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f504.png" alt="🔄" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 연동 과정 예시</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">1. 사용자가 "구글로 로그인" 클릭
2. 구글에서 인증 완료 후 사용자 정보 반환 (ProviderKey: google-12345)
3. AspNetUserLogins 테이블에서 해당 ProviderKey 검색
4. 기존 계정이 있으면 로그인, 없으면 새 계정 생성
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4bb.png" alt="💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실제 구현</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 외부 로그인 정보 추가
var loginInfo = new UserLoginInfo("Google", "google-12345", "Google");
await userManager.AddLoginAsync(user, loginInfo);

// 사용자의 모든 외부 로그인 조회
var logins = await userManager.GetLoginsAsync(user);
// 결과: [{ Provider: "Google", Key: "google-12345" }, ...]
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f39f.png" alt="🎟" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 6. AspNetUserTokens &#8211; 인증 토큰 관리</h2>



<p><strong>역할</strong>: 사용자별 각종 보안 토큰 저장 (리프레시 토큰, 인증 코드 등)</p>



<h3 class="wp-block-heading">구조</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>필드명</th><th>설명</th><th>예시</th></tr></thead><tbody><tr><td><code>UserId</code></td><td>사용자 ID</td><td><code>user123</code></td></tr><tr><td><code>LoginProvider</code></td><td>토큰 발급자</td><td><code>MyApp</code></td></tr><tr><td><code>Name</code></td><td>토큰 이름</td><td><code>RefreshToken</code></td></tr><tr><td><code>Value</code></td><td>토큰 실제 값</td><td><code>abc123def456...</code></td></tr></tbody></table></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f510.png" alt="🔐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 토큰 유형별 예시</h3>



<p><strong>1. 리프레시 토큰</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 리프레시 토큰 저장
await userManager.SetAuthenticationTokenAsync(
    user, "MyApp", "RefreshToken", "abc123def456ghi789");
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>2. 이메일 인증 토큰</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 이메일 인증 토큰 생성 및 저장
var emailToken = await userManager.GenerateEmailConfirmationTokenAsync(user);
// 자동으로 AspNetUserTokens에 저장됨
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>3. 비밀번호 재설정 토큰</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 비밀번호 재설정 토큰 생성
var resetToken = await userManager.GeneratePasswordResetTokenAsync(user);
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3af.png" alt="🎯" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 권한 검사 실제 활용법</h2>



<h3 class="wp-block-heading">1. 컨트롤러에서 역할 기반 권한 검사</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[Authorize(Roles = "Admin,Manager")]
public class AdminController : Controller
{
    public IActionResult Dashboard()
    {
        return View();
    }
}
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2. 클레임 기반 권한 검사</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[Authorize(Policy = "CanManageUsers")]
public class UserManagementController : Controller
{
    public async Task&lt;IActionResult> DeleteUser(string userId)
    {
        // 사용자 삭제 로직
        return Ok();
    }
}

// Startup.cs에서 정책 정의
services.AddAuthorization(options =>
{
    options.AddPolicy("CanManageUsers", policy =>
        policy.RequireClaim("Permission", "CanManageUsers"));
});
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3. 코드에서 동적 권한 검사</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public async Task&lt;IActionResult> SomeAction()
{
    // 현재 사용자가 특정 클레임을 가지고 있는지 확인
    if (User.HasClaim("Permission", "CanDeletePosts"))
    {
        // 게시물 삭제 가능
    }
    
    // 역할 확인
    if (User.IsInRole("Admin"))
    {
        // 관리자 전용 기능
    }
    
    return View();
}
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3e2.png" alt="🏢" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 실무 시나리오 예시</h2>



<h3 class="wp-block-heading">회사 내부 시스템 권한 설계</h3>



<p><strong>1. 역할 구조</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">SuperAdmin (최고 관리자)
├── Admin (일반 관리자)
├── HRManager (인사 관리자)
├── ProjectManager (프로젝트 관리자)
├── Developer (개발자)
├── Designer (디자이너)
└── Intern (인턴)
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>2. 클레임 설계</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 부서별 클레임
ClaimType: "Department"
Values: "Engineering", "Design", "HR", "Marketing"

// 권한별 클레임
ClaimType: "Permission"
Values: "ViewSalary", "EditProject", "DeleteUser", "ViewReports"

// 레벨별 클레임
ClaimType: "Level"
Values: "Senior", "Junior", "Lead"
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>3. 실제 적용</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 사용자 "김개발"의 권한 설정
var developer = await userManager.FindByNameAsync("kim_developer");

// 역할 할당
await userManager.AddToRoleAsync(developer, "Developer");

// 개인 클레임 할당
await userManager.AddClaimAsync(developer, new Claim("Department", "Engineering"));
await userManager.AddClaimAsync(developer, new Claim("Level", "Senior"));
await userManager.AddClaimAsync(developer, new Claim("Permission", "ViewReports"));
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>4. 권한 검사</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 급여 정보는 HR 부서와 본인만 볼 수 있음
[Authorize]
public async Task&lt;IActionResult> ViewSalary(string userId)
{
    var currentUser = await userManager.GetUserAsync(User);
    
    // 본인 정보이거나 HR 부서인 경우만 허용
    if (currentUser.Id == userId || 
        User.HasClaim("Department", "HR"))
    {
        return View();
    }
    
    return Forbid();
}
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4ca.png" alt="📊" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 테이블 간 관계 요약</h2>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">AspNetUsers (사용자)
    ├── AspNetUserRoles → AspNetRoles (역할)
    ├── AspNetUserClaims (개인 클레임)
    ├── AspNetUserLogins (외부 로그인)
    └── AspNetUserTokens (인증 토큰)

AspNetRoles (역할)
    └── AspNetRoleClaims (역할별 클레임)
</pre>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 핵심 포인트</h2>



<ol class="wp-block-list">
<li>역할(Role)은 사용자를 <strong>그룹으로 분류</strong>하는 개념</li>



<li>클레임(Claim)은 <strong>세부적인 권한과 속성</strong>을 정의하는 개념</li>



<li><strong>역할 클레임</strong>은 해당 역할의 모든 사용자에게 적용</li>



<li><strong>사용자 클레임</strong>은 특정 사용자에게만 적용</li>



<li><strong>외부 로그인</strong>으로 여러 인증 방식 통합 가능</li>



<li><strong>토큰</strong>으로 다양한 인증 시나리오 지원</li>
</ol>



<p>이러한 구조를 통해 복잡한 권한 관리와 인증 시스템을 유연하고 안전하게 구현할 수 있습니다.</p>



<p></p>
<p>The post <a href="https://lycos7560.com/c/asp-net/asp-net-identity-%eb%8d%b0%ec%9d%b4%ed%84%b0%eb%b2%a0%ec%9d%b4%ec%8a%a4/40160/">ASP.NET Identity 데이터베이스</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/asp-net/asp-net-identity-%eb%8d%b0%ec%9d%b4%ed%84%b0%eb%b2%a0%ec%9d%b4%ec%8a%a4/40160/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>.NET / C# Compiler, IL, DLL, EXE, JIT</title>
		<link>https://lycos7560.com/c/net-c-compiler-il-dll-exe-jit/40044/</link>
					<comments>https://lycos7560.com/c/net-c-compiler-il-dll-exe-jit/40044/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Wed, 16 Apr 2025 15:54:00 +0000</pubDate>
				<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[Compiler]]></category>
		<category><![CDATA[dotnet]]></category>
		<category><![CDATA[EXE]]></category>
		<category><![CDATA[IL]]></category>
		<category><![CDATA[JIT]]></category>
		<category><![CDATA[Roslyn]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[기초]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=40044</guid>

					<description><![CDATA[<p>1. 개요 C# 애플리케이션이 소스 코드에서 실행 가능한 프로그램으로 변환되는 과정은 여러 단계로 이루어져 있습니다. C# 컴파일러, 중간 언어(IL), 어셈블리(DLL, EXE), JIT 컴파일러 간의 관계와 전체 실행 흐름을 설명합니다. 2. 전체 실행 흐름 3. C# 컴파일러 (로슬린 &#8211; Roslyn) 역할 특징 컴파일러 파이프라인 구성요소 C# 코드 예시 4. 중간 언어 (IL, Intermediate Language) 특징 주요 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/c/net-c-compiler-il-dll-exe-jit/40044/">.NET / C# Compiler, IL, DLL, EXE, JIT</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-b89f8a18      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-개요" class="uagb-toc-link__trigger">1. 개요</a><li class="uagb-toc__list"><a href="#2-전체-실행-흐름" class="uagb-toc-link__trigger">2. 전체 실행 흐름</a><li class="uagb-toc__list"><a href="#3-c-컴파일러-로슬린-roslyn" class="uagb-toc-link__trigger">3. C# 컴파일러 (로슬린 &#8211; Roslyn)</a><li class="uagb-toc__list"><a href="#4-중간-언어-il-intermediate-language" class="uagb-toc-link__trigger">4. 중간 언어 (IL, Intermediate Language)</a><li class="uagb-toc__list"><a href="#5-dll과-exe-파일-어셈블리" class="uagb-toc-link__trigger">5. DLL과 EXE 파일 (어셈블리)</a><li class="uagb-toc__list"><a href="#6-clr공통-언어-런타임" class="uagb-toc-link__trigger">6. CLR(공통 언어 런타임)</a><li class="uagb-toc__list"><a href="#7-jit-컴파일러-just-in-time" class="uagb-toc-link__trigger">7. JIT 컴파일러 (Just-In-Time)</a><li class="uagb-toc__list"><a href="#8-상세-실행-흐름" class="uagb-toc-link__trigger">8. 상세 실행 흐름</a><li class="uagb-toc__list"><a href="#9-코드-변환-예시" class="uagb-toc-link__trigger">9. 코드 변환 예시</a><li class="uagb-toc__list"><a href="#10-추가-정보" class="uagb-toc-link__trigger">10. 추가 정보</a><li class="uagb-toc__list"><a href="#11-결론" class="uagb-toc-link__trigger">11. 결론</a></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">1. 개요</h2>



<p>C# 애플리케이션이 소스 코드에서 실행 가능한 프로그램으로 변환되는 과정은 여러 단계로 이루어져 있습니다. </p>



<p>C# 컴파일러, 중간 언어(IL), 어셈블리(DLL, EXE), JIT 컴파일러 간의 관계와 전체 실행 흐름을 설명합니다.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">2. 전체 실행 흐름</h2>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">C# 소스 코드 → C# 컴파일러(Roslyn) → IL 코드 포함 DLL/EXE → CLR 로딩 → JIT 컴파일러 → 네이티브 머신 코드 → CPU 실행</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">3. C# 컴파일러 (로슬린 &#8211; Roslyn)</h2>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">역할</h3>



<ul class="wp-block-list">
<li>C# 소스 코드(.cs 파일)를 <strong>중간 언어(IL, Intermediate Language)로 변환</strong></li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">특징</h3>



<ul class="wp-block-list">
<li>Microsoft의 오픈 소스 컴파일러 플랫폼</li>



<li><code>csc.exe</code>로 실행되거나 <code>dotnet build</code> 명령어 실행 시 자동 호출</li>



<li>어휘 분석, 구문 분석, 의미 분석, IL 생성의 단계적 작업 수행</li>



<li>주요 최적화는 JIT 컴파일 단계로 미루어짐</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">컴파일러 파이프라인 구성요소</h3>



<ul class="wp-block-list">
<li><strong>Syntax Tree API</strong>: 소스 코드의 구문적 구조 표현</li>



<li><strong>Semantic Model API</strong>: 코드의 의미론적 정보 제공</li>



<li><strong>Compilation API</strong>: 컴파일 과정 제어</li>



<li><strong>Workspace API</strong>: 솔루션, 프로젝트, 문서 등 작업 환경 관리</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">C# 코드 예시</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public class Program 
{
    public static void Main() 
    {
        Console.WriteLine("Hello, World!");
    }
}</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">4. 중간 언어 (IL, Intermediate Language)</h2>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">특징</h3>



<ul class="wp-block-list">
<li>CPU 아키텍처에 독립적인 저수준 언어</li>



<li>.dll 또는 .exe 파일 내에 포함됨</li>



<li>인간이 읽을 수 있는 형태(ILASM)로도 표현 가능</li>



<li>CLR(공통 언어 런타임)이 이해하는 언어</li>



<li>스택 기반 명령어 집합으로 구성</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">주요 특성</h3>



<ul class="wp-block-list">
<li><strong>스택 기반 명령어 집합</strong>: 주로 스택을 사용하여 연산 수행</li>



<li><strong>객체 지향 지원</strong>: 클래스, 상속, 가상 메서드 등 지원</li>



<li><strong>타입 안전성</strong>: 강력한 타입 시스템 유지</li>



<li><strong>메타데이터 통합</strong>: 코드와 메타데이터가 밀접하게 연결</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">IL 코드 예시</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">// 위 C# 코드의 IL 표현 (간략화)
.method public hidebysig static void Main() cil managed {
  .entrypoint
  ldstr "Hello, World!"
  call void [System.Console]System.Console::WriteLine(string)
  ret
}
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">5. DLL과 EXE 파일 (어셈블리)</h2>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">공통점</h3>



<ul class="wp-block-list">
<li>모두 PE(Portable Executable) 형식의 파일</li>



<li>메타데이터(형식 정보)와 IL 코드 포함</li>



<li>.NET 어셈블리로서 버전 관리, 보안 정보 등 포함</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">차이점</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>특성</th><th>DLL (동적 연결 라이브러리)</th><th>EXE (실행 파일)</th></tr></thead><tbody><tr><td>용도</td><td>재사용 가능한 라이브러리</td><td>실행 가능한 애플리케이션</td></tr><tr><td>진입점</td><td>없음</td><td><code>Main</code> 메서드 (IL에서 <code>.entrypoint</code> 표시)</td></tr><tr><td>내용</td><td>IL 코드와 메타데이터</td><td>IL 코드, 메타데이터 + 추가 실행 정보</td></tr><tr><td>실행</td><td>직접 실행 불가</td><td>직접 실행 가능</td></tr></tbody></table></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">어셈블리 구성요소</h3>



<ul class="wp-block-list">
<li><strong>매니페스트</strong>: 어셈블리 ID, 버전, 문화권, 강력한 이름 등의 정보</li>



<li><strong>타입 메타데이터</strong>: 클래스, 메서드, 프로퍼티 등의 정보</li>



<li><strong>IL 코드</strong>: 실행 가능한 중간 언어 코드</li>



<li><strong>리소스</strong>: 이미지, 문자열 등 포함 가능</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">6. CLR(공통 언어 런타임)</h2>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">역할</h3>



<ul class="wp-block-list">
<li>.NET 애플리케이션의 실행 환경 제공</li>



<li>메모리 관리, 보안, 예외 처리 등 다양한 서비스 제공</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">주요 구성요소</h3>



<ul class="wp-block-list">
<li><strong>클래스 로더</strong>: 필요한 타입을 메모리에 로드</li>



<li><strong>JIT 컴파일러</strong>: IL을 네이티브 코드로 변환</li>



<li><strong>가비지 컬렉터</strong>: 자동 메모리 관리</li>



<li><strong>보안 시스템</strong>: 코드 접근 보안(CAS) 등 제공</li>



<li><strong>스레드 관리</strong>: 다중 스레드 실행 지원</li>



<li><strong>예외 처리기</strong>: 예외 발생 및 처리 관리</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">7. JIT 컴파일러 (Just-In-Time)</h2>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">역할</h3>



<ul class="wp-block-list">
<li>IL 코드를 네이티브 머신 코드로 변환</li>



<li>실행 시점에 최적화 수행</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">작동 과정</h3>



<ol class="wp-block-list">
<li>애플리케이션 실행 시 CLR 로드</li>



<li>메서드 첫 호출 시 해당 IL 코드 JIT 컴파일</li>



<li>컴파일된 네이티브 코드 캐싱 (재사용)</li>



<li>실제 CPU에서 실행</li>
</ol>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">최적화 기법</h3>



<ul class="wp-block-list">
<li><strong>인라인 확장</strong>: 작은 메서드를 호출 위치에 직접 삽입</li>



<li><strong>가상 메서드 최적화</strong>: 런타임 시 실제 타입 기반 최적화</li>



<li><strong>루프 최적화</strong>: 루프 언롤링, 벡터화 등</li>



<li><strong>불필요한 경계 검사 제거</strong>: 배열 접근 최적화</li>



<li><strong>상수 폴딩</strong>: 컴파일 시점에 상수 표현식 계산</li>



<li><strong>티어드 컴파일</strong>: 처음에는 빠르게 컴파일된 덜 최적화된 코드 실행, 후에 더 최적화된 버전으로 대체</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">8. 상세 실행 흐름</h2>



<ol class="wp-block-list">
<li><strong>C# 소스 코드 작성</strong>
<ul class="wp-block-list">
<li>개발자가 <code>.cs</code> 파일에 C# 코드 작성</li>
</ul>
</li>



<li><strong>C# 컴파일러(Roslyn) 처리</strong>
<ul class="wp-block-list">
<li><strong>어휘 분석(Lexical Analysis)</strong>: 소스 코드를 토큰으로 분리</li>



<li><strong>구문 분석(Syntax Analysis)</strong>: 토큰을 구문 트리로 구성</li>



<li><strong>의미 분석(Semantic Analysis)</strong>: 타입 체킹, 타입 추론 등</li>



<li><strong>IL 코드 생성</strong>: 컴파일된 중간 언어(IL) 생성</li>



<li><strong>메타데이터 생성</strong>: 타입 정보, 어셈블리 참조 등 생성</li>
</ul>
</li>



<li><strong>어셈블리 생성</strong>
<ul class="wp-block-list">
<li>컴파일된 IL 코드와 메타데이터가 PE(Portable Executable) 형식의 파일(<code>.dll</code> 또는 <code>.exe</code>)로 패키징</li>



<li>어셈블리 매니페스트 포함: 버전, 문화권, 강력한 이름 등의 정보</li>
</ul>
</li>



<li><strong>애플리케이션 실행</strong>
<ul class="wp-block-list">
<li><code>.exe</code> 파일 실행시 CLR(공통 언어 런타임) 호스트(예: <code>CoreCLR</code>)가 로드됨</li>



<li>CLR은 메타데이터를 읽고 필요한 어셈블리 로드</li>
</ul>
</li>



<li><strong>JIT 컴파일</strong>
<ul class="wp-block-list">
<li>메서드가 처음 호출될 때 JIT 컴파일러가 해당 메서드의 IL 코드를 네이티브 코드로 변환</li>



<li>티어드 컴파일 적용 시 점진적 최적화</li>
</ul>
</li>



<li><strong>실행</strong>
<ul class="wp-block-list">
<li>생성된 네이티브 코드가 CPU에서 직접 실행</li>



<li>가비지 컬렉션, 예외 처리 등 CLR 서비스가 실행 중 관리</li>
</ul>
</li>
</ol>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">9. 코드 변환 예시</h2>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">C# 코드</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public static int AddNumbers(int a, int b) {
    return a + b;
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">변환된 IL 코드</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">.method public static int32 AddNumbers(int32 a, int32 b) cil managed {
    .maxstack 2
    ldarg.0     // a를 스택에 로드
    ldarg.1     // b를 스택에 로드
    add         // 스택의 두 값을 더함
    ret         // 결과 반환
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">x86-64 네이티브 코드 (JIT 컴파일 후)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">mov eax, ecx    ; 첫 번째 인수(a)를 eax로 이동
add eax, edx    ; 두 번째 인수(b)를 더함
ret             ; 결과(eax에 저장됨) 반환
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">10. 추가 정보</h2>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">AOT(Ahead-of-Time) 컴파일</h3>



<ul class="wp-block-list">
<li>JIT 대신 미리 네이티브 코드로 컴파일하는 방식</li>



<li>초기 시작 시간 단축 및 메모리 사용량 감소</li>



<li>모바일/임베디드 환경에 적합</li>



<li>일부 동적 기능 제한 (리플렉션 등)</li>



<li>.NET 6 이상에서 Native AOT 지원</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">티어드 컴파일(Tiered Compilation)</h3>



<ul class="wp-block-list">
<li>처음에는 빠른 컴파일 우선으로 덜 최적화된 코드 생성</li>



<li>자주 사용되는 메서드는 백그라운드에서 더 많은 최적화 적용</li>



<li>시작 시간과 장기 실행 성능 사이의 균형 제공</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">11. 결론</h2>



<p>.NET의 컴파일 및 실행 모델은 플랫폼 독립성과 성능 사이의 균형을 효과적으로 맞추고 있습니다. </p>



<p>C# 소스 코드가 Roslyn 컴파일러를 통해 IL로 변환되고, 이후 JIT 컴파일러에 의해 필요할 때 네이티브 코드로 변환되는 과정은 다양한 플랫폼에서의 실행을 가능하게 하면서도 최적화된 성능을 제공합니다.</p>



<p>최근 버전의 .NET에서는 AOT 컴파일, 티어드 컴파일 등 더 다양한 최적화 기법을 도입하여 성능을 더욱 향상시키고 있으며, 이는 클라우드 네이티브 애플리케이션과 마이크로서비스 아키텍처에서 특히 중요한 역할을 합니다.</p>



<p></p>
<p>The post <a href="https://lycos7560.com/c/net-c-compiler-il-dll-exe-jit/40044/">.NET / C# Compiler, IL, DLL, EXE, JIT</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/c/net-c-compiler-il-dll-exe-jit/40044/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Unity Serialization (직렬화)</title>
		<link>https://lycos7560.com/unity/unity-serialization-%ec%a7%81%eb%a0%ac%ed%99%94/39882/</link>
					<comments>https://lycos7560.com/unity/unity-serialization-%ec%a7%81%eb%a0%ac%ed%99%94/39882/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Tue, 25 Feb 2025 09:24:30 +0000</pubDate>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[Unity]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[Byte Stream]]></category>
		<category><![CDATA[Configuration Files]]></category>
		<category><![CDATA[Data Format]]></category>
		<category><![CDATA[Data Storage]]></category>
		<category><![CDATA[Data Structure]]></category>
		<category><![CDATA[data transfer]]></category>
		<category><![CDATA[Developer Tools]]></category>
		<category><![CDATA[Dictionary]]></category>
		<category><![CDATA[file saving]]></category>
		<category><![CDATA[Game Data]]></category>
		<category><![CDATA[Game Development]]></category>
		<category><![CDATA[Game State Saving]]></category>
		<category><![CDATA[Inspector]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[JsonUtility]]></category>
		<category><![CDATA[List]]></category>
		<category><![CDATA[Load System]]></category>
		<category><![CDATA[Memory Management]]></category>
		<category><![CDATA[Newtonsoft.Json]]></category>
		<category><![CDATA[Object Conversion]]></category>
		<category><![CDATA[parsing]]></category>
		<category><![CDATA[PlayerPrefs]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[Readability]]></category>
		<category><![CDATA[Runtime]]></category>
		<category><![CDATA[Save System]]></category>
		<category><![CDATA[Scene Management]]></category>
		<category><![CDATA[ScriptableObject]]></category>
		<category><![CDATA[scripting]]></category>
		<category><![CDATA[Serialization]]></category>
		<category><![CDATA[Serialization Rules]]></category>
		<category><![CDATA[SerializeField]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[System.Serializable]]></category>
		<category><![CDATA[Unity Editor]]></category>
		<category><![CDATA[Variable Types]]></category>
		<category><![CDATA[WebSockets]]></category>
		<category><![CDATA[XML]]></category>
		<category><![CDATA[YAML]]></category>
		<category><![CDATA[가독성]]></category>
		<category><![CDATA[개발자도구]]></category>
		<category><![CDATA[객체변환]]></category>
		<category><![CDATA[게임개발]]></category>
		<category><![CDATA[게임데이터]]></category>
		<category><![CDATA[게임상태저장]]></category>
		<category><![CDATA[공부]]></category>
		<category><![CDATA[데이터구조]]></category>
		<category><![CDATA[데이터저장]]></category>
		<category><![CDATA[데이터전송]]></category>
		<category><![CDATA[데이터포맷]]></category>
		<category><![CDATA[런타임]]></category>
		<category><![CDATA[로드시스템]]></category>
		<category><![CDATA[메모리관리]]></category>
		<category><![CDATA[바이트스트림]]></category>
		<category><![CDATA[변수타입]]></category>
		<category><![CDATA[설정파일]]></category>
		<category><![CDATA[스크립팅]]></category>
		<category><![CDATA[씬관리]]></category>
		<category><![CDATA[웹소켓]]></category>
		<category><![CDATA[유니티에디터]]></category>
		<category><![CDATA[직렬화]]></category>
		<category><![CDATA[직렬화규칙]]></category>
		<category><![CDATA[파싱]]></category>
		<category><![CDATA[파일저장]]></category>
		<category><![CDATA[프로그래밍]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=39882</guid>

					<description><![CDATA[<p>🔥 Serialization (직렬화) 개요 Serialization이란 객체 데이터를 바이트 스트림으로 변환하여 저장하거나 전송할 수 있도록 하는 과정 주 목적은 필요할 때 다시 개체로 만들 수 있도록 개체의 상태를 저장하는 것 반대로 Deserialization은 저장된 바이트 데이터를 다시 객체로 변환하는 과정입니다. 🔥 JSON, XML, YAML 일반적으로 많이 사용하는 데이터 직렬화 포맷들 1️⃣ JSON (JavaScript Object Notation) ✔️ 특징 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/unity/unity-serialization-%ec%a7%81%eb%a0%ac%ed%99%94/39882/">Unity Serialization (직렬화)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-571189ff      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#serialization-직렬화-개요" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Serialization (직렬화) 개요</a><li class="uagb-toc__list"><a href="#json-xml-yaml" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JSON, XML, YAML</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-json-javascript-object-notation" class="uagb-toc-link__trigger">1&#x20e3; JSON (JavaScript Object Notation)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-xml-extensible-markup-language" class="uagb-toc-link__trigger">2&#x20e3; XML (eXtensible Markup Language)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-yaml-yaml-aint-markup-language" class="uagb-toc-link__trigger">3&#x20e3; YAML (YAML Ain&#039;t Markup Language)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#비교-정리" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f50e.png" alt="🔎" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 비교 정리</a></li></ul></li><li class="uagb-toc__list"><a href="#unity에서의-스크립트-직렬화-serialization-in-unity" class="uagb-toc-link__trigger"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" />Unity에서의 스크립트 직렬화 (Serialization in Unity)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-unity에서-직렬화가-중요한-이유" class="uagb-toc-link__trigger">1&#x20e3; Unity에서 직렬화가 중요한 이유</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-unity에서-직렬화-가능한-타입" class="uagb-toc-link__trigger">2&#x20e3; Unity에서 직렬화 가능한 타입</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-unity-직렬화-적용-방법" class="uagb-toc-link__trigger">3&#x20e3; Unity 직렬화 적용 방법</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-unity-직렬화와-json-활용" class="uagb-toc-link__trigger">4&#x20e3; Unity 직렬화와 JSON 활용</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Serialization (직렬화) 개요</strong></h2>



<p><strong>Serialization</strong>이란<strong> 객체 데이터</strong>를 <strong>바이트 스트림</strong>으로 변환하여 <strong>저장</strong>하거나 <strong>전송</strong>할 수 있도록 하는 과정</p>



<p>주 목적은 필요할 때 다시 개체로 만들 수 있도록 개체의 상태를 저장하는 것</p>



<p>반대로 <strong>Deserialization</strong>은 저장된 바이트 데이터를 다시 객체로 변환하는 과정입니다.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="262" height="199" src="https://lycos7560.com/wp-content/uploads/2025/02/image-179.png" alt="" class="wp-image-39884"/><figcaption class="wp-element-caption"><a href="https://learn.microsoft.com/ko-kr/dotnet/visual-basic/programming-guide/concepts/serialization/" target="_blank" rel="noreferrer noopener">https://learn.microsoft.com/ko-kr/dotnet/visual-basic/programming-guide/concepts/serialization/</a></figcaption></figure>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<h2 class="wp-block-heading"><strong><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong></strong> JSON, XML, YAML</h2>



<p>일반적으로 많이 사용하는 데이터 직렬화 포맷들</p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--30);margin-bottom:var(--wp--preset--spacing--30)"/>



<h3 class="wp-block-heading">1&#x20e3; JSON (JavaScript Object Notation)</h3>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 특징</h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 가벼운 텍스트 기반 포맷<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 사람이 읽고 쓰기 쉬우며, 기계가 빠르게 처리 가능<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 대부분의 프로그래밍 언어에서 지원 (C#, Python, JavaScript 등)<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 키-값 쌍의 구조 (Dictionary, Object와 유사)</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 예제</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "name": "John",
  "age": 30,
  "skills": ["C#", "Unity", "Python"],
  "isDeveloper": true
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 장점</h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 가볍고 속도가 빠름<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 대부분의 언어에서 지원<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> API 및 데이터 전송에 적합 (REST API, WebSockets 등)</p>



<h4 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 단점</strong></h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 주석 지원이 안됨<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> XML이나 YAML보다 덜 유연함</p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40)"/>



<h3 class="wp-block-heading">2&#x20e3; XML (eXtensible Markup Language)</h3>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 특징</h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 데이터 구조를 계층적으로 표현 (HTML과 유사)<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 사람이 읽을 수 있지만, JSON보다 길이가 김<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 다양한 도메인에서 사용 (문서 저장, 설정 파일, 웹 서비스 등)<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> &lt;태그&gt;를 이용한 구조화된 데이터 표현</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 예제</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="xml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;Person>
    &lt;Name>John&lt;/Name>
    &lt;Age>30&lt;/Age>
    &lt;Skills>
        &lt;Skill>C#&lt;/Skill>
        &lt;Skill>Unity&lt;/Skill>
        &lt;Skill>Python&lt;/Skill>
    &lt;/Skills>
    &lt;IsDeveloper>true&lt;/IsDeveloper>
&lt;/Person>
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 장점</h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 데이터 구조가 명확함<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 주석 사용 가능 (&lt;!&#8211; 주석 &#8211;&gt;)<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 다양한 데이터 타입을 표현하기 적합</p>



<h4 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 단점</strong></h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JSON보다 크기가 크고, 가독성이 떨어짐<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 파싱 속도가 느림</p>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40)"/>



<h3 class="wp-block-heading">3&#x20e3; YAML (YAML Ain&#8217;t Markup Language)</h3>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 특징</h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JSON과 유사하지만 더 간결한 문법을 가짐<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 들여쓰기를 이용한 계층 구조 (Python과 유사)<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 설정 파일에서 많이 사용 (Docker, Kubernetes, Unity Config 등)</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 예제</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">name: John
age: 30
skills:
  - C#
  - Unity
  - Python
isDeveloper: true
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 장점</h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 사람이 읽기 쉬운 직관적인 문법<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JSON보다 간결하여 가독성이 좋음<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 주석 (#) 사용 가능</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 단점</strong></h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 들여쓰기 실수로 인한 오류 발생 가능<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 파싱 속도가 JSON보다 느림</p>



<figure class="wp-block-image size-full"><img decoding="async" width="727" height="481" src="https://lycos7560.com/wp-content/uploads/2025/02/image-180.jpg" alt="" class="wp-image-39885" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-180.jpg 727w, https://lycos7560.com/wp-content/uploads/2025/02/image-180-300x198.jpg 300w" sizes="(max-width: 727px) 100vw, 727px" /><figcaption class="wp-element-caption">Unity의 YAML<br><a href="https://unity.com/kr/blog/engine-platform/understanding-unitys-serialization-language-yaml" target="_blank" rel="noreferrer noopener">https://unity.com/kr/blog/engine-platform/understanding-unitys-serialization-language-yaml</a></figcaption></figure>



<hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--40)"/>



<h3 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f50e.png" alt="🔎" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 비교 정리</strong></h3>



<p>Unity에서는 JSON (JsonUtility, Newtonsoft.Json)이 가장 많이 사용되며, YAML은 Unity의 설정 파일 (.yml)에서 많이 활용됩니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>형식</th><th>문법</th><th>가독성</th><th>속도</th><th>사용 사례</th></tr></thead><tbody><tr><td><strong>JSON</strong></td><td><code>{ "key": "value" }</code></td><td>좋음</td><td>빠름 <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>API, 데이터 저장</td></tr><tr><td><strong>XML</strong></td><td><code>&lt;tag&gt;value&lt;/tag&gt;</code></td><td>중간</td><td>느림 <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>문서 저장, 설정 파일</td></tr><tr><td><strong>YAML</strong></td><td><code>key: value</code> (들여쓰기)</td><td>매우 좋음 <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>중간</td><td>설정 파일, DevOps</td></tr></tbody></table></figure>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="1620" height="714" src="https://lycos7560.com/wp-content/uploads/2025/02/image-180.png" alt="" class="wp-image-39886" style="width:771px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-180.png 1620w, https://lycos7560.com/wp-content/uploads/2025/02/image-180-300x132.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-180-768x338.png 768w, https://lycos7560.com/wp-content/uploads/2025/02/image-180-1536x677.png 1536w" sizes="(max-width: 1620px) 100vw, 1620px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<h2 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f525.png" alt="🔥" class="wp-smiley" style="height: 1em; max-height: 1em;" />Unity에서의 스크립트 직렬화 (Serialization in Unity)</strong></h2>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-default" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"/>



<h3 class="wp-block-heading">1&#x20e3; <strong>Unity에서 직렬화가 중요한 이유</strong></h3>



<p>Unity는 <strong>Inspector 창에서 데이터를 유지</strong>하거나 <strong>저장 시스템(예: PlayerPrefs, JSON, Binary 파일 등)에 데이터를 저장</strong>하기 위해 직렬화를 활용합니다.</p>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>씬(Scene) 저장 및 로드</strong>: Inspector에 입력한 값이 유지됨<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>ScriptableObject:</strong> 게임 데이터 관리<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Save/Load 시스템</strong>: JSON 또는 Binary 직렬화를 활용하여 파일 저장</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-default" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"/>



<h3 class="wp-block-heading">2&#x20e3; <strong>Unity에서 직렬화 가능한 타입</strong></h3>



<p><a href="https://docs.unity3d.com/6000.0/Documentation/Manual/script-serialization-rules.html" target="_blank" rel="noreferrer noopener">https://docs.unity3d.com/6000.0/Documentation/Manual/script-serialization-rules.html</a></p>



<p>Unity는 <strong>특정 데이터 타입만 직렬화할 수 있도록 제한</strong>하고 있습니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b55.png" alt="⭕" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 직렬화 가능한 타입 (Serializable)</h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>기본 데이터 타입</strong>: <code>int</code>, <code>float</code>, <code>bool</code>, <code>string</code> 등<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>Unity 기본 타입</strong>: <strong>배열 &amp; 리스트</strong>: <code>int[]</code>, <code>List&lt;float&gt;</code> 등 (단, <code>public</code> 또는 <code>[SerializeField]</code> 필요)<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>사용자 정의 클래스</strong> (<code>[System.Serializable]</code> 필요)<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>ScriptableObject</strong></p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> 직렬화 불가능한 타입 (Non-Serializable)</h4>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>딕셔너리(Dictionary)</strong><br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong>다차원 배열 (<code>int[,]</code>)</strong><br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><strong>속성(Property) (getter/setter)</strong></strong><br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><strong>Static 변수 (<code>static</code>)</strong></strong><br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><strong>추상 클래스 &amp; 인터페이스</strong></strong><br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <strong><strong>델리게이트(Delegate), 이벤트(Event)</strong></strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-default" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"/>



<h3 class="wp-block-heading">3&#x20e3; <strong>Unity 직렬화 적용 방법</strong></h3>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> [SerializeField]</h4>



<p><code>private</code> 변수도 Inspector에서 보이도록 만들고, 직렬화 가능하게 합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;

public class Player : MonoBehaviour
{
    [SerializeField] private int health = 100;  // 직렬화됨 (Inspector에서 수정 가능)
    private int score = 0;  // 직렬화되지 않음 (Inspector에서 보이지 않음)
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> [SerializeField]를 사용하면 <code>private</code> 변수도 직렬화됨<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 하지만 <code>static</code>, <code>const</code>, <code>readonly</code> 변수는 직렬화되지 않음</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> [System.Serializable]</h4>



<p>사용자 정의 클래스를 직렬화하려면 <code>[System.Serializable]</code>을 붙여야 합니다.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;

[System.Serializable]  
public class Weapon
{
    public string name;
    public int damage;
}

public class Player : MonoBehaviour
{
    public Weapon myWeapon;  // Weapon 클래스가 직렬화되므로 Inspector에서 수정 가능
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 클래스를 <code>System.Serializable</code>로 만들면 Unity Inspector에서 확인 가능<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 하지만 <code>Dictionary</code>, <code>Properties</code>, <code>Static 변수</code> 등은 여전히 직렬화되지 않음</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ScriptableObject</h4>



<p>게임 데이터를 저장 및 관리하는 특수한 직렬화 객체</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;

[CreateAssetMenu(fileName = "New Weapon", menuName = "Weapon")]
public class WeaponData : ScriptableObject
{
    public string weaponName;
    public int damage;
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 씬(Scene)과 독립적으로 데이터 관리 가능<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 메모리 효율적 관리 가능 (오브젝트 풀링, 설정 저장 등)<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 런타임에서 수정된 값은 저장되지 않음 (Play 모드가 끝나면 초기화됨)</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-default" style="margin-top:var(--wp--preset--spacing--50);margin-bottom:var(--wp--preset--spacing--50)"/>



<h3 class="wp-block-heading">4&#x20e3; <strong>Unity 직렬화와 JSON 활용</strong></h3>



<p>Unity에서 JSON 직렬화를 활용하면 <strong>데이터 저장 &amp; 로드</strong>가 가능해집니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JsonUtility (빠르지만 제한적)</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;

[System.Serializable]
public class PlayerData
{
    public string name;
    public int level;
}

public class SaveLoadSystem : MonoBehaviour
{
    void Start()
    {
        PlayerData player = new PlayerData { name = "John", level = 10 };
        string json = JsonUtility.ToJson(player);
        Debug.Log(json);  // {"name":"John","level":10}

        PlayerData loadedPlayer = JsonUtility.FromJson&lt;PlayerData>(json);
        Debug.Log(loadedPlayer.name);  // John
    }
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 빠름 &amp; Unity 기본 지원<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Dictionary, List&lt;T&gt; 같은 복잡한 자료형 지원 X</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Newtonsoft.Json (더 강력한 JSON 직렬화)</h4>



<p><code>Newtonsoft.Json</code> 라이브러리를 사용하면 <strong>Dictionary, List, Interface 등도 직렬화 가능</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using UnityEngine;
using Newtonsoft.Json;
using System.Collections.Generic;

public class PlayerData
{
    public string name;
    public int level;
    public Dictionary&lt;string, int> inventory = new Dictionary&lt;string, int>();
}

public class SaveLoadSystem : MonoBehaviour
{
    void Start()
    {
        PlayerData player = new PlayerData { name = "John", level = 10 };
        player.inventory.Add("Sword", 1);
        player.inventory.Add("Potion", 5);

        string json = JsonConvert.SerializeObject(player, Formatting.Indented);
        Debug.Log(json);

        PlayerData loadedPlayer = JsonConvert.DeserializeObject&lt;PlayerData>(json);
        Debug.Log(loadedPlayer.inventory["Sword"]);  // 1
    }
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Dictionary, List&lt;T&gt; 등 복잡한 자료형 지원<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> JsonUtility보다 속도가 느림<br><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 추가 라이브러리 설치 필요 (<code>Newtonsoft.Json.dll</code>)</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="[유니티 TIPS] 데이터 시리얼라이제이션 완전 정복하기" width="1778" height="1000" src="https://www.youtube.com/embed/kEu_AQ_Es-8?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div><figcaption class="wp-element-caption">공부 참고 영상</figcaption></figure>



<p></p>
<p>The post <a href="https://lycos7560.com/unity/unity-serialization-%ec%a7%81%eb%a0%ac%ed%99%94/39882/">Unity Serialization (직렬화)</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/unity/unity-serialization-%ec%a7%81%eb%a0%ac%ed%99%94/39882/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ECS &#8211; EntityQuery 정리</title>
		<link>https://lycos7560.com/unity/ecs-entityquery-%ec%a0%95%eb%a6%ac/39563/</link>
					<comments>https://lycos7560.com/unity/ecs-entityquery-%ec%a0%95%eb%a6%ac/39563/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Sun, 09 Feb 2025 10:22:42 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[AsNativeArray]]></category>
		<category><![CDATA[AsNativeArray()]]></category>
		<category><![CDATA[BufferTypeHandle]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Chunk components]]></category>
		<category><![CDATA[Cleanup components]]></category>
		<category><![CDATA[Component]]></category>
		<category><![CDATA[concept]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[DOTS]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[Entity Component System]]></category>
		<category><![CDATA[EntityQuery]]></category>
		<category><![CDATA[IJobChunk]]></category>
		<category><![CDATA[job]]></category>
		<category><![CDATA[Job 시스템]]></category>
		<category><![CDATA[Managed Components]]></category>
		<category><![CDATA[OnUpdate]]></category>
		<category><![CDATA[OnUpdate()]]></category>
		<category><![CDATA[Physics]]></category>
		<category><![CDATA[Physics Scene Basic]]></category>
		<category><![CDATA[Sample]]></category>
		<category><![CDATA[ScheduleParallel]]></category>
		<category><![CDATA[Shared Components]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[SystemAPI]]></category>
		<category><![CDATA[Tag components]]></category>
		<category><![CDATA[ToEntityArray()]]></category>
		<category><![CDATA[Unity Physics 101]]></category>
		<category><![CDATA[Unmanaged]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=39563</guid>

					<description><![CDATA[<p>EntityQuery 정리 ECS(Entity Component System)에서 특정 조건을 만족하는 Entity들을 효율적으로 검색하는 도구 Entity가 가진 컴포넌트 조합을 기준으로 검색하며, 이를 통해 메모리 캐시 효율을 극대화 1. EntityQuery의 기본 개념 &#8211; 특정 컴포넌트를 가진 Entity 찾기 2. EntityQuery의 주요 조건 조건 설명 WithAll&#60;T&#62;() 해당 컴포넌트를 모두 가진 Entity만 검색 WithAny&#60;T&#62;() 지정한 컴포넌트 중 하나라도 가진 Entity 검색 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/unity/ecs-entityquery-%ec%a0%95%eb%a6%ac/39563/">ECS &#8211; EntityQuery 정리</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-0a6626f3      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							EntityQuery						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#entityquery-정리" class="uagb-toc-link__trigger">EntityQuery 정리</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-entityquery의-기본-개념" class="uagb-toc-link__trigger">1. EntityQuery의 기본 개념</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#특정-컴포넌트를-가진-entity-찾기" class="uagb-toc-link__trigger">&#8211; 특정 컴포넌트를 가진 Entity 찾기</a></li></ul><li class="uagb-toc__list"><a href="#2-entityquery의-주요-조건" class="uagb-toc-link__trigger">2. EntityQuery의 주요 조건</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#특정-조건의-엔티티-찾기" class="uagb-toc-link__trigger">&#8211; 특정 조건의 엔티티 찾기</a></li></ul><li class="uagb-toc__list"><a href="#3-entityquery를-사용한-데이터-접근-방법" class="uagb-toc-link__trigger">3. EntityQuery를 사용한 데이터 접근 방법</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-systemapiquery-사용" class="uagb-toc-link__trigger">(1) SystemAPI.Query() 사용</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-toentityarray로-entity-리스트-가져오기" class="uagb-toc-link__trigger">(2) ToEntityArray()로 Entity 리스트 가져오기</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-tocomponentdataarray로-특정-컴포넌트-리스트-가져오기" class="uagb-toc-link__trigger">(3) ToComponentDataArray()로 특정 컴포넌트 리스트 가져오기</a></li></ul><li class="uagb-toc__list"><a href="#4-ijobchunk과-함께-사용하기" class="uagb-toc-link__trigger">4. IJobChunk과 함께 사용하기</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#q-componenttypehandle을-사용하여-nativearray를-생성하는-것과-미리-nativearray를-넘겨주는-것의-차이점" class="uagb-toc-link__trigger">(Q) ComponentTypeHandle을 사용하여 NativeArray를 생성하는 것과 미리 NativeArray를 넘겨주는 것의 차이점</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-componenttypehandlet을-사용하는-이유" class="uagb-toc-link__trigger">(1) ComponentTypeHandle을 사용하는 이유</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-componenttypehandlet과-nativearrayt-차이점-비교" class="uagb-toc-link__trigger">(2) ComponentTypeHandle과 NativeArray 차이점 비교</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-componenttypehandlet-방식" class="uagb-toc-link__trigger">(3) ComponentTypeHandle 방식</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-nativearray-job-방식" class="uagb-toc-link__trigger">(4) NativeArray  Job 방식</a></li></ul></li></ul><li class="uagb-toc__list"><a href="#5-entityquery의-추가-기능" class="uagb-toc-link__trigger">5. EntityQuery의 추가 기능</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-calculateentitycount로-개수-확인" class="uagb-toc-link__trigger">(1) CalculateEntityCount()로 개수 확인</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-setchangedversionfilter로-변경된-컴포넌트만-검색" class="uagb-toc-link__trigger">(2) SetChangedVersionFilter()로 변경된 컴포넌트만 검색</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-enabledisable로-쿼리-활성화비활성화" class="uagb-toc-link__trigger">(3) Enable/Disable로 쿼리 활성화/비활성화</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">EntityQuery 정리</h2>



<p>ECS(Entity Component System)에서 <strong>특정 조건을 만족하는 Entity들을 효율적으로 검색</strong>하는 도구</p>



<p>Entity가 가진 <strong>컴포넌트 조합을 기준</strong>으로 검색하며, 이를 통해 <strong>메모리 캐시 효율을 극대화</strong></p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1. <strong>EntityQuery의 기본 개념</strong></h3>



<ul class="wp-block-list">
<li><strong>Entity 필터링</strong>: 특정한 <strong>컴포넌트를 포함하거나 제외하는 기준</strong>을 정할 수 있다.</li>



<li><strong>Chunk 단위 검색</strong>: <code>EntityQuery</code>는 Entity 개별 조회가 아닌 Chunk 단위(Archetype Chunk)로 데이터를 가져옴</li>



<li><strong>즉시 검색 or <code>Job</code> 시스템과 병렬 처리</strong> 가능</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">&#8211; 특정 컴포넌트를 가진 Entity 찾기</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public partial struct FindEntitiesSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        // PositionComponent와 VelocityComponent를 가진 Entity 찾기
        EntityQuery query = SystemAPI.QueryBuilder()
            .WithAll&lt;PositionComponent, VelocityComponent>()
            .Build();

        foreach (var (position, velocity) in SystemAPI.Query&lt;RefRW&lt;PositionComponent>, RefRO&lt;VelocityComponent>>())
        {
            position.ValueRW.Value += velocity.ValueRO.Value * SystemAPI.Time.DeltaTime;
        }
    }
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<ul class="wp-block-list">
<li>WithAll() → 모든 엔티티 중 PositionComponent와 VelocityComponent를 가진 것만 검색</li>



<li>RefRW → 읽기/쓰기 가능</li>



<li>RefRO → 읽기 전용</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<h3 class="wp-block-heading">2. EntityQuery의 주요 조건</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>조건</th><th>설명</th></tr></thead><tbody><tr><td><code>WithAll&lt;T&gt;()</code></td><td>해당 <strong>컴포넌트를 모두 가진 Entity만 검색</strong></td></tr><tr><td><code>WithAny&lt;T&gt;()</code></td><td>지정한 <strong>컴포넌트 중 하나라도 가진 <strong>Entity</strong> 검색</strong></td></tr><tr><td><code>WithNone&lt;T&gt;()</code></td><td>특정한 <strong>컴포넌트를 가지지 않은 <strong>Entity</strong> 검색</strong></td></tr><tr><td><code>WithDisabled&lt;T&gt;()</code></td><td><strong>비활성화된 컴포넌트</strong>가 있는 Entity 검색</td></tr><tr><td><code>WithAbsent&lt;T&gt;()</code></td><td>특정한 <strong>컴포넌트가 없는 <strong>Entity</strong> 검색</strong></td></tr></tbody></table></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">&#8211; <strong>특정 조건의 엔티티 찾기</strong></h4>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">EntityQuery query = SystemAPI.QueryBuilder()
    .WithAll&lt;PositionComponent>()   // PositionComponent가 있어야 함
    .WithAny&lt;VelocityComponent, AccelerationComponent>()  // 둘 중 하나만 있으면 됨
    .WithNone&lt;DestroyedTag>()        // DestroyedTag가 있으면 제외
    .Build();
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<ul class="wp-block-list">
<li>WithAll() → 반드시 포함</li>



<li>WithAny() → 하나라도 포함</li>



<li>WithNone() → 포함하면 안 됨</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<h3 class="wp-block-heading">3. EntityQuery를 사용한 데이터 접근 방법</h3>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">(1) <strong>SystemAPI.Query() 사용</strong></h4>



<ul class="wp-block-list">
<li><code>EntityQuery</code>를 생성하고 <code>foreach</code> 루프에서 엔티티 데이터를 순회 가능</li>
</ul>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">foreach (var (position, velocity) in SystemAPI.Query&lt;RefRW&lt;PositionComponent>, RefRO&lt;VelocityComponent>>())
{
    position.ValueRW.Value += velocity.ValueRO.Value * SystemAPI.Time.DeltaTime;
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">(2) <strong>ToEntityArray()로 Entity 리스트 가져오기</strong></h4>



<ul class="wp-block-list">
<li><code>ToEntityArray()</code>를 사용하면 <strong>해당 <code>EntityQuery</code>에 해당하는 Entity 리스트를 가져옴</strong></li>
</ul>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var entities = query.ToEntityArray(Allocator.Temp);
foreach (var entity in entities)
{
    Debug.Log($"찾은 Entity: {entity}");
}
entities.Dispose();
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<ul class="wp-block-list">
<li><code>ToEntityArray(Allocator.Temp)</code> → <strong>NativeArray로 Entity를 가져옴</strong></li>



<li><code>Dispose()</code> 필수! <strong>메모리 해제 안 하면 메모리 누수 발생</strong></li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">(3) <strong>ToComponentDataArray()로 특정 컴포넌트 리스트 가져오기</strong></h4>



<ul class="wp-block-list">
<li>특정 컴포넌트의 값만 가져올 수도 있음.</li>
</ul>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var positions = query.ToComponentDataArray&lt;PositionComponent>(Allocator.Temp);
foreach (var position in positions)
{
    Debug.Log($"Position: {position.Value}");
}
positions.Dispose();
</pre>



<ul class="wp-block-list">
<li><strong>데이터만 필요한 경우 성능 최적화 가능</strong></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<h3 class="wp-block-heading">4. <strong>IJobChunk과 함께 사용하기</strong></h3>



<ul class="wp-block-list">
<li><strong><code>IJobChunk</code>를 사용하면 <code>EntityQuery</code>를 멀티스레드로 병렬 처리 가능</strong></li>



<li> <code>ArchetypeChunk</code>을 사용하여 <strong>Chunk 단위 데이터 접근</strong> 가능</li>
</ul>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[BurstCompile]
public struct MoveJob : IJobChunk
{
    public ComponentTypeHandle&lt;PositionComponent> PositionHandle;
    [ReadOnly] public ComponentTypeHandle&lt;VelocityComponent> VelocityHandle;
    public float DeltaTime;

    public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex)
    {
        var positions = chunk.GetNativeArray(PositionHandle);
        var velocities = chunk.GetNativeArray(VelocityHandle);

        for (int i = 0; i &lt; chunk.Count; i++)
        {
            positions[i] = new PositionComponent
            {
                Value = positions[i].Value + velocities[i].Value * DeltaTime
            };
        }
    }
}

public partial struct MoveSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        var query = SystemAPI.QueryBuilder()
            .WithAll&lt;PositionComponent, VelocityComponent>()
            .Build();

        var positionHandle = SystemAPI.GetComponentTypeHandle&lt;PositionComponent>();
        var velocityHandle = SystemAPI.GetComponentTypeHandle&lt;VelocityComponent>(isReadOnly: true);

        var job = new MoveJob
        {
            PositionHandle = positionHandle,
            VelocityHandle = velocityHandle,
            DeltaTime = SystemAPI.Time.DeltaTime
        };
        // query를 기반으로 Schedule()을 호출
        state.Dependency = job.Schedule(query, state.Dependency);
    }
}
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<ul class="wp-block-list">
<li><code>GetComponentTypeHandle&lt;T>()</code> 사용 → <strong>컴포넌트 데이터를 가져올 때 캐시 최적화</strong><br><code>GetComponentTypeHandle&lt;T>()</code>은 <strong>ECS(Worlds)의 청크 단위 데이터 접근을 최적화</strong>하기 위한 API</li>



<li><code>IJobChunk</code>으로 <strong>멀티스레드 병렬 처리 가능</strong></li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong><code>job.Schedule(query, state.Dependency)</code>의 실행 과정</strong></p>



<p><strong><code>query</code>를 사용하여 <code>IJobChunk</code>가 처리할 청크들을 찾음</strong></p>



<ul class="wp-block-list">
<li><code>query</code>는 <code>PositionComponent</code>와 <code>VelocityComponent</code>를 포함하는 엔티티들의 <strong>청크 목록</strong>을 가져옴.</li>



<li><code>IJobChunk</code>는 이 청크 목록을 기반으로 실행됨.</li>
</ul>



<p> <strong><code>job.Schedule()</code>을 호출하여 <code>MoveJob</code>을 실행</strong></p>



<ul class="wp-block-list">
<li><code>query</code>에서 반환된 청크를 <code>MoveJob</code>에서 병렬적으로 처리.</li>



<li>내부적으로 <strong>각 청크별로 <code>MoveJob.Execute()</code>가 실행됨</strong>.</li>
</ul>



<p><strong><code>state.Dependency</code>를 업데이트하여 잡 실행을 관리</strong></p>



<ul class="wp-block-list">
<li><code>state.Dependency</code>는 현재 시스템의 <strong>의존성 체인</strong>을 관리.</li>



<li>이전 프레임에서 실행된 다른 잡들과의 <strong>의존 관계를 고려하여 동기화</strong>함.</li>



<li>병렬 처리를 안전하게 실행하기 위해 필요함.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>매개변수</th><th>타입</th><th>설명</th></tr></thead><tbody><tr><td><code>chunk</code></td><td><code>ArchetypeChunk</code></td><td>현재 처리 중인 <strong>Chunk</strong> 객체. <br>해당 <strong>Chunk</strong> 내의 Entity 및 컴포넌트에 접근할 수 있음.</td></tr><tr><td><code>chunkIndex</code></td><td><code>int</code></td><td>현재 <strong><strong>Chunk</strong> 의 인덱스</strong> (0부터 시작). <br>병렬 작업을 할 때 <strong>Chunk</strong> 가 몇 번째인지 확인 가능.</td></tr><tr><td><code>firstEntityIndex</code></td><td><code>int</code></td><td>현재 <strong>Chunk</strong> 에서 <strong>첫 번째 Entity 의 전역 인덱스</strong>.<br>(전체 Entity  중 몇 번째인지 나타냄)</td></tr></tbody></table></figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">(Q) ComponentTypeHandle을 사용하여 NativeArray를 생성하는 것과 미리 NativeArray를 넘겨주는 것의 차이점</h4>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">(1) <code>ComponentTypeHandle&lt;T></code>을 사용하는 이유</h5>



<p><code>GetComponentTypeHandle&lt;T>()</code>을 사용하여 <code>IJobChunk</code> 내부에서 <code>NativeArray</code>를 가져오는 이유는 <strong>Chunk 단위 접근 최적화</strong> 때문이다.</p>



<ul class="wp-block-list">
<li><code>ComponentTypeHandle&lt;T></code>을 사용하면 <strong>각 Chunk에 맞는 <code>NativeArray</code>를 개별적으로 가져올 수 있음</strong></li>



<li>만약 <code>NativeArray</code>를 미리 만들어서 넘겨준다면, <strong>Chunk별로 나눠진 데이터가 하나의 배열에 병합되어야 하므로 메모리 효율이 떨어지고 캐시 활용도가 낮아짐</strong></li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">(2) <code>ComponentTypeHandle&lt;T></code>과 <code>NativeArray&lt;T></code> 차이점 비교</h5>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>방식</th><th>설명</th><th>장점</th><th>단점</th></tr></thead><tbody><tr><td><strong><code>ComponentTypeHandle&lt;T></code> 사용 </strong><br><strong>(<code>IJobChunk</code> 내부에서 <code>GetNativeArray()</code>)</strong></td><td><strong>각 청크에서 해당 컴포넌트의 데이터 배열을 가져옴</strong></td><td><strong>&#8211; 청크별로 최적화된 데이터 접근 가능</strong><br><strong>&#8211; 캐시 친화적이며 성능 최적화 가능</strong><br><strong>&#8211; Burst 및 멀티스레딩에 적합</strong></td><td><strong>&#8211; <code>IJobChunk</code>을 사용해야 함</strong></td></tr><tr><td><strong><code>NativeArray&lt;T&gt;</code>를 미리 만들어 Job에 넘김</strong></td><td><strong>전체 데이터를 하나의 <code>NativeArray</code>로 만들고 Job에서 사용</strong></td><td><strong>&#8211; 간단한 구조, 직관적</strong><br><strong>&#8211; 작은 데이터셋에서는 부담이 적음</strong></td><td><strong>&#8211; 전체 데이터를 하나의 배열로 만들기 때문에 캐시 미스 발생 가능성 증가</strong><br><strong>&#8211; Chunk별 최적화가 불가능하여 성능 저하 가능성 있음</strong></td></tr></tbody></table></figure>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">(3) <code>ComponentTypeHandle&lt;T></code> 방식</h5>



<p>1> <code>SystemAPI.GetComponentTypeHandle&lt;T>()</code> 실행</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var positionHandle = SystemAPI.GetComponentTypeHandle&lt;PositionComponent>();
var velocityHandle = SystemAPI.GetComponentTypeHandle&lt;VelocityComponent>(isReadOnly: true);
</pre>



<ul class="wp-block-list">
<li><code>ComponentTypeHandle&lt;T></code>을 가져옴</li>



<li>Chunk별로 데이터가 분산되어 있기 때문에 <code>NativeArray&lt;T></code>로 직접 변환하지 않음</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>2> <code>MoveJob.Schedule()</code> 실행</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var job = new MoveJob
{
    PositionHandle = positionHandle,
    VelocityHandle = velocityHandle,
    DeltaTime = SystemAPI.Time.DeltaTime
};

state.Dependency = job.Schedule(query, state.Dependency);
</pre>



<ul class="wp-block-list">
<li><code>MoveJob</code>을 스케줄할 때 <code>ComponentTypeHandle&lt;T></code>을 넘김</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>3> <code>MoveJob.Execute()</code> 내부에서 <code>GetNativeArray()</code> 실행</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">var positions = chunk.GetNativeArray(PositionHandle);
var velocities = chunk.GetNativeArray(VelocityHandle);
</pre>



<ul class="wp-block-list">
<li>각 Chunk에서 <code>NativeArray&lt;T></code>를 가져와서 사용함.</li>



<li>이렇게 하면 각 Chunk가 독립적으로 자신의 데이터에 접근할 수 있으며, 캐시 최적화 및 멀티스레딩 효율 증가</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">(4) <code>NativeArray</code>  Job 방식</h5>



<p>만약 <code>NativeArray&lt;T></code>를 미리 만들어서 Job에 넘기면, <strong>멀티스레딩 최적화가 어렵고 성능 저하 가능성 증가</strong>.</p>



<p><strong>전체 데이터를 하나의 <code>NativeArray</code>로 병합</strong>하는 과정이 필요, <strong>Chunk별 데이터 레이아웃이 깨질 수 있음</strong> → CPU 캐시 효율 저하.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">NativeArray&lt;PositionComponent> positions = new NativeArray&lt;PositionComponent>(entityCount, Allocator.TempJob);
NativeArray&lt;VelocityComponent> velocities = new NativeArray&lt;VelocityComponent>(entityCount, Allocator.TempJob);

// Job에 NativeArray를 직접 넘김
var job = new MoveJob
{
    Positions = positions,
    Velocities = velocities,
    DeltaTime = SystemAPI.Time.DeltaTime
};

state.Dependency = job.Schedule(state.Dependency);
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>이 경우, <strong><code>positions</code>와 <code>velocities</code>를 Chunk별로 나누지 않고 전체 데이터를 하나의 배열로 만듦.</strong></p>



<p>Chunk별 데이터가 섞일 가능성이 높아져 캐시 미스(Cache Miss)가 증가할 수 있음</p>



<hr class="wp-block-separator has-alpha-channel-opacity is-style-wide" style="margin-top:var(--wp--preset--spacing--80);margin-bottom:var(--wp--preset--spacing--80)"/>



<h3 class="wp-block-heading">5. <strong>EntityQuery의 추가 기능</strong></h3>



<h4 class="wp-block-heading">(1) CalculateEntityCount()로 개수 확인</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">int entityCount = query.CalculateEntityCount();
Debug.Log($"현재 쿼리 결과 개수: {entityCount}");
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">(2) SetChangedVersionFilter()로 변경된 컴포넌트만 검색</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">query.SetChangedVersionFilter&lt;PositionComponent>();
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>최근 변경된 <code>PositionComponent</code>를 가진 엔티티만 검색</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">(3) Enable/Disable로 쿼리 활성화/비활성화</h4>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">query.SetEnabled(false);  // 쿼리 비활성화
query.SetEnabled(true);   // 다시 활성화
</pre>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>쿼리를 특정 조건에서만 사용하고 싶을 때 활용</p>
<p>The post <a href="https://lycos7560.com/unity/ecs-entityquery-%ec%a0%95%eb%a6%ac/39563/">ECS &#8211; EntityQuery 정리</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/unity/ecs-entityquery-%ec%a0%95%eb%a6%ac/39563/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ECS &#8211; MouseHoverAuthoring</title>
		<link>https://lycos7560.com/unity/ecs-mousehoverauthoring/39539/</link>
					<comments>https://lycos7560.com/unity/ecs-mousehoverauthoring/39539/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Wed, 05 Feb 2025 10:02:15 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[AsNativeArray]]></category>
		<category><![CDATA[AsNativeArray()]]></category>
		<category><![CDATA[BufferTypeHandle]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Chunk components]]></category>
		<category><![CDATA[Cleanup components]]></category>
		<category><![CDATA[Component]]></category>
		<category><![CDATA[concept]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[DOTS]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[Entity Component System]]></category>
		<category><![CDATA[IJobChunk]]></category>
		<category><![CDATA[job]]></category>
		<category><![CDATA[Job 시스템]]></category>
		<category><![CDATA[Managed Components]]></category>
		<category><![CDATA[OnUpdate]]></category>
		<category><![CDATA[OnUpdate()]]></category>
		<category><![CDATA[Physics]]></category>
		<category><![CDATA[Physics Scene Basic]]></category>
		<category><![CDATA[Sample]]></category>
		<category><![CDATA[ScheduleParallel]]></category>
		<category><![CDATA[Shared Components]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Tag components]]></category>
		<category><![CDATA[Unity Physics 101]]></category>
		<category><![CDATA[Unmanaged]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=39539</guid>

					<description><![CDATA[<p>1. MouseHover (IComponentData) 2. MouseHoverBaker (Baker) 3. MouseHoverSystem (SystemBase) 1) InitializationSystemGroup 초기화 단계에서 실행됨 2) WorldRaycastJob (IJob) 마우스 위치에서 Raycast를 실행하는 IJob 3) OnUpdate() 1> CollisionWorld 가져오기 2> 마우스 위치에서 레이(Ray) 생성 3> RaycastInput 설정 RaycastInput은 Unity Physics에서 사용되는 구조체로, 물리 엔진이 사용할 레이캐스트 정보를 포함 4> MouseHover 컴포넌트 가져오기 5> 레이캐스트 실행 NativeReference&#60;RaycastHit>을 사용하여 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/unity/ecs-mousehoverauthoring/39539/">ECS &#8211; MouseHoverAuthoring</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-767988eb      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							ECS &#8211; MouseHoverAuthoring						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-mousehover-icomponentdata" class="uagb-toc-link__trigger">1. MouseHover (IComponentData)</a><li class="uagb-toc__list"><a href="#2-mousehoverbaker-baker" class="uagb-toc-link__trigger">2. MouseHoverBaker (Baker)</a><li class="uagb-toc__list"><a href="#3-mousehoversystem-systembase" class="uagb-toc-link__trigger">3. MouseHoverSystem (SystemBase)</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-initializationsystemgroup" class="uagb-toc-link__trigger">1) InitializationSystemGroup</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-worldraycastjob-ijob" class="uagb-toc-link__trigger">2) WorldRaycastJob (IJob)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-onupdate" class="uagb-toc-link__trigger">3) OnUpdate()</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-collisionworld-가져오기" class="uagb-toc-link__trigger">1&gt; CollisionWorld 가져오기</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-마우스-위치에서-레이ray-생성" class="uagb-toc-link__trigger">2&gt; 마우스 위치에서 레이(Ray) 생성</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-raycastinput-설정" class="uagb-toc-link__trigger">3&gt; RaycastInput 설정</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#4-mousehover-컴포넌트-가져오기" class="uagb-toc-link__trigger">4&gt; MouseHover 컴포넌트 가져오기</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#5-레이캐스트-실행" class="uagb-toc-link__trigger">5&gt; 레이캐스트 실행</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#5-물리-entity에서-그래픽-entity-찾기" class="uagb-toc-link__trigger">5&gt; 물리 Entity에서 그래픽 Entity 찾기</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#6-동일한-엔터티를-계속-가리키면-무시" class="uagb-toc-link__trigger">6&gt; 동일한 엔터티를 계속 가리키면 무시</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#7-이전-및-현재-마우스-오버-entity-업데이트" class="uagb-toc-link__trigger">7&gt; 이전 및 현재 마우스 오버 Entity 업데이트</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#8-이전-entity의-원래-상태-복구" class="uagb-toc-link__trigger">8&gt; 이전 Entity의 원래 상태 복구</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#9-새로운-entity의-마우스-hover-효과-적용" class="uagb-toc-link__trigger">9&gt; 새로운 Entity의 마우스 Hover 효과 적용</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#10-마우스-hover-머티리얼-적용" class="uagb-toc-link__trigger">10&gt; 마우스 Hover 머티리얼 적용</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-video"><video height="1228" style="aspect-ratio: 1952 / 1228;" width="1952" controls src="https://lycos7560.com/wp-content/uploads/2025/02/녹화_2025_02_05_19_00_37_49.mp4"></video><figcaption class="wp-element-caption">Physics Scene Basic &#8211; Query</figcaption></figure>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics.GraphicsIntegration;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;
using static Unity.Physics.Extensions.PhysicsSamplesExtensions;

namespace Unity.Physics.Extensions
{
    public class MouseHover : IComponentData
    {
        public bool IgnoreTriggers;
        public bool IgnoreStatic;
        public Entity PreviousEntity;
        public Entity CurrentEntity;
        public Entity HoverEntity;
        public MaterialMeshInfo OriginalMeshInfo;
        public RenderMeshArray OriginalRenderMeshes;
    }

    [DisallowMultipleComponent]
    public class MouseHoverAuthoring : MonoBehaviour
    {
        public GameObject HoverPrefab;
        public bool IgnoreTriggers = true;
        public bool IgnoreStatic = true;

        // Note: override OnEnable to be able to disable the component in the editor
        protected void OnEnable() {}
    }

    class MouseHoverBaker : Baker&lt;MouseHoverAuthoring>
    {
        public override void Bake(MouseHoverAuthoring authoring)
        {
            var entity = GetEntity(TransformUsageFlags.None);
            AddComponentObject(entity, new MouseHover()
            {
                PreviousEntity = Entity.Null,
                CurrentEntity = Entity.Null,
                IgnoreTriggers = authoring.IgnoreTriggers,
                IgnoreStatic = authoring.IgnoreStatic,
                HoverEntity = GetEntity(authoring.HoverPrefab, TransformUsageFlags.Dynamic),
            });
        }
    }

    // Applies any mouse spring as a change in velocity on the entity's motion component
    // Limitations: works only if the physics objects in the scene come from the same subscene as MouseHoverAuthoring
    // Will be fixable if there is a Unity.Rendering API that lets you get the UnityEngine.Mesh that an entity is using for renderin.

    [UpdateInGroup(typeof(InitializationSystemGroup))]
    public partial class MouseHoverSystem : SystemBase
    {
        [BurstCompile]
        public struct WorldRaycastJob : IJob
        {
            public RaycastInput RayInput;
            [ReadOnly] public CollisionWorld CollisionWorld;
            [ReadOnly] public bool IgnoreTriggers;
            [ReadOnly] public bool IgnoreStatic;

            public NativeReference&lt;RaycastHit> RaycastHitRef;

            public void Execute()
            {
                var mousePickCollector = new MousePickCollector(CollisionWorld.NumDynamicBodies)
                {
                    IgnoreTriggers = IgnoreTriggers,
                    IgnoreStatic = IgnoreStatic
                };

                if (CollisionWorld.CastRay(RayInput, ref mousePickCollector))
                {
                    RaycastHitRef.Value = mousePickCollector.Hit;
                }
            }
        }

        protected override void OnCreate()
        {
            base.OnCreate();
            RequireForUpdate&lt;MouseHover>();
        }

        // Find the Entity holding the Graphical representation of a Physics Shape.
        // It may be that the Physics and Graphics representation are on the same Entity.
        public Entity FindGraphicsEntityFromPhysics(Entity bodyEntity) => FindGraphicsEntityFromPhysics(bodyEntity, ColliderKey.Empty);
        public Entity FindGraphicsEntityFromPhysics(Entity bodyEntity, ColliderKey leafColliderKey)
        {
            if (bodyEntity.Equals(Entity.Null))
            {
                // No Physics so no Graphics
                return Entity.Null;
            }

            // Set the Graphics Entity to the supplied Physics Entity
            var renderEntity = bodyEntity;

            // Check if we have hit a leaf node
            if (!leafColliderKey.Equals(ColliderKey.Empty))
            {
                // Get the Physics Collider
                var rootCollider = EntityManager.GetComponentData&lt;PhysicsCollider>(bodyEntity).Value;

                // If we hit a CompoundCollider we need to find the original Entity associated
                // the actual leaf Collider that was hit.
                if (rootCollider.Value.Type == ColliderType.Compound)
                {
                    #region Find a Leaf Entity and ColliderKey
                    var leafEntity = Entity.Null;
                    unsafe
                    {
                        var rootColliderPtr = rootCollider.AsPtr();

                        // Get the leaf Collider and check if we hit was a PolygonCollider (i.e. a Triangle or a Quad)
                        rootColliderPtr->GetLeaf(leafColliderKey, out var childCollider);
                        leafEntity = childCollider.Entity;

                        // PolygonColliders are likely to not have an original Entity associated with them
                        // So if we have a Polygon and it has no Entity then we really need to check for
                        // the higher level Mesh or Terrain Collider instead.
                        var childColliderType = childCollider.Collider->Type;
                        var childColliderIsPolygon = childColliderType == ColliderType.Triangle || childColliderType == ColliderType.Quad;
                        if (childColliderIsPolygon &amp;&amp; childCollider.Entity.Equals(Entity.Null))
                        {
                            // Get the ColliderKey of the Polygon's parent
                            if (TryGetParentColliderKey(rootColliderPtr, leafColliderKey, out leafColliderKey))
                            {
                                // Get the Mesh or Terrain Collider of the Polygon
                                TryGetChildInHierarchy(rootColliderPtr, leafColliderKey, out childCollider);
                                leafEntity = childCollider.Entity;
                            }
                        }
                    }
                    #endregion

                    // The Entities recorded in the leaves of a CompoundCollider may have been correct
                    // at the time of conversion. However, if the Collider blob is shared, or came up
                    // through a sub scene, we cannot assume that the baked Entities in the
                    // CompoundCollider are still valid.

                    // On conversion Entities using a CompoundCollider have an extra dynamic buffer added
                    // which holds a list of Entity/ColliderKey pairs. This buffer should be patched up
                    // automatically and be valid with each instance, at least until you start messing
                    // with the Entity hierarchy yourself e.g. by deleting Entities.

                    #region Check the Leaf Entity is valid
                    // If the leafEntity was never assigned in the first place
                    // there is no point in looking up any Buffers.
                    if (!leafEntity.Equals(Entity.Null))
                    {
                        // Check for an Key/Entity pair buffer first.
                        // This should exist if the Physics conversion pipeline was invoked.
                        var colliderKeyEntityPairBuffers = GetBufferLookup&lt;PhysicsColliderKeyEntityPair>(true);
                        if (colliderKeyEntityPairBuffers.HasBuffer(bodyEntity))
                        {
                            var colliderKeyEntityBuffer = colliderKeyEntityPairBuffers[bodyEntity];
                            for (int i = 0; i &lt; colliderKeyEntityBuffer.Length; i++)
                            {
                                var bufferColliderKey = colliderKeyEntityBuffer[i].Key;
                                if (leafColliderKey.Equals(bufferColliderKey))
                                {
                                    renderEntity = colliderKeyEntityBuffer[i].Entity;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            // We haven't found a Key/Entity pair buffer so the compound collider
                            // may have been created in code.

                            // We'll assume the Entity in the CompoundCollider is valid
                            renderEntity = leafEntity;

                            // If this CompoundCollider was instanced from a prefab then the entities
                            // in the compound children would actually reference the original prefab hierarchy.
                            var rootEntityFromLeaf = leafEntity;
                            while (SystemAPI.HasComponent&lt;Parent>(rootEntityFromLeaf))
                            {
                                rootEntityFromLeaf = SystemAPI.GetComponent&lt;Parent>(rootEntityFromLeaf).Value;
                            }

                            // If the root Entity found from the leaf does not match the body Entity
                            // then we have hit an instance using the same CompoundCollider.
                            // This means we can try and remap the leaf Entity to the new hierarchy.
                            if (!rootEntityFromLeaf.Equals(bodyEntity))
                            {
                                // This assumes there is a LinkedEntityGroup Buffer on original and instance Entity.
                                // No doubt there is a more optimal way of doing this remap with more specific
                                // knowledge of the final application.
                                var linkedEntityGroupBuffers = GetBufferLookup&lt;LinkedEntityGroup>(true);

                                // Only remap if the buffers exist, have been created and are of equal length.
                                bool hasBufferRootEntity = linkedEntityGroupBuffers.HasBuffer(rootEntityFromLeaf);
                                bool hasBufferBodyEntity = linkedEntityGroupBuffers.HasBuffer(bodyEntity);
                                if (hasBufferRootEntity &amp;&amp; hasBufferBodyEntity)
                                {
                                    var prefabEntityGroupBuffer = linkedEntityGroupBuffers[rootEntityFromLeaf];
                                    var instanceEntityGroupBuffer = linkedEntityGroupBuffers[bodyEntity];

                                    if (prefabEntityGroupBuffer.IsCreated &amp;&amp; instanceEntityGroupBuffer.IsCreated
                                        &amp;&amp; (prefabEntityGroupBuffer.Length == instanceEntityGroupBuffer.Length))
                                    {
                                        var prefabEntityGroup = prefabEntityGroupBuffer.AsNativeArray();
                                        var instanceEntityGroup = instanceEntityGroupBuffer.AsNativeArray();

                                        for (int i = 0; i &lt; prefabEntityGroup.Length; i++)
                                        {
                                            // If we've found the renderEntity index in the prefab hierarchy,
                                            // set the renderEntity to the equivalent Entity in the instance
                                            if (prefabEntityGroup[i].Value.Equals(renderEntity))
                                            {
                                                renderEntity = instanceEntityGroup[i].Value;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    #endregion
                }
            }

            // Finally check to see if we have a graphics redirection on the shape Entity.
            if (SystemAPI.HasComponent&lt;PhysicsRenderEntity>(renderEntity))
            {
                renderEntity = SystemAPI.GetComponent&lt;PhysicsRenderEntity>(renderEntity).Entity;
            }

            // If no render info is found on the located render entity, we try to find any child which has render info
            if (renderEntity != Entity.Null &amp;&amp; !EntityManager.HasComponent&lt;MaterialMeshInfo>(renderEntity))
            {
                // No render information on this entity. Try to find the actual render entity in the hierarchy.
                if (EntityManager.HasBuffer&lt;Child>(renderEntity))
                {
                    var children = EntityManager.GetBuffer&lt;Child>(renderEntity);
                    foreach (var childElement in children)
                    {
                        // find the first child with render info
                        if (EntityManager.HasComponent&lt;MaterialMeshInfo>(childElement.Value))
                        {
                            renderEntity = childElement.Value;
                        }
                    }
                }
            }

            return renderEntity;
        }

        protected override void OnUpdate()
        {
            var collisionWorld = SystemAPI.GetSingleton&lt;PhysicsWorldSingleton>().CollisionWorld;
            Vector2 mousePosition = Input.mousePosition;
            UnityEngine.Ray unityRay = Camera.main.ScreenPointToRay(mousePosition);
            var rayInput = new RaycastInput
            {
                Start = unityRay.origin,
                End = unityRay.origin + unityRay.direction * MousePickSystem.k_MaxDistance,
                Filter = CollisionFilter.Default,
            };

            var mouseHover = SystemAPI.ManagedAPI.GetSingleton&lt;MouseHover>();

            RaycastHit hit;
            using (var raycastHitRef = new NativeReference&lt;RaycastHit>(Allocator.TempJob))
            {
                var rcj = new WorldRaycastJob()
                {
                    CollisionWorld = collisionWorld,
                    RayInput = rayInput,
                    IgnoreTriggers = mouseHover.IgnoreTriggers,
                    IgnoreStatic = mouseHover.IgnoreStatic,
                    RaycastHitRef = raycastHitRef
                };
                rcj.Run();
                hit = raycastHitRef.Value;
            }

            var graphicsEntity = FindGraphicsEntityFromPhysics(hit.Entity, hit.ColliderKey);

            // If still hovering over the same entity then do nothing.
            if (mouseHover.CurrentEntity.Equals(graphicsEntity)) return;

            mouseHover.PreviousEntity = mouseHover.CurrentEntity;
            mouseHover.CurrentEntity = graphicsEntity;

            bool hasPreviousEntity = !mouseHover.PreviousEntity.Equals(Entity.Null);
            bool hasCurrentEntity = !mouseHover.CurrentEntity.Equals(Entity.Null);

            if (hasPreviousEntity &amp;&amp; EntityManager.HasComponent&lt;MaterialMeshInfo>(mouseHover.PreviousEntity))
            {
                // restore render info to original in the last entity we were hovering over
                EntityManager.SetComponentData(mouseHover.PreviousEntity, mouseHover.OriginalMeshInfo);
                EntityManager.SetSharedComponentManaged(mouseHover.PreviousEntity, mouseHover.OriginalRenderMeshes);
            }

            if (hasCurrentEntity &amp;&amp; EntityManager.HasComponent&lt;MaterialMeshInfo>(mouseHover.CurrentEntity) &amp;&amp; EntityManager.HasComponent&lt;RenderMeshArray>(mouseHover.CurrentEntity))
            {
                mouseHover.PreviousEntity = mouseHover.CurrentEntity;
                mouseHover.CurrentEntity = graphicsEntity;
                mouseHover.OriginalMeshInfo = EntityManager.GetComponentData&lt;MaterialMeshInfo>(mouseHover.CurrentEntity);
                mouseHover.OriginalRenderMeshes = EntityManager.GetSharedComponentManaged&lt;RenderMeshArray>(mouseHover.CurrentEntity);

                // get render info from the hover entity
                var hoverMeshInfo = EntityManager.GetComponentData&lt;MaterialMeshInfo>(mouseHover.HoverEntity);
                var hoverRenderMeshes = EntityManager.GetSharedComponentManaged&lt;RenderMeshArray>(mouseHover.HoverEntity);

                // create new render info for the current entity that we hover over:

                // use the materials from the hover entity, but the meshes from the current entity
                var newRenderMeshes = new RenderMeshArray(hoverRenderMeshes.MaterialReferences, mouseHover.OriginalRenderMeshes.MeshReferences);

                // use the material id from the hover entity, but the mesh id from the current entity
                var newMeshInfo = MaterialMeshInfo.FromRenderMeshArrayIndices(hoverMeshInfo.Material, mouseHover.OriginalMeshInfo.Mesh);

                // apply the new render info to the current entity
                EntityManager.SetComponentData(mouseHover.CurrentEntity, newMeshInfo);
                EntityManager.SetSharedComponentManaged(mouseHover.CurrentEntity, newRenderMeshes);
            }
        }
    }
}
</pre>



<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1. MouseHover (IComponentData)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">
// 관리되는 객체이므로 Class를 사용 
public class MouseHover : IComponentData
{
    public bool IgnoreTriggers;
    public bool IgnoreStatic;
    public Entity PreviousEntity; // 이전 프레임에서 Hover 상태의 Entity
    public Entity CurrentEntity; // 현재 프레임에서 Hover 상태의 Entity
    public Entity HoverEntity; // Hover 상태일 때 적용할 메쉬/머터리얼이 있는 Entity
    public MaterialMeshInfo OriginalMeshInfo; // 원래의 렌더링 정보를 저장
    public RenderMeshArray OriginalRenderMeshes; //   "
}
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2. MouseHoverBaker (Baker)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">
[DisallowMultipleComponent]
public class MouseHoverAuthoring : MonoBehaviour
{
    public GameObject HoverPrefab;
    public bool IgnoreTriggers = true;
    public bool IgnoreStatic = true;

    // 참고: 편집기에서 구성 요소를 비활성화하려면 OnEnable을 재정의합니다
    protected void OnEnable() { }
}

// MouseHover 컴포넌트를 Entity에 추가
class MouseHoverBaker : Baker&lt;MouseHoverAuthoring>
{
    public override void Bake(MouseHoverAuthoring authoring)
    {
        var entity = GetEntity(TransformUsageFlags.None);

        AddComponentObject(entity, new MouseHover()
        {
            PreviousEntity = Entity.Null,
            CurrentEntity = Entity.Null,
            IgnoreTriggers = authoring.IgnoreTriggers,
            IgnoreStatic = authoring.IgnoreStatic,
            HoverEntity = GetEntity(authoring.HoverPrefab, TransformUsageFlags.Dynamic), // HoverPrefab을 HoverEntity로 변환하여 저장
        });
    }
}
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">3. MouseHoverSystem (SystemBase)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">    // Applies any mouse spring as a change in velocity on the entity's motion component
    // Limitations: works only if the physics objects in the scene come from the same subscene as MouseHoverAuthoring
    // Will be fixable if there is a Unity.Rendering API that lets you get the UnityEngine.Mesh that an entity is using for renderin.
    
    [UpdateInGroup(typeof(InitializationSystemGroup))]
    public partial class MouseHoverSystem : SystemBase
    {
        [BurstCompile]
        public struct WorldRaycastJob : IJob
        {
            public RaycastInput RayInput;
            [ReadOnly] public CollisionWorld CollisionWorld;
            [ReadOnly] public bool IgnoreTriggers;
            [ReadOnly] public bool IgnoreStatic;

            public NativeReference&lt;RaycastHit> RaycastHitRef;

            public void Execute()
            {
                var mousePickCollector = new MousePickCollector(CollisionWorld.NumDynamicBodies)
                {
                    IgnoreTriggers = IgnoreTriggers,
                    IgnoreStatic = IgnoreStatic
                };

                if (CollisionWorld.CastRay(RayInput, ref mousePickCollector))
                {
                    RaycastHitRef.Value = mousePickCollector.Hit;
                }
            }
        }

        protected override void OnCreate()
        {
            base.OnCreate();
            RequireForUpdate&lt;MouseHover>();
        }

        // Find the Entity holding the Graphical representation of a Physics Shape.
        // It may be that the Physics and Graphics representation are on the same Entity.
        public Entity FindGraphicsEntityFromPhysics(Entity bodyEntity) => FindGraphicsEntityFromPhysics(bodyEntity, ColliderKey.Empty);
        public Entity FindGraphicsEntityFromPhysics(Entity bodyEntity, ColliderKey leafColliderKey)
        {
            if (bodyEntity.Equals(Entity.Null))
            {
                // No Physics so no Graphics
                return Entity.Null;
            }

            // Set the Graphics Entity to the supplied Physics Entity
            var renderEntity = bodyEntity;

            // Check if we have hit a leaf node
            if (!leafColliderKey.Equals(ColliderKey.Empty))
            {
                // Get the Physics Collider
                var rootCollider = EntityManager.GetComponentData&lt;PhysicsCollider>(bodyEntity).Value;

                // If we hit a CompoundCollider we need to find the original Entity associated
                // the actual leaf Collider that was hit.
                if (rootCollider.Value.Type == ColliderType.Compound)
                {
                    #region Find a Leaf Entity and ColliderKey
                    var leafEntity = Entity.Null;
                    unsafe
                    {
                        var rootColliderPtr = rootCollider.AsPtr();

                        // Get the leaf Collider and check if we hit was a PolygonCollider (i.e. a Triangle or a Quad)
                        rootColliderPtr->GetLeaf(leafColliderKey, out var childCollider);
                        leafEntity = childCollider.Entity;

                        // PolygonColliders are likely to not have an original Entity associated with them
                        // So if we have a Polygon and it has no Entity then we really need to check for
                        // the higher level Mesh or Terrain Collider instead.
                        var childColliderType = childCollider.Collider->Type;
                        var childColliderIsPolygon = childColliderType == ColliderType.Triangle || childColliderType == ColliderType.Quad;
                        if (childColliderIsPolygon &amp;&amp; childCollider.Entity.Equals(Entity.Null))
                        {
                            // Get the ColliderKey of the Polygon's parent
                            if (TryGetParentColliderKey(rootColliderPtr, leafColliderKey, out leafColliderKey))
                            {
                                // Get the Mesh or Terrain Collider of the Polygon
                                TryGetChildInHierarchy(rootColliderPtr, leafColliderKey, out childCollider);
                                leafEntity = childCollider.Entity;
                            }
                        }
                    }
                    #endregion

                    // The Entities recorded in the leaves of a CompoundCollider may have been correct
                    // at the time of conversion. However, if the Collider blob is shared, or came up
                    // through a sub scene, we cannot assume that the baked Entities in the
                    // CompoundCollider are still valid.

                    // On conversion Entities using a CompoundCollider have an extra dynamic buffer added
                    // which holds a list of Entity/ColliderKey pairs. This buffer should be patched up
                    // automatically and be valid with each instance, at least until you start messing
                    // with the Entity hierarchy yourself e.g. by deleting Entities.

                    #region Check the Leaf Entity is valid
                    // If the leafEntity was never assigned in the first place
                    // there is no point in looking up any Buffers.
                    if (!leafEntity.Equals(Entity.Null))
                    {
                        // Check for an Key/Entity pair buffer first.
                        // This should exist if the Physics conversion pipeline was invoked.
                        var colliderKeyEntityPairBuffers = GetBufferLookup&lt;PhysicsColliderKeyEntityPair>(true);
                        if (colliderKeyEntityPairBuffers.HasBuffer(bodyEntity))
                        {
                            var colliderKeyEntityBuffer = colliderKeyEntityPairBuffers[bodyEntity];
                            for (int i = 0; i &lt; colliderKeyEntityBuffer.Length; i++)
                            {
                                var bufferColliderKey = colliderKeyEntityBuffer[i].Key;
                                if (leafColliderKey.Equals(bufferColliderKey))
                                {
                                    renderEntity = colliderKeyEntityBuffer[i].Entity;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            // We haven't found a Key/Entity pair buffer so the compound collider
                            // may have been created in code.

                            // We'll assume the Entity in the CompoundCollider is valid
                            renderEntity = leafEntity;

                            // If this CompoundCollider was instanced from a prefab then the entities
                            // in the compound children would actually reference the original prefab hierarchy.
                            var rootEntityFromLeaf = leafEntity;
                            while (SystemAPI.HasComponent&lt;Parent>(rootEntityFromLeaf))
                            {
                                rootEntityFromLeaf = SystemAPI.GetComponent&lt;Parent>(rootEntityFromLeaf).Value;
                            }

                            // If the root Entity found from the leaf does not match the body Entity
                            // then we have hit an instance using the same CompoundCollider.
                            // This means we can try and remap the leaf Entity to the new hierarchy.
                            if (!rootEntityFromLeaf.Equals(bodyEntity))
                            {
                                // This assumes there is a LinkedEntityGroup Buffer on original and instance Entity.
                                // No doubt there is a more optimal way of doing this remap with more specific
                                // knowledge of the final application.
                                var linkedEntityGroupBuffers = GetBufferLookup&lt;LinkedEntityGroup>(true);

                                // Only remap if the buffers exist, have been created and are of equal length.
                                bool hasBufferRootEntity = linkedEntityGroupBuffers.HasBuffer(rootEntityFromLeaf);
                                bool hasBufferBodyEntity = linkedEntityGroupBuffers.HasBuffer(bodyEntity);
                                if (hasBufferRootEntity &amp;&amp; hasBufferBodyEntity)
                                {
                                    var prefabEntityGroupBuffer = linkedEntityGroupBuffers[rootEntityFromLeaf];
                                    var instanceEntityGroupBuffer = linkedEntityGroupBuffers[bodyEntity];

                                    if (prefabEntityGroupBuffer.IsCreated &amp;&amp; instanceEntityGroupBuffer.IsCreated
                                        &amp;&amp; (prefabEntityGroupBuffer.Length == instanceEntityGroupBuffer.Length))
                                    {
                                        var prefabEntityGroup = prefabEntityGroupBuffer.AsNativeArray();
                                        var instanceEntityGroup = instanceEntityGroupBuffer.AsNativeArray();

                                        for (int i = 0; i &lt; prefabEntityGroup.Length; i++)
                                        {
                                            // If we've found the renderEntity index in the prefab hierarchy,
                                            // set the renderEntity to the equivalent Entity in the instance
                                            if (prefabEntityGroup[i].Value.Equals(renderEntity))
                                            {
                                                renderEntity = instanceEntityGroup[i].Value;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    #endregion
                }
            }

            // Finally check to see if we have a graphics redirection on the shape Entity.
            if (SystemAPI.HasComponent&lt;PhysicsRenderEntity>(renderEntity))
            {
                renderEntity = SystemAPI.GetComponent&lt;PhysicsRenderEntity>(renderEntity).Entity;
            }

            // If no render info is found on the located render entity, we try to find any child which has render info
            if (renderEntity != Entity.Null &amp;&amp; !EntityManager.HasComponent&lt;MaterialMeshInfo>(renderEntity))
            {
                // No render information on this entity. Try to find the actual render entity in the hierarchy.
                if (EntityManager.HasBuffer&lt;Child>(renderEntity))
                {
                    var children = EntityManager.GetBuffer&lt;Child>(renderEntity);
                    foreach (var childElement in children)
                    {
                        // find the first child with render info
                        if (EntityManager.HasComponent&lt;MaterialMeshInfo>(childElement.Value))
                        {
                            renderEntity = childElement.Value;
                        }
                    }
                }
            }

            return renderEntity;
        }

        protected override void OnUpdate()
        {
            var collisionWorld = SystemAPI.GetSingleton&lt;PhysicsWorldSingleton>().CollisionWorld;
            Vector2 mousePosition = Input.mousePosition;
            UnityEngine.Ray unityRay = Camera.main.ScreenPointToRay(mousePosition);
            var rayInput = new RaycastInput
            {
                Start = unityRay.origin,
                End = unityRay.origin + unityRay.direction * MousePickSystem.k_MaxDistance,
                Filter = CollisionFilter.Default,
            };

            var mouseHover = SystemAPI.ManagedAPI.GetSingleton&lt;MouseHover>();

            RaycastHit hit;
            using (var raycastHitRef = new NativeReference&lt;RaycastHit>(Allocator.TempJob))
            {
                var rcj = new WorldRaycastJob()
                {
                    CollisionWorld = collisionWorld,
                    RayInput = rayInput,
                    IgnoreTriggers = mouseHover.IgnoreTriggers,
                    IgnoreStatic = mouseHover.IgnoreStatic,
                    RaycastHitRef = raycastHitRef
                };
                rcj.Run();
                hit = raycastHitRef.Value;
            }

            var graphicsEntity = FindGraphicsEntityFromPhysics(hit.Entity, hit.ColliderKey);

            // If still hovering over the same entity then do nothing.
            if (mouseHover.CurrentEntity.Equals(graphicsEntity)) return;

            mouseHover.PreviousEntity = mouseHover.CurrentEntity;
            mouseHover.CurrentEntity = graphicsEntity;

            bool hasPreviousEntity = !mouseHover.PreviousEntity.Equals(Entity.Null);
            bool hasCurrentEntity = !mouseHover.CurrentEntity.Equals(Entity.Null);

            if (hasPreviousEntity &amp;&amp; EntityManager.HasComponent&lt;MaterialMeshInfo>(mouseHover.PreviousEntity))
            {
                // restore render info to original in the last entity we were hovering over
                EntityManager.SetComponentData(mouseHover.PreviousEntity, mouseHover.OriginalMeshInfo);
                EntityManager.SetSharedComponentManaged(mouseHover.PreviousEntity, mouseHover.OriginalRenderMeshes);
            }

            if (hasCurrentEntity &amp;&amp; EntityManager.HasComponent&lt;MaterialMeshInfo>(mouseHover.CurrentEntity) &amp;&amp; EntityManager.HasComponent&lt;RenderMeshArray>(mouseHover.CurrentEntity))
            {
                mouseHover.PreviousEntity = mouseHover.CurrentEntity;
                mouseHover.CurrentEntity = graphicsEntity;
                mouseHover.OriginalMeshInfo = EntityManager.GetComponentData&lt;MaterialMeshInfo>(mouseHover.CurrentEntity);
                mouseHover.OriginalRenderMeshes = EntityManager.GetSharedComponentManaged&lt;RenderMeshArray>(mouseHover.CurrentEntity);

                // get render info from the hover entity
                var hoverMeshInfo = EntityManager.GetComponentData&lt;MaterialMeshInfo>(mouseHover.HoverEntity);
                var hoverRenderMeshes = EntityManager.GetSharedComponentManaged&lt;RenderMeshArray>(mouseHover.HoverEntity);

                // create new render info for the current entity that we hover over:

                // use the materials from the hover entity, but the meshes from the current entity
                var newRenderMeshes = new RenderMeshArray(hoverRenderMeshes.MaterialReferences, mouseHover.OriginalRenderMeshes.MeshReferences);

                // use the material id from the hover entity, but the mesh id from the current entity
                var newMeshInfo = MaterialMeshInfo.FromRenderMeshArrayIndices(hoverMeshInfo.Material, mouseHover.OriginalMeshInfo.Mesh);

                // apply the new render info to the current entity
                EntityManager.SetComponentData(mouseHover.CurrentEntity, newMeshInfo);
                EntityManager.SetSharedComponentManaged(mouseHover.CurrentEntity, newRenderMeshes);
            }
        }
    }</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">1) InitializationSystemGroup</h4>



<figure class="wp-block-image size-full"><img decoding="async" width="590" height="153" src="https://lycos7560.com/wp-content/uploads/2025/02/image-2.png" alt="" class="wp-image-39540" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-2.png 590w, https://lycos7560.com/wp-content/uploads/2025/02/image-2-300x78.png 300w" sizes="(max-width: 590px) 100vw, 590px" /></figure>



<p>초기화 단계에서 실행됨</p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">2) WorldRaycastJob (IJob)</h4>



<figure class="wp-block-image size-full"><img decoding="async" width="886" height="585" src="https://lycos7560.com/wp-content/uploads/2025/02/image-3.png" alt="" class="wp-image-39541" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-3.png 886w, https://lycos7560.com/wp-content/uploads/2025/02/image-3-300x198.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-3-768x507.png 768w" sizes="(max-width: 886px) 100vw, 886px" /></figure>



<p>마우스 위치에서 Raycast를 실행하는 <code>IJob</code></p>



<ul class="wp-block-list">
<li><code>CollisionWorld.CastRay()</code>를 사용하여 마우스가 클릭한 지점의 물리 객체를 찾음</li>



<li><code>mousePickCollector</code>를 사용하여 필터링 (<code>IgnoreTriggers</code>, <code>IgnoreStatic</code>)</li>
</ul>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">3) OnUpdate()</h4>



<figure class="wp-block-image size-full"><img decoding="async" width="1471" height="1593" src="https://lycos7560.com/wp-content/uploads/2025/02/image-4.jpg" alt="" class="wp-image-39542" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-4.jpg 1471w, https://lycos7560.com/wp-content/uploads/2025/02/image-4-277x300.jpg 277w, https://lycos7560.com/wp-content/uploads/2025/02/image-4-768x832.jpg 768w, https://lycos7560.com/wp-content/uploads/2025/02/image-4-1418x1536.jpg 1418w" sizes="(max-width: 1471px) 100vw, 1471px" /></figure>



<p></p>



<h5 class="wp-block-heading">1> CollisionWorld 가져오기</h5>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="946" height="55" src="https://lycos7560.com/wp-content/uploads/2025/02/image-4.png" alt="" class="wp-image-39543" style="width:946px;height:auto" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-4.png 946w, https://lycos7560.com/wp-content/uploads/2025/02/image-4-300x17.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-4-768x45.png 768w" sizes="(max-width: 946px) 100vw, 946px" /></figure>



<ul class="wp-block-list">
<li><code>PhysicsWorldSingleton</code>은 Unity Physics 시스템에서 현재의 물리 월드(<code>CollisionWorld</code>)를 관리하는 싱글톤</li>



<li><code>CollisionWorld</code>는 물리적인 충돌을 검사하는 데 사용</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">2> 마우스 위치에서 레이(Ray) 생성</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="977" height="65" src="https://lycos7560.com/wp-content/uploads/2025/02/image-5.png" alt="" class="wp-image-39544" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-5.png 977w, https://lycos7560.com/wp-content/uploads/2025/02/image-5-300x20.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-5-768x51.png 768w" sizes="(max-width: 977px) 100vw, 977px" /></figure>



<ul class="wp-block-list">
<li><code>Input.mousePosition</code>을 사용하여 현재 마우스 커서의 화면 좌표를 가져오기</li>



<li><code>Camera.main.ScreenPointToRay(mousePosition)</code>을 통해 이 화면 좌표를 기준으로 3D 월드에서의 <code>Ray</code>를 생성</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">3> RaycastInput 설정</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="946" height="196" src="https://lycos7560.com/wp-content/uploads/2025/02/image-6.png" alt="" class="wp-image-39545" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-6.png 946w, https://lycos7560.com/wp-content/uploads/2025/02/image-6-300x62.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-6-768x159.png 768w" sizes="(max-width: 946px) 100vw, 946px" /></figure>



<p><code>RaycastInput</code>은 Unity Physics에서 사용되는 구조체로, 물리 엔진이 사용할 레이캐스트 정보를 포함</p>



<ul class="wp-block-list">
<li><code>Start</code>: 레이의 시작점 (<code>unityRay.origin</code>)</li>



<li><code>End</code>: 레이의 끝점 (<code>unityRay.origin + unityRay.direction * MousePickSystem.k_MaxDistance</code>)
<ul class="wp-block-list">
<li><code>MousePickSystem.k_MaxDistance</code>는 레이의 최대 탐색 거리로, 너무 멀리까지 체크하지 않도록 제한</li>
</ul>
</li>



<li><code>Filter</code>: <code>CollisionFilter.Default</code>는 기본적인 충돌 필터로, 충돌 검사를 수행할 물리 레이어를 정의</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">4> MouseHover 컴포넌트 가져오기</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="801" height="55" src="https://lycos7560.com/wp-content/uploads/2025/02/image-7.png" alt="" class="wp-image-39546" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-7.png 801w, https://lycos7560.com/wp-content/uploads/2025/02/image-7-300x21.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-7-768x53.png 768w" sizes="(max-width: 801px) 100vw, 801px" /></figure>



<ul class="wp-block-list">
<li><code>MouseHover</code>는 마우스 오버 이벤트를 관리하는 <code>IComponentData</code></li>



<li><code>SystemAPI.ManagedAPI.GetSingleton&lt;MouseHover>()</code>을 사용하여 현재 씬에 존재하는 <code>MouseHover</code> 컴포넌트를 가져옴</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">5> 레이캐스트 실행</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="1016" height="429" src="https://lycos7560.com/wp-content/uploads/2025/02/image-8.png" alt="" class="wp-image-39547" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-8.png 1016w, https://lycos7560.com/wp-content/uploads/2025/02/image-8-300x127.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-8-768x324.png 768w" sizes="(max-width: 1016px) 100vw, 1016px" /></figure>



<p><code>NativeReference&lt;RaycastHit></code>을 사용하여 레이캐스트 결과(<code>RaycastHit</code>)를 저장할 공간을 확보</p>



<p><code>WorldRaycastJob</code>을 생성하여 레이캐스트를 수행</p>



<ul class="wp-block-list">
<li><code>CollisionWorld</code>을 사용하여 실제 물리 충돌을 검사합니다.</li>



<li><code>IgnoreTriggers</code>, <code>IgnoreStatic</code> 값을 기반으로 충돌 조건을 설정합니다.</li>
</ul>



<p><code>rcj.Run();</code>을 실행하여 <code>Job</code>을 즉시 수행한 후 결과를 <code>hit</code>에 저장합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">5> 물리 Entity에서 그래픽 Entity 찾기</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="863" height="57" src="https://lycos7560.com/wp-content/uploads/2025/02/image-9.png" alt="" class="wp-image-39548" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-9.png 863w, https://lycos7560.com/wp-content/uploads/2025/02/image-9-300x20.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-9-768x51.png 768w" sizes="(max-width: 863px) 100vw, 863px" /></figure>



<p><code>hit.Entity</code>는 <code>RaycastHit</code> 결과에서 얻은 <strong>충돌한 물리 Entity</strong></p>



<p><code>FindGraphicsEntityFromPhysics(hit.Entity, hit.ColliderKey)</code>를 호출하여 <strong>실제 렌더링을 담당하는 Entity</strong>를 찾습니다.</p>



<ul class="wp-block-list">
<li>물리 Entity 와 그래픽 Entity 는 다를 수 있기 때문에 이 과정을 거칩니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">6> 동일한 엔터티를 계속 가리키면 무시</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="819" height="91" src="https://lycos7560.com/wp-content/uploads/2025/02/image-10.png" alt="" class="wp-image-39549" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-10.png 819w, https://lycos7560.com/wp-content/uploads/2025/02/image-10-300x33.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-10-768x85.png 768w" sizes="(max-width: 819px) 100vw, 819px" /></figure>



<p>현재 마우스가 가리키는 <code>graphicsEntity</code>가 이전 프레임과 동일하면 추가 작업을 하지 않고 바로 반환합니다.</p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">7> 이전 및 현재 마우스 오버 Entity 업데이트</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="698" height="73" src="https://lycos7560.com/wp-content/uploads/2025/02/image-11.png" alt="" class="wp-image-39551" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-11.png 698w, https://lycos7560.com/wp-content/uploads/2025/02/image-11-300x31.png 300w" sizes="(max-width: 698px) 100vw, 698px" /></figure>



<ul class="wp-block-list">
<li><code>PreviousEntity</code>에 이전 프레임에서 마우스가 가리키던 Entity를 저장합니다.</li>



<li><code>CurrentEntity</code>에 이번 프레임에서 새롭게 감지된 Entity를 저장합니다.</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">8> 이전 Entity의 원래 상태 복구</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="1081" height="247" src="https://lycos7560.com/wp-content/uploads/2025/02/image-12.png" alt="" class="wp-image-39552" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-12.png 1081w, https://lycos7560.com/wp-content/uploads/2025/02/image-12-300x69.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-12-768x175.png 768w" sizes="(max-width: 1081px) 100vw, 1081px" /></figure>



<p><code>hasPreviousEntity</code>: 이전 프레임에서 마우스가 가리키던 Entity가 존재하는지 확인</p>



<p><code>EntityManager.HasComponent&lt;MaterialMeshInfo>(mouseHover.PreviousEntity)</code>: 이전 Entity가 <code>MaterialMeshInfo</code> 컴포넌트를 가지고 있는지 확인</p>



<p><strong>이전 Entity의 렌더링 데이터를 원래 값으로 복원</strong>:</p>



<ul class="wp-block-list">
<li><code>MaterialMeshInfo</code>: 이전에 저장해 둔 원래의 메쉬 정보를 복원</li>



<li><code>RenderMeshArray</code>: 이전에 저장해 둔 원래의 <code>Material</code> 배열을 복원</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">9> 새로운 Entity의 마우스 Hover 효과 적용</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="1718" height="173" src="https://lycos7560.com/wp-content/uploads/2025/02/image-13.png" alt="" class="wp-image-39553" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-13.png 1718w, https://lycos7560.com/wp-content/uploads/2025/02/image-13-300x30.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-13-768x77.png 768w, https://lycos7560.com/wp-content/uploads/2025/02/image-13-1536x155.png 1536w" sizes="(max-width: 1718px) 100vw, 1718px" /></figure>



<p>현재 프레임에서 감지된 <code>CurrentEntity</code>가 존재하는지 확인</p>



<p><code>MaterialMeshInfo</code> 및 <code>RenderMeshArray</code> 컴포넌트가 있는 경우, <strong>원래 상태를 저장</strong>:</p>



<ul class="wp-block-list">
<li><code>OriginalMeshInfo</code>: 원래 <code>MaterialMeshInfo</code> 값을 저장</li>



<li><code>OriginalRenderMeshes</code>: 원래 <code>RenderMeshArray</code> 값을 저장</li>
</ul>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h5 class="wp-block-heading">10> 마우스 Hover 머티리얼 적용</h5>



<figure class="wp-block-image size-full"><img decoding="async" width="1419" height="420" src="https://lycos7560.com/wp-content/uploads/2025/02/image-14.png" alt="" class="wp-image-39554" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-14.png 1419w, https://lycos7560.com/wp-content/uploads/2025/02/image-14-300x89.png 300w, https://lycos7560.com/wp-content/uploads/2025/02/image-14-768x227.png 768w" sizes="(max-width: 1419px) 100vw, 1419px" /></figure>



<p><code>mouseHover.HoverEntity</code>는 마우스 Hover 효과를 표시하는 데 사용할 프리팹(Material 정보 포함)</p>



<p><code>hoverMeshInfo</code>와 <code>hoverRenderMeshes</code>를 가져와서 현재 마우스가 가리키는 Entity에 적용</p>



<p></p>
<p>The post <a href="https://lycos7560.com/unity/ecs-mousehoverauthoring/39539/">ECS &#8211; MouseHoverAuthoring</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/unity/ecs-mousehoverauthoring/39539/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="https://lycos7560.com/wp-content/uploads/2025/02/녹화_2025_02_05_19_00_37_49.mp4" length="14855134" type="video/mp4" />

			</item>
		<item>
		<title>ECS &#8211; BufferTypeHandle</title>
		<link>https://lycos7560.com/unity/ecs-buffertypehandle/39536/</link>
					<comments>https://lycos7560.com/unity/ecs-buffertypehandle/39536/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Wed, 05 Feb 2025 06:26:40 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[AsNativeArray]]></category>
		<category><![CDATA[AsNativeArray()]]></category>
		<category><![CDATA[BufferTypeHandle]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Chunk components]]></category>
		<category><![CDATA[Cleanup components]]></category>
		<category><![CDATA[Component]]></category>
		<category><![CDATA[concept]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[DOTS]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[Entity Component System]]></category>
		<category><![CDATA[IJobChunk]]></category>
		<category><![CDATA[job]]></category>
		<category><![CDATA[Job 시스템]]></category>
		<category><![CDATA[Managed Components]]></category>
		<category><![CDATA[OnUpdate]]></category>
		<category><![CDATA[OnUpdate()]]></category>
		<category><![CDATA[Physics]]></category>
		<category><![CDATA[Sample]]></category>
		<category><![CDATA[ScheduleParallel]]></category>
		<category><![CDATA[Shared Components]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Tag components]]></category>
		<category><![CDATA[Unity Physics 101]]></category>
		<category><![CDATA[Unmanaged]]></category>
		<category><![CDATA[Unmanaged components]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=39536</guid>

					<description><![CDATA[<p>ECS &#8211; BufferTypeHandle ECS에서 DynamicBuffer&#60;T>를 처리하려면 BufferTypeHandle&#60;T>를 사용해야 한다. 즉, &#8220;IComponentData가 아닌 버퍼 데이터를 읽고/쓰는 Handle&#8220; 1. BufferTypeHandle&#60;T>란? 2. 사용 예제 1) OnCreate()에서 초기화 2) OnUpdate()에서 매 프레임 업데이트 3. BufferTypeHandle 상세 사용법 1) 기본 사용법 (IJobChunk에서 사용) 2) OnUpdate()에서 ScheduleParallel 실행 3) IJobParallelFor에서 BufferTypeHandle를 사용할 때 일반적으로 IJobParallelFor에서는 직접 사용할 수 없고, AsNativeArray() 를 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/unity/ecs-buffertypehandle/39536/">ECS &#8211; BufferTypeHandle</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-189571fa      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차 테이블						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#ecs-buffertypehandle" class="uagb-toc-link__trigger">ECS &#8211; BufferTypeHandle</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-buffertypehandlet란" class="uagb-toc-link__trigger">1. BufferTypeHandle란?</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-사용-예제" class="uagb-toc-link__trigger">2. 사용 예제</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-oncreate에서-초기화" class="uagb-toc-link__trigger">1) OnCreate()에서 초기화</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-onupdate에서-매-프레임-업데이트" class="uagb-toc-link__trigger">2) OnUpdate()에서 매 프레임 업데이트</a></li></ul><li class="uagb-toc__list"><a href="#3-buffertypehandle-상세-사용법" class="uagb-toc-link__trigger">3. BufferTypeHandle 상세 사용법</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#1-기본-사용법-ijobchunk에서-사용" class="uagb-toc-link__trigger">1) 기본 사용법 (IJobChunk에서 사용)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#2-onupdate에서-scheduleparallel-실행" class="uagb-toc-link__trigger">2) OnUpdate()에서 ScheduleParallel 실행</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#3-ijobparallelfor에서-buffertypehandle를-사용할-때" class="uagb-toc-link__trigger">3) IJobParallelFor에서 BufferTypeHandle를 사용할 때</a></li></ul><li class="uagb-toc__list"><a href="#4-buffertypehandle-요약" class="uagb-toc-link__trigger">4. BufferTypeHandle 요약</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#buffertypehandlet는-언제-사용" class="uagb-toc-link__trigger">BufferTypeHandle는 언제 사용?</a></ul></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>



<h1 class="wp-block-heading"><strong>ECS &#8211; BufferTypeHandle</strong></h1>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p>ECS에서 <code>DynamicBuffer&lt;T></code>를 처리하려면 <code>BufferTypeHandle&lt;T></code>를 사용해야 한다.</p>



<p>즉, <strong>&#8220;IComponentData가 아닌 버퍼 데이터를 읽고/쓰는 <strong>Handle</strong>&#8220;</strong></p>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">1. <strong>BufferTypeHandle&lt;T>란?</strong></h2>



<ul class="wp-block-list">
<li><strong>ECS에서 <code>DynamicBuffer&lt;T></code>를 읽고/쓰기 위한 <strong><strong>Handle</strong></strong></strong></li>



<li><code><strong>IJobChunk</strong></code> 내부에서 사용됨</li>



<li><code><strong>GetBufferAccessor(ref BufferTypeHandle&lt;T>)</strong></code>를 이용해 버퍼 데이터 접근</li>



<li>쓰기 여부에 따라 <strong>읽기 전용(<code>true</code>) / 읽기+쓰기(<code>false</code>)</strong> 로 설정 가능</li>
</ul>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">2. <strong>사용 예제</strong></h2>



<h3 class="wp-block-heading">1) <strong>OnCreate()에서 초기화</strong></h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">_BufferTypeHandle = state.GetBufferTypeHandle&lt;GridCellBufferComponent>(isReadOnly: false);
</pre>



<ul class="wp-block-list">
<li><code>isReadOnly: true</code> → 읽기 전용</li>



<li><code>isReadOnly: false</code> → 읽기 &amp; 쓰기 가능</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2) <strong>OnUpdate()에서 매 프레임 업데이트</strong></h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">_BufferTypeHandle.Update(ref state);
</pre>



<ul class="wp-block-list">
<li><code>Update(ref state)</code>를 호출해야 최신 데이터를 가져올 수 있다.</li>
</ul>



<div style="height:70px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">3. <strong>BufferTypeHandle 상세 사용법</strong></h2>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">1) <strong>기본 사용법 (IJobChunk에서 사용)</strong></h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[BurstCompile]
public struct CountJob : IJobChunk
{
    public BufferTypeHandle&lt;GridCellBufferComponent> BufferTypeHandle;

    public void Execute(in ArchetypeChunk chunk, int chunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
    {
        var buffers = chunk.GetBufferAccessor(ref BufferTypeHandle);

        for (int i = 0; i &lt; chunk.Count; i++)
        {
            DynamicBuffer&lt;GridCellBufferComponent> buffer = buffers[i];

            for (int j = 0; j &lt; buffer.Length; j++)
            {
                buffer[j] = new GridCellBufferComponent
                {
                    Position = buffer[j].Position,
                    IsOccupied = buffer[j].IsOccupied,
                    Count = buffer[j].Count + 1 // Count 증가
                };
            }
        }
    }
}
</pre>



<ul class="wp-block-list">
<li><code>chunk.GetBufferAccessor(ref BufferTypeHandle)</code>을 사용하여 <code>DynamicBuffer</code>에 접근</li>



<li>각 <code>chunk</code>에 속한 모든 <code>DynamicBuffer&lt;GridCellBufferComponent></code>에 대해 반복문 실행</li>
</ul>



<div style="height:70px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">2) <strong>OnUpdate()에서 ScheduleParallel 실행</strong></h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public void OnUpdate(ref SystemState state)
{
    _BufferTypeHandle.Update(ref state);

    var job = new CountJob
    {
        BufferTypeHandle = _BufferTypeHandle
    };

    state.Dependency = job.ScheduleParallel(_GridQuery, state.Dependency);
}
</pre>



<ul class="wp-block-list">
<li><code>_BufferTypeHandle.Update(ref state);</code> → 최신 상태 반영</li>



<li><code>ScheduleParallel()</code> → 여러 개의 <code>chunk</code>를 병렬 실행</li>
</ul>



<div style="height:70px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><strong>3) IJobParallelFor에서 BufferTypeHandle를 사용할 때</strong></h3>



<p>일반적으로 <code>IJobParallelFor</code>에서는 직접 사용할 수 없고, <strong><code>AsNativeArray()</code></strong> 를 사용해야 함.</p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[BurstCompile]
public struct CountJobParallelFor : IJobParallelFor
{
    public NativeArray&lt;GridCellBufferComponent> BufferNativeArray;

    public void Execute(int index)
    {
        BufferNativeArray[index] = new GridCellBufferComponent
        {
            Position = BufferNativeArray[index].Position,
            IsOccupied = BufferNativeArray[index].IsOccupied,
            Count = BufferNativeArray[index].Count + 1
        };
    }
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong><code>OnUpdate()</code>에서 실행:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public void OnUpdate(ref SystemState state)
{
    _BufferTypeHandle.Update(ref state);
    var chunks = _GridQuery.ToArchetypeChunkArray(Allocator.TempJob);

    for (int i = 0; i &lt; chunks.Length; i++)
    {
        BufferAccessor&lt;GridCellBufferComponent> bufferAccessor = chunks[i].GetBufferAccessor(ref _BufferTypeHandle);
        for (int j = 0; j &lt; bufferAccessor.Length; j++)
        {
            DynamicBuffer&lt;GridCellBufferComponent> buffer = bufferAccessor[j];

            var job = new CountJobParallelFor
            {
                BufferNativeArray = buffer.AsNativeArray()
            };

            job.Schedule(buffer.Length, 1).Complete(); // 즉시 실행
        }
    }
    chunks.Dispose();
}
</pre>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>주의할 점</strong></p>



<ul class="wp-block-list">
<li><code>DynamicBuffer&lt;T></code>에서 <strong><code>AsNativeArray()</code></strong> 를 호출하여 <code>IJobParallelFor</code>에서 사용</li>



<li><code>.Complete()</code> 없이 <code>state.Dependency = job.Schedule();</code>을 하면 <strong>Write Conflict 에러 발생</strong></li>



<li><code>buffer.AsNativeArray()</code>는 <strong>해당 프레임에서만 유효</strong></li>
</ul>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">4. <strong>BufferTypeHandle 요약</strong></h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>기능</th><th>설명</th></tr></thead><tbody><tr><td><code>BufferTypeHandle&lt;T&gt;</code></td><td><code>DynamicBuffer&lt;T></code>를 읽고/쓰기 위한 Handle</td></tr><tr><td><code>.Update(ref state)</code></td><td>매 프레임 최신 상태로 갱신</td></tr><tr><td><code>GetBufferAccessor(ref BufferTypeHandle&lt;T&gt;)</code></td><td><code>DynamicBuffer&lt;T&gt;</code>를 가져오는 함수</td></tr><tr><td><code>ScheduleParallel()</code></td><td>여러 <code>chunk</code>를 병렬 실행</td></tr><tr><td><code>AsNativeArray()</code></td><td><code>IJobParallelFor</code>에서 사용 가능</td></tr></tbody></table></figure>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading has-medium-font-size"><strong>BufferTypeHandle&lt;T>는 언제 사용?</strong></h4>



<ul class="wp-block-list">
<li>많은 데이터가 DynamicBuffer에 저장될 때</li>



<li>ECS에서 <code>IJobChunk</code> 또는 <code>IJobParallelFor</code>을 활용할 때</li>



<li>최대 성능을 끌어내기 위해 <code>ScheduleParallel</code>을 사용할 때</li>
</ul>



<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>
<p>The post <a href="https://lycos7560.com/unity/ecs-buffertypehandle/39536/">ECS &#8211; BufferTypeHandle</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/unity/ecs-buffertypehandle/39536/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ECS &#8211; System concepts</title>
		<link>https://lycos7560.com/unity/ecs-system-concepts/39534/</link>
					<comments>https://lycos7560.com/unity/ecs-system-concepts/39534/#respond</comments>
		
		<dc:creator><![CDATA[lycos7560]]></dc:creator>
		<pubDate>Tue, 04 Feb 2025 02:16:18 +0000</pubDate>
				<category><![CDATA[Unity]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[Chunk components]]></category>
		<category><![CDATA[Cleanup components]]></category>
		<category><![CDATA[Component]]></category>
		<category><![CDATA[concept]]></category>
		<category><![CDATA[Data]]></category>
		<category><![CDATA[DOTS]]></category>
		<category><![CDATA[ECS]]></category>
		<category><![CDATA[Entity Component System]]></category>
		<category><![CDATA[job]]></category>
		<category><![CDATA[Job 시스템]]></category>
		<category><![CDATA[Managed Components]]></category>
		<category><![CDATA[Physics]]></category>
		<category><![CDATA[Sample]]></category>
		<category><![CDATA[Shared Components]]></category>
		<category><![CDATA[study]]></category>
		<category><![CDATA[Tag components]]></category>
		<category><![CDATA[Unity Physics 101]]></category>
		<category><![CDATA[Unmanaged]]></category>
		<category><![CDATA[Unmanaged components]]></category>
		<guid isPermaLink="false">https://lycos7560.com/?p=39534</guid>

					<description><![CDATA[<p>ECS &#8211; System concepts https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/concepts-systems.html System concepts System의 역할 System의 유형 시스템 유형 설명 SystemBase C# 클래스 기반, 관리형 시스템 (GC 영향 있음) ISystem 구조체 기반, 비관리형 시스템 (성능 최적화) EntityCommandBufferSystem Entity 생성/삭제 등의 구조적 변경을 최적화하는 시스템 ComponentSystemGroup 여러 시스템을 그룹화하여 실행 순서 제어 System 실행 순서 (System Groups) 시스템 그룹 설명 InitializationSystemGroup 초기화 [&#8230;]</p>
<p>The post <a href="https://lycos7560.com/unity/ecs-system-concepts/39534/">ECS &#8211; System concepts</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></description>
										<content:encoded><![CDATA[				<div class="wp-block-uagb-table-of-contents uagb-toc__align-left uagb-toc__columns-1  uagb-block-edbd8396      "
					data-scroll= "1"
					data-offset= "30"
					style=""
				>
				<div class="uagb-toc__wrap">
						<div class="uagb-toc__title">
							목차						</div>
																						<div class="uagb-toc__list-wrap ">
						<ol class="uagb-toc__list"><li class="uagb-toc__list"><a href="#ecs-system-concepts" class="uagb-toc-link__trigger">ECS &#8211; System concepts</a><ul class="uagb-toc__list"><li class="uagb-toc__list"><a href="#system-concepts" class="uagb-toc-link__trigger">System concepts</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#systembase-예제-관리형-시스템" class="uagb-toc-link__trigger">SystemBase 예제 (관리형 시스템)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#isystem-예제-비관리형-시스템" class="uagb-toc-link__trigger">ISystem 예제 (비관리형 시스템)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#system-실행-순서-제어-system-group-활용" class="uagb-toc-link__trigger">System 실행 순서 제어 (System Group 활용)</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#inspecting-systems" class="uagb-toc-link__trigger">Inspecting systems</a><li class="uagb-toc__list"><li class="uagb-toc__list"><a href="#에디터에서-systems" class="uagb-toc-link__trigger">에디터에서 Systems</a></ul></ol>					</div>
									</div>
				</div>
			


<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">ECS &#8211; System concepts</h2>



<p><a href="https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/concepts-systems.html" target="_blank" rel="noreferrer noopener">https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/concepts-systems.html</a></p>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-full"><img decoding="async" width="695" height="278" src="https://lycos7560.com/wp-content/uploads/2025/02/image-1.png" alt="" class="wp-image-39532" srcset="https://lycos7560.com/wp-content/uploads/2025/02/image-1.png 695w, https://lycos7560.com/wp-content/uploads/2025/02/image-1-300x120.png 300w" sizes="(max-width: 695px) 100vw, 695px" /></figure>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading"><strong>System concepts</strong></h3>



<p><strong>System의 역할</strong></p>



<ul class="wp-block-list">
<li><code>System</code>은 컴포넌트 데이터를 읽고, 수정하여 <strong>프레임마다 업데이트</strong>하는 역할을 수행</li>



<li><code>OnUpdate()</code>가 <strong>매 프레임 호출</strong>되며, Entity 데이터를 갱신함</li>



<li><code>SystemBase</code>(관리형 시스템)와 <code>ISystem</code>(비관리형 시스템) 두 가지 유형이 있음</li>
</ul>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>System의 유형</strong></p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>시스템 유형</th><th>설명</th></tr></thead><tbody><tr><td><code>SystemBase</code></td><td>C# 클래스 기반, 관리형 시스템 (GC 영향 있음)</td></tr><tr><td><code>ISystem</code></td><td>구조체 기반, 비관리형 시스템 (성능 최적화)</td></tr><tr><td><code>EntityCommandBufferSystem</code></td><td>Entity 생성/삭제 등의 구조적 변경을 최적화하는 시스템</td></tr><tr><td><code>ComponentSystemGroup</code></td><td>여러 시스템을 그룹화하여 실행 순서 제어</td></tr></tbody></table></figure>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<p><strong>System 실행 순서 (System Groups)</strong></p>



<ul class="wp-block-list">
<li>ECS는 시스템을 <strong>Auto Bootstrap</strong>하며, 기본적으로 <strong>3가지 그룹</strong>으로 실행됨.<br>(Auto Bootstrap &#8211; 시스템을 자동으로 생성하고 실행하는 과정)</li>
</ul>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>시스템 그룹</th><th>설명</th></tr></thead><tbody><tr><td><code>InitializationSystemGroup</code></td><td>초기화 관련 시스템 실행 (프레임 시작 시)</td></tr><tr><td><code>SimulationSystemGroup</code></td><td>대부분의 게임 로직이 실행되는 기본 그룹</td></tr><tr><td><code>PresentationSystemGroup</code></td><td>렌더링과 관련된 시스템 실행 (프레임 마지막)</td></tr></tbody></table></figure>



<ul class="wp-block-list">
<li>특정 그룹에서 실행되도록 하려면 <code>[UpdateInGroup(typeof(그룹))]</code> 속성을 사용.</li>
</ul>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">SystemBase 예제 (관리형 시스템)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Unity.Burst;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using UnityEngine;

// [UpdateInGroup(typeof(SimulationSystemGroup))]  // 특정 그룹에서 실행 가능
public partial class MoveSystem : SystemBase
{
    protected override void OnUpdate()
    {
        float deltaTime = SystemAPI.Time.DeltaTime; // 현재 프레임의 Delta Time

        Entities.ForEach((ref LocalTransform transform) =>
        {
            transform.Position += new float3(1, 0, 0) * deltaTime; // X축 이동
        }).ScheduleParallel();
    }
}
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">ISystem 예제 (비관리형 시스템)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Unity.Burst;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;

// 성능 최적화를 위해 [BurstCompile] 적용
[BurstCompile]
public partial struct MoveSystem_ISystem : ISystem
{
    public void OnCreate(ref SystemState state) { }  // 초기화

    public void OnDestroy(ref SystemState state) { } // 시스템 종료 시 실행

    public void OnUpdate(ref SystemState state)
    {
        float deltaTime = SystemAPI.Time.DeltaTime;

        foreach (var transform in SystemAPI.Query&lt;RefRW&lt;LocalTransform>>())
        {
            transform.ValueRW.Position += new float3(1, 0, 0) * deltaTime;
        }
    }
}
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">System 실행 순서 제어 (System Group 활용)</h3>



<pre class="EnlighterJSRAW" data-enlighter-language="csharp" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="false" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">using Unity.Entities;

[UpdateInGroup(typeof(InitializationSystemGroup))] // 초기화 단계에서 실행
public partial class InitSystem : SystemBase
{
    protected override void OnUpdate()
    {
        UnityEngine.Debug.Log("초기화 시스템 실행됨!");
    }
}
</pre>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="inspecting-systems">Inspecting systems</h3>



<p>시스템 창을 사용하여 각 세계의 시스템 업데이트 순서를 검사하고 시스템 그룹의 전체 계층을 볼 수 있습니다. </p>



<p>자세한 내용은 <a href="https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/editor-systems-window.html" target="_blank" rel="noreferrer noopener">Systems window reference</a>를 참조하세요.</p>



<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading" id="systems-in-the-editor">에디터에서 Systems</h3>



<p>편집기에서 다음 아이콘은 다양한 유형의 시스템을 나타냅니다. </p>



<p> <a href="https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/editor-workflows.html" target="_blank" rel="noreferrer noopener">Entities windows와 Inspectors</a>를 사용하면 이를 확인할 수 있습니다.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th><strong>Icon</strong></th><th><strong>Represents</strong></th></tr></thead><tbody><tr><td><img decoding="async" src="https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/images/editor-system-group.png" alt=""></td><td>A system group</td></tr><tr><td><img decoding="async" src="https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/images/editor-system.png" alt=""></td><td>A system</td></tr><tr><td><img decoding="async" src="https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/images/editor-system-start-step.png" alt=""></td><td><a href="https://docs.unity3d.com/Packages/com.unity.entities@1.3/api/Unity.Entities.UpdateInGroupAttribute.OrderFirst.html" target="_blank" rel="noreferrer noopener">OrderFirst 인수</a>를 사용하여 시스템 그룹의 시작 시 실행되도록 설정된 Entity 명령 버퍼 시스템</td></tr><tr><td><img decoding="async" src="https://docs.unity3d.com/Packages/com.unity.entities@1.3/manual/images/editor-system-end-step.png" alt=""></td><td><a href="https://docs.unity3d.com/Packages/com.unity.entities@1.3/api/Unity.Entities.UpdateInGroupAttribute.OrderLast.html" target="_blank" rel="noreferrer noopener">OrderLast 인수</a>를 사용하여 시스템 그룹의 끝에서 실행되도록 설정된 엔티티 명령 버퍼 시스템입니다.</td></tr></tbody></table></figure>



<p></p>
<p>The post <a href="https://lycos7560.com/unity/ecs-system-concepts/39534/">ECS &#8211; System concepts</a> appeared first on <a href="https://lycos7560.com">어제와 내일의 나 그 사이의 이야기</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://lycos7560.com/unity/ecs-system-concepts/39534/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
