<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sugar_ghost.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 21 Feb 2025 17:15:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sugar_ghost.log</title>
            <url>https://velog.velcdn.com/cloudflare/sugar_ghost/d20d2732-64fd-425a-80f9-19a38766df2c/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sugar_ghost.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sugar_ghost" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[항해 플러스 AI 솔직 후기: 개발자가 직접 경험한 현실]]></title>
            <link>https://velog.io/@sugar_ghost/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-AI-%EC%86%94%EC%A7%81-%ED%9B%84%EA%B8%B0-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%A7%81%EC%A0%91-%EA%B2%BD%ED%97%98%ED%95%9C-%ED%98%84%EC%8B%A4</link>
            <guid>https://velog.io/@sugar_ghost/%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-AI-%EC%86%94%EC%A7%81-%ED%9B%84%EA%B8%B0-%EA%B0%9C%EB%B0%9C%EC%9E%90%EA%B0%80-%EC%A7%81%EC%A0%91-%EA%B2%BD%ED%97%98%ED%95%9C-%ED%98%84%EC%8B%A4</guid>
            <pubDate>Fri, 21 Feb 2025 17:15:54 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/9d5367ac-4738-484b-b117-343a38491833/image.png" alt=""></p>
<h3 id="1-간단한-자기소개">1. 간단한 자기소개</h3>
<p>비전공자 출신으로 C++를 2년 정도 쓰다가 Spring 웹 개발자로 전향해서 현재 3년차가 되었다.
이전에 스파르타 코딩 클럽에서 운영하는 항해99 과정에서 리액트를 배운 경험이 있다.
위메이드에서 진행하는 블록체인 부트캠프도 참여한 적이 있으니 나름 다양한 기술과 다양한 부트 캠프를 경험해 왔다.</p>
<h3 id="2-항해-플러스에-들어오기-전-현업에서-고민했던-점">2. 항해 플러스에 들어오기 전, 현업에서 고민했던 점</h3>
<p>&#39;AI가 개발자를 대체할 것이다&#39;
흔하게 듣는 말이다.
AI가 점점 중요한 기술이 되고 있다는 것은 알고 있었지만, 어디서부터 시작해야 할지 막막했다. 온라인 강의나 책을 통해 이론을 익힐 수는 있었지만, 다양한 AI를 사용해보자니 무엇부터 해야하는지 막막함이 가장 큰 고민이었다.</p>
<h3 id="3-항해-플러스를-선택하게-된-결정적-계기">3. 항해 플러스를 선택하게 된 결정적 계기</h3>
<p>이전에 항해99과정을 참여했기 때문에 추가 할인을 받을 수 있었다.
나름대로 가격을 고려한 합리적인 선택이었다.
또한 기존 항해99과정에서 느꼇던 스스로 몰입하는 과정과 새롭게 플러스 과정에 추가된 성적 시스템이 마음에 들었다.</p>
<h3 id="4-항해-플러스-ai-코스의-장점">4. 항해 플러스 AI 코스의 장점</h3>
<p>✅ 딥러닝 기초 개념부터 LLM 활용까지 다루는 커리큘럼</p>
<p>기초적인 신경망 학습부터 Transformer 모델까지 배우며, 단계별로 AI 과정을 따라 갈 수 있다.</p>
<p>✅ AI 서비스 개발 경험</p>
<p>단순한 코딩 실습이 아니라, AI를 기반으로 실제 프로젝트를 진행하는 경험을 얻을 수 있다.
즉 포트폴리오가 하나 남는다.</p>
<p>✅ 현직 AI 종사자 코치진의 멘토링</p>
<p>현업에서 AI를 활용하는 방법에 대한 조언을 받을 수 있었고, AI 개발자로 살아가려면 어떻게 준비해야하는지, 현업에 경험을 들을 수 있다.</p>
<p>✅ 단계별 과제 해결을 통한 실력 인증 시스템</p>
<p>매주 나오는 과제를 수행하는 것으로 끝이 아닌 책점과 피드백을 기반으로 실력 인증 시스템이 있다.</p>
<h3 id="5-항해-플러스-이전과-이후-어떻게-달라졌나">5. 항해 플러스 이전과 이후 어떻게 달라졌나</h3>
<p>가장 중요한 것은 최소한 내가 뭘 모르는지 알게 되는 것과 내가 시작을 했다는 것이다.
과정이 끝나고 돌이켜 보면 이미 AI를 배워가는 과정중에 있기 때문에 다음 스텝을 밟을 수 있게 된다.</p>
<h3 id="6-항해-플러스에-투자한-시간-돈이-아깝지-않은-이유">6. 항해 플러스에 투자한 시간, 돈이 아깝지 않은 이유</h3>
<p>독학으로 배우기 어려운 AI 경험을 쌓을 수 있다.</p>
<p>이론 + 실습 + 프로젝트를 모두 포함한 과정이라 학습 효율이 높다.</p>
<p>이제 살면서 내가 AI를 배웠다고 할 수 있다. 이력서에 적을 내용 늘리는 것은 돈만 준다고 되는게 아니다.</p>
<h3 id="7-항해-플러스-코스를-고민하고-있는-개발자들에게-한마디">7. 항해 플러스 코스를 고민하고 있는 개발자들에게 한마디</h3>
<p>중요한 것은 무슨 코스를 하느냐가 아니다.
본인의 마음가짐이 가장 중요하다.
하는 만큼 얻는 것이다.</p>
<h3 id="8-수료생-추천-할인-혜택-안내">8. 수료생 추천 할인 혜택 안내</h3>
<p>항해 플러스 AI 코스를 등록할 때 수료생 추천 할인 코드를 입력하면 20만 원 할인이 적용된다.</p>
<p>할인 코드는 오픈 채팅이나 비밀 댓글로 공유해 드릴 수 있으니, 관심 있는 분들은 댓글을 달아주길 바란다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 Clean의 의미]]></title>
            <link>https://velog.io/@sugar_ghost/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Clean%EC%9D%98-%EC%9D%98%EB%AF%B8</link>
            <guid>https://velog.io/@sugar_ghost/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Clean%EC%9D%98-%EC%9D%98%EB%AF%B8</guid>
            <pubDate>Sat, 23 Nov 2024 14:33:44 GMT</pubDate>
            <description><![CDATA[<p>이클립스에서 작업을 저장하면 <code>Build Automatically</code>를 통해서 자동으로 반영이 되는데 가끔 반영이 안되는 경우</p>
<p>우린 Clean을 사용한다.</p>
<p>Maven 구성을 쓴다고 가정하면 Clean은 다음과 같이 구분 할 수 있다.</p>
<p>Maven - Clean
Project - Clean
Tomcat - Clean</p>
<h1 id="maven-clean">Maven Clean</h1>
<p>Maven 자체 빌드 생명주기에서 동작해 이전 빌드에서 생성된 <code>target</code> 디렉토리를 초기화함
주로 <code>.jar</code> 또는 <code>.war</code> 파일과 같은 컴파일된 출력물을 제거</p>
<h1 id="eclipse-project-clean">Eclipse Project Clean</h1>
<p>IDE에서 제공되는 기능으로 프로젝트 빌드 경로 내 파일을 삭제하고 재 컴파일함
IDE 내부 빌더를 사용해 생성된 클래스 파일, 기타 빌드 산출물을 관리하는 방법</p>
<h1 id="tomcat-clean">Tomcat Clean</h1>
<p>Maven, Elipse Clean과 다른 개념으로
Tomcat에서 관리하는 애플리케이션을 서버에서 실행하기 위한 컨테이너에 배포된 임시 파일, 캐시, 컴파일된 JSP 등을 삭제</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 DTO는 private인데 JSP는 그냥 접근한다.]]></title>
            <link>https://velog.io/@sugar_ghost/%EB%82%B4-DTO%EB%8A%94-private%EC%9D%B8%EB%8D%B0-JSP%EB%8A%94-%EA%B7%B8%EB%83%A5-%EC%A0%91%EA%B7%BC%ED%95%9C%EB%8B%A4</link>
            <guid>https://velog.io/@sugar_ghost/%EB%82%B4-DTO%EB%8A%94-private%EC%9D%B8%EB%8D%B0-JSP%EB%8A%94-%EA%B7%B8%EB%83%A5-%EC%A0%91%EA%B7%BC%ED%95%9C%EB%8B%A4</guid>
            <pubDate>Thu, 16 Feb 2023 01:53:56 GMT</pubDate>
            <description><![CDATA[<p>내겐 BoardDTO가 있다.</p>
<pre><code class="language-Java">public class BoardDTO {
    private int boardId;
    private String title;

    public int getArticleId() {
        return articleId;
      }

    public void setArticleId(int articleId) {
        this.articleId = articleId;
      }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
}
</code></pre>
<p>나는 이 DTO에 데이터를 담아 JSP에서 사용하기 위해
<code>request.setAttribute(&quot;boardDTO&quot;, boardDTO)</code>를 사용했다.</p>
<p>이때 JSP에서는 값을 가져와 보여주는 2가지 방법이 있다.</p>
<pre><code>${articleDTO.getTitle()}
${articleDTO.title}</code></pre><p><code>getTitle()</code>은 public이고 우리가 익숙한 메소드다.
<code>title</code>은 우리가 명시해주었지만 <code>private</code>다</p>
<p>어떻게 jsp는 <code>private</code> 변수에 접근이 가능한 걸까?</p>
<p>궁금해서 찾아봤지만 해답은 간단했다.
JSP는 DTO 구문을 해석해 변수명이 명시되면 해당 변수에 매칭되는 get 메소드를 호출시킨다
결국 title은 <code>getTitle()</code>로 변환되어 실행되는 것이다.</p>
<p>좀더 명확히는
JSP가 Java 객체의 속성에 액세스 할때 JavaBeans의 사양을 사용한다.
JavaBeans 사양에 따라 property를 해석하며 public getter가 있다고 판단하는 것이다</p>
<p>Java 객체가 setAttribute()로 설정되면 JSP 컨테이너는 원래 객체를 래핑하는 proxy object를 생성한다.
이 프록시 객체는 원본 객체에 대한 getter, setter 메소드에대한 접근을 제공한다.
즉 프록시 객체는 인터페이스 역할을 해준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JSP에서 UTF-8 인코딩하기(feat. include)]]></title>
            <link>https://velog.io/@sugar_ghost/JSP%EC%97%90%EC%84%9C-UTF-8-%EC%9D%B8%EC%BD%94%EB%94%A9%ED%95%98%EA%B8%B0feat.-include</link>
            <guid>https://velog.io/@sugar_ghost/JSP%EC%97%90%EC%84%9C-UTF-8-%EC%9D%B8%EC%BD%94%EB%94%A9%ED%95%98%EA%B8%B0feat.-include</guid>
            <pubDate>Mon, 13 Feb 2023 06:55:24 GMT</pubDate>
            <description><![CDATA[<h1 id="jsp에서-인코딩하기">JSP에서 인코딩하기</h1>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/27e7bf22-8b5f-470a-8023-7a65e42686b1/image.png" alt=""></p>
<p>여기 UTF-8로 돌아가는 JSP페이지가 있다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/9812eeae-5e19-41a7-a9d0-a1e5e42ad007/image.png" alt="">
한글 검색도 된다.</p>
<pre><code>List.jsp?searchWord=한글&amp;pageNum=1</code></pre><p>이 페이지에서는 한글을 처리하기 위해 다양한 노력을 한다</p>
<pre><code class="language-html">&lt;%@ page contentType=&quot;text/html; charset=UTF-8&quot; pageEncoding=&quot;UTF-8&quot; language=&quot;java&quot; %&gt;
// JSP 처리에 대한 설정
// contentType: JSP가 처리되고 생성할 문서에 대한 타입 지정
// charset: 브라우저가 받아볼 인코딩 형식 즉 생성될 문서의 인코딩
// pageEncoding: JSP 파일 자체 인코딩 방식

&lt;meta http-equiv=&quot;content-type&quot; content=&quot;text/html; charset=UTF-8&quot;&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
// 현재 html파일의 인코딩을 알려주는 태그
// 웹브라우저가 html파일을 화면에 그려주기 위해 처리시 어떤 문자 표현 방식을 사용하는지 알려주기 위해 지정
// 위에 두 코드는 동일한 역할을 가지며 버전 차이가 존재
// HTML 4.01: meta http-equiv 방식 사용
// HTML%: meta charset 방식 사용

request.setCharacterEncoding(&quot;UTF-8&quot;);
// JSP에서 값을 post 방식으로 서버에 전달할때 적용
// List에서 get 방식으로 검색하는 지금 예제에는 의미없지만 인코딩 설정할때 같이 많이 써서 넣음
// get 방식은 Tomcat에서 알아서 UTF-8 설정해줌</code></pre>
<p>만약 이 설정들이 없다면 어떻게 될까?</p>
<h2 id="인코딩-설정이-없다면">인코딩 설정이 없다면</h2>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/4bb40b48-883c-49b0-ab18-2ac7f3b49c0c/image.png" alt="">
전체적으로 난장판이 된다.
JSP의 인코딩 정보도, 브라우저가 받는 인코딩 정보도 다 사라졌다</p>
<p>사실 중요한건 
<code>&lt;%@ page contentType=&quot;text/html; charset=UTF-8&quot; pageEncoding=&quot;UTF-8&quot; language=&quot;java&quot; %&gt;</code>
이 구문 하나다.</p>
<p>해당 JSP 설정에 대한 구문이 생성될 html에 대한 인코딩 타입까지 포함하고 있기 때문이다.</p>
<p>하지만 어떤 일이 생길지 모르니 다른 설정도 같이 세트로 묶어두는게 좋다.</p>
<h2 id="include로-모듈화">include로 모듈화</h2>
<p>그렇다면 저 설정들을 모든 페이지에 공통적으로 반영하면 좋지 않을까?</p>
<p><code>&lt;%@ include file = &quot;페이지&quot; %&gt;</code>
JSP에서 include 하는 방법은 여러가지 있지만, 결과만 가져오냐 내용 자체를 합치냐 차이가 존재한다.
@ incloud 방식은 해당 파일 자체를 대상 파일에 합쳐버린다.
즉, include 페이지에 인코딩 설정을 넣고, 다른 jsp에 포함시키면, 인코딩 설정을 공통적으로 할 수 있지 않을까?</p>
<pre><code>// Encode.jsp 페이지
&lt;%@ page contentType=&quot;text/html; charset=UTF-8&quot; pageEncoding=&quot;UTF-8&quot; language=&quot;java&quot; %&gt;
&lt;meta http-equiv=&quot;content-type&quot; content=&quot;text/html; charset=UTF-8&quot;&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
&lt;%
  request.setCharacterEncoding(&quot;UTF-8&quot;);
%&gt;</code></pre><pre><code>// List.jsp
&lt;%@ include file=&quot;/common/Encode.jsp&quot; %&gt;</code></pre><p><img src="https://velog.velcdn.com/images/sugar_ghost/post/60fafde9-3e61-4fda-9458-d5e600497fe9/image.png" alt="">
대환장 파티가 펼쳐진다.</p>
<p>분명하게도 html에 인코딩을 망가졌다.
html을 처리하는 과정에 이 파일에 대한 인코딩을 결정하지 못한 것이다.</p>
<p>하지만 include에 page가 적용안된걸까?
테이블 내용을 바라보면 세종대왕이라는 한글이 보인다.
이전에 모든 내용이 망가지는 것과는 다른 결과다.</p>
<p>인코딩 설정이 영향을 미치지만, 제대로된 반영이 안되는 것으로 보인다.
어차피 결론은 존재한다.
인코딩 설정을 공통적으로 하고 싶다면 include 대신 Spring이나 servlet에서 설정하는 방안이 있다.</p>
<p>web.xml에 encodingfilter 방식, mvc-dispatcher-servlet.xml 수정 등 다양한 대안이 존재한다.</p>
<p>현재 JSP에 초점을 맞춰서 include 방식을 알아봤지만, 제대로된 반영이 안됨을 확인할 수 있다.
include 방식을 통한 모듈화는 html 관련 요소를 설정할때 사용하고, java 처리 관련 사항은 다른 다양한 대안이 존재한다.</p>
<p>@ page 설정은 JSP 파일마다 성실하게 하자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] 논리연산자는 삼항연산자보다 먼저 실행된다. ]]></title>
            <link>https://velog.io/@sugar_ghost/JS-%EB%85%BC%EB%A6%AC%EC%97%B0%EC%82%B0%EC%9E%90%EB%8A%94-%EC%82%BC%ED%95%AD%EC%97%B0%EC%82%B0%EC%9E%90%EB%B3%B4%EB%8B%A4-%EB%A8%BC%EC%A0%80-%EC%8B%A4%ED%96%89%EB%90%9C%EB%8B%A4</link>
            <guid>https://velog.io/@sugar_ghost/JS-%EB%85%BC%EB%A6%AC%EC%97%B0%EC%82%B0%EC%9E%90%EB%8A%94-%EC%82%BC%ED%95%AD%EC%97%B0%EC%82%B0%EC%9E%90%EB%B3%B4%EB%8B%A4-%EB%A8%BC%EC%A0%80-%EC%8B%A4%ED%96%89%EB%90%9C%EB%8B%A4</guid>
            <pubDate>Fri, 03 Feb 2023 09:10:29 GMT</pubDate>
            <description><![CDATA[<p>예제부터 보자</p>
<pre><code class="language-jsx">
console.log(true ? true : false &amp;&amp; false);
// 결과: true</code></pre>
<p>처음 로직을 짤때 내 생각은 이랬다.
&#39;삼항연산자로 앞에서 조건을 필터링하고 그 결과를 논리(&amp;&amp;) 연산자로 다시 검증해야지&#39;</p>
<p>위 로직이 내가 생각한대로 삼항연산자를 먼저 실행시키려면 <code>괄호()</code>를 이용해야한다.</p>
<pre><code class="language-jsx">true ? true : false &amp;&amp; false // true
(true ? true : false) &amp;&amp; false // false</code></pre>
<p>괄호를 치지 않는다면 다음과 같은 순서로 실행이 된다.</p>
<pre><code class="language-jsx">true ? true : (false &amp;&amp; false)
// false &amp;&amp; false =&gt; false
// 다음과 같이 변환됨 =&gt; 
true ? true : false // true</code></pre>
<p>mdn docs를 확인해보자
<a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#%ED%91%9C">연산자 우선순위</a>
<img src="https://velog.velcdn.com/images/sugar_ghost/post/fc66c20c-dfdd-45b0-b5e5-9d333b4568cd/image.png" alt=""></p>
<p>왼쪽에 번호는 실행 우선순위로 조건(삼항) 연산자의 우선순위는 3으로 논리 AND 연산자(5)보다 낮은 것을 확인할 수 있다.</p>
<p>연산자와 조건을 섞어 사용할때는 <code>괄호()</code>를 활용하거나 우선순위를 잘 고려하도록 하자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[절대 경로 잡아줬는데 vue 파일 대신 vue.js 파일 찾을때(Vue3 + Typescript +Vite)]]></title>
            <link>https://velog.io/@sugar_ghost/%EC%A0%88%EB%8C%80-%EA%B2%BD%EB%A1%9C-%EC%9E%A1%EC%95%84%EC%A4%AC%EB%8A%94%EB%8D%B0-vue-%ED%8C%8C%EC%9D%BC-%EB%8C%80%EC%8B%A0-vue.js-%ED%8C%8C%EC%9D%BC-%EC%B0%BE%EC%9D%84%EB%95%8CVue3-Typescript-Vite</link>
            <guid>https://velog.io/@sugar_ghost/%EC%A0%88%EB%8C%80-%EA%B2%BD%EB%A1%9C-%EC%9E%A1%EC%95%84%EC%A4%AC%EB%8A%94%EB%8D%B0-vue-%ED%8C%8C%EC%9D%BC-%EB%8C%80%EC%8B%A0-vue.js-%ED%8C%8C%EC%9D%BC-%EC%B0%BE%EC%9D%84%EB%95%8CVue3-Typescript-Vite</guid>
            <pubDate>Thu, 02 Feb 2023 09:25:06 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/a79692d4-49fb-4e60-bb9e-08af7b7c3a6b/image.png" alt=""></p>
<p>먼저 다음과 같이 <code>vite.config.ts</code>에서 절대 경로를 잡아준다.</p>
<pre><code class="language-tsx">  resolve: {
    alias: {
      &quot;@&quot;: fileURLToPath(new URL(&quot;./src&quot;, import.meta.url)),
      &quot;@components&quot;: fileURLToPath(
        new URL(&quot;./src/components&quot;, import.meta.url)
      ),
    },
  },</code></pre>
<p><code>tsconfig.json</code> 또는 <code>tsconfig.app.json</code>에도 다음과 같이 동일하게 경로를 잡아준다.</p>
<pre><code class="language-jsx">&quot;compilerOptions&quot;: {
    &quot;composite&quot;: true,
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;paths&quot;: {
      &quot;@/*&quot;: [&quot;./src/*&quot;],
      &quot;@components/*&quot;: [&quot;./src/components/*&quot;]
    }
  }</code></pre>
<p>그리고 import를 통해 경로를 찾으려 하지만 다음과 같은 오류가 발생한다
<code>Could not find a declaration file for module &#39;예시경로/MainHeader.vue&#39;. &#39;예시경로/MainHeader.vue.js&#39; implicitly has an &#39;any&#39; type.ts(7016)</code></p>
<p>vue 파일을 연결했지만, vue.js를 찾는다.
typescript 환경에서는 <code>&lt;script setup lang=&quot;ts&quot;&gt;</code>와 같이 <code>lang=&quot;ts&quot;</code>를 설정해줘야지 js 경로를 인식한다.
호출단과 호출되는 파일 모두에 lang 설정을 함으로써 해결할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vue3+Vite를 github page로 올려보자(github action과 함께!)]]></title>
            <link>https://velog.io/@sugar_ghost/Vue3Vite%EB%A5%BC-github-page%EB%A1%9C-%EC%98%AC%EB%A0%A4%EB%B3%B4%EC%9E%90github-action%EA%B3%BC-%ED%95%A8%EA%BB%98</link>
            <guid>https://velog.io/@sugar_ghost/Vue3Vite%EB%A5%BC-github-page%EB%A1%9C-%EC%98%AC%EB%A0%A4%EB%B3%B4%EC%9E%90github-action%EA%B3%BC-%ED%95%A8%EA%BB%98</guid>
            <pubDate>Tue, 31 Jan 2023 09:28:08 GMT</pubDate>
            <description><![CDATA[<h1 id="주의">주의</h1>
<p>본 예제에서는 기본적으로 [githubId].github.io가 이미 만들어졌다고 가정합니다.
예제에서는 [githubId].github.io/[repoName] 형식으로 github Page를 할당합니다.</p>
<p>Node.js와 Vue가 설치가 되있음을 전제합니다.</p>
<h2 id="프로젝트-init">프로젝트 init</h2>
<p><a href="https://v3-docs.vuejs-korea.org/guide/quick-start.html#with-build-tools">https://v3-docs.vuejs-korea.org/guide/quick-start.html#with-build-tools</a>
Vue 공식문서를 참조하여 커맨드로 초기 프로젝트를 설정
프로젝트 폴더를 생성하고자 하는 상위 폴더에서 다음의 커맨드를 입력</p>
<pre><code class="language-jsx">npm init vue@latest</code></pre>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/bb4bd138-4cd4-43b1-b1ce-6cbebd86c8c2/image.png" alt=""></p>
<p>프로젝트 초기화를 진행하면 세부 요소에 대해서 선택이 가능(상황과 선호에 맞춰서 자유롭게 설정)
<img src="https://velog.velcdn.com/images/sugar_ghost/post/9f82eee1-f063-4d52-8bb0-aba71784b4fb/image.png" alt=""></p>
<p>생성된 프로젝트 내부 구조를 확인하면 Webpack 대신 Vite를 사용함
<img src="https://velog.velcdn.com/images/sugar_ghost/post/4ab2722c-04e1-4cc2-80e3-f310f121343c/image.png" alt="">
배포 관련 설정을 위해 Vite.config.ts를 Open하고 base 내용을 추가
![]<img src="https://velog.velcdn.com/images/sugar_ghost/post/9e1ce066-16fb-4139-9ba1-12037f9343d1/image.png" alt="">
만약 <code>https://&lt;USERNAME&gt;.github.io/</code> 형태로 배포하는 경우는 생략하거나 기본값 <code>/</code>으로 지정하면 됨
<code>https://&lt;USERNAME&gt;.github.io/&lt;REPO&gt;/</code> 형태로 배포하는 경우 <code>/&lt;REPO&gt;/</code>로 지정
이 값은 추후에 생성할 github 레포지토리 명과 동일해야 하며, 추후에 배포될 하위 주소명과 동일해야함</p>
<p>이제 프로젝트에 대한 <code>npm install</code> 및 <code>lint</code>를 진행</p>
<pre><code class="language-bash"># 만들어진 프로젝트 명 따라서 진입 
cd pratice-vue
npm install
npm run lint</code></pre>
<h2 id="git-repo-생성">git repo 생성</h2>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/39c19f58-76f8-4613-a414-510003a3ab7a/image.png" alt=""></p>
<p>설치가 끝났다면 실제 github에 올리기 위해 github repository 생성
<img src="https://velog.velcdn.com/images/sugar_ghost/post/152059bf-73ca-4a84-8baf-71f63fe48b18/image.png" alt=""></p>
<p>Repository name는 base에 설정한 값과 일치해야함</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/2c16031b-c20a-4ef3-b608-3e88e4d7e07f/image.png" alt="">
Repository이 생성 되었다면 주소를 복사해 remote로 연결 진행
<img src="https://velog.velcdn.com/images/sugar_ghost/post/e1c1ae32-3b40-4957-b130-42451f6319e0/image.png" alt="">
<img src="https://velog.velcdn.com/images/sugar_ghost/post/1cb43c2f-0e2e-4ab8-a605-40f2722bb6e3/image.png" alt=""></p>
<pre><code class="language-bash"># git 레포지토리 초기화
git init
# 생성한 레포지토리를 remote로 연결
git remote add origin &lt;복사해둔 github repo 주소&gt;
# 현재 변경 내용을 저장
git add .
# 저장된 변경 내용을 commit해 반영
git commit -m &quot;커밋 메시지&quot;
# commit 내용을 연결해둔 remote로 전송
git push origin master</code></pre>
<h2 id="git-pages">git pages</h2>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/d2176fb7-1c9a-4af6-aa9d-c2658c98bd9c/image.png" alt=""></p>
<p>여기까지 완료하면 레포지토리가 잘 등록됨을 확인 할 수 있음
Setting을 클릭해 page 설정을 진행</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/e738fd9e-2755-4dc3-8340-f2346f102cec/image.png" alt="">
 왼쪽의 Pages를 클릭하고 설정 내용중 중앙에 Source(Deploy from a branch)를 클릭하면 GitHub Actions를 선택 가능</p>
<p> <img src="https://velog.velcdn.com/images/sugar_ghost/post/ebfaec7a-ded2-4d4c-829e-e91e78c40dfc/image.png" alt="">
Github Actions을 선택하면 구성이 바뀌며, <code>Static HTML</code>을 선택해 진행</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/5cad4fb5-bab0-4d6e-844f-d158fe3fe4d0/image.png" alt=""></p>
<p>Action workflow 등록을 위한 페이지가 열리며 내용을 다음과 같이 수정</p>
<pre><code class="language-yml"># Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages

on:
  # Runs on pushes targeting the default branch
  push:
    branches: [&quot;master&quot;]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# Allow one concurrent deployment
concurrency:
  group: &quot;pages&quot;
  cancel-in-progress: true

jobs:
  # Single deploy job since we&#39;re just deploying
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup Pages
        uses: actions/configure-pages@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 16
          # cache: &#39;npm&#39;

      - run: npm ci --legacy-peer-deps
      - run: npm run build
        env:
          CI: false
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v1
        with:
          # Upload entire repository
          path: &#39;./dist&#39;
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v1</code></pre>
<p>차이점을 비교해보면 다음과 같음
<img src="https://velog.velcdn.com/images/sugar_ghost/post/7bb096a2-0962-47c7-96d7-778375c8947c/image.png" alt=""></p>
<p>배포(deploy) 과정에서 node를 setup하고 build하는 과정을 추가,
이후 <code>upload-pages-artifact</code> 과정에서 배포할 대상 위치를 build 결과물이 있는 <code>./dist</code> 경로로 수정</p>
<p>내용 작성 후 commit을 반영해 파일을 생성</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/ec0eab13-dfb8-4beb-8f2b-ee3c61b0b919/image.png" alt=""></p>
<p>workflows가 반영 되면 <code>Actions</code> 탭에서 확인 가능
<img src="https://velog.velcdn.com/images/sugar_ghost/post/46349beb-b1e4-4e13-b95c-4f36fab44390/image.png" alt=""></p>
<p>deploy가 완료되면 <code>https://&lt;github아이디&gt;.github.io/&lt;repo이름&gt;/</code> 주소로 접근해서 확인 가능</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/a8b50c07-174e-4b36-9e6b-30101e182d03/image.png" alt=""></p>
<p>Vue init에서 제공되는 페이지가 표시되며, 이후 commit시 build한 결과물이 반영됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GO의 병행성, 병렬성]]></title>
            <link>https://velog.io/@sugar_ghost/GO%EC%9D%98-%EB%B3%91%ED%96%89%EC%84%B1-%EB%B3%91%EB%A0%AC%EC%84%B1</link>
            <guid>https://velog.io/@sugar_ghost/GO%EC%9D%98-%EB%B3%91%ED%96%89%EC%84%B1-%EB%B3%91%EB%A0%AC%EC%84%B1</guid>
            <pubDate>Mon, 30 Jan 2023 08:29:38 GMT</pubDate>
            <description><![CDATA[<p>Go 언어는 <strong>병행성(Concureency, 동시성)</strong>을 제공함
<strong>병행성</strong>과 <strong>병렬성(Paraleelism)</strong>은 서로 다른 개념</p>
<p><strong>병행성</strong></p>
<ul>
<li>동시 처리의 <strong>논리적인 개념(구조, 설계)</strong></li>
<li>단일(또는 멀티) 코어에서 스레드 여러 개가 시간을 쪼개어 <strong>순차적(시분할)으로 실행</strong>됨</li>
<li><strong>사람이 느끼기에 동시</strong>로 느껴짐</li>
</ul>
<p><strong>병렬성</strong></p>
<ul>
<li>동시 처리의 <strong>물리적인 개념(실행</strong>)</li>
<li>작업을 여러 CPU에 나눠서 <strong>동시에 처리하는 상태</strong></li>
<li>실제로도 <strong>실행이 동시적인 시점</strong>을 가짐</li>
</ul>
<p>동시성(병행성)은 두 개 이상의 작업이 동시에 진행되도록 <strong>프로그램을 구성하는 것</strong>과 관련됨
<strong>병렬성은 두 개 이상의 작업이 동시에 실행</strong>되도록 함
<strong>병렬 처리에는 둘 이상의 프로세서나 스레드가 필요</strong>하지만 동시성은 필요 없음</p>
<p>다음은 <strong>동시성</strong>의 예제</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/8daf0df7-fa0a-40f5-931f-83c842548529/image.png" alt=""></p>
<p>2개의 Task를 Processor에서는 번갈아 가면서 진행함
두 Task 사이에 전환 시간은 매우 짧음으로 <strong>인간이 느끼기에 동시에 실행</strong>된다고 느껴짐</p>
<p>다음은 <strong>병행성</strong>에 예제</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/3759192d-6058-4f2d-b7b6-4c0d34b56929/image.png" alt=""></p>
<p>여러개의 프로세스를 통해 여러개의 문제를 동시에 실행함(<strong>물리적, 시간적으로 동시</strong>)
병렬성을 사용해 복잡한 한개의 문제를 작은 조각으로 나눠 동시에 실행해 <strong>전체 문제가 해결되는 속도를 높일 수 있음</strong>
만약 단 1개의 프로세서만 존재한다면, <strong>순차적</strong>으로 처리됨</p>
<p><strong>Go의 동시성</strong></p>
<p>Go에서 동시성을 활용하기 위해 <strong>고루틴</strong>을 사용함
고루틴은 <strong>자체 스레드 또는 프로세스에서 실행되지 않음</strong>
Go 런타임은 고루틴을 계속 실행하기 위해 필요에 따라 스레드에 고루틴을 <strong>동적으로 다중화</strong> 함</p>
<p>다음은 동시성을 테스트하기 위한 예제</p>
<pre><code class="language-go">package main
import (
  &quot;fmt&quot;
  &quot;time&quot;
)
func numSearch(targetVal int) {
  searchList := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
  time.Sleep((time.Duration(targetVal) * 100) * time.Millisecond)
  for _, num := range searchList {
    if num == targetVal {
      fmt.Printf(&quot;I found the targetVal: %d \n&quot;, targetVal)
      return
    }
  }
  fmt.Println(&quot;targetVal not found :(&quot;)
}
func main() {
  numSearch(10)
  numSearch(1)
  // time.Sleep(10000 * time.Millisecond) 
  fmt.Println(&quot;The program has finished executing.&quot;)
}

// 실행 결과
I found the targetVal: 10
I found the targetVal: 1
The program has finished executing.</code></pre>
<p>마지막 두번쨰 줄 <code>time.Sleep</code>을 주석 상태로 실행하면 코드가 <strong>순차적으로 실행</strong>됨을 확인 가능
<code>numSearch</code> 함수를 확인하면 <code>time.Duration</code>에 넘겨지는 targetVal이 높을 수록 나중에 실행됨을 알 수 있음
즉, 매개변수를 1을 전달할 때보다 10을 전달할 때 실행하는 데 더 오래걸림
하지만 <strong>함수 호출이 순차적</strong>으로 실행 되기 때문에 두 번째 호출은 첫 번째 호출이 완료될 떄까지 시작되지 않음</p>
<p>만약 <code>numSearch</code> 함수를 고루틴으로 바꾸면 다른 결과가 나옴</p>
<pre><code class="language-go">package main
import (
  &quot;fmt&quot;
  &quot;time&quot;
)
func numSearch(targetVal int) {
  searchList := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
  time.Sleep((time.Duration(targetVal) * 100) * time.Millisecond)
  for _, num := range searchList {
    if num == targetVal {
      fmt.Printf(&quot;I found the targetVal: %d \n&quot;, targetVal)
      return
    }
  }
  fmt.Println(&quot;targetVal not found :(&quot;)
}
func main() {
  go numSearch(10) // 고루틴으로 변경
  go numSearch(1) // 고루틴으로 변경
  time.Sleep(10000 * time.Millisecond) // 주석 해제
  fmt.Println(&quot;The program has finished executing.&quot;)
}

// 실행 결과
I found the targetVal: 1
I found the targetVal: 10
The program has finished executing.</code></pre>
<p>결과를 보면 두번째로 호출된 <code>numSearch(1)</code>의 결과가 먼저 출력되었는데 고루틴을 통해 첫 번째 호출인 <code>numSearch(10)</code>의 결과를 기다리지 않았기 때문</p>
<p>위는 간단한 예제로 동시성의 이점을 살릴 수 있는 상황중 하나를 예시로 설명하고자 함
사용자 요청을 처리하기 위한 서버가 여러개로 분산화 되어있는 상황
순차적으로 처리하는 경우, 1번 서버, 2번  서버에 요청을 보내고 대기하면서 해당 서버가 요청이 처리 가능한 판별
<strong>순차적 방식은 효율적인 선택을 보장하지 못함</strong>
처리 가능한 느린 2번 서버와 빠른 5번 서버가 존재하는 경우, 먼저 응답을 받은 2번 서버를 선택</p>
<p>동기적으로 처리하는 경우 만약 10개의 서버가 존재한다면, 10개 서버에 동시에 요청을 보내고 대기,
이후 응답이 돌아온 서버 중 사용자 요청을 처리 가능하면서 가장 빨리 응답이 되돌아온 서버를 선택하고, 기존 미해결 프로세스(다른 서버에 요청을 보내고 대기하는 등..)를 종료 가능</p>
<p>동시성은 작업을 수행하기 위해 병렬성을 사용할 수 있지만 <strong>병렬성이 동시성의 궁극적인 목표는 아님</strong>
<strong>동시성</strong>은 독립적인 여러 작업을 한 번에 수행하도록 <strong>설계</strong>하는 쪽에 무게를 둔 개념
<strong>병렬성</strong>은 하나의 작업을 설계된 대로 분할해서 동시에 <strong>수행</strong>하는 쪽에 무게를 둔 개념</p>
<blockquote>
<p><strong>Concurrency is about dealing with lots of things at once.
Parallelism is about doing lots of things at once</strong></p>
<p>동시성은 여러 가지 일을 한 번에 <strong>처리</strong> 하는 것
병렬성은 여러 가지 일을 한 번에 <strong>수행</strong> 하는 것</p>
<ul>
<li>Go Concurrency Patterns, Rob Pike -</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Javascript for in, of 차이]]></title>
            <link>https://velog.io/@sugar_ghost/Javascript-for-in-of-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@sugar_ghost/Javascript-for-in-of-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Thu, 26 Jan 2023 03:55:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/72e50ff3-630d-4321-b896-456a712619f6/image.png" alt=""></p>
<h2 id="forin">for...in</h2>
<p><code>for...in</code> 명령문은 상속된 열거 가능한 속성들을 포함해서 객체에서 문자열로 지정된 모든 열거 가능한 속성에 대해 반복</p>
<pre><code class="language-jsx">var obj = {
    &#39;a&#39;:1,
    &#39;b&#39;:2,
    &#39;c&#39;:3,
    1:4
}
var arr = [1,2,3]

console.log(obj)
console.log(arr)

console.log(&quot;print for in obj&quot;)
for (var item in obj) {
    console.log(item)
}

console.log(&quot;print for in arr&quot;)
for (var item in arr) {
    console.log(item)
}</code></pre>
<p>실행 결과는 다음과 같음</p>
<pre><code class="language-bash">{ &#39;1&#39;: 4, a: 1, b: 2, c: 3 }
[ 1, 2, 3 ]
print for in obj
1
a
b
c
print for in arr
0
1
2</code></pre>
<p>예제에서 보다시피 obj에 경우 for in 방식 사용시 문자열로 구성된 Key 값을 출력
숫자 1의 경우 obj에 포함되는 순간 문자열로 변경되어 문자열 key로 출력
arr에 경우 내부 value 대신 0부터 시작하는 index 값을 출력</p>
<p><code>for...in</code>은  열거 가능한 속성을 타겟으로 하기 때문에, array(배열)의 값이 아닌 열거 가능한 index를 기준으로 함</p>
<h2 id="forof">for...of</h2>
<p><code>for...of</code> 명령문은 반복가능(Iteration)한 객체에 대해서 반복하고 각 개별 속성값에 대해 루프를 생성
여기서 객체는 <code>Array</code>, <code>Map</code>, <code>Set</code>, <code>String</code>, <code>TypedArray</code>, <code>arguments</code>가 포함됨</p>
<pre><code class="language-jsx">var arr = [1,2,3]

console.log(arr)

console.log(&quot;print for of arr&quot;)
for (var item of arr) {
    console.log(item)
}</code></pre>
<p>실행 결과는 다음과 같음</p>
<pre><code class="language-bash">[ 1, 2, 3 ]
print for of arr
1
2
3</code></pre>
<p><code>for...in</code>과 다르게 출력의 대상이 index가 아닌 개별 속성값임
<code>obj</code>는 이번 예제에 포함되지 않았는데, 객체는 반복가능(<code>iterator</code>) 속성이 없기 때문에 오류가 발생</p>
<pre><code class="language-bash">
TypeError: obj is not iterable</code></pre>
<p>obj와 비슷한 Map 구조를 사용하면 <code>for...of</code>를 사용가능</p>
<pre><code class="language-jsx">let iterable = new Map([[&quot;a&quot;, 1], [&quot;b&quot;, 2], [&quot;c&quot;, 3]]);

for (let entry of iterable) {
  console.log(entry);
}

for (let [key, value] of iterable) {
  console.log(key,&quot;:&quot;,value);
}</code></pre>
<p>실행 결과는 다음과 같음</p>
<pre><code class="language-bash">[ &#39;a&#39;, 1 ]
[ &#39;b&#39;, 2 ]
[ &#39;c&#39;, 3 ]
a : 1
b : 2
c : 3</code></pre>
<p>Map에 경우 추출한 값에 Key, Value의 맵구조를 보유하고 있음
받는 변수의 개수를 2개로 나누면, Key와 Value가 별도로 대입되는 구조를 가짐</p>
<h2 id="차이">차이</h2>
<p><code>for...of</code>와 <code>for...in</code>에 차이를 둔다면 <code>배열</code>과 <code>객체</code>라는 사용 대상의 차이를 둘 수 있음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vue3 라이플 사이클]]></title>
            <link>https://velog.io/@sugar_ghost/Vue3-%EB%9D%BC%EC%9D%B4%ED%94%8C-%EC%82%AC%EC%9D%B4%ED%81%B4</link>
            <guid>https://velog.io/@sugar_ghost/Vue3-%EB%9D%BC%EC%9D%B4%ED%94%8C-%EC%82%AC%EC%9D%B4%ED%81%B4</guid>
            <pubDate>Wed, 25 Jan 2023 14:50:26 GMT</pubDate>
            <description><![CDATA[<h1 id="라이프-사이클">라이프 사이클</h1>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/30825477-45c3-4742-99bf-9eccb931c301/image.png" alt=""></p>
<p>Vue3에는 다음의 main hook이 존재하며 다음의 순서를 가짐
<code>beforeCreate</code> -&gt; <code>created</code> -&gt; <code>beforeMount</code> -&gt; <code>mounted</code> -&gt;
<code>beforeUpdate</code> -&gt; <code>updated</code> -&gt; <code>beforeUnmount</code> -&gt;<code>unmounted</code></p>
<h2 id="beforecreate">beforeCreate</h2>
<p>다른 hook 처리전 인스턴스가 초기화될 때 호출됨
<code>data()</code>와 <code>computed</code> hook이 호출하기 전에 호출됨
<code>beforeCreate</code> 호출 후 Options API에 대한 초기화가 진행됨</p>
<h2 id="created">created</h2>
<p>인스턴스가 모든 state 관련 options(예, Data(), computed)을 처리한 후에 호출 됨
<code>created</code> 후 <code>beforeMount</code>를 호출하기 전에 컴파일 된 템플릿에 여부를 확인해 템플릿을 컴파일함</p>
<h2 id="beforemount">beforeMount</h2>
<p>컴포넌트가 마운팅 되기 전에 호출됨
컴포넌트가 관련된 state에 대한 설정을 마쳤지만, DOM node가 생성되기 전 단계
<code>beforeMount</code> 이후 처음으로 DOM render를 실행하고 DOM node를 구성</p>
<h2 id="mounted">mounted</h2>
<p>컴포넌트와 인스턴스가 마운트 된 후 호출 됨
컴포넌트의 렌더링 된 DOM 요소에 접근해야할 때 사용
<code>mounted</code>가 실행된 이후는 데이터 변화에 따른 <code>re-render</code>에 따라서 다른 hook을 호출함</p>
<h2 id="beforeupdate">beforeUpdate</h2>
<p>데이터 변화에 따라 <code>re-render</code> 및 <code>patch</code>가 발생할 때 컴포넌트가 DOM tree를 업데이트하기 직전(즉 반영하기 전)에 호출됨</p>
<p>Vue가 DOM을 업데이트 하고 반영되기 전에 DOM에 상태에 접근하거나 전처리를 하는데 사용 가능</p>
<h2 id="updated">updated</h2>
<p><code>beforeUpdate</code> 처리 후 Vue가 DOM tree를 업데이트 한 이후에 호출 됨
상태 변화가 반영 된 이후에 호출되기 때문에 <code>updated</code>에서 DOM을 조작하거나 update 하는 방법은 권장되지 않음
만일 <code>updated</code> 내부에서 DOM을 변경하거나 조작하는 경우 렌더링이 중복될 수 있음</p>
<h2 id="beforeunmount">beforeUnmount</h2>
<p>컴포넌트가 unmount 되기 직전에 호출됨
컴포넌트 제거 전에 정리 작업에 유용한 훅
<code>interval</code>, <code>event listener</code> 등의 의존관계를 정리하는데 좋음</p>
<h2 id="unmounted">unmounted</h2>
<p>컴포넌트가 마운트 해제/제거 된 이후에 호출되는 라이프 사이클의 마지막 단계
컴포넌트에 내부에 존재하던 변수는 이 단계에서 사용 불가능함</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[nestjs mysql 연결 정보 숨기기(.env)]]></title>
            <link>https://velog.io/@sugar_ghost/nestjs-mysql-%EC%97%B0%EA%B2%B0-%EC%A0%95%EB%B3%B4-%EC%88%A8%EA%B8%B0%EA%B8%B0.env</link>
            <guid>https://velog.io/@sugar_ghost/nestjs-mysql-%EC%97%B0%EA%B2%B0-%EC%A0%95%EB%B3%B4-%EC%88%A8%EA%B8%B0%EA%B8%B0.env</guid>
            <pubDate>Tue, 15 Nov 2022 11:18:56 GMT</pubDate>
            <description><![CDATA[<p> 먼저 구성 패키지는 다음과 같다.
 @nestjs/config: nest에서 환경 설정을 효율적으로 하기 위해 제공하는 패키지
 cross-env: 동적으로 환경변수를 등록하기 위한 패키지
 joi: 변수의 타입, 스키마 등의 유효성 검증을 위한 패키지
 typeorm: 객체 관계형 매퍼 라이브러리로 객체와 데이터베이스를 연결해줌</p>
<p> 다음 명령어로 필요한 패키지를 다운받도록 하자
 <code>npm install --save @nestjs/config @nestjs/typeorm typeorm mysql2 joi cross-env</code></p>
<p> 먼저 <code>package.json</code>에서 구동을 위한 <code>scripts</code> 부분을 수정한다.</p>
<pre><code class="language-tsx">// package.json
    &quot;start:dev&quot;: &quot;cross-env NODE_ENV=dev nest start --watch&quot;,
    &quot;start:debug&quot;: &quot;cross-env NODE_ENV=dev nest start --debug --watch&quot;,
    &quot;start:prod&quot;: &quot;cross-env NODE_ENV=prod node dist/main&quot;,</code></pre>
<p> <code>cross-env</code>를 사용해 <code>NODE_ENV</code> 정보를 설정한다.</p>
<p> NODE_ENV에 설정한 dev, prod(이름은 자유롭게 줘도 됨)에 맞춰서 
 .env파일을 각각 생성한다.
 설정한 이름에 맞춰서 네이밍을 줘야하며 예시로 .dev.env와 .prod.env를 생성하고 내용은 다음과 같이 설정하자</p>
<pre><code class="language-tsx">// 아래 내역은 본인 mysql 환경에 맞춰서 변경
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=user
DB_PASSWD=password
DB_DATABASE=db</code></pre>
<p> 생성된 .env는 <code>gitignore</code>에 등록해 잘못 공유되지 않도록 설정한다.</p>
<pre><code class="language-txt"> # 아래 내용 추가하기
*.env</code></pre>
<p> 이제 등록한 .env 파일을 불러와야 하며, <code>app.module.ts</code>을 설정해보자</p>
<pre><code class="language-tsx">import { Module } from &#39;@nestjs/common&#39;;
import { TypeOrmModule } from &#39;@nestjs/typeorm&#39;;
import { ConfigModule } from &#39;@nestjs/config&#39;;
import { AppController } from &#39;./app.controller&#39;;
import { AppService } from &#39;./app.service&#39;;
import * as Joi from &#39;joi&#39;;

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: process.env.NODE_ENV == &#39;dev&#39; ? &#39;.dev.env&#39; : &#39;.prod.env&#39;,
      isGlobal: true,
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid(&#39;dev&#39;, &#39;prod&#39;).required(),
        DB_HOST: Joi.string().required(),
        DB_PORT: Joi.string().required(),
        DB_USERNAME: Joi.string().required(),
        DB_PASSWD: Joi.string().required(),
        DB_DATABASE: Joi.string().required(),
      }),
    }),
    TypeOrmModule.forRoot({
      type: &#39;mysql&#39;,
      host: process.env.DB_HOST,
      port: +process.env.DB_PORT,
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWD,
      database: process.env.DB_DATABASE,
      entities: [__dirname + &#39;/../**/*.entity.{js.ts}&#39;],
      synchronize: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
</code></pre>
<p>조금씩 나누어 알아보자</p>
<pre><code class="language-tsx">// 설정을 하는데 중요한 모듈은 아래와 같다.
import { TypeOrmModule } from &#39;@nestjs/typeorm&#39;; // DB 연결을 위한 모듈
import { ConfigModule } from &#39;@nestjs/config&#39;; // config 설정을 위한 모듈</code></pre>
<p>ConfigModule은 환경변수 설정을 위해 사용한다.</p>
<pre><code class="language-tsx">    ConfigModule.forRoot({
      // 환경 설정을 가져올 경로를 뜻하며, package script에서 설정한 NODE_ENV 내용에 따라 .dev.env를 가져올지, .prod.env를 가져올지 결정
      envFilePath: process.env.NODE_ENV == &#39;dev&#39; ? &#39;.dev.env&#39; : &#39;.prod.env&#39;,
      isGlobal: true,
      // 가져올 환경설정에 대한 유효성 검사 과정
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid(&#39;dev&#39;, &#39;prod&#39;).required(),
        DB_HOST: Joi.string().required(),
        DB_PORT: Joi.string().required(),
        DB_USERNAME: Joi.string().required(),
        DB_PASSWD: Joi.string().required(),
        DB_DATABASE: Joi.string().required(),
      }),
    }),
</code></pre>
<p>TypeOrmModule은 실질적으로 mysql과 연결하는데 사용되는 모듈로, 환경변수로 등록된 연결 정보를 참조해서 넣어준다.</p>
<pre><code class="language-tsx">    TypeOrmModule.forRoot({
      type: &#39;mysql&#39;,
      // .env 파일에서 명시한 네이밍을 이용해 연결 정보를 가져온다.
      host: process.env.DB_HOST,
      port: +process.env.DB_PORT,
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWD,
      database: process.env.DB_DATABASE,
      entities: [__dirname + &#39;/../**/*.entity.{js.ts}&#39;],
      synchronize: true,
    }),</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[typesciprt 에러 정리]]></title>
            <link>https://velog.io/@sugar_ghost/typesciprt-%EC%97%90%EB%9F%AC-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@sugar_ghost/typesciprt-%EC%97%90%EB%9F%AC-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 07 Nov 2022 14:50:54 GMT</pubDate>
            <description><![CDATA[<h3 id="ts1002">TS1002</h3>
<p><code>error TS1002: Unterminated string literal.</code>
단순하게 문자열을 적고 마지막에 &#39; 또는 &quot;을 사용해 닫지 않으면 발생
종종 백틱을 쓰는걸 까먹고 여러줄에 쓰다가 발생하는 경우가 있음</p>
<pre><code class="language-tsx">// 에러남
const text = &#39;텍스트

// 좋은 예
const text1 = &#39;잊지말고 닫기&#39;;

const text2 = &#39;여러줄은&#39; +
      &#39;꼬박꼬박 닫고 넘기기&#39;;
const text3 = `편하게
    백틱 사용하기`;
</code></pre>
<h3 id="ts1016">TS1016</h3>
<p><code>error TS1016: A required parameter cannot follow an optional parameter.</code>
선택적 매개변수를 필수 매개변수 앞으로 두면 보게되는 에러
컴포넌트에 들어가는 매개변수를 너무 늘리다가 정신 놓으면 보게됨</p>
<pre><code class="language-tsx">// 나쁜 예
function fun1(v1: string, v2?: string, v3: number) {
  // ...
}
// 좋은 예
function fun2(v1: string, v2?: string, v3?: number) {
  // ...
}

function fun3(v1: string, v3: number, v2?: string) {
  // ...
}</code></pre>
<h3 id="ts1109">TS1109</h3>
<p><code>error TS1109: Expression expected.</code>
표현식과 명령문을 혼동하거나 변수에 담아야 할 것을 헷갈리면 발생</p>
<pre><code class="language-tsx">// 나쁜 예
const data = throw new Error(&#39;Data Missing&#39;);

// 좋은 예
const data = undefined;
if (!data) {
  throw new Error(&#39;Data Missing&#39;);
}</code></pre>
<h3 id="ts2304">TS2304</h3>
<p><code>error TS2304: Cannot find name ‘world’.</code>
알수 없는 런타임 환경이나 다른 라이브러리 접근시 발생 가능
TypeScript가 알 수 있도록 주변 환경을 선언해주는 방식 필요</p>
<pre><code class="language-tsx">// 에러 발생 가능
console.log(world.name);

// 해결
declare var world: {
  name: string;
};

console.log(world.name);</code></pre>
<h3 id="ts2322">TS2322</h3>
<p><code>error TS2322: Type ‘string’ is not assignable to type ‘number’.</code>
반환값 지정하고 number 계산시켰는데 백틱을 사용해 조합하는 경우 종종 예외처리를 놓치는 에러</p>
<pre><code class="language-tsx">// 나쁜 예
export function add(a: number, b: number): number {
  return `${a + b}`;
}
// 좋은 예
export function add(a: number, b: number): number {
  return parseInt(`${a + b}`, 10);
}</code></pre>
<h3 id="ts2345">TS2345</h3>
<p><code>error TS2345: Argument of type ‘number‘ is not assignable to parameter of type ‘TimerHandler‘.</code>
에러랑 연관 깊은 상황은 아니지만, setTimeout 사용시 첫번째 매개변수를 함수로 다이렉트로 꽃아서 발생함 함수 실행 결과인 계산된 number가 반환되고, setTimeout은 callback을 원하니 타입이 일치하지 않아 발생</p>
<pre><code class="language-tsx">function add(a: number, b: number): number {
  return a + b;
}

// 나쁜 예
setTimeout(add(1000, 337), 5000);
// 좋은 예
setTimeout(() =&gt; add(1000, 337), 5000);</code></pre>
<h3 id="ts2531">TS2531</h3>
<p><code>error TS2531: Object is possibly ‘null’.</code>
가능성을 고려해 null일 수 있다는 의견 제시하는 에러
예외처리를 해줘야함</p>
<pre><code class="language-tsx">type Data = {
  value1: {
    value2: string;
  } | null
}

// 나쁜 예
function checkData(data: Data): void {
  console.log(data.value1.value2);
}

// 좋은 예
function checkData(data: Data): void {
  console.log(data.value1?.value2);
}</code></pre>
<h3 id="ts2532">TS2532</h3>
<p><code>error TS2532: Object is possibly ‘undefined’.</code>
가능성을 고려해 undefined일 수 있다는 의견 제시하는 에러
예외처리를 해줘야함</p>
<pre><code class="language-tsx">type Data = {
  value: string;
}

// 나쁜 예
function checkData(data?: Data): void {
  console.log(data.value);
}
// 좋은 예

function checkData(data?: Data): void {
  if(data)
      console.log(data.value);
}

function checkData(data?: Data): void {
  console.log(data?.value);
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[javascript를 이용해 mouse로 html 요소 drag하기(코드 저장용)]]></title>
            <link>https://velog.io/@sugar_ghost/javascript%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-mouse%EB%A1%9C-html-%EC%9A%94%EC%86%8C-drag%ED%95%98%EA%B8%B0%EC%BD%94%EB%93%9C-%EC%A0%80%EC%9E%A5%EC%9A%A9</link>
            <guid>https://velog.io/@sugar_ghost/javascript%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-mouse%EB%A1%9C-html-%EC%9A%94%EC%86%8C-drag%ED%95%98%EA%B8%B0%EC%BD%94%EB%93%9C-%EC%A0%80%EC%9E%A5%EC%9A%A9</guid>
            <pubDate>Thu, 03 Nov 2022 08:53:17 GMT</pubDate>
            <description><![CDATA[<p>저번에 htmldom을 참조해 element의 마우스를 통한 크기 조절을 진행했다.
이번에는 위치에 대한 이벤트가 필요해 정리하기로 했다.</p>
<h3 id="마우스로-drag-하기">마우스로 drag 하기</h3>
<p><strong>HTML</strong></p>
<pre><code class="language-html">&lt;div class=&quot;container&quot;&gt; 
  &lt;div class=&quot;draggable&quot; id=&quot;dragMe&quot;&gt;Drag me&lt;/div&gt;
&lt;/div&gt;</code></pre>
<p><strong>CSS</strong></p>
<pre><code class="language-css">.container {
  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;

  /* 높이 지정 */
  min-height: 32rem;
}
.draggable {
  /* 커서를 이동형식으로 주고, 위치를 절대형식으로 지정 */
  cursor: move;
  position: absolute;
  user-select: none;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;

  /* 디자인 */
  border: 1px solid #cbd5e0;
  height: 8rem;
  width: 8rem;
}</code></pre>
<p><strong>JavaScript</strong></p>
<pre><code class="language-javascript">
document.addEventListener(&#39;DOMContentLoaded&#39;, function () {
  // 마우스의 위치값 저장
  let x = 0;
  let y = 0;

  // 대상 Element 가져옴
  const ele = document.getElementById(&#39;dragMe&#39;);

  // 마우스 누른 순간 이벤트
  const mouseDownHandler = function (e) {
    // 누른 마우스 위치값을 가져와 저장
    x = e.clientX;
    y = e.clientY;

    // 마우스 이동 및 해제 이벤트를 등록
    document.addEventListener(&#39;mousemove&#39;, mouseMoveHandler);
    document.addEventListener(&#39;mouseup&#39;, mouseUpHandler);
  };

  const mouseMoveHandler = function (e) {
    // 마우스 이동시 초기 위치와의 거리차 계산
    const dx = e.clientX - x;
    const dy = e.clientY - y;

    // 마우스 이동 거리 만큼 Element의 top, left 위치값에 반영
    ele.style.top = `${ele.offsetTop + dy}px`;
    ele.style.left = `${ele.offsetLeft + dx}px`;

    // 기준 위치 값을 현재 마우스 위치로 update
    x = e.clientX;
    y = e.clientY;
  };

  const mouseUpHandler = function () {
    // 마우스가 해제되면 이벤트도 같이 해제
    document.removeEventListener(&#39;mousemove&#39;, mouseMoveHandler);
    document.removeEventListener(&#39;mouseup&#39;, mouseUpHandler);
  };

  ele.addEventListener(&#39;mousedown&#39;, mouseDownHandler);
});</code></pre>
<p>!codepen[sugarghost/embed/qBKZJOo?default-tab=html%2Cresult]</p>
<p><a href="https://htmldom.dev/make-a-draggable-element/">출처</a></p>
<h3 id="drag-영역-나누기">drag 영역 나누기</h3>
<p>단순히 위치만 옮겨다니는 Drag 대신 영역을 지정해 붙는 형식이 필요하다
<strong>HTML</strong></p>
<pre><code class="language-html">&lt;div style=&quot;align-items: center; display: flex; justify-content: center; padding: 4rem 0&quot;&gt;
  &lt;div id=&quot;list&quot;&gt;
    &lt;div class=&quot;draggable&quot;&gt;A&lt;/div&gt;
    &lt;div class=&quot;draggable&quot;&gt;B&lt;/div&gt;
    &lt;div class=&quot;draggable&quot;&gt;C&lt;/div&gt;
    &lt;div class=&quot;draggable&quot;&gt;D&lt;/div&gt;
    &lt;div class=&quot;draggable&quot;&gt;E&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
<p><strong>CSS</strong></p>
<pre><code class="language-css">.draggable {
  /* 커서를 이동형식으로 줌*/
  cursor: move;
  margin-bottom: 1rem;
  user-select: none;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;

  /* 크기 지정 */
  height: 4rem;
  width: 16rem;

  /* Misc */
  border: 1px solid #cbd5e0;
}
.placeholder {
  background-color: #edf2f7;
  border: 2px dashed #cbd5e0;
  margin-bottom: 1rem;
}</code></pre>
<p><strong>JavaScript</strong></p>
<pre><code class="language-javascript">
document.addEventListener(&#39;DOMContentLoaded&#39;, function () {
  // list div element를 가져옴
  const list = document.getElementById(&#39;list&#39;);

  let draggingEle;
  let placeholder;
  let isDraggingStarted = false;

  // 마우스 위치값을 위해 선언
  let x = 0;
  let y = 0;

  // 노드의 위치를 바꾸는 함수
  const swap = function (nodeA, nodeB) {
    // 상위 컨테이너 요소를 가져옴
    const parentA = nodeA.parentNode;
    // nodeA에 형제(다음 요소)가 nodeB라면 nodeA, 아닌 경우 다음 요소를 담음
    // sibling에서 조건처리하는 이유는 placeholder와 drag element의 위치를 조정하기 위해서
    const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling;

    // nodeA를 nodeB의 뒤로 삽입
    nodeB.parentNode.insertBefore(nodeA, nodeB);

    // nobeB를 nodeA에 형제(다음요소) 뒤에 넣음
    parentA.insertBefore(nodeB, siblingA);
  };

  // nodeA가 nodeB 위에 존재하는지 확인하는 함수
  const isAbove = function (nodeA, nodeB) {
    // 각 node의 위치정보를 가져옴
    const rectA = nodeA.getBoundingClientRect();
    const rectB = nodeB.getBoundingClientRect();

    // top을 기준으로 A가 B보다 작으면 화면 위치상 A는 B보다 상위에 존재함으로 above가 성립
    return rectA.top + rectA.height / 2 &lt; rectB.top + rectB.height / 2;
  };

  const mouseDownHandler = function (e) {
    // 현재 이벤트가 발생한 element를 저장
    draggingEle = e.target;

    // 선택된 element의 위치를 중심으로 마우스 클릭 위치를 계산
    const rect = draggingEle.getBoundingClientRect();
    x = e.pageX - rect.left;
    y = e.pageY - rect.top;

    // mouse 움직임, 해제 이벤트 적용
    document.addEventListener(&#39;mousemove&#39;, mouseMoveHandler);
    document.addEventListener(&#39;mouseup&#39;, mouseUpHandler);
  };

  const mouseMoveHandler = function (e) {
    // 현재 이벤트가 발생한 element의 위치 정보 가져옴
    const draggingRect = draggingEle.getBoundingClientRect();

    // 첫 움직임인 경우 적용
    if (!isDraggingStarted) {
      isDraggingStarted = true;

      // 선택한 element가 움직이는 동안 표시할 placeholder element를 생성
      placeholder = document.createElement(&#39;div&#39;);
      placeholder.classList.add(&#39;placeholder&#39;);
      draggingEle.parentNode.insertBefore(placeholder, draggingEle.nextSibling);
      placeholder.style.height = `${draggingRect.height}px`;
    }

    // 드래그 하는 동안 element가 마우스 위치를 따라가도록 설정
    draggingEle.style.position = &#39;absolute&#39;;
    draggingEle.style.top = `${e.pageY - y}px`;
    draggingEle.style.left = `${e.pageX - x}px`;

    // 선택 element에 전 element와 다음 element를 선택함
    // placeholder가 들어가 있기 때문에 순서는 다음과 같음
    // 이전 element
    // drag 중인 element
    // placeholder element
    // 다음 element
    const prevEle = draggingEle.previousElementSibling;
    const nextEle = placeholder.nextElementSibling;

    // 이전 element가 존재하고 drag 중인 element가 이전 element 위에 존재하는 경우
    if (prevEle &amp;&amp; isAbove(draggingEle, prevEle)) {
      // 서로의 위치를 바꿈
      // swap을 통해 place와 drag를 바꾸는 이유는 한번 움직이는게 아닌 연속적으로 상위요소로 올라갈 때, preEle 처리전 place와 drag의 위치를 정리하기 위해서임
      swap(placeholder, draggingEle);
      swap(placeholder, prevEle);
      return;
    }

    // 다음 element가 존재하고 drag 중인 element가 다음 element 위에 존재하는 경우
    if (nextEle &amp;&amp; isAbove(nextEle, draggingEle)) {
      swap(nextEle, placeholder);
      swap(nextEle, draggingEle);
    }
  };

  const mouseUpHandler = function () {
    // placeholder가 존재하면 지워줌
    placeholder &amp;&amp; placeholder.parentNode.removeChild(placeholder);

    // absolute로 움직이던 속성을 다 제거
    draggingEle.style.removeProperty(&#39;top&#39;);
    draggingEle.style.removeProperty(&#39;left&#39;);
    draggingEle.style.removeProperty(&#39;position&#39;);

    // 위치와 element 관련 date를 초기화
    x = null;
    y = null;
    draggingEle = null;
    isDraggingStarted = false;

    // 이벤트 해제
    document.removeEventListener(&#39;mousemove&#39;, mouseMoveHandler);
    document.removeEventListener(&#39;mouseup&#39;, mouseUpHandler);
  };

  // 모든 draggable 요소에 mouseDown 이벤트 적용
  [].slice.call(list.querySelectorAll(&#39;.draggable&#39;)).forEach(function (item) {
    item.addEventListener(&#39;mousedown&#39;, mouseDownHandler);
  });
});</code></pre>
<p>!codepen[sugarghost/embed/eYKZPRE?default-tab=html%2Cresult]</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[javascript를 이용해 mouse로 html 요소 resize하기(코드 저장용)]]></title>
            <link>https://velog.io/@sugar_ghost/javascript%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-mouse%EB%A1%9C-html-%EC%9A%94%EC%86%8C-resize%ED%95%98%EA%B8%B0%EC%BD%94%EB%93%9C-%EC%A0%80%EC%9E%A5%EC%9A%A9</link>
            <guid>https://velog.io/@sugar_ghost/javascript%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-mouse%EB%A1%9C-html-%EC%9A%94%EC%86%8C-resize%ED%95%98%EA%B8%B0%EC%BD%94%EB%93%9C-%EC%A0%80%EC%9E%A5%EC%9A%A9</guid>
            <pubDate>Tue, 01 Nov 2022 15:48:26 GMT</pubDate>
            <description><![CDATA[<h3 id="left-right로-나뉜-split-구조에-적용">Left, Right로 나뉜 split 구조에 적용</h3>
<p><strong>HTML</strong></p>
<pre><code class="language-html">&lt;div class=&quot;container&quot;&gt;
    &lt;div class=&quot;left&quot;&gt;Left&lt;/div&gt;
    &lt;div class=&quot;resizer&quot; id=&quot;dragMe&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;right&quot;&gt;Right&lt;/div&gt;
&lt;/div&gt;</code></pre>
<p><strong>CSS</strong></p>
<pre><code class="language-css">.container {
  display: flex;

  /* 영역 구분을 위해 선을 설정 */
  border: 1px solid #cbd5e0;
  height: 16rem;
  width: 100%;
}
.left {
  /* 초기 크기를 절반으로 설정 */
  width: 50%;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;
}
.resizer {
  background-color: #cbd5e0;
  cursor: ew-resize;
  height: 100%;
  width: 2px;
}
.right {
  /* left가 차지하고 남은 공간에 따라 유동적으로 변화 */
  flex: 1;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;
}</code></pre>
<p><strong>JavaScript</strong></p>
<pre><code class="language-javascript">// 대상 Element 선택
const resizer = document.getElementById(&#39;dragMe&#39;);
const leftSide = resizer.previousElementSibling;
const rightSide = resizer.nextElementSibling;

// 마우스의 위치값 저장을 위해 선언
let x = 0;
let y = 0;

// 크기 조절시 왼쪽 Element를 기준으로 삼기 위해 선언
let leftWidth = 0;

// resizer에 마우스 이벤트가 발생하면 실행하는 Handler
const mouseDownHandler = function (e) {
    // 마우스 위치값을 가져와 x, y에 할당
    x = e.clientX;
    y = e.clientY;
    // left Element에 Viewport 상 width 값을 가져와 넣음
    leftWidth = leftSide.getBoundingClientRect().width;

    // 마우스 이동과 해제 이벤트를 등록
    document.addEventListener(&#39;mousemove&#39;, mouseMoveHandler);
    document.addEventListener(&#39;mouseup&#39;, mouseUpHandler);
};

const mouseMoveHandler = function (e) {
    // 마우스가 움직이면 기존 초기 마우스 위치에서 현재 위치값과의 차이를 계산
    const dx = e.clientX - x;
    const dy = e.clientY - y;

      // 크기 조절 중 마우스 커서를 변경함
    // class=&quot;resizer&quot;에 적용하면 위치가 변경되면서 커서가 해제되기 때문에 body에 적용
    document.body.style.cursor = &#39;col-resize&#39;;

    // 이동 중 양쪽 영역(왼쪽, 오른쪽)에서 마우스 이벤트와 텍스트 선택을 방지하기 위해 추가
    leftSide.style.userSelect = &#39;none&#39;;
    leftSide.style.pointerEvents = &#39;none&#39;;

    rightSide.style.userSelect = &#39;none&#39;;
    rightSide.style.pointerEvents = &#39;none&#39;;

    // 초기 width 값과 마우스 드래그 거리를 더한 뒤 상위요소(container)의 너비를 이용해 퍼센티지를 구함
    // 계산된 퍼센티지는 새롭게 left의 width로 적용
    const newLeftWidth = ((leftWidth + dx) * 100) / resizer.parentNode.getBoundingClientRect().width;
    leftSide.style.width = `${newLeftWidth}%`;
};

const mouseUpHandler = function () {
    // 모든 커서 관련 사항은 마우스 이동이 끝나면 제거됨
    resizer.style.removeProperty(&#39;cursor&#39;);
    document.body.style.removeProperty(&#39;cursor&#39;);

    leftSide.style.removeProperty(&#39;user-select&#39;);
    leftSide.style.removeProperty(&#39;pointer-events&#39;);

    rightSide.style.removeProperty(&#39;user-select&#39;);
    rightSide.style.removeProperty(&#39;pointer-events&#39;);

    // 등록한 마우스 이벤트를 제거
    document.removeEventListener(&#39;mousemove&#39;, mouseMoveHandler);
    document.removeEventListener(&#39;mouseup&#39;, mouseUpHandler);
};

// 마우스 down 이벤트를 등록
resizer.addEventListener(&#39;mousedown&#39;, mouseDownHandler);</code></pre>
<p>!codesandbox[resizable-split-views-left-right-lkiqc8?fontsize=14&amp;hidenavigation=1&amp;theme=dark]</p>
<h3 id="right에-추가로-top-bottom으로-나뉜-split-구조에-적용">Right에 추가로 Top, Bottom으로 나뉜 split 구조에 적용</h3>
<p><strong>HTML</strong></p>
<pre><code class="language-html">&lt;div class=&quot;container&quot;&gt;
  &lt;div class=&quot;left&quot;&gt;Left&lt;/div&gt;
  &lt;div class=&quot;resizer&quot; data-direction=&quot;horizontal&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;right&quot;&gt;
    &lt;div class=&quot;top&quot;&gt;Top&lt;/div&gt;
    &lt;div class=&quot;resizer&quot; data-direction=&quot;vertical&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;bottom&quot;&gt;Bottom&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
<p><strong>CSS</strong></p>
<pre><code class="language-css">.container {
  display: flex;

  /* 영역 구분을 위해 선을 설정 */
  border: 1px solid #cbd5e0;
  height: 32rem;
  width: 100%;
}
.resizer[data-direction=&quot;horizontal&quot;] {
  background-color: #cbd5e0;
  cursor: ew-resize;
  height: 100%;
  width: 2px;
}
.resizer[data-direction=&quot;vertical&quot;] {
  background-color: #cbd5e0;
  cursor: ns-resize;
  height: 2px;
  width: 100%;
}
.left {
  /* 초기 크기를 1/4으로 설정 */
  width: 25%;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;
}
.right {
  /* left가 차지하고 남은 공간에 따라 유동적으로 변화 */
  flex: 1;

  /* 중앙 정렬 및 하위 요소 배치를 column 식으로 내려가듯이 배치*/
  align-items: center;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.top {
  /* 초기 높이값 지정 */
  height: 12rem;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;
}
.bottom {
  /* top이 차지하고 남은 공간에 따라 유동적으로 변화 */
  flex: 1;

  /* 중앙 정렬 */
  align-items: center;
  display: flex;
  justify-content: center;
}
</code></pre>
<p><strong>JavaScript</strong></p>
<pre><code class="language-javascript">import &quot;./styles.css&quot;;
const resizable = function (resizer) {
  const direction = resizer.getAttribute(&quot;data-direction&quot;) || &quot;horizontal&quot;;
  const prevSibling = resizer.previousElementSibling;
  const nextSibling = resizer.nextElementSibling;

  //  마우스의 위치값 저장을 위해 선언
  let x = 0;
  let y = 0;
  let prevSiblingHeight = 0;
  let prevSiblingWidth = 0;

  // resizer에 마우스 이벤트가 발생하면 실행하는 Handler
  const mouseDownHandler = function (e) {
    // 마우스 위치값을 가져와 x, y에 할당
    x = e.clientX;
    y = e.clientY;
    // 대상 Element에 위치 정보를 가져옴
    const rect = prevSibling.getBoundingClientRect();
    // 기존 높이와 너비를 각각 할당함
    prevSiblingHeight = rect.height;
    prevSiblingWidth = rect.width;

    // 마우스 이동과 해제 이벤트를 등록
    document.addEventListener(&quot;mousemove&quot;, mouseMoveHandler);
    document.addEventListener(&quot;mouseup&quot;, mouseUpHandler);
  };

  const mouseMoveHandler = function (e) {
    // 마우스가 움직이면 기존 초기 마우스 위치에서 현재 위치값과의 차이를 계산
    const dx = e.clientX - x;
    const dy = e.clientY - y;

    // 이동 방향에 따라서 별도 동작
    // 기본 동작은 동일하게 기존 크기에 마우스 드래그 거리를 더한 뒤 상위요소(container)를 이용해 퍼센티지를 구함
    // 계산 대상이 x 또는 y인지에 차이가 있음
    switch (direction) {
      case &quot;vertical&quot;:
        const h =
          ((prevSiblingHeight + dy) * 100) /
          resizer.parentNode.getBoundingClientRect().height;
        prevSibling.style.height = `${h}%`;
        break;
      case &quot;horizontal&quot;:
      default:
        const w =
          ((prevSiblingWidth + dx) * 100) /
          resizer.parentNode.getBoundingClientRect().width;
        prevSibling.style.width = `${w}%`;
        break;
    }

    // 크기 조절 중 마우스 커서를 변경함
    // class=&quot;resizer&quot;에 적용하면 위치가 변경되면서 커서가 해제되기 때문에 body에 적용
    const cursor = direction === &quot;horizontal&quot; ? &quot;col-resize&quot; : &quot;row-resize&quot;;
    resizer.style.cursor = cursor;
    document.body.style.cursor = cursor;

    prevSibling.style.userSelect = &quot;none&quot;;
    prevSibling.style.pointerEvents = &quot;none&quot;;

    nextSibling.style.userSelect = &quot;none&quot;;
    nextSibling.style.pointerEvents = &quot;none&quot;;
  };

  const mouseUpHandler = function () {
    // 모든 커서 관련 사항은 마우스 이동이 끝나면 제거됨
    resizer.style.removeProperty(&quot;cursor&quot;);
    document.body.style.removeProperty(&quot;cursor&quot;);

    prevSibling.style.removeProperty(&quot;user-select&quot;);
    prevSibling.style.removeProperty(&quot;pointer-events&quot;);

    nextSibling.style.removeProperty(&quot;user-select&quot;);
    nextSibling.style.removeProperty(&quot;pointer-events&quot;);

    // 등록한 마우스 이벤트를 제거
    document.removeEventListener(&quot;mousemove&quot;, mouseMoveHandler);
    document.removeEventListener(&quot;mouseup&quot;, mouseUpHandler);
  };

  // 마우스 down 이벤트를 등록
  resizer.addEventListener(&quot;mousedown&quot;, mouseDownHandler);
};

// 모든 resizer에 만들어진 resizable을 적용
document.querySelectorAll(&quot;.resizer&quot;).forEach(function (ele) {
  resizable(ele);
});</code></pre>
<p>!codesandbox[resizable-split-views-left-right-top-bottom-nkxdo1?fontsize=14&amp;hidenavigation=1&amp;theme=dark]</p>
<p><a href="https://htmldom.dev/create-resizable-split-views/">출처</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ThreeJs+React로 마인크래프트 만들기 번역(by Daniel Bark from freeCodeCamp) - TextureSelector, World]]></title>
            <link>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-TextureSelector-World</link>
            <guid>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-TextureSelector-World</guid>
            <pubDate>Tue, 11 Oct 2022 10:16:05 GMT</pubDate>
            <description><![CDATA[<p>ThreeJs, React로 마인크래프트 만들기 글의 마지막 글이다.
숫자 키패드를 사용해 1, 2, 3, 4, 5로 설치할 블록의 질감을 변경하는 기능을 만들어보자
먼저 src/hooks/에 useStore.js를 수정해보자</p>
<pre><code class="language-javascript">// src/hooks/useStore.js
import { nanoid } from &quot;nanoid&quot;;
import create from &quot;zustand&quot;;

export const useStore = create((set) =&gt; ({
  texture: &quot;dirt&quot;,
  cubes: [],
  addCube: (x, y, z) =&gt; {
    set((prev) =&gt; ({
      cubes: [
        ...prev.cubes,
        {
          key: nanoid(),
          pos: [x, y, z],
          texture: prev.texture,
        },
      ],
    }));
  },
  removeCube: (x, y, z) =&gt; {
    set((prev) =&gt; ({
      cubes: prev.cubes.filter((cube) =&gt; {
        const [X, Y, Z] = cube.pos;
        return X !== x || Y !== y || Z !== z;
      }),
    }));
  },
  // texture를 인자로 받아 현재 state에 반영시켜주자
  setTexture: (texture) =&gt; {
    set(() =&gt; ({
      texture,
    }));
  },
  saveWorld: () =&gt; {},
  resetWorld: () =&gt; {},
}));</code></pre>
<p>기존 useStore에 작은 수정사항이기에 별다른 어려움은 없다.
Texture 선택을 위해서 별도의 파일을 만들자
src/components/ 경로에 TextureSelector.js 파일을 생성하자</p>
<pre><code class="language-javascript">// src/components/TextureSelector.js
import { useState, useEffect } from &quot;react&quot;;
import { useKeyboard } from &quot;../hooks/useKeyboard&quot;;
import { useStore } from &quot;../hooks/useStore&quot;;
import { dirtImg, grassImg, glassImg, woodImg, logImg } from &quot;../images/images&quot;;

// TextureSelector를 그릴때 내부 구성 Texture만큼 반복하기 위해 선언
const images = {
  dirt: dirtImg,
  grass: grassImg,
  glass: glassImg,
  wood: woodImg,
  log: logImg,
};

export const TextureSelector = () =&gt; {
  // TextrueSelector가 보이는지 여부를 관리하는 state
  const [visible, setVisible] = useState(false);
  // 현재 활성화 textrue를 구분하고 선택한 texture를 반영하기 위해 사용
  const [activeTexture, setTexture] = useStore((state) =&gt; [
    state.texture,
    state.setTexture,
  ]);
  // 특정 Texture에 해당되는 num(숫자)가 눌렀는지 여부를 담은 상태값
  const { dirt, grass, glass, wood, log } = useKeyboard();

  // 만약 Texture 상태값이 변경(num을 누른 경우)되면 발생하는 Effect
  useEffect(() =&gt; {
    const textures = {
      dirt,
      grass,
      glass,
      wood,
      log,
    };
    // textures 오브젝트를 검사해 각 textures 요소 중 값이 true인 경우를 반환
    // 즉, useKeyboard를 통해 input이 발생해 상태가 true가 된 texture가 반환
    const pressedTexture = Object.entries(textures).find(([k, v]) =&gt; v);
    if (pressedTexture) {
      // 해당되는(눌려서 true가 된) texture의 key(예: dirt)를 현재 Texture로 설정
      setTexture(pressedTexture[0]);
    }
  }, [setTexture, dirt, grass, glass, wood, log]);

  // num을 통해 Textrue를 선택한 순간 화면에 잠시동안 TextureSelector가 보이게 하기 위한 Effect
  useEffect(() =&gt; {
    // 비동기 처리를 이용해 2초후에 visible을 false로 설정한다.
    const visibbilityTimeout = setTimeout(() =&gt; {
      setVisible(false);
    }, 2000);
    // visible을 true로 변경시킨다. 2초뒤 setTimeout의 callback이 실행되면, 다시 false가 된다.
    setVisible(true);
    return () =&gt; {
      // 2초가 지나기 전에 useEffect가 다시 발생하면 setTimeout이 중첩되어 실행됨
      // 만약 useEffect가 다시 발생하면 clearTimeout을 이용해 setTimeout을 제거해 시간마다 1번의 실행을 보증
      clearTimeout(visibbilityTimeout);
    };
  }, [activeTexture]);
  return (
    // visible 상태에 따라서 TextureSelector를 보이게함
    visible &amp;&amp; (
      &lt;div className=&quot;absolute centered texture-selector&quot;&gt;
          // images 요소만큼 반복해 Texture 이미지를 가져옴 
        {Object.entries(images).map(([k, src]) =&gt; {
          return (
            &lt;img
              key={k}
              src={src}
              alt={k}
              className={`${k === activeTexture ? &quot;active&quot; : &quot;&quot;}`}
            /&gt;
          );
        })}
      &lt;/div&gt;
    )
  );
};</code></pre>
<p>영상에서는 단계를 따라서 조금씩 코드를 불려나갔지만, 글에서는 한번에 통일해서 설명하고자 최종본으로 설명하기에 내용이 조금 길다고 느낄 수 있다.
중요한 내용은 주석으로 설명됬기에 별도의 설명은 생략한다.
코드 마지막에 보면 별도의 className을 정의해 주었다.
이에 맞춰서 src/index.css에 다음과 같은 내용을 추가해주자</p>
<pre><code class="language-css">/* 크기가 작기에 확장을 시켜줌 */
.texture-selector {
  transform: scale(5);
}
/* 선택된 이미지에 강조를 하기 위한 css */
.texture-selector img.active {
  border: 2px solid red;
}</code></pre>
<p>css 추가가 되었다면 작업사항을 app.js에 반영해 확인해보자</p>
<pre><code class="language-javascript">// src/App.js
import { Canvas } from &quot;@react-three/fiber&quot;;
import { Sky } from &quot;@react-three/drei&quot;;
import { Physics } from &quot;@react-three/cannon&quot;;
import { Ground } from &quot;./components/Ground&quot;;
import { Player } from &quot;./components/Player&quot;;
import { FPV } from &quot;./components/FPV&quot;;
import { Cubes } from &quot;./components/Cubes&quot;;
import { TextureSelector } from &quot;./components/TextureSelector&quot;; // + 만든 selector를 가져오자

function App() {
  return (
    &lt;&gt;
      &lt;Canvas&gt;
        &lt;Sky sunPosition={[100, 100, 20]} /&gt;
        &lt;ambientLight intensity={0.5} /&gt;
        &lt;FPV /&gt;
        &lt;Physics&gt;
          &lt;Player /&gt;
          &lt;Cubes /&gt;
          &lt;Ground /&gt;
        &lt;/Physics&gt;
      &lt;/Canvas&gt;
      &lt;div className=&quot;absolute centered cursor&quot;&gt;+&lt;/div&gt;
      &lt;TextureSelector /&gt; // + textureSelector를 반영해주자
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>이제 숫자 1, 2, 3, 4, 5를 누를 때마다 Texture selector가 잠시 화면에 보이고, 블럭 설치시 선택한 texture가 반영이 된다.</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/08a544e9-66d6-42d1-b5b9-837a7aefb6e2/image.png" alt=""></p>
<p>질감을 선택하고 블록을 설치하다보면 한가지 아쉬운 점이 있다.
내가 현재 어떤 블록에 마우스를 가져다대고 있는지 잘 안보인다는 점과 유리의 질감에 투명도가 없다는 것이다.
Cube.js를 수정해 반영을 해보자</p>
<pre><code class="language-javascript">// src/components/Cube.js
import { useState } from &quot;react&quot;; // + state 관리를 위해 추가
import { useBox } from &quot;@react-three/cannon&quot;;
import { useStore } from &quot;../hooks/useStore&quot;;
import * as textures from &quot;../images/textures&quot;;
export const Cube = ({ position, texture }) =&gt; {
  const [isHovered, setIsHovered] = useState(false); // + 해당 cube가 Hover 중인지 확인용
  const [ref] = useBox(() =&gt; ({
    type: &quot;Static&quot;,
    position,
  }));

  const [addCube, removeCube] = useStore((state) =&gt; [
    state.addCube,
    state.removeCube,
  ]);

  const activeTexture = textures[texture + &quot;Texture&quot;];

  return (
    &lt;mesh
      // 현재 물체에 마우스 Pointer가 움직이는지
      onPointerMove={(e) =&gt; {
        e.stopPropagation();
        setIsHovered(true);
      }}
      // 현재 물체에 마우스 Pointer가 벗어났는지
      onPointerOut={(e) =&gt; {
        e.stopPropagation();
        setIsHovered(false);
      }}
      onClick={(e) =&gt; {
        e.stopPropagation();
        const clickedFace = Math.floor(e.faceIndex / 2);
        const { x, y, z } = ref.current.position;
        if (e.altKey) {
          removeCube(x, y, z);
          return;
        } else if (clickedFace === 0) {
          addCube(x + 1, y, z);
          return;
        } else if (clickedFace === 1) {
          addCube(x - 1, y, z);
          return;
        } else if (clickedFace === 2) {
          addCube(x, y + 1, z);
          return;
        } else if (clickedFace === 3) {
          addCube(x, y - 1, z);
          return;
        } else if (clickedFace === 4) {
          addCube(x, y, z + 1);
          return;
        } else if (clickedFace === 5) {
          addCube(x, y, z - 1);
          return;
        }
      }}
      ref={ref}
    &gt;
      &lt;boxBufferGeometry attach=&quot;geometry&quot; /&gt;
      &lt;meshStandardMaterial
        // 만약 물체가 Hover중 이라면, 질감에 색상을 추가한다.
        color={isHovered ? &quot;gray&quot; : &quot;white&quot;}
        map={activeTexture}
        // 투명도 적용을 위해 transparent 속성을 true로 설정
        transparent={true}
        // 만약 cube의 질감이 유리라면, opacity 값을 0.6으로 설정
        opacity={texture === &quot;glass&quot; ? 0.6 : 1}
        attach=&quot;material&quot;
      /&gt;
    &lt;/mesh&gt;
  );
};</code></pre>
<p>몇가지 새로운 개념이 보인다.
먼저 물체(mesh)에 새롭게 이벤트가 바인딩 되었는데, <code>onPointerMove</code>와 <code>onPointerOut</code>이다.
두 요소는 <code>react-three-fiber</code>에 <a href="https://docs.pmnd.rs/react-three-fiber/api/events"><code>Event</code></a>항목에서 찾아 볼 수 있다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/bc8fa026-98b6-4813-9dcb-4cde020b2f03/image.png" alt=""></p>
<p>새롭게 질감 부분에서 <a href="https://threejs.org/docs/#api/en/materials/Material.transparent"><code>transparent</code></a>, <a href="https://threejs.org/docs/#api/en/materials/Material.opacity"><code>opacity</code></a> 개념이 추가되었다.
<a href="https://threejs.org/docs/#api/en/materials/Material.transparent"><code>transparent</code></a>는 질감이 투명한지 여부를 정의한다. true로 설정 시 투명해지는 정도는 <a href="https://threejs.org/docs/#api/en/materials/Material.opacity"><code>opacity</code></a> 속성을 설정해 제어된다.</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/39b74879-eb95-4b67-97f8-dbe9075a6145/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/d2c62f11-a69e-44ba-9897-26950ef9b6e8/image.png" alt=""></p>
<p>코드를 반영하면 유리의 질감과 hover시의 색상 반영을 확인 할 수 있다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/f3999feb-937c-475d-9b53-63b0191f0f42/image.png" alt=""></p>
<p>이젠 마지막으로 우리가 저장한 block을 저장하고 reset시키는 world 기능을 구현해보자
먼저 useStore.js를 수정해 world 관련 기능을 만들어보자</p>
<pre><code class="language-javascript">// src/hooks/useStore.js
import { nanoid } from &quot;nanoid&quot;;
import create from &quot;zustand&quot;;

// + localStorege를 저장 및 가져오기 위해 선언
const getLocalStorage = (key) =&gt; JSON.parse(window.localStorage.getItem(key));
const setLocalStorage = (key, value) =&gt;
  window.localStorage.setItem(key, JSON.stringify(value));

export const useStore = create((set) =&gt; ({
  texture: &quot;dirt&quot;,
  // 기존 sample 내용은 다 지우고, localStorege에 내용이 존재하면 가져오거나, []을 할당
  cubes: getLocalStorage(&quot;cubes&quot;) || [],
  addCube: (x, y, z) =&gt; {
    set((prev) =&gt; ({
      cubes: [
        ...prev.cubes,
        {
          key: nanoid(),
          pos: [x, y, z],
          texture: prev.texture,
        },
      ],
    }));
  },
  removeCube: (x, y, z) =&gt; {
    set((prev) =&gt; ({
      cubes: prev.cubes.filter((cube) =&gt; {
        const [X, Y, Z] = cube.pos;
        return X !== x || Y !== y || Z !== z;
      }),
    }));
  },
  setTexture: (texture) =&gt; {
    set(() =&gt; ({
      texture,
    }));
  },
  // 저장 시 localStorege에 cubes 내용을 저장
  saveWorld: () =&gt; {
    set((prev) =&gt; {
      setLocalStorage(&quot;cubes&quot;, prev.cubes);
    });
  },
  // reset시 현재 state를 초기화
  resetWorld: () =&gt; {
    set(() =&gt; ({
      cubes: [],
    }));
  },
}));</code></pre>
<p>기능을 만들고 이제 기능을 사용하기 위한 컴포넌트를 만들어보자 src/components/ 경로에 Menu.js 파일을 생성해보자</p>
<pre><code class="language-javascript">// src/componenets/Menu.js 
import { useStore } from &quot;../hooks/useStore&quot;;

export const Menu = () =&gt; {
  const [saveWorld, resetWorld] = useStore((state) =&gt; [
    state.saveWorld,
    state.resetWorld,
  ]);

  return (
    &lt;div className=&quot;menu absolute&quot;&gt;
      &lt;button onClick={() =&gt; saveWorld()}&gt;Save&lt;/button&gt;
      &lt;button onClick={() =&gt; resetWorld()}&gt;Reset&lt;/button&gt;
    &lt;/div&gt;
  );
};</code></pre>
<p>메뉴 컴포넌트를 App.js에 적용하자</p>
<pre><code class="language-javascript">// src/App.js
import { Canvas } from &quot;@react-three/fiber&quot;;
import { Sky } from &quot;@react-three/drei&quot;;
import { Physics } from &quot;@react-three/cannon&quot;;
import { Ground } from &quot;./components/Ground&quot;;
import { Player } from &quot;./components/Player&quot;;
import { FPV } from &quot;./components/FPV&quot;;
import { Cubes } from &quot;./components/Cubes&quot;;
import { TextureSelector } from &quot;./components/TextureSelector&quot;;
import { Menu } from &quot;./components/Menu&quot;; // + 추가

function App() {
  return (
    &lt;&gt;
      &lt;Canvas&gt;
        &lt;Sky sunPosition={[100, 100, 20]} /&gt;
        &lt;ambientLight intensity={0.5} /&gt;
        &lt;FPV /&gt;
        &lt;Physics&gt;
          &lt;Player /&gt;
          &lt;Cubes /&gt;
          &lt;Ground /&gt;
        &lt;/Physics&gt;
      &lt;/Canvas&gt;
      &lt;div className=&quot;absolute centered cursor&quot;&gt;+&lt;/div&gt;
      &lt;TextureSelector /&gt;
      &lt;Menu /&gt; // + 추가
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>마지막으로 world 관련 버튼의 배치를 위해 index.css에 다음과 같이 추가해주자</p>
<pre><code class="language-css">.menu {
  top: 0px;
  left: 10px;
}</code></pre>
<p>모든 작업이 끝났다면 왼쪽 상단에 save와 reset버튼이 생성된다.
저장되는 cubes의 포멧은 다음과 같이 localStorege에 저장된다.</p>
<pre><code>❮
[
    {
        &quot;key&quot;: &quot;0vMyojwn_UVJBKMHNE7lH&quot;,
        &quot;pos&quot;: [
            1,
            0,
            10
        ],
        &quot;texture&quot;: &quot;glass&quot;
    },
    {
        &quot;key&quot;: &quot;FPvfypxgz0vEhWBjPuAde&quot;,
        &quot;pos&quot;: [
            4,
            0,
            10
        ],
        &quot;texture&quot;: &quot;dirt&quot;
    },
    {
        &quot;key&quot;: &quot;daMnP8g97tQaFFPEBQbGk&quot;,
        &quot;pos&quot;: [
            2,
            0,
            10
        ],
        &quot;texture&quot;: &quot;grass&quot;
    },
    {
        &quot;key&quot;: &quot;eHOYl0k1DqFkiqVmULoZU&quot;,
        &quot;pos&quot;: [
            0,
            0,
            10
        ],
        &quot;texture&quot;: &quot;wood&quot;
    },
    {
        &quot;key&quot;: &quot;INtx3L7hJFyyfOzgn6xwE&quot;,
        &quot;pos&quot;: [
            -1,
            0,
            10
        ],
        &quot;texture&quot;: &quot;log&quot;
    },
    {
        &quot;key&quot;: &quot;akcT8xv966eXj0w7nzwO4&quot;,
        &quot;pos&quot;: [
            3,
            0,
            10
        ],
        &quot;texture&quot;: &quot;dirt&quot;
    },
    {
        &quot;key&quot;: &quot;FR7XWyNYRMsn-Vyd97DtU&quot;,
        &quot;pos&quot;: [
            3,
            0,
            9
        ],
        &quot;texture&quot;: &quot;dirt&quot;
    }
]</code></pre><p>이걸로 모든 작업이 끝났고, 우리는 원하는 질감으로 블록을 설치해 집을 만들고, 영원히 저장하고 초기화 할 수 있게 되었다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/5664ed51-1cff-4959-bed7-186b677c10c0/image.png" alt=""></p>
<p>글의 마무리로는 처음으로 3D를 접하고 threeJS를 써가면서 글을 쓴 감상을 이야기하고자 한다.
단순히 복사해 사용하는게 아닌 이해하면서 사용하기 위해 글을 써봤지만, 생각보다 복잡하고, 생각보다 오래 걸린다는 것을 깨달았다.</p>
<p>하지만 글을 쓰기위해 공식문서를 참조해가고 라이브러리 소스코드를 열어보는 것은 좋은 경험이 되었다.
누군가의 글이나 강의를 글로 써내리는건 좀더 신중해지겠지만, 처음 한번쯤은 익숙해지기 위해서 해볼만 하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ThreeJs+React로 마인크래프트 만들기 번역(by Daniel Bark from freeCodeCamp) - Cubes]]></title>
            <link>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-Cubes</link>
            <guid>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-Cubes</guid>
            <pubDate>Sun, 09 Oct 2022 09:57:05 GMT</pubDate>
            <description><![CDATA[<p>이번에는 블록과 관련된 작업을 진행할 것이다.
먼저 블록에 대한 3d 물리적 처리를 진행할 컴포넌트를 만들어 보자
src/components 경로에 Cube.js 파일을 생성하자</p>
<pre><code class="language-javascript">// src/components/Cube.js
import { useBox } from &quot;@react-three/cannon&quot;;
import * as textures from &quot;../images/textures&quot;; 
export const Cube = ({ position, texture }) =&gt; {
  const [ref] = useBox(() =&gt; ({
    type: &quot;Static&quot;,
    position,
  }));

  const activeTexture = textures[texture + &quot;Texture&quot;];

  return (
    &lt;mesh ref={ref}&gt;
      &lt;boxBufferGeometry attach=&quot;geometry&quot; /&gt;
      &lt;meshStandardMaterial map={activeTexture} attach=&quot;material&quot; /&gt;
    &lt;/mesh&gt;
  );
};</code></pre>
<p>이제는 소스코드가 어느정도 익숙해졌다.
<code>Cube</code>는 위치를 위한 <code>position</code>과 질감을 구분할 <code>texture</code>를 인자로 받는다.
<code>useBox</code>는 Box 형태의 물리적 충돌을 계산하기 위해 사용한다.
여기서 우리는 type을 <a href="https://github.com/pmndrs/use-cannon/tree/master/packages/react-three-cannon#types">Static</a>으로 주었는데,
<a href="https://github.com/pmndrs/use-cannon/tree/master/packages/react-three-cannon#types">Static</a>은 움직이지 않으며 무한한 질량을 가진 것처럼 동작한다. position에 대한 수정을 통해 움직일 수는 있지만, 속도는 항상 0이다.
우리는 정적인 블록 상태로 사용할 것이기에 중요한 요소는 아니다.</p>
<p>Box 형태의 물리적 요소가 준비되면 실제 물체에 매핑을 시킨다.
mesh를 통해 물체를 만들고, geometry, material을 활용해 부피와 질감을 선언한다.
질감을 선언할때 map을 통해 파라미터를 통해 들어온 현재 텍스쳐를 등록한다.</p>
<p>물리적인 블록을 생성하는 컴포넌트를 생성했으니 다수의 블록을 그려주는 컴포넌트와 그 블록들을 전역으로 관리할 hook을 만들어주자
먼저 전역 상태관리를 위해 src/hooks/ 경로에 useStore.js라는 파일을 생성하자</p>
<pre><code class="language-javascript">// src/hooks/useStore.js
import { nanoid } from &quot;nanoid&quot;;
import create from &quot;zustand&quot;;

export const useStore = create((set) =&gt; ({
  texture: &quot;dirt&quot;,
  cubes: [
    {
      key: nanoid(),
      pos: [1, 1, 1],
      texture: &quot;dirt&quot;,
    },
  ],
  addCube: (x, y, z) =&gt; {
    set((prev) =&gt; ({
      cubes: [
        ...prev.cubes,
        {
          key: nanoid(),
          pos: [x, y, z],
          texture: prev.texture,
        },
      ],
    }));
  },
  removeCube: () =&gt; {},
  setTexture: () =&gt; {},
  saveWorld: () =&gt; {},
  resetWorld: () =&gt; {},
}));</code></pre>
<p>처음에 우리가 받아놓고 사용하지 않고있던 라이브러리를 쓰게 되었다.
nanoid는 단순히 중복되지 않는 string key를 만들기 위한 라이브러리다.
zustand는 전역관리를 위해 사용하며, create로 생성되어 set을 통해 전역 상태를 관리한다.
3D 관련된 요소는 지금은 이정도로 간략히 설명하고 넘어가겠다.
현재 선택된 texture를 관리하기 위해 texture의 상태를 관리한다.(기본값 &quot;dirt&quot;)
cube는 key,pos,texture로 구성된다.
pos는 좌표값의 위치인 position을 뜻하며, texture는 해당 cube의 질감을 담는다.
addCube는 좌표값을 받고 set을 통해 기존 cubes 상태에 기존값 + 새로운 {객체}를 추가로 할당한다.
나머지 기능은 나중에 만들자
이제 src/components/ 경로에 Cubes.js 파일을 추가하자</p>
<pre><code class="language-javascript">// src/components/Cubes.js
import { useStore } from &quot;../hooks/useStore&quot;;
import { Cube } from &quot;./Cube&quot;;
export const Cubes = () =&gt; {
  // 전역으로 관리되고 있는 cubes를 가져온다.
  const [cubes] = useStore((state) =&gt; [state.cubes]);
  // 현재 등록된 cubes 만큼 반복하며 물체 Cube를 그려준다.
  return cubes.map(({ key, pos, texture }) =&gt; {
    return &lt;Cube key={key} position={pos} texture={texture} /&gt;;
  });
};</code></pre>
<p>이 부분에 대해서는 별다른 부가 설명은 불필요 하기 때문에 넘어가고,
우리가 추가한 cubes를 App.js을 통해서 반영시켜주자</p>
<pre><code class="language-javascript">import { Canvas } from &quot;@react-three/fiber&quot;;
import { Sky } from &quot;@react-three/drei&quot;;
import { Physics } from &quot;@react-three/cannon&quot;;
import { Ground } from &quot;./components/Ground&quot;;
import { Player } from &quot;./components/Player&quot;;
import { FPV } from &quot;./components/FPV&quot;;
import { Cubes } from &quot;./components/Cubes&quot;; // + Cubes 가져오기

function App() {
  return (
    &lt;&gt;
      &lt;Canvas&gt;
        &lt;Sky sunPosition={[100, 100, 20]} /&gt;
        &lt;ambientLight intensity={0.5} /&gt;
        &lt;FPV /&gt;
        &lt;Physics&gt;
          &lt;Player /&gt;
          &lt;Cubes /&gt; // + 물리적 영향을 받기에 Physics에 등록
          &lt;Ground /&gt;
        &lt;/Physics&gt;
      &lt;/Canvas&gt;
      &lt;div className=&quot;absolute centered cursor&quot;&gt;+&lt;/div&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>우리는 useStore에 cubes에 초기값을 1개 넣어주었고, 실행을 시켜보면 1개의 흙 블록을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/4c56002f-81ac-4f5d-a96e-a713ab30871b/image.png" alt=""></p>
<p>첫번째로, 처음에 우리가 지면을 만들때와 같이 도트가 흐릿해 보이는데, 텍스쳐에 별도로 필터 적용이 필요하기 때문이다.
두번째로, 블럭이 지면에서 약간 뜬 상태로 생성되는데, 이 부분은 지면에 대한 높이값 조정을 통해서 맞춰 줄 수 있다.
먼저 texture에 대해 수정을 해보자</p>
<pre><code class="language-javascript">// src/images/textures.js
import { NearestFilter, TextureLoader, RepeatWrapping } from &quot;three&quot;; // + RepeatWrapping를 추가해주자
import { dirtImg, logImg, glassImg, grassImg, woodImg } from &quot;./images&quot;;

const dirtTexture = new TextureLoader().load(dirtImg);
const logTexture = new TextureLoader().load(logImg);
const grassTexture = new TextureLoader().load(grassImg);
const glassTexture = new TextureLoader().load(glassImg);
const woodTexture = new TextureLoader().load(woodImg);
const groundTexture = new TextureLoader().load(grassImg);

// 아래의 모든 텍스쳐에 대해서 Filter를 적용시켜주자
dirtTexture.magFilter = NearestFilter;
logTexture.magFilter = NearestFilter;
grassTexture.magFilter = NearestFilter;
glassTexture.magFilter = NearestFilter;
woodTexture.magFilter = NearestFilter;
groundTexture.magFilter = NearestFilter;
groundTexture.wrapS = RepeatWrapping;
groundTexture.wrapT = RepeatWrapping;

export {
  dirtTexture,
  logTexture,
  grassTexture,
  glassTexture,
  woodTexture,
  groundTexture,
};</code></pre>
<p>Texture.js에서 wrapS, wrapT, Filter 적용을 했으니 기존 Ground에 Filter 내용은 지워주어야 한다.
Ground를 작업하는 김에 지면의 높이를 재 조정해서 블록과 일치시키고, 새롭게 만든 addCube를 사용하기 위해 onClick 이벤트를 추가해주자.</p>
<pre><code class="language-javascript">// src/components/Ground.js
import { usePlane } from &quot;@react-three/cannon&quot;;
import { NearestFilter, RepeatWrapping } from &quot;three&quot;;
import { groundTexture } from &quot;../images/textures&quot;;
import { useStore } from &quot;../hooks/useStore&quot;; // + addCube를 위해 hook을 가져오자

export const Ground = () =&gt; {
  const [ref] = usePlane(() =&gt; ({
    rotation: [-Math.PI / 2, 0, 0],
    position: [0, -0.5, 0], // 지면의 높이를 조정해 블록과 일치시키자.
  }));
  const [addCube] = useStore((state) =&gt; [state.addCube]); // + 가져온 hook에서 addCube 기능을 꺼내주자

  // - Texture와 곂치는 부분 제거
  // groundTexture.magFilter = NearestFilter; 
  // groundTexture.wrapS = RepeatWrapping;
  // groundTexture.wrapT = RepeatWrapping;
  groundTexture.repeat.set(100, 100);

  return (
    &lt;mesh
    // + 기존에 만든 지면 물체에 OnClick 이벤트를 할당하자.
      onClick={(e) =&gt; {
        // 이벤트 버블링을 막아 이벤트의 의도치 않은 동작을 막는다.
        e.stopPropagation();
        // 마우스로 좌표를 클릭하면 세밀한 소수점 단위의 좌표가 선택된다. 우리는 블록 단위로 좌표를 구하기 때문에 반올림 해서 x, y, z를 각각 추출한다.
        const [x, y, z] = Object.values(e.point).map((val) =&gt; Math.ceil(val));
        addCube(x, y, z);
      }}
      ref={ref}
    &gt;
      &lt;planeBufferGeometry attach=&quot;geometry&quot; args={[100, 100]} /&gt;
      &lt;meshStandardMaterial attach=&quot;material&quot; map={groundTexture} /&gt;
    &lt;/mesh&gt;
  );
};</code></pre>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/7561d080-eceb-45e9-b401-df3b4ba0d259/image.png" alt=""></p>
<p>자 이제 우리가 원하는 대로 마우스를 클릭하면 블록이 생성되고, 텍스쳐가 잘 적용되었다.
하지만 오로지 지면에서만 추가가 되며, 다른 블록에 연속적으로 설치는 안된다.</p>
<p>Cube.js와 useStore.js를 수정해서 큐브 이어 붙이기와 큐브 삭제 기능을 만들어보자</p>
<pre><code class="language-javascript">// src/hooks/useStore.js
import { nanoid } from &quot;nanoid&quot;;
import create from &quot;zustand&quot;;

export const useStore = create((set) =&gt; ({
  texture: &quot;dirt&quot;,
  cubes: [
    {
      key: nanoid(),
      pos: [1, 1, 1],
      texture: &quot;dirt&quot;,
    },
  ],
  addCube: (x, y, z) =&gt; {
    set((prev) =&gt; ({
      cubes: [
        ...prev.cubes,
        {
          key: nanoid(),
          pos: [x, y, z],
          texture: prev.texture,
        },
      ],
    }));
  },
  // removeCube는 position 값을 받아와 filter를 이용해 삭제 대상 위치값을 가진 cubes 항목을 걸러준다.
  removeCube: (x, y, z) =&gt; {
    set((prev) =&gt; ({
      cubes: prev.cubes.filter((cube) =&gt; {
        const [X, Y, Z] = cube.pos;
        return X !== x || Y !== y || Z !== z;
      }),
    }));
  },
  setTexture: () =&gt; {},
  saveWorld: () =&gt; {},
  resetWorld: () =&gt; {},
}));</code></pre>
<p>remove Cube에 대해서는 간단한 기능이기에 설명은 넘어가고, useStore 수정이 끝났다면 Cube.js를 수정해보자</p>
<pre><code class="language-javascript">// src/components/Cube.js
import { useBox } from &quot;@react-three/cannon&quot;;
import { useStore } from &quot;../hooks/useStore&quot;; // + useStore를 호출
import * as textures from &quot;../images/textures&quot;; 
export const Cube = ({ position, texture }) =&gt; {
  const [ref] = useBox(() =&gt; ({
    type: &quot;Static&quot;,
    position,
  }));

  // + hook을 통해 addCube와 removeCube 기능을 가져오자
  const [addCube, removeCube] = useStore((state) =&gt; [
    state.addCube,
    state.removeCube,
  ]);

  const activeTexture = textures[texture + &quot;Texture&quot;];

  return (
    &lt;mesh
      // + cube 물체에 onClick 이벤트를 부여
      onClick={(e) =&gt; {
        e.stopPropagation();
        // 현재 물체의 이벤트가 발생한 면 Number를 가져온다.
        const clickedFace = Math.floor(e.faceIndex / 2);
        // 물체의 위치값을 가져온다.
        const { x, y, z } = ref.current.position;
        // 만약 이벤트가 alt가 눌린채로 발생했다면 현재 이벤트 발생 위치의 cube를 지워준다.
        if (e.altKey) {
          removeCube(x, y, z);
          return;
        // 이후부터는 이벤트가 발생한 면 Number에 따라서 각 위치에 맞춰 addCube를 발생시킨다.
        } else if (clickedFace === 0) {
          addCube(x + 1, y, z);
          return;
        } else if (clickedFace === 1) {
          addCube(x - 1, y, z);
          return;
        } else if (clickedFace === 2) {
          addCube(x, y + 1, z);
          return;
        } else if (clickedFace === 3) {
          addCube(x, y - 1, z);
          return;
        } else if (clickedFace === 4) {
          addCube(x, y, z + 1);
          return;
        } else if (clickedFace === 5) {
          addCube(x, y, z - 1);
          return;
        }
      }}
      ref={ref}
    &gt;
      &lt;boxBufferGeometry attach=&quot;geometry&quot; /&gt;
      &lt;meshStandardMaterial map={activeTexture} attach=&quot;material&quot; /&gt;
    &lt;/mesh&gt;
  );
};</code></pre>
<p>새로운 개념이 나왔다.
<code>faceIndex</code>
우리가 만드는 블록은 6개의 면을 가지고 있다고 생각하겠지만, threeJs에는 면을 3개의 꼭지점을 가진 삼각형으로 처리한다.
즉 블록의 면 구성은 다음과 같다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/839eca7c-52ce-4cfb-b2d3-57cd8f2bf5b7/image.png" alt="">
우리가 한 면이라고 생각하는 면은 2개의 <a href="https://threejs.org/docs/?q=face#examples/en/math/convexhull/Face">face</a>로 구성되어있다.
그래서 소스코드 상으로는 <code>faceIndex</code>를 받아 2로 나눠준 뒤 <code>Math.floor()</code>를 통해서 반내림을 시켜준다.
각 Face의 순서는 지금 이해하려할 필요는 없는 것 같다.
지금은 주어진 소스코드를 그대로 적용시키며 face에 대한 이해만 해두자
<img src="https://velog.velcdn.com/images/sugar_ghost/post/cde1013b-0112-4472-b9d2-134621574e8a/image.png" alt=""></p>
<p>자 이제 실행을 해보고 Cube를 설치해보고, 삭제해보자</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/9920ed8b-1c38-4725-a127-759f012efe50/image.png" alt=""></p>
<p>이제 Cube에 연속적으로 반영이 되는것을 확인 할 수 있다.
다음번에는 마지막으로 텍스쳐를 선택해 반영하고, localStorege에 world 정보를 반영하는 부분으로 마무리 지어보자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ThreeJs+React로 마인크래프트 만들기 번역(by Daniel Bark from freeCodeCamp) - Player & input with Movement ...etc]]></title>
            <link>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-Player-input-with-Movement-...etc</link>
            <guid>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-Player-input-with-Movement-...etc</guid>
            <pubDate>Sat, 08 Oct 2022 04:54:10 GMT</pubDate>
            <description><![CDATA[<p>이번에는 Player와 Player를 구성하는 1인 뷰 및 keyboard input과 관련된 처리를 하고자 한다.</p>
<p>먼저 src/components 경로에 Player.js라는 파일을 만들고 다음과 같이 구성하자</p>
<pre><code class="language-javascript">// src/components/Player.js
import { useEffect, useRef } from &quot;react&quot;;
import { useFrame, useThree } from &quot;@react-three/fiber&quot;;
import { useSphere } from &quot;@react-three/cannon&quot;;
import { Vector3 } from &quot;three&quot;;
export const Player = () =&gt; {
  const { camera } = useThree();
  const [ref, api] = useSphere(() =&gt; ({
    mass: 1,
    type: &quot;Dynamic&quot;,
    position: [0, 0, 0],
  }));

  const pos = useRef([0, 0, 0]);
  useEffect(() =&gt; {
    api.position.subscribe((p) =&gt; (pos.current = p));
  }, [api.position]);
  useFrame(() =&gt; {
    camera.position.copy(
      new Vector3(pos.current[0], pos.current[1], pos.current[2])
    );
  });
  return &lt;mesh ref={ref}&gt;&lt;/mesh&gt;;
};</code></pre>
<p>일단은 작성한 코드를 반영하기 위해서 App.js에도 Player 사항을 추가해주자</p>
<pre><code class="language-javascript">// src/App.js
import { Canvas } from &quot;@react-three/fiber&quot;;
import { Sky } from &quot;@react-three/drei&quot;;
import { Physics } from &quot;@react-three/cannon&quot;;
import { Ground } from &quot;./components/Ground&quot;;
import { Player } from &quot;./components/Player&quot;; // + 새로 만든 Player를 import

function App() {
  return (
    &lt;&gt;
      &lt;Canvas&gt;
        &lt;Sky sunPosition={[100, 100, 20]} /&gt;
        &lt;ambientLight intensity={0.5} /&gt;
        &lt;Physics&gt;
          &lt;Player /&gt; // + 물리적인 영향을 받으니 Physics 내부에 추가해준다.
          &lt;Ground /&gt;
        &lt;/Physics&gt;
      &lt;/Canvas&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>본격적으로 Player에 대해 설명을 시작하면서 선언부 부터 새로운 개념을 하나씩 알아보자
먼저 우리는 <code>@react-three/fiber</code>에 <a href="https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree"><code>useThree</code></a>를 통해 <code>camera</code>라는 요소에 접근을 했다.
<a href="https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree"><code>useThree</code></a>는 기본 렌더러, 장면, 카메라 등의 상태 모델에 액세스를 지원하는 hook이다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/ce1b9471-6d7a-496f-bfbe-a5ed733af43f/image.png" alt="">
camera에 대해서는 나중에 실제 사용 부분에서 좀더 설명을 하겠다.
이제 다음 코드를 살펴보면 <code>useSphere</code>가 사용되었다.
threeJs에서 Sphere는 구체 혹은 입체적인 형체를 뜻한다.
다만 cannon 라이브러리에 요소이기 때문에 시각적인 요소가 아닌 물리적 계산에 관련된 개념으로 이해하면 될 것 같다.
우리는 인자값으로 mass, type, position을 준다.
<a href="https://pmndrs.github.io/cannon-es/docs/classes/Body.html#mass">mass</a>는 질량을 의미하며 충돌 계산 또는 중력에 의한 물리적 계산에 영향을 미친다.
<a href="https://github.com/pmndrs/use-cannon/tree/master/packages/react-three-cannon#types">type</a>은 공식 github FAQ에 명시되어 있으며, 우리가 준 dynamic 속성의 경우
유저의 의해 움직일 수 있으며, 0이 아닌 질량을 가진 채로 다른 요소들과 충돌이 가능하다.
즉, 사용자 input에 따라 조작 가능한 요소를 지정할때 알맞은 type이다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/5cb4e943-2f79-4e50-a428-804a1714ec0b/image.png" alt=""></p>
<p>우리는 시야를 위한 <code>camera</code>가 존재하고, 플레이어를 위한 물리적 대상(<code>useSphere</code>)이 존재한다.
이제 이 두가지를 결합하자
먼저 우리는 좌표를 계산하기 위해</p>
<pre><code class="language-javascript">  const pos = useRef([0, 0, 0]);</code></pre>
<p>를 선언했다.</p>
<pre><code class="language-javascript">useEffect(() =&gt; {
  api.position.subscribe((p) =&gt; (pos.current = p));
}, [api.position]);</code></pre>
<p>이 구문은 우리가 생성한 물리적인 Player의 위치값을 pos 변수에 지속적으로 update하기 위한 구문이다.
api 및 position, subscribe 등의 내용은 <a href="https://github.com/pmndrs/use-cannon/tree/master/packages/react-three-cannon#how-it-works">github readme</a>를 참조하면 알 수 있다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/dd05bd6b-7597-47a2-ab46-5defcc29852a/image.png" alt=""></p>
<p>그 다음 코드를 살펴보자.</p>
<pre><code class="language-javascript">useFrame(() =&gt; {
  camera.position.copy(
    new Vector3(pos.current[0], pos.current[1], pos.current[2])
  );
});</code></pre>
<p><a href="https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe">useFrame</a>은 effect가 실행되거나 update가 발생하는 등의 모든 frame에 대한 렌더링이 발생하면, 내부에 코드를 실행시킨다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/e70bfefb-ffc3-4a70-8523-7b6897e67762/image.png" alt=""></p>
<p>우리의 예제 상으로는 렌더링이 발생하면 pos에 담긴 Player의 위치값을 camera에 position값으로 복사해준다.
즉, 우리가 Player에 대한 상호작용을 통해 위치값이 변화한다면, 우리의 시점을 Player에 위치값과 일치시켜 시야가 Player를 따라다니도록 하는 것이다.</p>
<p>마지막으로 물리적인 상호작용을 위해서는 실제 물체가 필요하기에 mesh를 선언하고 Player의 물리적인 상태를 ref로 연결해준다.</p>
<pre><code class="language-javascript">  return &lt;mesh ref={ref}&gt;&lt;/mesh&gt;;</code></pre>
<p>이제 실제로 어떻게 동작하는지 실행을 해보자
<img src="https://velog.velcdn.com/images/sugar_ghost/post/6b77708e-a4cf-4e86-a684-ef3f31673a43/image.png" alt="">
상호작용은 아직 불가능 하지만 우리는 Player를 생성했다.
처음 실행을 시켜보면 위로 튀어오르고, 다시 땅으로 내려오는 모습을 보게 될 것이다.</p>
<p>Player에 위치값을 0으로 설정하면서 지면과 충돌이 발생해 반발력에 의해 위로 튀어오르고, 질량(mass)에 의해서 중력에 영향으로 다시 아래로 떨어지기 때문이다.</p>
<p>높이에 대한 처리를 위해 Player.js를 다음과 같이 변경하자</p>
<pre><code class="language-javascript">// src/components/Player.js
import { useEffect, useRef } from &quot;react&quot;;
import { useFrame, useThree } from &quot;@react-three/fiber&quot;;
import { useSphere } from &quot;@react-three/cannon&quot;;
import { Vector3 } from &quot;three&quot;;
export const Player = () =&gt; {
  const { camera } = useThree();
  const [ref, api] = useSphere(() =&gt; ({
    mass: 1,
    type: &quot;Dynamic&quot;,
    position: [0, 1, 0], // position에 높이에 해당되는 두번째 인자를 1로 변경
  }));

  // + 추후에 Player에 대한 Event 처리를 위해 아래에 코드를 추가하자
  const vel = useRef([0, 0, 0]);
  useEffect(() =&gt; {
    api.velocity.subscribe((v) =&gt; (vel.current = v));
  }, [api.velocity]);

  const pos = useRef([0, 0, 0]);
  useEffect(() =&gt; {
    api.position.subscribe((p) =&gt; (pos.current = p));
  }, [api.position]);

  useFrame(() =&gt; {
    camera.position.copy(
      new Vector3(pos.current[0], pos.current[1], pos.current[2])
    );
  });
  return &lt;mesh ref={ref}&gt;&lt;/mesh&gt;;
};
</code></pre>
<p>간단히 Player의 position 값을 변경해주는 것으로 튕기는 현상은 사라졌다.
새롭게 추가된 코드 중 <a href="https://pmndrs.github.io/cannon-es/docs/classes/Body.html#velocity">velocity</a>가 존재하는데, 우리가 Player에 대한 조작(이동, 점프 등) 시 그 속도를 처리하기 위한 상태값이다.</p>
<p>Player를 준비했으니 이제 key input 관련 상호작용을 만들어보자.
src/hooks/ 경로에 새롭게 useKeyboard.js 라는 이름의 파일을 생성하고 다음과 같이 구성하자</p>
<pre><code class="language-javascript">// src/hooks/useKeyboard.js
import { useCallback, useEffect, useState } from &quot;react&quot;;

  // key Action 종류에 따라서 반환할 값을 Map 형태로 준비
function actionByKey(key) {
  const keyActionMap = {
    KeyW: &quot;moveForward&quot;,
    KeyS: &quot;moveBackward&quot;,
    KeyA: &quot;moveLeft&quot;,
    KeyD: &quot;moveRight&quot;,
    Space: &quot;jump&quot;,
    Digit1: &quot;dirt&quot;,
    Digit2: &quot;grass&quot;,
    Digit3: &quot;glass&quot;,
    Digit4: &quot;wood&quot;,
    Digit5: &quot;log&quot;,
  };
  return keyActionMap[key];
}
export const useKeyboard = () =&gt; {

 // Key 입력에 대한 상태를 관리하기 위한 State
  const [actions, setActions] = useState({
    moveForward: false,
    moveBackward: false,
    moveLeft: false,
    moveRight: false,
    jump: false,
    dirt: false,
    grass: false,
    glass: false,
    wood: false,
    log: false,
  });

  // Key 입력이 발생하면 해당 Key에 대응되는 actions에 state를 true로 변경
  const handleKeyDown = useCallback((e) =&gt; {
    const action = actionByKey(e.code);
    if (action) {
      setActions((prev) =&gt; {
        return {
          ...prev,
          [action]: true,
        };
      });
    }
  }, []);

// Key 해제가 발생하면 해당 Key에 대응되는 actions에 state를 false로 변경
  const handleKeyUp = useCallback((e) =&gt; {
    const action = actionByKey(e.code);
    if (action) {
      setActions((prev) =&gt; {
        return {
          ...prev,
          [action]: false,
        };
      });
    }
  }, []);

  // Key 이벤트를 반영
  useEffect(() =&gt; {
    document.addEventListener(&quot;keydown&quot;, handleKeyDown);
    document.addEventListener(&quot;keyup&quot;, handleKeyUp);
    return () =&gt; {
      document.removeEventListener(&quot;keydown&quot;, handleKeyDown);
      document.removeEventListener(&quot;keyup&quot;, handleKeyUp);
    };
  }, [handleKeyDown, handleKeyUp]);
  return actions;
};</code></pre>
<p>이번에는 threeJs 관련된 사항은 없기 때문에 자세한 설명은 생략하겠다.
사용자의 Key 이벤트가 발생하면, 해당 Key의 종류를 판별해 사전에 명시한 keyActionMap의 값을 반환하고, 해당 값을 기준으로 actions state를 true, false를 전환한다.
즉, 특정 키가 눌리면, 해당 key에 대응되는 state를 true로 전환하고,
keyup(키가 해제) 되면 해당 key에 대응되는 state를 false로 전환한다.</p>
<p>사용자 input에 대한 처리가 끝났으니 실제로 해당 hook을 가져와 반영을 해보자
src/components/에 Player를 다음과 같이 수정하자</p>
<pre><code class="language-javascript">// src/components/Player.js
import { useEffect, useRef } from &quot;react&quot;;
import { useFrame, useThree } from &quot;@react-three/fiber&quot;;
import { useSphere } from &quot;@react-three/cannon&quot;;
import { Vector3 } from &quot;three&quot;;
import { useKeyboard } from &quot;../hooks/useKeyboard&quot;; // + 만들어 둔 hook을 추가하자
const JUMP_FORCE = 4; // + Player JUMP 발생시 적용할 힘의 값

export const Player = () =&gt; {
  const { camera } = useThree();
  const [ref, api] = useSphere(() =&gt; ({
    mass: 1,
    type: &quot;Dynamic&quot;,
    position: [0, 1, 0],
  }));


  const vel = useRef([0, 0, 0]);
  useEffect(() =&gt; {
    api.velocity.subscribe((v) =&gt; (vel.current = v));
  }, [api.velocity]);

  const pos = useRef([0, 0, 0]);
  useEffect(() =&gt; {
    api.position.subscribe((p) =&gt; (pos.current = p));
  }, [api.position]);

  useFrame(() =&gt; {
    camera.position.copy(
      new Vector3(pos.current[0], pos.current[1], pos.current[2])
    );

    // + 가져온 actions hook에서 jump의 값이 true인 경우(key down이 발생한 경우) 실행
    if (actions.jump) {
      // Player의 속도값 중 y축의 값 즉, 높이를 JUMP_FORCE = 4 만큼 적용시킨다.
      api.velocity.set(vel.current[0], JUMP_FORCE, vel.current[2]);
    }

  });
  return &lt;mesh ref={ref}&gt;&lt;/mesh&gt;;
};
</code></pre>
<p>모든 구성이 끝났다면 적용시켜 확인해보자
<img src="https://velog.velcdn.com/images/sugar_ghost/post/b0043e5a-15db-4d0c-a8ac-3976a0729aab/image.png" alt=""></p>
<p>space를 누르는 순간 Player가 성공적으로 점프를 한다.
다만 한가지 문제가 있다면, Space를 연타해서 누르는 만큼 점프가 멈추지 않고 계속 상위로 올라간 다는 것이다.
우리는 현재 Key input 값이 true인지만 확인하고 true라면 지속적으로 속도값이 update되기 때문이다.</p>
<p>연속적인 jump를 방지하기 위해 Player.js를 다시 수정해주자.</p>
<pre><code class="language-javascript">// src/components/Player.js
import { useEffect, useRef } from &quot;react&quot;;
import { useFrame, useThree } from &quot;@react-three/fiber&quot;;
import { useSphere } from &quot;@react-three/cannon&quot;;
import { Vector3 } from &quot;three&quot;;
import { useKeyboard } from &quot;../hooks/useKeyboard&quot;;
const JUMP_FORCE = 4;

export const Player = () =&gt; {
  const actions = useKeyboard();
  const { camera } = useThree();
  const [ref, api] = useSphere(() =&gt; ({
    mass: 1,
    type: &quot;Dynamic&quot;,
    position: [0, 1, 0],
  }));

  const vel = useRef([0, 0, 0]);
  useEffect(() =&gt; {
    api.velocity.subscribe((v) =&gt; (vel.current = v));
  }, [api.velocity]);

  const pos = useRef([0, 0, 0]);
  useEffect(() =&gt; {
    api.position.subscribe((p) =&gt; (pos.current = p));
  }, [api.position]);

  useFrame(() =&gt; {
    camera.position.copy(
      new Vector3(pos.current[0], pos.current[1], pos.current[2])
    );

    // + if 제약조건에 Math.abs(vel.current[1]) &lt; 0.05를 추가하자
    if (actions.jump &amp;&amp; Math.abs(vel.current[1]) &lt; 0.05) {
      api.velocity.set(vel.current[0], JUMP_FORCE, vel.current[2]);
    }
  });
  return &lt;mesh ref={ref}&gt;&lt;/mesh&gt;;
};
</code></pre>
<p>만약 속도값이 올라가든(+) 내려가든(-) 절대값(Math.abs)을 기반으로 0.05 미만인 경우에만 JUMP_FORCE 속도값을 적용시켜준다.
물론 점프 후 상공에서 속도값이 정지하는 정확한 타이밍에 space바를 누른다면 계속 상승하겠지만 어느정도는 상쇄했다.</p>
<p>이제는 Player에 움직임을 적용할 차례이다.
Player.js를 다음과 같이 수정해보자</p>
<pre><code class="language-javascript">// src/components/Player.js
import { useEffect, useRef } from &quot;react&quot;;
import { useFrame, useThree } from &quot;@react-three/fiber&quot;;
import { useSphere } from &quot;@react-three/cannon&quot;;
import { Vector3 } from &quot;three&quot;;
import { useKeyboard } from &quot;../hooks/useKeyboard&quot;;
const JUMP_FORCE = 4;
const SPEED = 4; // + 이동 SPEED를 위해 추가
export const Player = () =&gt; {
  const { moveBackward, moveForward, moveLeft, moveRight, jump } =
    useKeyboard(); // + 기존에 actions로 통칭되던 요소를 개별요소를 직접 다 가져오도록 변경
  const { camera } = useThree();
  const [ref, api] = useSphere(() =&gt; ({
    mass: 1,
    type: &quot;Dynamic&quot;,
    position: [0, 1, 0],
  }));

  const vel = useRef([0, 0, 0]);
  useEffect(() =&gt; {
    api.velocity.subscribe((v) =&gt; (vel.current = v));
  }, [api.velocity]);

  const pos = useRef([0, 0, 0]);
  useEffect(() =&gt; {
    api.position.subscribe((p) =&gt; (pos.current = p));
  }, [api.position]);

  useFrame(() =&gt; {
    camera.position.copy(
      new Vector3(pos.current[0], pos.current[1], pos.current[2])
    );
    // + 여기서부터 새로 추가될 코드들

    const direction = new Vector3();

    // z(정면)축 기준으로 이동 계산
    // 만약 앞+뒤가 동시에 눌리면 -를 통해 값을 0으로 만들어 이동하지 않음
    const frontVector = new Vector3(
      0,
      0,
      (moveBackward ? 1 : 0) - (moveForward ? 1 : 0)
    );

    // x축 기준 이동 계산
    const sideVector = new Vector3(
      (moveLeft ? 1 : 0) - (moveRight ? 1 : 0),
      0,
      0
    );

    direction
      .subVectors(frontVector, sideVector)
      .normalize()
      .multiplyScalar(SPEED)
      .applyEuler(camera.rotation);

    // 이동 속도를 반영
    api.velocity.set(direction.x, vel.current[1], direction.z);
    // 여기까지 추가!

    // 아래 if 조건에서 기존에 actions.jump를 그냥 jump로 대체
    if (jump &amp;&amp; Math.abs(vel.current[1]) &lt; 0.05) {
      api.velocity.set(vel.current[0], JUMP_FORCE, vel.current[2]);
    }
  });
  return &lt;mesh ref={ref}&gt;&lt;/mesh&gt;;
};
</code></pre>
<p>새롭게 <code>subVectors</code>, <code>normalize</code>, <code>multiplyScalar</code>, <code>applyEuler</code> 등이 보인다.
하나씩 알아보도록 하자
<a href="https://threejs.org/docs/?q=vector#api/en/math/Vector3.subVectors"><code>subVectors</code></a>는 2개의 Vector3(a와 b)를 인자로 받아 현재 Vector를 a-b의 Vector로 설정한다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/41a03753-336f-4224-ac45-937945235795/image.png" alt="">
즉 우리는 앞뒤(frontVector) 움직임과 양옆(sideVector)를 움직임을 같이 고려해 대각선의 Vector 이동을 계산하기 위해서 사용한다.</p>
<p><a href="https://threejs.org/docs/?q=vector#api/en/math/Vector3.normalize"><code>normalize</code></a>는 현재 Vector를 unit vector로 변경해준다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/344716e3-a93d-4e56-b0e0-4cad971e6ee3/image.png" alt="">
Vector는 좌표를 나타내기도 하지만, 방향을 나타내기도 한다.
우린 frontVector, sideVector의 subVectors를 통해 Vector의 방향을 가지게 되었다.
<a href="https://en.wikipedia.org/wiki/Unit_vector">unit vector</a>는 길이가 1인 vector를 뜻하며, normalize를 통해 우리의 방향을 가진 Vector의 길이는 1이 되는 것이다.</p>
<p><a href="https://threejs.org/docs/?q=vector#api/en/math/Vector3.multiplyScalar"><code>multiplyScalar</code></a>는 현재 vector에 scalar 값을 곱하는데, 
vector는 크기와 방향을 가지고, scalar는 방향이 없이 크기만 가지고 있다.
이는 속도(velocity)와 속력(speed)의 차이로, 우리는 SPEED = 4 를 통해 4의 값을 넘겨줌으로, 현재 방향이 계산된 Vector에 4의 속력을 곱해주는 개념이다.</p>
<p><a href="https://threejs.org/docs/?q=vector#api/en/math/Vector3.applyEuler"><code>applyEuler</code></a>는 Euler 객체를 Quaternion으로 변환하고 현재 Vector에 Euler 변화를 적용시킨다.
Euler, Quaternion 모두 3D 변환에서 회전을 표현하는 방식이다.
Euler 각도는 x,y,z의 3개 축을 기준으로 회전시키는 각도계를 의미한다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/1d1a3df2-776a-4875-bff2-77921e98f8a3/image.png" alt=""></p>
<p>다만 회전 중 같은 방향으로 오브젝트의 두 회전 축이 겹치는 짐벌락 현상이 발생하는 문제가 존재하고, 그 한계를 보완하기 위해 벡터(x,y,z)에 스칼라(w)를 더한 Quaternion이 등장했다.
이 부분은 복잡하고 내 지식이 미흡하기에 간단히 넘어가자면, 우리가 이동을 위해 계산한 Vector에 현재 camera의 회전값을 적용시키는 것으로 이동시 회전 정보를 반영시킨다.</p>
<p>설명이 좀 길어졌으니 이제 실행을 해봐서 테스트를 해보자
별다른 사진은 첨부하지 않았지만, w,a,s,d를 통해서 사용자의 위치를 이동시킬 수 있을 것이다.
다만 우리가 방금 이야기한 camera의 회전값은 적용이 되지 않았는데, camera에 회전 이벤트가 존재하지 않기 때문이다.</p>
<p>이제 Player에 마우스기반 1인칭 뷰를 추가해보자</p>
<p>먼저 src/components/ 경로에 FPV.js 파일을 생성하고 다음과 같이 구성하자</p>
<pre><code class="language-javascript">// src/components/FPV.js
import { PointerLockControls } from &quot;@react-three/drei&quot;;
import { useThree } from &quot;@react-three/fiber&quot;;

export const FPV = () =&gt; {
  const { camera, gl } = useThree();

  return &lt;PointerLockControls args={[camera, gl.domElement]} /&gt;;
};</code></pre>
<p>또한 새로 추가된 FPV를 반영하기 위해 App.js를 열어 코드를 추가하자</p>
<pre><code class="language-javascript">// src/App.js
import { Canvas } from &quot;@react-three/fiber&quot;;
import { Sky } from &quot;@react-three/drei&quot;;
import { Physics } from &quot;@react-three/cannon&quot;;
import { Ground } from &quot;./components/Ground&quot;;
import { Player } from &quot;./components/Player&quot;;
import { FPV } from &quot;./components/FPV&quot;; // + 컴포넌트 추가

function App() {
  return (
    &lt;&gt;
      &lt;Canvas&gt;
        &lt;Sky sunPosition={[100, 100, 20]} /&gt;
        &lt;ambientLight intensity={0.5} /&gt;
        &lt;FPV /&gt; // + 물리적 영향을 받는 요소가 아니기에 Physics 외부에 추가
        &lt;Physics&gt;
          &lt;Player /&gt;
          &lt;Ground /&gt;
        &lt;/Physics&gt;
      &lt;/Canvas&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>다시 FPV.js로 돌아가 주요 요소에 대해서 알아보자
<code>useThree</code>를 통해 가져온 <a href="https://docs.pmnd.rs/react-three-fiber/api/hooks#state-properties"><code>gl</code></a>은 Renderer를 참조하는 prop으로 threeJs에 WebGLRenderer type을 기반으로 한다.
추후에 PointerLockControls를 통해 인자로 넘겨지는 <a href="https://threejs.org/docs/?q=gl#api/en/renderers/WebGLRenderer.domElement"><code>gl.domElemnet</code></a>는 Renderer가 출력을 그리는 canvas다</p>
<p><a href="https://github.com/pmndrs/drei#controls"><code>PointerLockControls</code></a>은 drei에 제공되는 Control 방식 중, 마우스 포인터를 잠근채로 마우스로 컨트롤을 하는 방식이다.
drei의 control들은 ThreeJs의 control을 참조함으로 ThreeJs의 <a href="https://threejs.org/docs/?q=control#examples/en/controls/PointerLockControls"><code>PointerLockControls</code></a> 과 <a href="https://threejs.org/docs/?q=control#examples/en/controls/FirstPersonControls"><code>FirstPersonControls</code></a> 문서를 첨부하는 것으로 Props에 대한 설명을 생략하겠다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/69e82569-8ca8-4e1a-b614-ef931cf1413c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/d4ee768b-b714-496f-81bc-6a03d8ac24b4/image.png" alt=""></p>
<p>모든 구성이 끝났다면 우리는 마우스 회전에 따라 시야가 회전하는 Player 컨트롤을 가지게 된다.
하지만 조준점이 없으면 허전하니 조준점을 간단히 추가해보자</p>
<pre><code class="language-javascript">// src/App.js
import { Canvas } from &quot;@react-three/fiber&quot;;
import { Sky } from &quot;@react-three/drei&quot;;
import { Physics } from &quot;@react-three/cannon&quot;;
import { Ground } from &quot;./components/Ground&quot;;
import { Player } from &quot;./components/Player&quot;;
import { FPV } from &quot;./components/FPV&quot;;

function App() {
  return (
    &lt;&gt;
      &lt;Canvas&gt;
        &lt;Sky sunPosition={[100, 100, 20]} /&gt;
        &lt;ambientLight intensity={0.5} /&gt;
        &lt;FPV /&gt;
        &lt;Physics&gt;
          &lt;Player /&gt;
          &lt;Ground /&gt;
        &lt;/Physics&gt;
      &lt;/Canvas&gt;
      &lt;div className=&quot;absolute centered cursor&quot;&gt;+&lt;/div&gt; // + 간단한 조준점을 추가하자
    &lt;/&gt;
  );
}

export default App;
</code></pre>
<p>조준점에 대한 CSS를 추가해야하니
src/index.css에 가장 아래에 다음과 같은 내용을 추가하자</p>
<pre><code class="language-css">.centered {
  top: 50%;
  left:50%;
  transform: translate(-50%,-50%);
}
.cursor {
  color: white;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/7d78044e-8d60-4d84-b9f2-5fd49967f434/image.png" alt=""></p>
<p>드디어 Player 구성이 끝났다.
다음번에는 실제로 마인크래프트 블럭을 설치해보자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ThreeJs+React로 마인크래프트 만들기 번역(by Daniel Bark from freeCodeCamp) - Textures and images & Ground]]></title>
            <link>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-Textures-and-images-Ground</link>
            <guid>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-Textures-and-images-Ground</guid>
            <pubDate>Fri, 07 Oct 2022 13:52:46 GMT</pubDate>
            <description><![CDATA[<p>이번에는 기존 images 폴더에 존재하는 이미지 파일을 이용해 3D 환경에서 표현될 질감으로 만들고자 한다.
이미 ThreeJS에서 이미지-&gt;질감으로 만들기 위한 기능을 제공하기에 코드 자체는 단순하다</p>
<p>먼저 images 폴더에 기본 제공되는 images.js를 열어보자</p>
<pre><code class="language-javascript">// src/images/images.js
import dirtImg from &#39;./dirt.jpg&#39;;
import grassImg from &#39;./grass.jpg&#39;;
import glassImg from &#39;./glass.png&#39;;
import logImg from &#39;./log.jpg&#39;;
import woodImg from &#39;./wood.png&#39;;

const images = {
    dirtImg,
    grassImg,
    glassImg,
    woodImg,
    logImg,
}

export default images</code></pre>
<p><code>images</code>라는 이름으로 export 되고있는 코드 구성을 좀더 단순하게 변경해보자</p>
<pre><code class="language-javascript">// src/images/images.js
// 내 환경에서는 ESLint를 사용하기에 기존 &#39;으로 구성된 문자열이 &quot;으로 변경됨(필수는 아님)
import dirtImg from &quot;./dirt.jpg&quot;;
import grassImg from &quot;./grass.jpg&quot;;
import glassImg from &quot;./glass.png&quot;;
import logImg from &quot;./log.jpg&quot;;
import woodImg from &quot;./wood.png&quot;;

export { dirtImg, grassImg, glassImg, woodImg, logImg }; // images라는 통합된 이름 대신 구성요소를 개별로 분할</code></pre>
<p>image 파일들이 export 되었다면 images 폴더에 textures.js 파일을 생성해주고 다음과 같이 구성하자</p>
<pre><code class="language-javascript">// src/images/textures.js
import { TextureLoader } from &quot;three&quot;; // 이미지 파일을 Texture로 변환하기 위한 기능
import { dirtImg, logImg, glassImg, grassImg, woodImg } from &quot;./images&quot;;

const dirtTexture = new TextureLoader().load(dirtImg); // load()를 호출해 이미지를 할당하고 만들어진 texture 결과를 변수에 할당
const logTexture = new TextureLoader().load(logImg);
const grassTexture = new TextureLoader().load(grassImg);
const glassTexture = new TextureLoader().load(glassImg);
const woodTexture = new TextureLoader().load(woodImg);
const groundTexture = new TextureLoader().load(grassImg);

export {
  dirtTexture,
  logTexture,
  grassTexture,
  glassTexture,
  woodTexture,
  groundTexture,
};
</code></pre>
<p>TextureLoader를 통해 각 Texture 변수들에는 각각의 이미지를 활용한 텍스쳐가 담겨있다.
텍스쳐를 생성하고, 이 텍스쳐를 활용한 지면(Ground)을 만들어보자</p>
<p>src 경로에 components 폴더를 만들고 Ground.js 파일을 생성한뒤 다음과 같이 구성하자</p>
<pre><code class="language-javascript">import { usePlane } from &quot;@react-three/cannon&quot;; // 평면을 구성하기 위한 기능
import { groundTexture } from &quot;../images/textures&quot;;

export const Ground = () =&gt; {
  // usePlane을 통해 평면을 구성하고 ref를 통해 접근한다.
  const [ref] = usePlane(() =&gt; ({
    rotation: [0, 0, 0], // 평면의 기울기
    position: [0, 0, 0], // 평면의 위치값
  }));
  return (
    // 다음에 mesh, planeBufferGeometry, meshStandardMaterial는 three-types.d.ts에 정의되어 있다.
    &lt;mesh ref={ref}&gt; //mesh는 3D 화면을 구성하는 물체이며 ref를 통해 cannon을 통해 구성한 평면의 물리적인 속성을 적용한다.
    // 물체(mesh)는 부피(Geometry)와 질감(Material)의 정보로 구성된다.
      &lt;planeBufferGeometry attach=&quot;geometry&quot; args={[100, 100]} /&gt; 
      &lt;meshStandardMaterial attach=&quot;material&quot; map={groundTexture} /&gt;
    &lt;/mesh&gt;
  );
};
</code></pre>
<p>모든것을 code에서 주석으로 설명하는 대신 중요 구성요소에 대해서 별도로 진행하고자 한다.
먼저 usePlane에 대해서 알아보자
cannon 라이브러리 ReadMe를 참조하면, API에 대해 명시가 되어있다.
<a href="https://github.com/pmndrs/use-cannon/tree/master/packages/react-three-cannon#exports">API 링크</a>
API에는 반환값이 API로 표시되어 있기에 react-three-cannon에 명시되어 있는 usePlane 선언부를 가져와보자</p>
<pre><code class="language-javascript">// react-three-cannon/src/hooks.ts
export function usePlane&lt;O extends Object3D&gt;(
  fn: GetByIndex&lt;PlaneProps&gt;,
  fwdRef?: Ref&lt;O&gt;,
  deps?: DependencyList,
) {
  return useBody(&#39;Plane&#39;, fn, () =&gt; [], fwdRef, deps)
}</code></pre>
<p><code>usePlane</code>은 함수를 인자로 받으며, 해당 구성은 <code>PlaneProps</code>를 따른다.
PlaneProps는 <code>@pmndrs/cannon-worker-api</code> package로부터 import 되었다.</p>
<pre><code class="language-javascript">export type PlaneProps = BodyProps</code></pre>
<p><code>PlaneProps</code>는 BodyProps를 interface 형식으로 export한 개념이며, 실질적인 타입은 다음과 같다.</p>
<pre><code class="language-javascript">export type BodyProps&lt;T extends any[] = unknown[]&gt; = 
  Partial&lt;AtomicProps&gt; &amp;
  Partial&lt;VectorProps&gt; &amp; {
    args?: T
    onCollide?: (e: CollideEvent) =&gt; void
    onCollideBegin?: (e: CollideBeginEvent) =&gt; void
    onCollideEnd?: (e: CollideEndEvent) =&gt; void
    quaternion?: Quad
    rotation?: Triplet
    type?: &#39;Dynamic&#39; | &#39;Static&#39; | &#39;Kinematic&#39;
  }
</code></pre>
<p>우리가 넘기고자 했던 rotation에 대한 정보를 받고, position 정보에 대해서는 <code>Partial&lt;VectorProps&gt;</code> 내부에 선언되어 있다.
이러한 과정은 지금 중요한건 아니지만
readMe 상으로는 간략한 정보만 제공되고 상세 document가 안보였기에 잠시 구조를 알아보는 연습을 했다
지금은 간단히 개념만 이해하고 추가로 threeJS 자체에 대한 공부가 선행이 필요할 것 같다.</p>
<p>다음으로 mesh는 실제 3D 환경에서 구현될 물체를 나타낸다. ref를 연결해 cannon을 통한 평면의 물리적 정보를 연결시켜 주었고 추가로 부피(Geometry)와 질감(Material)의 정보를 구성한다.
<code>planeBufferGeometry</code>과<code>meshStandardMaterial</code>은 모두 threeJS document를 통해 그 구성을 확인 할 수 있다.
threeJS 코드를 확인하면 planeBufferGeometry에 대해 다음과 같이 명시되어있다.</p>
<pre><code class="language-javascript">class PlaneBufferGeometry extends PlaneGeometry {
    constructor(width, height, widthSegments, heightSegments) {
        console.warn(&#39;THREE.PlaneBufferGeometry has been renamed to THREE.PlaneGeometry.&#39;);
        super(width, height, widthSegments, heightSegments);
    }

} // r144</code></pre>
<p>threeJs document를 통해 <a href="https://threejs.org/docs/api/en/geometries/PlaneGeometry.html">PlaneGeometry</a>에 대한 내용을 확인해보자
<img src="https://velog.velcdn.com/images/sugar_ghost/post/208848d2-5112-422b-8535-76a44787bcd3/image.png" alt=""></p>
<p>생성자를 통해 받는 인자값을 확인할 수 있으며
우리가 넘겨준 값은 [100,100]으로 각각 width(100)와 height(100)를 구성한다는 것을 알 수 있다.
즉 물체의 크기 100x100(폭x높이)의 구성을 가지게 된다.</p>
<p><a href="https://threejs.org/docs/api/en/materials/MeshStandardMaterial.html">MeshStandardMaterial</a>에 대해서는 다음과 같이 Object를 파라미터로 받는다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/58e5f11e-10e2-4219-b9c8-8bd555404602/image.png" alt="">
우리가 넘겨준 Object는 TextureLoader를 통해 Texture로 변환된 grassImg(잔디)를 넘겨준다.</p>
<p>각각의 PlaneBufferGeometry과 meshStandardMaterial에는 attach라는 속성이 붙어있다.
해당 속성은 react-three-fiber를 통한 속성이며 <a href="https://docs.pmnd.rs/react-three-fiber/api/objects#attach">document</a>에서 확인해 볼 수 있다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/74ed6a0d-05b1-4bff-8fb1-d54fe7dd5993/image.png" alt="">
해당 부피와 질감 등의 객체들을 원형이 되는 부모에 연결하기 위한 속성이지만, <code>material</code> 및 <code>geometry</code>로 끝나는 객체는 자동으로 연결이 되기 때문에 생략이 가능하다.</p>
<p>설명이 좀 길어졌지만, 지면을 구성했으면 반영을 위해 App.js에 추가를 해줘야한다.</p>
<pre><code class="language-javascript">// src/App.js
import { Canvas } from &quot;@react-three/fiber&quot;;
import { Sky } from &quot;@react-three/drei&quot;;
import { Physics } from &quot;@react-three/cannon&quot;; // 물리적 계산 범위을 지정하기 위한 요소
import { Ground } from &quot;./components/Ground&quot;; // 새롭게 만든 Ground 컴포넌트를 import

function App() {
  return (
    &lt;&gt;
      &lt;Canvas&gt;
        &lt;Sky sunPosition={[100, 100, 20]} /&gt;
        &lt;ambientLight intensity={0.5} /&gt; // 물체에 적용될 광원에 밝기
        &lt;Physics&gt; // 물리적인 계산이 필요한 요소는 Physics 내부에 선언한다.
          &lt;Ground /&gt; // 지면은 플레이어 및 블록들과 물리적인 연산이 필요함으로 Physics 내부에 선언
        &lt;/Physics&gt;
      &lt;/Canvas&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>새롭게 추가된 코드 중에 <a href="https://threejs.org/docs/api/en/lights/AmbientLight.html"><code>ambientLight</code></a>가 존재한다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/fa954454-c2e4-433d-9a91-f31c20b7c92d/image.png" alt=""></p>
<p>우리는 <code>Sky</code>를 통해 광원을 추가했지만, 광원이 물체에 작용하기 위해서는 빛의 속성을 정의해주어야 한다.
<code>AmbientLight</code>는 빛의 방향과는 관계 없이 빛 자체가 물체에 반영되는 작용을 정의한다.
우리는 color에 대해서 지정하지 않고 <code>intensity</code>의 값을 0.5로 주었다.
즉 빛의 강도를 0.5를 기준으로 각 물체에 균일하게 적용시켜준다.</p>
<p>모든 소스코드를 작성하고 반영하면 우리는 다음과 같은 초록색 화면을 보게 된다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/370725a0-3dcb-4fd6-abcf-1b03a9cab53c/image.png" alt="">
우리가 원했던 것은 다음과 같은 잔디 이미지가 반영이 되는 것이다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/4b6ab712-8414-4e18-aee3-0b1cb6a16456/image.png" alt="">
우리가 반영한 잔디 텍스쳐가 100x100에 사이즈에 맞춰서 확장이 되었기 때문에 텍스쳐를 반복시키는 작업이 필요하다.
다시 Ground.js로 돌아가 몇가지 소스코드를 추가해주자.</p>
<pre><code class="language-javascript">// src/components/Ground.js
import { usePlane } from &quot;@react-three/cannon&quot;;
import { RepeatWrapping } from &quot;three&quot;; // + 텍스쳐 반복을 위해 추가
import { groundTexture } from &quot;../images/textures&quot;;

export const Ground = () =&gt; {
  const [ref] = usePlane(() =&gt; ({
    rotation: [0, 0, 0],
    position: [0, 0, 0],
  }));

  // 아래 3줄의 코드를 추가
  groundTexture.wrapS = RepeatWrapping;
  groundTexture.wrapT = RepeatWrapping;
  groundTexture.repeat.set(100, 100);

  return (
    &lt;mesh ref={ref}&gt;
      &lt;planeBufferGeometry attach=&quot;geometry&quot; args={[100, 100]} /&gt;
      &lt;meshStandardMaterial attach=&quot;material&quot; map={groundTexture} /&gt;
    &lt;/mesh&gt;
  );
};</code></pre>
<p><code>groundTexture</code>는 기존에<code>new TextureLoader().load()</code>를 통해 Texture화 되었다.
그렇기에 <code>wrapS</code>와 <code>wrapT</code>의 정보는 threeJs document에 <a href="https://threejs.org/docs/#api/en/textures/Texture">Texture</a> 항목에서 찾을 수 있다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/e3ab0037-bd27-4e7d-aec4-697e71559904/image.png" alt="">
<img src="https://velog.velcdn.com/images/sugar_ghost/post/1a4b787b-450e-41a1-b6fe-b898de37cc3c/image.png" alt=""></p>
<p>wrap은 텍스쳐가 래핑되는 방식을 지정하며, S는 수평이자 U, T는 수직이자 V를 뜻한다
UV는 2차원 그림을 3차원 모델로 만드는 UV 매핑을 의미한다.
<a href="https://ko.wikipedia.org/wiki/UV_%EB%A7%A4%ED%95%91">UV 매핑</a>
<img src="https://velog.velcdn.com/images/sugar_ghost/post/c4f810b7-8e2d-4b3a-8f10-9586ec50f2ce/image.png" alt=""></p>
<p>우리가 지정한 <a href="https://threejs.org/docs/#api/en/constants/Textures">RepeatWrapping</a>은 wrapS와 wrapT를 정의하는 속성으로 텍스쳐를 무한대로 반복시키며 매핑을 하는 방식이다.</p>
<pre><code class="language-javascript">groundTexture.repeat.set(100, 100);</code></pre>
<p>텍스쳐를 매핑하는 방식에 대해 정의를 했으면, repeat를 통해서 얼마나 반복을 진행할 것인지 알려준다.
우리는 Geometry를 통해 평면의 부피를 100x100으로 정의해 주었고 그 기준에 맞추어 텍스쳐의 반복을 수평 수직 100,100으로 진행해준다.</p>
<p>코드가 어떻게 동작하는지 실제로 확인해보자
<img src="https://velog.velcdn.com/images/sugar_ghost/post/dd0b4d02-c877-4fd6-94c6-8c892ea7d0dd/image.png" alt="">
이제 좀더 입체적인 패턴이 보이기 시작했다.
하지만 우리가 원한 네모 도트의 느낌보다는 약간 섞여버린 무언가가 나타났다.</p>
<p>다시 Ground.js로 돌아가 소스코드를 수정해보자</p>
<pre><code class="language-javascript">// src/components/Ground.js
import { usePlane } from &quot;@react-three/cannon&quot;;
import { NearestFilter, RepeatWrapping } from &quot;three&quot;; // + NearestFilter 추가
import { groundTexture } from &quot;../images/textures&quot;;

export const Ground = () =&gt; {
  const [ref] = usePlane(() =&gt; ({
    rotation: [0, 0, 0],
    position: [0, 0, 0],
  }));

  groundTexture.magFilter = NearestFilter; // Filter를 추가
  groundTexture.wrapS = RepeatWrapping;
  groundTexture.wrapT = RepeatWrapping;
  groundTexture.repeat.set(100, 100);

  return (
    &lt;mesh ref={ref}&gt;
      &lt;planeBufferGeometry attach=&quot;geometry&quot; args={[100, 100]} /&gt;
      &lt;meshStandardMaterial attach=&quot;material&quot; map={groundTexture} /&gt;
    &lt;/mesh&gt;
  );
};</code></pre>
<p>새롭게 <a href="https://threejs.org/docs/#api/en/textures/Texture.magFilter">magFilter</a>가 추가되었다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/74f9c095-ca7f-4080-8f2b-c45481a81c20/image.png" alt="">
magFilter는 텍스쳐가 중첩되었을 때 샘플링 되는 방식으로 기본값은 4개의 가까운 픽셀을 이용해 보간된다.
NearestFilter를 사용하면 가장 가까운 텍스쳐 요소의 값을 반환한다.
그 결과는 다음과 같다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/affe5bfd-c9e1-4e90-81b1-016a224115ae/image.png" alt="">
좀더 자연스러운 픽셀이 표현되었고, 이것으로 지면이 구성되었다.
하지만 우리는 지면을 정면으로 마주보고 있는데, 평면 지면을 위해서 우리는 지면을 회전시켜야한다.</p>
<p>Ground.js의 소스코드를 수정해보자</p>
<pre><code class="language-javascript">// src/components/Ground.js
import { usePlane } from &quot;@react-three/cannon&quot;;
import { NearestFilter, RepeatWrapping } from &quot;three&quot;;
import { groundTexture } from &quot;../images/textures&quot;;

export const Ground = () =&gt; {
  const [ref] = usePlane(() =&gt; ({
    rotation: [-Math.PI / 2, 0, 0], // rotation 값을 수정해 기울기를 조절하자
    position: [0, 0, 0],
  }));

  groundTexture.mapFilter = NearestFilter;
  groundTexture.wrapS = RepeatWrapping;
  groundTexture.wrapT = RepeatWrapping;
  groundTexture.repeat.set(100, 100);

  return (
    &lt;mesh ref={ref}&gt;
      &lt;planeBufferGeometry attach=&quot;geometry&quot; args={[100, 100]} /&gt;
      &lt;meshStandardMaterial attach=&quot;material&quot; map={groundTexture} /&gt;
    &lt;/mesh&gt;
  );
};</code></pre>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/1ce21175-dc91-4e13-8635-f4e0d3254d7b/image.png" alt="">
각도를 구할때 기준점은 π(파이)를 사용한다.
π/2 는 90도, π는 180도 로 계산할 수 있다.
지면을 회전시킬때, 우리는 x 축을 기준으로 한다. 
즉 우리가 정면에서 바라볼때 x축의 각도를 조절하면 앞으로 넘어지거나 뒤로 넘어가는 형식으로 회전할 것이다.
다만 우리는 앞으로 넘어져 뒤집어 지기 보다는, 우리가 보는 그대로 뒤로 넘어져 깔리기를 원한다.
그러니 <code>Math.PI</code>에 -를 주고 /2로 나눠주자
그럼 지면은 뒤로 90도 만큼 누울 것이다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/e436d132-bb37-40f9-8166-2fb012129b33/image.png" alt="">
결과를 보면 지면이 사라진 것처럼 보이지만, 그저 회전을 했을 뿐 지면이라는 요소는 존재한다.
만약 각도를 <code>-Math.PI / 4</code> 정도로 살짝 덜 기울게 한다면 우리는 결과를 볼 수 있다.</p>
<p><img src="blob:https://velog.io/24f672f0-c9ae-4b95-97a2-1588219befb1" alt="업로드중.."></p>
<p>이것으로 지면은 끝났다.
다음은 Player에 대한 처리를 진행해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ThreeJs+React로 마인크래프트 만들기 번역(by Daniel Bark from freeCodeCamp) - Sky]]></title>
            <link>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-Sky</link>
            <guid>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-Sky</guid>
            <pubDate>Fri, 07 Oct 2022 09:46:53 GMT</pubDate>
            <description><![CDATA[<p>첫번째로 할 것은 바로 광원을 만드는 것이다.
3D 환경에서 요소들이 시각적으로 보일려면 빛이 존재해야한다.</p>
<p>아래의 이미지가 우리가 처음 프로젝트를 실행시키면 보게 될 화면이다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/24e30a85-c9a8-44ac-a6e4-37daae5d52c7/image.png" alt=""></p>
<pre><code class="language-jsx">// App.js
import { Canvas } from &#39;@react-three/fiber&#39;;

function App() {
  return (
    &lt;&gt;
      &lt;div&gt;Outside Canvas&lt;/div&gt;
      &lt;Canvas&gt;
      &lt;/Canvas&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>다양한 구성요소를 보유하고있는 <code>drei</code> 라이브러리에서는 광원과 하늘을 자연스럽게 표현해주는 <code>Sky</code> 라는 구성요소를 가지고 있다.</p>
<pre><code class="language-jsx">&lt;Sky distance={450000} sunPosition={[0, 1, 0]} inclination={0} azimuth={0.25} {...props} /&gt;</code></pre>
<p>방식으로 사용하며
App.js를 열고 코드를 다음과 같이 수정하자</p>
<pre><code class="language-jsx">import { Canvas } from &quot;@react-three/fiber&quot;;
import { Sky } from &quot;@react-three/drei&quot;; // + drei 라이브러리에 Sky 구성요소를 가져오자

function App() {
  return (
    &lt;&gt;
      // - 기존 &lt;div&gt;Outside Canvas&lt;/div&gt;는 지워주자
      &lt;Canvas&gt;
        &lt;Sky sunPosition={[100, 100, 20]} /&gt; // 광원의 위치를 sunPosition을 통해 잡아준다.
      &lt;/Canvas&gt;
    &lt;/&gt;
  );
}

export default App;
</code></pre>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/f485fb13-bd94-4b09-b9a7-c8be7efc4f87/image.png" alt="">
실행시 화면에는 광원과 함께 하늘색의 그라데이션이 표현이 된다.
다음은 Textures와 images를 작업해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ThreeJs+React로 마인크래프트 만들기 번역(by Daniel Bark from freeCodeCamp) - 소개]]></title>
            <link>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@sugar_ghost/ThreeJsReact%EB%A1%9C-%EB%A7%88%EC%9D%B8%ED%81%AC%EB%9E%98%ED%94%84%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%B2%88%EC%97%ADby-Daniel-Bark-from-freeCodeCamp-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Fri, 07 Oct 2022 09:15:27 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.youtube.com/channel/UCgUCptbp4T5saC5WXePe1sw">Daniel Bark Youtube</a>
<a href="https://www.freecodecamp.org/news/code-a-minecraft-clone-using-react-and-three-js/">https://www.freecodecamp.org/news/code-a-minecraft-clone-using-react-and-three-js/</a></p>
<p>!youtube[qpOZup_3P_A]</p>
<p><a href="https://minecraft-freecodecamp.vercel.app/">Demo</a>
<a href="https://github.com/danba340/minecraft-freecodecamp/tree/start">Starting Code</a>
<a href="https://github.com/danba340/minecraft-freecodecamp">완성코드</a></p>
<p>영상의 챕터를 따라서 다음의 듀토리얼 기능을 하나씩 완수해 나갈 수 있다.
Boilerplate
Sky
Textures and images
Ground
Player
Keyboard inputs
Movement
First person view
Gravity
State management
Cubes
Adding cubes
Removing cubes
Cube type selector
Save world in localstorage
Hover state on cubes
Build a house</p>
<p><a href="https://github.com/danba340/minecraft-freecodecamp/tree/start">Starting Code</a> 링크를 참조해 git 소스코드를 받으면 다음과 같이 구성이 되어있다.
<img src="https://velog.velcdn.com/images/sugar_ghost/post/5aab246c-ce5e-46d8-ba32-adf72581db5a/image.png" alt="">
마인크래프트의 블록을 표현하기 위한 images들이 존재하며,
package.json을 참조하면 다음과 같은 라이브러리 구성을 가지고 있다.</p>
<p><img src="https://velog.velcdn.com/images/sugar_ghost/post/f26fce78-aa3b-4148-9492-6a9b75b25d32/image.png" alt=""></p>
<p>threeJS는 WebGL을 사용해 웹브라우저에서 애니메이션 3D 컴퓨터 그래픽을 만들고 표시하는데 사용되는 Javascript 라이브러리이다.</p>
<p>조금 생소한 것이 있다면 아마 nanoid, zustand일 것이다.
nanoid는 특정 요소에 문자열로 구성 된 식별자를 지정하기 위해 고유 ID를 자동 생성해주는 라이브러리다.
zustand는 전역 상태 관리를 위한 라이브러리다.
이번 프로젝트에서는 두 라이브러리가 중요한 역할을 하지는 않으니 넘어가고 react-three 라이브러리에 대해서 설명하고자 한다.
<a href="https://github.com/pmndrs/react-three-fiber"><code>react-three/fiber</code></a>: threejs를 위한 React 렌더러이다. threeJs 연관 컴포넌트를 리액트 state, hooks과 props를 참조하여 사용 할 수 있도록 도와주며, react 환경에서 threeJs를 통한 3D 모델링 및 애니메이션을 만드는 과정을 최적화 해준다.
<a href="https://github.com/pmndrs/drei"><code>react-three/drei</code></a>: <code>react-three/fiber</code>에 대한 도우미 라이브러리로, 3D 환경에서 주로 사용하는 기능이나 유용한 일반 구성 요소(또는 기능)을 모아둔 라이브러리다.
<a href="https://github.com/pmndrs/use-cannon"><code>react-three/cannon</code></a>: web에서 경량화된(Lightweight) 3D 물리학을 적용시키기 위한 라이브러리다. 즉, threeJs 환경에서 물리 엔진을 담당하며 충돌 감지, 좀더 다양한 형태, 접촉, 마찰과 제약(제동)을 표현하기 위해 사용한다.</p>
<p>라이브러리에 대한 설명은 간단히 진행하고 실제로 강의 영상을 따라서 사용하는 기술별로 설명을 진행할 예정이다.
다음으로 넘어가기 전에 받은 <a href="https://github.com/danba340/minecraft-freecodecamp/tree/start">Starting Code</a> 환경에서 <code>npm install</code>을 사용해 필요한 패키지를 다운받고 <code>npm run start</code>를 통해서 프로젝트를 실행시켜 보자.</p>
]]></description>
        </item>
    </channel>
</rss>