<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>TanukiWorld</title>
    <link>https://happytanuki.tistory.com/</link>
    <description>happytanuki 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 21:08:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>happytanuki</managingEditor>
    <image>
      <title>TanukiWorld</title>
      <url>https://tistory1.daumcdn.net/tistory/7507310/attach/6c656abee0cd44d59be416200c4b597d</url>
      <link>https://happytanuki.tistory.com</link>
    </image>
    <item>
      <title>CI/CD Jenkins &amp;amp; Canary release</title>
      <link>https://happytanuki.tistory.com/7</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CI/CD 파이프라인을 구성하려면 여러 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 그 중 한가지 Jenkins와 도커를 활용해서 Canary release를 구성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비물로는 리눅스 서버 둘을 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 코드는 다음과 같다&lt;br /&gt;&lt;a href=&quot;https://github.com/HappyTanuki/canary_test.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/HappyTanuki/canary_test.git&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774937038526&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - HappyTanuki/canary_test&quot; data-og-description=&quot;Contribute to HappyTanuki/canary_test development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/HappyTanuki/canary_test.git&quot; data-og-url=&quot;https://github.com/HappyTanuki/canary_test&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github.com/HappyTanuki/canary_test.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/HappyTanuki/canary_test.git&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - HappyTanuki/canary_test&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to HappyTanuki/canary_test development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Server.js&lt;/p&gt;
&lt;pre id=&quot;code_1774937068060&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const http = require(&quot;http&quot;);

const PORT = process.env.PORT || 8080;
const COLOR = process.env.COLOR || 'blue';
const VERSION = 'V2';

http.createServer((req, res) =&amp;gt; {
    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end(`[${VERSION}] 현재 응답 서버 식별: ${COLOR} (포트: ${PORT})\n`);
}).listen(PORT, () =&amp;gt; {
    console.log(`서버 가동 완료 (PORT: ${PORT})`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;default.conf&lt;/p&gt;
&lt;pre id=&quot;code_1774937128971&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;include /etc/nginx/conf.d/upstream.inc;

server {
    listen 80;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx/Dockerfile&lt;/p&gt;
&lt;pre id=&quot;code_1774937177322&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM nginx:alpine
COPY default.conf /etc/nginx/conf.d/default.conf
RUN echo &quot;upstream backend { server app-blue:8080; }&quot; &amp;gt; /etc/nginx/conf.d/upstream.inc&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dockerfile&lt;/p&gt;
&lt;pre id=&quot;code_1774937205403&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:18-alpine

WORKDIR /usr/src/app

COPY server.js .

# 가동시점이 다르다, 이거와 ENTRYPOINT는 실행시, RUN은 빌드 시
CMD [&quot;node&quot;, &quot;server.js&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음 명령어로 기본적으로 서버 배포 환경을 먼저 구성한다:&lt;/p&gt;
&lt;pre id=&quot;code_1774937470005&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker network create canary-net
cd nginx
docker build -t my-nginx .
docker run -d --name nginx-proxy --network canary-net -p 8088:80 my-nginx
cd ..
docker build -t my-canary-app .
docker run -d --name app-blue --network canary-net -e PORT=8080 -e COLOR=&quot;  BLUE&quot; my-canary-app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 잘 수행되었다면 상황은 다음과 같이 된다:&lt;/p&gt;
&lt;pre id=&quot;code_1774937564946&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;happytanuki@tanukiworld:~/canary_test$ docker ps
CONTAINER ID   IMAGE                                          COMMAND                  CREATED             STATUS             PORTS                                     NAMES
aba1b7bab705   my-nginx                                       &quot;/docker-entrypoint.&amp;hellip;&quot;   52 minutes ago      Up 52 minutes      0.0.0.0:8088-&amp;gt;80/tcp, [::]:8088-&amp;gt;80/tcp   nginx-proxy
78e274dc3d24   my-canary-app                                  &quot;docker-entrypoint.s&amp;hellip;&quot;   About an hour ago   Up About an hour                                             app-blue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx reverse proxy를 설정하여 이 서비스를 외부에서 접속할 수 있도록 설정한다:&lt;/p&gt;
&lt;pre id=&quot;code_1774943505857&quot; class=&quot;properties&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;happytanuki@tanukiworld:~$ cat /etc/nginx/sites-available/canary_test
server {
    server_name canary.happytanuki.kr;
    set $jenkins_addr http://127.0.0.1:8088;

    proxy_set_header Host $Host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Frowarded-Proto $scheme;

    # set timeout
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;
    send_timeout       600s;

    location / {
            proxy_pass $jenkins_addr;
    }


    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/canary.happytanuki.kr/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/canary.happytanuki.kr/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = canary.happytanuki.kr) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    server_name canary.happytanuki.kr;
    listen 80;
    return 404; # managed by Certbot


}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 접근이 되는지 확인해보자&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djm3ya/dJMcag53zvD/L4ub5O39XxZGdLRZwkcH50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djm3ya/dJMcag53zvD/L4ub5O39XxZGdLRZwkcH50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djm3ya/dJMcag53zvD/L4ub5O39XxZGdLRZwkcH50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdjm3ya%2FdJMcag53zvD%2FL4ub5O39XxZGdLRZwkcH50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;355&quot; height=&quot;78&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;잘 되었다면 다음 단계로 진행한다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 server.js를 교체하는데 canary 방식을 사용하기 위해서 jenkins를 준비해 보겠다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 truenas scale 환경에 jenkins를 올려서 설정을 해 주었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dMRkF4/dJMcafTB8Na/1yF3TyA8x6lLzG6wm15c6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dMRkF4/dJMcafTB8Na/1yF3TyA8x6lLzG6wm15c6K/img.png&quot; data-alt=&quot;truenas apps 패널&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dMRkF4/dJMcafTB8Na/1yF3TyA8x6lLzG6wm15c6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdMRkF4%2FdJMcafTB8Na%2F1yF3TyA8x6lLzG6wm15c6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;264&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;truenas apps 패널&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAflvw/dJMcahYbqrB/CxtK607gOXNJ7eEfceZnB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAflvw/dJMcahYbqrB/CxtK607gOXNJ7eEfceZnB1/img.png&quot; data-alt=&quot;nginx 리버스 프록시 세팅&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAflvw/dJMcahYbqrB/CxtK607gOXNJ7eEfceZnB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAflvw%2FdJMcahYbqrB%2FCxtK607gOXNJ7eEfceZnB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;500&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nginx 리버스 프록시 세팅&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/github-webhook/에만 접속을 허용함으로써 보안성을 챙기고 기본적인 https 설정을 해 주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jenkins 유저를 만들고 배포 환경(다른 환경)에 접속 권한을 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;jenkins docker 내부에 접속해서 ssh-keygen을 실행하여 키 페어를 만들고 공개키를 배포 서버의 authorized_keys에 공개키를 등록한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eyafJk/dJMcabczPQu/FFKFdnw1mzfsBDFTe6Kdv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eyafJk/dJMcabczPQu/FFKFdnw1mzfsBDFTe6Kdv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eyafJk/dJMcabczPQu/FFKFdnw1mzfsBDFTe6Kdv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeyafJk%2FdJMcabczPQu%2FFFKFdnw1mzfsBDFTe6Kdv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;928&quot; height=&quot;130&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편, github에서도 프로필 &amp;gt; settings &amp;gt; developer settings &amp;gt; personal access tokens &amp;gt;&lt;br /&gt;fine-grained personal access token을 발급받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c52lbn/dJMcaco22Yt/xPQomehXkCBdAts6dSMpSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c52lbn/dJMcaco22Yt/xPQomehXkCBdAts6dSMpSk/img.png&quot; data-alt=&quot;웹훅 수신과 clone을 위한 권한 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c52lbn/dJMcaco22Yt/xPQomehXkCBdAts6dSMpSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc52lbn%2FdJMcaco22Yt%2FxPQomehXkCBdAts6dSMpSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;795&quot; height=&quot;413&quot; data-origin-width=&quot;795&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;웹훅 수신과 clone을 위한 권한 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;권한 설정은 위와 같이 설정하여 토큰을 발급받아 기억해 두고 github repository &amp;gt; settings &amp;gt; webhooks 로 이동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tU8MM/dJMcacWRIg0/CNkW4k5YiV4q11XtkZvyrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tU8MM/dJMcacWRIg0/CNkW4k5YiV4q11XtkZvyrk/img.png&quot; data-alt=&quot;webhooks&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tU8MM/dJMcacWRIg0/CNkW4k5YiV4q11XtkZvyrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtU8MM%2FdJMcacWRIg0%2FCNkW4k5YiV4q11XtkZvyrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;785&quot; height=&quot;118&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;webhooks&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Add webhook을 눌러 새 웹훅을 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;813&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drIGGF/dJMcadOXG3D/DVkPEonEL6M9mZanPkKRd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drIGGF/dJMcadOXG3D/DVkPEonEL6M9mZanPkKRd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drIGGF/dJMcadOXG3D/DVkPEonEL6M9mZanPkKRd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrIGGF%2FdJMcadOXG3D%2FDVkPEonEL6M9mZanPkKRd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;783&quot; height=&quot;813&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;813&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 설정한다 (Payload URL은 자신의 서버 도메인을 연결하자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Add webhook을 눌러주면 이제 github가 로컬의 jenkins에게 알림을 주게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jenkins에 새 프로젝트 프리스타일로 만든다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;369&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlSDqg/dJMcacvPVHu/VrlyKSoDGjCU9X61hN3FFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlSDqg/dJMcacvPVHu/VrlyKSoDGjCU9X61hN3FFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlSDqg/dJMcacvPVHu/VrlyKSoDGjCU9X61hN3FFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlSDqg%2FdJMcacvPVHu%2FVrlyKSoDGjCU9X61hN3FFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1261&quot; height=&quot;369&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;369&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신의 git 리포 주소를 넣어주고 오른쪽의 add를 눌러 credential을 추가한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XkMyW/dJMcaflL7iz/qPBEvRCdNKd9KKKCveXo4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XkMyW/dJMcaflL7iz/qPBEvRCdNKd9KKKCveXo4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XkMyW/dJMcaflL7iz/qPBEvRCdNKd9KKKCveXo4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXkMyW%2FdJMcaflL7iz%2FqPBEvRCdNKd9KKKCveXo4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;145&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u7yI6/dJMcahKFFCt/fc9P8Lwlfe3kK24nSPyOWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u7yI6/dJMcahKFFCt/fc9P8Lwlfe3kK24nSPyOWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u7yI6/dJMcahKFFCt/fc9P8Lwlfe3kK24nSPyOWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu7yI6%2FdJMcahKFFCt%2Ffc9P8Lwlfe3kK24nSPyOWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;740&quot; data-origin-width=&quot;569&quot; data-origin-height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username with password를 선택해 준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKcgvu/dJMcabjoC9y/vD3PnO05k86h7FLkE1ogyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKcgvu/dJMcabjoC9y/vD3PnO05k86h7FLkE1ogyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKcgvu/dJMcabjoC9y/vD3PnO05k86h7FLkE1ogyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKcgvu%2FdJMcabjoC9y%2FvD3PnO05k86h7FLkE1ogyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;715&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username에 자신의 깃허브 계정명을 넣어주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 발급받았던 토큰을 password에 넣어준다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo8dQy/dJMcafzjUHW/kGyeONhKRCw0TzdeeBJrDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo8dQy/dJMcafzjUHW/kGyeONhKRCw0TzdeeBJrDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo8dQy/dJMcafzjUHW/kGyeONhKRCw0TzdeeBJrDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo8dQy%2FdJMcafzjUHW%2FkGyeONhKRCw0TzdeeBJrDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;278&quot; height=&quot;173&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드할 브랜치를 선택해 주고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lFsmb/dJMcaax0C9b/d45lU5kiuT5tRFHvpFfpp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lFsmb/dJMcaax0C9b/d45lU5kiuT5tRFHvpFfpp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lFsmb/dJMcaax0C9b/d45lU5kiuT5tRFHvpFfpp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlFsmb%2FdJMcaax0C9b%2Fd45lU5kiuT5tRFHvpFfpp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;247&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;247&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브 웹훅에 반응하도록 GitHub hook trigger for GITScm polling을 체크해 준다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Add build step을 해서 다음과 같은 쉘 스크립트 빌드 단계를 입력해 준다.&lt;/p&gt;
&lt;pre id=&quot;code_1774942067480&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

REMOTE_ENVIROMENT=&quot;-p 8000 happytanuki.kr&quot;
REMOTE_PATH=&quot;~/canary_test&quot;

function update_nginx_weight() {
    local BLUE=$1
    local GREEN=$2
    echo &quot;&amp;gt;&amp;gt; 트래픽 비율 변경 중... Blue(${BLUE}%) / Green(${GREEN}%)&quot;

    # weight가 0인 서버는 설정에서 아예 제외합니다! (Nginx 에러 방지)
    CONF=&quot;upstream backend { &quot;
    [ &quot;$BLUE&quot; -gt 0 ] &amp;amp;&amp;amp; CONF=&quot;${CONF} server app-blue:8080 weight=${BLUE}; &quot;
    [ &quot;$GREEN&quot; -gt 0 ] &amp;amp;&amp;amp; CONF=&quot;${CONF} server app-green:8081 weight=${GREEN}; &quot;
    CONF=&quot;${CONF} }&quot;

	ssh-keyscan ${REMOTE_ENVIROMENT} &amp;gt; ~/.ssh/known_hosts
    ssh ${REMOTE_ENVIROMENT} &quot;cd ${REMOTE_PATH} &amp;amp;&amp;amp; docker exec nginx-proxy sh -c \&quot;echo '$CONF' &amp;gt; /etc/nginx/conf.d/upstream.inc\&quot;&quot;
	ssh-keyscan ${REMOTE_ENVIROMENT} &amp;gt; ~/.ssh/known_hosts
    ssh ${REMOTE_ENVIROMENT} &quot;cd ${REMOTE_PATH} &amp;amp;&amp;amp; docker exec nginx-proxy nginx -s reload&quot;
}

# 1. 지금 켜져있는 서버가 누군지 검사합니다.
ssh-keyscan ${REMOTE_ENVIROMENT} &amp;gt; ~/.ssh/known_hosts
IS_BLUE=$(ssh ${REMOTE_ENVIROMENT} docker ps -q -f name=&quot;^app-blue$&quot;)

if [ -n &quot;$IS_BLUE&quot; ]; then
    CURRENT=&quot;blue&quot;; TARGET=&quot;green&quot;; TARGET_PORT=8081; TARGET_COLOR=&quot;  GREEN&quot;
else
    CURRENT=&quot;green&quot;; TARGET=&quot;blue&quot;; TARGET_PORT=8080; TARGET_COLOR=&quot;  BLUE&quot;
fi

echo &quot;  새로운 버전 [${TARGET}] 구동 시작!&quot;

# 2. 최신 코드로 도커 이미지를 굽습니다(build).
echo &quot;building canary app&quot;
ssh-keyscan ${REMOTE_ENVIROMENT} &amp;gt; ~/.ssh/known_hosts
ssh ${REMOTE_ENVIROMENT} &quot;cd ${REMOTE_PATH} &amp;amp;&amp;amp; docker build -t my-canary-app .&quot;

# 3. 혹시 예전에 쓰다 남은 타겟 컨테이너가 있다면 미리 삭제합니다.
echo &quot;deleteing canary app&quot;
ssh-keyscan ${REMOTE_ENVIROMENT} &amp;gt; ~/.ssh/known_hosts
ssh ${REMOTE_ENVIROMENT} &quot;cd ${REMOTE_PATH} &amp;amp;&amp;amp; docker rm -f app-$TARGET 2&amp;gt;/dev/null&quot;

# 4. 새 컨테이너를 실행합니다! (핵심: --network 로 같은 가상망에 묶어줍니다)
echo &quot;running canary app&quot;
ssh-keyscan ${REMOTE_ENVIROMENT} &amp;gt; ~/.ssh/known_hosts
ssh ${REMOTE_ENVIROMENT} &quot;cd ${REMOTE_PATH} &amp;amp;&amp;amp; docker run -d --name app-$TARGET \
  --network canary-net \
  -e PORT=$TARGET_PORT \
  -e COLOR=\&quot;$TARGET_COLOR\&quot; \
  my-canary-app&quot;

# 5. Health Check: Nginx가 새 서버랑 대화가 잘 되는지 확인합니다.
sleep 5
ssh-keyscan ${REMOTE_ENVIROMENT} &amp;gt; ~/.ssh/known_hosts
RESPONSE=$(ssh ${REMOTE_ENVIROMENT} &quot;cd ${REMOTE_PATH} &amp;amp;&amp;amp; docker exec nginx-proxy sh -c \&quot;wget -qO- http://app-$TARGET:${TARGET_PORT}\&quot;&quot;)
if [ -z &quot;$RESPONSE&quot; ]; then
    echo &quot;❌ 실패! 새 서버가 제대로 켜지지 않았습니다. 롤백합니다.&quot;
	ssh-keyscan ${REMOTE_ENVIROMENT} &amp;gt; ~/.ssh/known_hosts
    ssh ${REMOTE_ENVIROMENT} &quot;cd ${REMOTE_PATH} &amp;amp;&amp;amp; docker rm -f app-$TARGET&quot;
    exit 1
fi

# ==========================================
# 6. 점진적 트래픽 전환 (Canary Stages)
# ==========================================
echo &quot;✅ [1단계] 10% 카나리 오픈 (15초 대기)&quot;
if [ &quot;$TARGET&quot; == &quot;green&quot; ]; then update_nginx_weight 90 10; else update_nginx_weight 10 90; fi; sleep 15

echo &quot;✅ [2단계] 50% 트래픽 전환 (15초 대기)&quot;
if [ &quot;$TARGET&quot; == &quot;green&quot; ]; then update_nginx_weight 50 50; else update_nginx_weight 50 50; fi; sleep 15

echo &quot;  [3단계] 100% 완전 전환!&quot;
if [ &quot;$TARGET&quot; == &quot;green&quot; ]; then update_nginx_weight 0 100; else update_nginx_weight 100 0; fi

# 7. 이제 쓸모없어진 구버전 컨테이너를 삭제(rm)합니다!
ssh-keyscan ${REMOTE_ENVIROMENT} &amp;gt; ~/.ssh/known_hosts
ssh ${REMOTE_ENVIROMENT} &quot;cd ${REMOTE_PATH} &amp;amp;&amp;amp; docker rm -f app-$CURRENT&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 완료되었다면 save를 눌러 빠져나와 주면 main 브랜치에 push하거나&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UNrm0/dJMcahDREci/IUQDMqYlkiIoO5a55UpfHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UNrm0/dJMcahDREci/IUQDMqYlkiIoO5a55UpfHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UNrm0/dJMcahDREci/IUQDMqYlkiIoO5a55UpfHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUNrm0%2FdJMcahDREci%2FIUQDMqYlkiIoO5a55UpfHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;331&quot; height=&quot;221&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 빌드 버튼을 눌러주면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드와 배포가자동으로 되기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 url에 일정 비율만큼 green blue가 트래픽을 나눠받는 걸 f5를 연타함으로써 확인해볼 수 있다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP1S1M/dJMcacihDhe/UI5PYqoeiXbu06NBmRlWrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP1S1M/dJMcacihDhe/UI5PYqoeiXbu06NBmRlWrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP1S1M/dJMcacihDhe/UI5PYqoeiXbu06NBmRlWrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP1S1M%2FdJMcacihDhe%2FUI5PYqoeiXbu06NBmRlWrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;340&quot; height=&quot;157&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;393&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ttYor/dJMcahRptHx/lN3qEmOfAH6XKxOeYe3uD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ttYor/dJMcahRptHx/lN3qEmOfAH6XKxOeYe3uD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ttYor/dJMcahRptHx/lN3qEmOfAH6XKxOeYe3uD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FttYor%2FdJMcahRptHx%2FlN3qEmOfAH6XKxOeYe3uD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;393&quot; height=&quot;81&quot; data-origin-width=&quot;393&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZDFH8/dJMcajn5S18/Sz8SJOFt2tVLStu8dpnDFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZDFH8/dJMcajn5S18/Sz8SJOFt2tVLStu8dpnDFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZDFH8/dJMcajn5S18/Sz8SJOFt2tVLStu8dpnDFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZDFH8%2FdJMcajn5S18%2FSz8SJOFt2tVLStu8dpnDFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;351&quot; height=&quot;82&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;82&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발공부</category>
      <author>happytanuki</author>
      <guid isPermaLink="true">https://happytanuki.tistory.com/7</guid>
      <comments>https://happytanuki.tistory.com/7#entry7comment</comments>
      <pubDate>Tue, 31 Mar 2026 17:14:41 +0900</pubDate>
    </item>
    <item>
      <title>[Unreal 5.5.1] Gameplay Framework</title>
      <link>https://happytanuki.tistory.com/6</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP3gIb/btsLC494RQn/2O6eOwWqifOy1HYuUHjUcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP3gIb/btsLC494RQn/2O6eOwWqifOy1HYuUHjUcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP3gIb/btsLC494RQn/2O6eOwWqifOy1HYuUHjUcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP3gIb%2FbtsLC494RQn%2F2O6eOwWqifOy1HYuUHjUcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;853&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티는 어떤지 모르겠지만 언리얼에서는 엔진이 알아서 해주는 부분이 엄청나게 많고, 유니티에서처럼 기본 객체를 가지고 무엇이든지 다 만들 수 있는 것이 아니라 언리얼의 엄격한 틀에 맞춰서 적절한 클래스를 상속받고 적절한 위치에 적절한 코드를 배치해야 최적화와 확장성을 다 챙길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 Level Blueprint 내지 GameInstance, GameMode에 모든 코드를 다 써넣는다거나 하는 짓은 가능하지만 일단 된다고 넘어갔다가 피본다.. 꼭 하라는대로 안하고 왜? 를 좋아하는 내가 되는대로 짰다가 코드를 여러번 뒤집어 엎었다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 GameMode가 있다면 이 게임모드를 교체함으로써 플레이 도중에 게임의 규칙을 바꾼다거나 할 수 있는 행동을 위탁하는 전략 패턴(Strategy pattern)을 사용할 수 있게 되므로 유연하고 창의적인 자유도를 위해서 역설적으로 구조를 강제하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임플레이 프레임워크의 요소를 몇 개 보면. 각 클래스는 에픽게임즈의 의도대로 적절히 활용한다면 코드 수정없이 멀티플레이 게임을 구성할 수 있도록 모듈화되어 있으므로 멀티플레이어 게임을 상정하고 있다면 각 클래스의 멀티플레이어에서의 역할도 잘 보아 놓는 것이 중요할 것이다. &lt;br /&gt;GameInstance&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GameMode &lt;br /&gt;GameState &lt;br /&gt;PlayerState&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HUD&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pawn&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;etc..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;GameInstance:&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;게임이 실행중인 동안 계속 저장해야 할 정보를 저장한다. 이 객체는 서버와 클라이언트가 각각 가지고 있으나 서로의 값을 가져올 수 없고 리플리케이트되지 않는다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;GameMode:&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임의 규칙이나 게임 승리 조건 등등을 정의한다, 이 객체는 서버에서 각 클라이언트로 리플리케이트(Replicate)되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시말해 여기에서 정의한 규칙은 클라이언트에서 읽으려고 시도할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이 객체에는 플레이 도중에 바뀔 필요가 있거나 클라이언트가 알아야 하는 데이터 (예) 팀 스코어)를 포함할 수 없다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;GameState:&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;게임의 현재 상황에 대한 정보를 가지고 있다. 또한 각 클라이언트가 알아야 할 정보 (예) 접속한 플레이어의 수, 팀 스코어 등등.)를 가지고 있으며 이 객체는 서버에서 각 클라이언트로 리플리케이트된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다시말해 여기에서 정의한 규칙은 클라이언트에서 읽을 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 읽기 전용이므로 GameState의 값을 수정할 수는 없다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;PlayerState:&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;플레이어의 현재 상황에 대한 정보를 가지고 있다. 또한 각 클라이언트가 알아야 할 정보 (예) 플레이어의 점수, 플레이어의 이름 등등.)를 가지고 있다. 이 객체는 서버에서 각 클라이언트로 리플리케이트된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다시말해 여기에서 정의한 규칙은 클라이언트에서 읽을 수 있다는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 모든 클라이언트와 서버에게 리플리케이트되므로 다른 클라이언트나 서버에서도 값을 읽을 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;클라이언트에서 다른 클라이언트의 값을 수정할 수 없다.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;HUD:&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에만 존재하는 객체로, 화면에 오버레이로 표시되는 체력 바, 글씨, UI등등이 포함된다. 보통 각 클라이언트의 PlayerController가 소유하고 있도록 설계된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해, 서버에서는 각 클라이언트의 HUD객체를 얻을 수 없고, 그럴 필요도 없다.&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Controller:&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pawn을 소유하는 객체로, 주로 AIController와 PlayerController가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AIController는 NPC를 조종하는 컨트롤러로, 서버에만 존재하며 각 클라이언트에게 리플리케이트되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PlayerController는 플레이어의 입력을 처리하여 서버에 적절히 전달하며, 서버와 각 클라이언트에 전부 존재하나 클라이언트에서는 다른 클라이언트의 PlayerController를 읽을 수 없다.&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Pawn:&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Possessable(소유 가능)한 Actor로, AIController나 PlayerController가 소유(Possess)함으로써 조종할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pawn 또는 Actor 또는 Charactor 객체는 서버에서 각 클라이언트에 리플리케이트 될 수도, 되지 않을 수도 있다.(조정할 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 언리얼을 다룬 지 오래됐고, 인터넷에는 이 객체를 부적절하게 활용하는 예제가 많아 틀린 내용이 있을 수 있다. 인지하는대로 고치도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틀린 내용이 있다면 댓글로 알려주면 수정하도록 하겠다.&lt;/p&gt;</description>
      <category>게임공부</category>
      <category>unreal</category>
      <author>happytanuki</author>
      <guid isPermaLink="true">https://happytanuki.tistory.com/6</guid>
      <comments>https://happytanuki.tistory.com/6#entry6comment</comments>
      <pubDate>Fri, 3 Jan 2025 00:50:33 +0900</pubDate>
    </item>
    <item>
      <title>[Promether #0] 기획 정리</title>
      <link>https://happytanuki.tistory.com/5</link>
      <description>&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1033&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KgYM5/btsLgtazwzZ/BK4niUqz9eRRPYyDprUdI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KgYM5/btsLgtazwzZ/BK4niUqz9eRRPYyDprUdI0/img.png&quot; data-alt=&quot;캐릭터 Riana, 리아나&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KgYM5/btsLgtazwzZ/BK4niUqz9eRRPYyDprUdI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKgYM5%2FbtsLgtazwzZ%2FBK4niUqz9eRRPYyDprUdI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;1033&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1033&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;캐릭터 Riana, 리아나&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 2023년도에 친구들과 진행했던 프로젝트 Promether는 언리얼을 이용해서 만든 온라인 멀티플레이어 MOBA 게임이었다.&lt;br&gt;1:1대전 게임도 아니고 여러명이, 그것도 스킬이 있는 멀티플레이어 게임을 만든다는것이 얼마나 힘든 일인지 알았다면 조금 더 개발 계획을 타이트하고 길게 잡고 할걸 이라는 아쉬움이 남는다.&lt;br&gt;결국 프로젝트는 겨우 게임상에서 온라인으로 만나 평타를 쳐서 상대를 죽일 수 있다 수준까지만 구현되고 끝이 났었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;게임의 기획의도&lt;br&gt;1. 당시 졸업 작품을 목표로 6개월동안 목표를 실현하기 위한 시도와 과정 자체가 포트폴리오로써 의미가 있는 프로젝트.&lt;br&gt;2. 시장조사결과 10~30대를 타겟으로 하여 주중 게임 평균 이용시간인 78분 이내로 2~3게임 이상 즐길 수 있는 게임.&lt;br&gt;3. 이 게임만의 확실한 차별성과 메리트를 확보하고 컨텐츠의 볼륨이 아닌 유저의 특성과 환경을 고려.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 217px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr style=&quot;height: 20px;&quot;&gt;&lt;td style=&quot;width: 16.6667%; height: 20px;&quot;&gt;게임명&lt;/td&gt;&lt;td style=&quot;width: 16.6667%; height: 20px;&quot;&gt;Promether&lt;/td&gt;&lt;td style=&quot;width: 16.6667%; height: 20px;&quot;&gt;장르&lt;/td&gt;&lt;td style=&quot;width: 50.0001%; height: 20px;&quot; colspan=&quot;3&quot;&gt;3D 쿼터뷰 MOBA&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 60px;&quot;&gt;&lt;td style=&quot;width: 16.6667%; height: 60px;&quot;&gt;플랫폼&lt;/td&gt;&lt;td style=&quot;width: 16.6667%; height: 60px;&quot;&gt;Windows&lt;/td&gt;&lt;td style=&quot;width: 16.6667%; height: 60px;&quot;&gt;타겟 유저&lt;/td&gt;&lt;td style=&quot;width: 16.6667%; height: 60px;&quot;&gt;10~30대 PC게임 유저, 주로 10대 남성&lt;/td&gt;&lt;td style=&quot;width: 16.6667%; height: 60px;&quot;&gt;경쟁 게임&lt;/td&gt;&lt;td style=&quot;width: 16.6667%; height: 60px;&quot;&gt;League of legend, heroes of the storm, DOTA2&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 120px;&quot;&gt;&lt;td style=&quot;width: 16.6667%; height: 120px;&quot;&gt;게임 컨셉&lt;/td&gt;&lt;td style=&quot;width: 83.3335%; height: 120px;&quot; colspan=&quot;5&quot;&gt;1. 6명의 유저가 한 전장에 배치되어 적을 쓰러뜨리거나 오브젝트를 부숴 적의 라이프를 0으로 만드는 MOBA 게임이다.&lt;br&gt;2. 캐릭터가 가진 스킬을 활용하여 상대를 처치할 수 있으며, 상대를 처치하거나 목표를 차지하는 경우 상대의 라이프가 줄어들고, 라이프를 0으로 만들 경우 승리한다.&lt;br&gt;3. 플레이시간&lt;span style=&quot;color: #3a3838;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;10~15&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;분 내외의 짧은 호흡의 게임으로 기획하여 &lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;시간에 &lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;게임 이상 즐길 수 있는 것을 목표로 한다&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;.&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr style=&quot;height: 17px;&quot;&gt;&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;기획 의도&lt;/td&gt;&lt;td style=&quot;width: 83.3335%; height: 17px;&quot; colspan=&quot;5&quot;&gt;1. MOBA &lt;span style=&quot;color: #3a3838;&quot;&gt;장르를 선호하지만 게임 이용시간이 적거나&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;그로 인해 적은 게임 횟수를 아쉬워 하는 유저를 대상으로 게임 한 판의 호흡이 짧은 &lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;MOBA&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;를 만들어 기존 게임과 차별성을 두면서 &lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;대 유저를 타겟으로 한 장점으로 삼는다&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;2. &lt;/span&gt;AOS &lt;span style=&quot;color: #3a3838;&quot;&gt;형식으로 진행하기 이전 단계로 &lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;3&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;대&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;3 &lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt;데스매치&lt;/span&gt;&lt;span style=&quot;color: #3a3838;&quot;&gt; 형식의 게임으로 향후 발전 시켜 다른 룰로 진행하거나 해당 방식을 발전시킬 예정&lt;/span&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmRcds/btsLfZVceGN/3CUCsS8Kry8G83AlHOisaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmRcds/btsLfZVceGN/3CUCsS8Kry8G83AlHOisaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmRcds/btsLfZVceGN/3CUCsS8Kry8G83AlHOisaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmRcds%2FbtsLfZVceGN%2F3CUCsS8Kry8G83AlHOisaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1208&quot; height=&quot;511&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7H8kr/btsLeqNGaU5/EyJyL60Pi6OPn8r4MkTrSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7H8kr/btsLeqNGaU5/EyJyL60Pi6OPn8r4MkTrSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7H8kr/btsLeqNGaU5/EyJyL60Pi6OPn8r4MkTrSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7H8kr%2FbtsLeqNGaU5%2FEyJyL60Pi6OPn8r4MkTrSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1194&quot; height=&quot;286&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1221&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RtAwP/btsLenXMRNx/wKRH0WF6rAx1Q9a4v55m80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RtAwP/btsLenXMRNx/wKRH0WF6rAx1Q9a4v55m80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RtAwP/btsLenXMRNx/wKRH0WF6rAx1Q9a4v55m80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRtAwP%2FbtsLenXMRNx%2FwKRH0WF6rAx1Q9a4v55m80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1221&quot; height=&quot;282&quot; data-origin-width=&quot;1221&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gi81Q/btsLfG9yczw/OJxiCCzsDmdF8gCGZ14q01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gi81Q/btsLfG9yczw/OJxiCCzsDmdF8gCGZ14q01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gi81Q/btsLfG9yczw/OJxiCCzsDmdF8gCGZ14q01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGi81Q%2FbtsLfG9yczw%2FOJxiCCzsDmdF8gCGZ14q01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1194&quot; height=&quot;271&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;271&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8iqzu/btsLgaI8iyk/q1L7ocF4GVUGsbkM03EQaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8iqzu/btsLgaI8iyk/q1L7ocF4GVUGsbkM03EQaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8iqzu/btsLgaI8iyk/q1L7ocF4GVUGsbkM03EQaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8iqzu%2FbtsLgaI8iyk%2Fq1L7ocF4GVUGsbkM03EQaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1212&quot; height=&quot;281&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;게임플레이-승리조건&lt;/b&gt;&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1225&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csr0BU/btsLfpNPnWE/B6m4cQI7OY7LRwx6kS8WZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csr0BU/btsLfpNPnWE/B6m4cQI7OY7LRwx6kS8WZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csr0BU/btsLfpNPnWE/B6m4cQI7OY7LRwx6kS8WZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcsr0BU%2FbtsLfpNPnWE%2FB6m4cQI7OY7LRwx6kS8WZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1225&quot; height=&quot;628&quot; data-origin-width=&quot;1225&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;게임플레이-캐릭터 강화&lt;/b&gt;&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0UDXw/btsLe6npIfq/Qg734fiw659JDejxEmNDF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0UDXw/btsLe6npIfq/Qg734fiw659JDejxEmNDF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0UDXw/btsLe6npIfq/Qg734fiw659JDejxEmNDF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0UDXw%2FbtsLe6npIfq%2FQg734fiw659JDejxEmNDF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1244&quot; height=&quot;156&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tl2cj/btsLeqAahfE/7ol3npHfjDdYy9fFAf31CK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tl2cj/btsLeqAahfE/7ol3npHfjDdYy9fFAf31CK/img.png&quot; data-alt=&quot;적을 처치하거나 석상을 파괴하여 일정량의 경험치를 획득하면, 강화 포인트를 획득하고 강화할 스킬을 선택하여 강화할 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tl2cj/btsLeqAahfE/7ol3npHfjDdYy9fFAf31CK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTl2cj%2FbtsLeqAahfE%2F7ol3npHfjDdYy9fFAf31CK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;872&quot; height=&quot;277&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;적을 처치하거나 석상을 파괴하여 일정량의 경험치를 획득하면, 강화 포인트를 획득하고 강화할 스킬을 선택하여 강화할 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;강화 요소-탈리스만&lt;/b&gt;&lt;/h2&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S5GM4/btsLgGt44aO/ZJ8IHllvljZAErJKoTDgVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S5GM4/btsLgGt44aO/ZJ8IHllvljZAErJKoTDgVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S5GM4/btsLgGt44aO/ZJ8IHllvljZAErJKoTDgVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS5GM4%2FbtsLgGt44aO%2FZJ8IHllvljZAErJKoTDgVK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1418&quot; height=&quot;709&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;1023&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PcjOn/btsLet4sH37/GQBJKEiysBQTISijoGdcCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PcjOn/btsLet4sH37/GQBJKEiysBQTISijoGdcCK/img.png&quot; data-alt=&quot;탈리스만 색깔별 능력치 의미&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PcjOn/btsLet4sH37/GQBJKEiysBQTISijoGdcCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPcjOn%2FbtsLet4sH37%2FGQBJKEiysBQTISijoGdcCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1826&quot; height=&quot;1023&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;1023&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;탈리스만 색깔별 능력치 의미&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 이대로는 상당히 게임 템포가 느려지거나 1분 이내로 1레벨 이상 렙업을 해야 하는 등 게임 템포가 극단적이 되므로 당시 기획자 친구와 논의하여 조정을 거쳤다.&lt;br&gt;조정 내용은 다음과 같다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;탈리스만은 각 3레벨까지만으로 한다.&lt;/li&gt;&lt;li&gt;스킬은 애초 계획을 변경하여 3레벨까지만 강화 가능하도록 한다.&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기획서에 없는 내용으로, 모든 캐릭터는 스킬을 우선 가지고 시작한다.&lt;br&gt;또한 이 탈리스만과 스킬들은 모두 다 찍을 일이 없어도 상관없으므로 한 스킬이나 한 탈리스만에만 몰아서 투자하여도 상관없도록 설계함을 원칙으로 한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;.&lt;br&gt;.&lt;br&gt;.&lt;br&gt;기록을 찾는대로 업데이트하여 유지하도록 하겠다.&lt;/p&gt;</description>
      <category>게임개발</category>
      <author>happytanuki</author>
      <guid isPermaLink="true">https://happytanuki.tistory.com/5</guid>
      <comments>https://happytanuki.tistory.com/5#entry5comment</comments>
      <pubDate>Thu, 12 Dec 2024 02:43:31 +0900</pubDate>
    </item>
    <item>
      <title>티스토리 도메인 등록기</title>
      <link>https://happytanuki.tistory.com/4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;티스토리를 시작하면서 happytanuki.kr인 내 도메인을 연결하려고 하자 오류가 났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브도메인인 blog.happytanuki.kr에 연결하자 조금 시간이 지나 티스토리 상에서는 연결이 되었다고 뜨나, 실제 접속하면 접속할 수 없다고 뜬다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGEEkT/btsLboP0Z49/sT9V13jcXjLS34YLhJcy7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGEEkT/btsLboP0Z49/sT9V13jcXjLS34YLhJcy7K/img.png&quot; data-alt=&quot;이게 뭔 버그고..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGEEkT/btsLboP0Z49/sT9V13jcXjLS34YLhJcy7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGEEkT%2FbtsLboP0Z49%2FsT9V13jcXjLS34YLhJcy7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;879&quot; height=&quot;515&quot; data-origin-width=&quot;879&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이게 뭔 버그고..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷을 찾아보니 웹서버에서 301리다이렉트를 걸어주면 된다고 하여 간단하게 걸어보겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bv63RU/btsLbbC3LPS/ctzGG7soB3ejziIWGcw1tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bv63RU/btsLbbC3LPS/ctzGG7soB3ejziIWGcw1tk/img.png&quot; data-alt=&quot;nginx 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bv63RU/btsLbbC3LPS/ctzGG7soB3ejziIWGcw1tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbv63RU%2FbtsLbbC3LPS%2FctzGG7soB3ejziIWGcw1tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;212&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;nginx 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nginx 설정 경로로 접속하여 가상 호스트 설정 파일을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;blog 파일:&lt;/p&gt;
&lt;pre id=&quot;code_1733738788918&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
        server_name happytanuki.kr www.happytanuki.kr;

        location / {
                return 301 https://blog.happytanuki.kr$request_uri;
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 아직도 유효한 인증서가 없다고 하는 중...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷을 찾아봐도 딱히 뭐가 없는거 보면 그냥 일시적인 버그일수도 있으니 최대 일주일까지 기다렸다가 티스토리에 문의해야겠다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KST 2024:12:09:10:06 - 결국 시간이 해결해줬다.. 그냥 뭔가 티스토리 서버가 불안정했을수도..&lt;/p&gt;</description>
      <category>서버</category>
      <author>happytanuki</author>
      <guid isPermaLink="true">https://happytanuki.tistory.com/4</guid>
      <comments>https://happytanuki.tistory.com/4#entry4comment</comments>
      <pubDate>Mon, 9 Dec 2024 19:20:30 +0900</pubDate>
    </item>
    <item>
      <title>티스토리 스킨 개발환경 만들기 [#2]</title>
      <link>https://happytanuki.tistory.com/3</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;티스토리의 스킨 파일 구조는 몇 개의 파일과 디렉터리로 이뤄진다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;SKIN ─┬─ index.xml
      ├─ skin.html
      ├─ style.css
      ├─ preview.gif
      ├─ preview256.jpg
      ├─ preview560.jpg
      ├─ preview1600.jpg
      └─ images ─┬─ script.js
                 ├─ background.jpg
                 ├─ template.css
                 └─ etc.. etc..&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 파일은 역할을 가지고 있는데.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;index.xml&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킨의 메타데이터를 담는 파일이다. 이 내용으로 스킨 제작자, 이름 등이 포함된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;skin.html&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킨 템플릿 파일이다. 여기에 적은 내용을 바탕으로 치환자를 치환하여 블로그 페이지를 렌더링하게 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;style.css&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스타일시트이다. 아무래도 일반 HTML의 그것과 같은 것인 듯?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;preview&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미리보기용이라고 한다. 나중에 알게 될 듯?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;preview.gif : 미리보기 기본 파일로 아래 파일이 없는 경우에 사용 (112x84)&lt;/li&gt;
&lt;li&gt;preview256.jpg : 사용 중인 스킨 미리보기 (256x192)&lt;/li&gt;
&lt;li&gt;preview560.jpg : 스킨 목록 미리보기 (560x420)&lt;/li&gt;
&lt;li&gt;preview1600.jpg : 스킨 상세보기 미리보기 (1600x1200)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;images&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 필수가 아닌 파일들이 모두 위치한 짬통.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;index.xml&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.xml 파일은 스킨의 기본 정보를 정의하는 파일이므로 일단 먼저 정의하고 가면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 내용이 많아서 아마 나중에 구조를 보면서 다시 정리해야 하지 않을까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 알게되는 대로 업데이트 하여 수정하겠다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;skin&amp;gt;
  &amp;lt;information&amp;gt;
    &amp;lt;name&amp;gt;기본 스킨&amp;lt;/name&amp;gt;
    &amp;lt;version&amp;gt;1.1&amp;lt;/version&amp;gt;
    &amp;lt;description&amp;gt;
      &amp;lt;![CDATA[웹표준을 준수한 XHTML 기반의 Tistory 기본 스킨입니다.]]&amp;gt;
    &amp;lt;/description&amp;gt;
    &amp;lt;license&amp;gt;
      &amp;lt;![CDATA[자유롭게 수정이 가능하며, 저작권 표시하에 재배포 가능합니다.]]&amp;gt;
    &amp;lt;/license&amp;gt;
  &amp;lt;/information&amp;gt;
  &amp;lt;author&amp;gt;
    &amp;lt;name&amp;gt;tistory&amp;lt;/name&amp;gt;
    &amp;lt;homepage&amp;gt;http://notice.tistory.com&amp;lt;/homepage&amp;gt;
    &amp;lt;email&amp;gt;tistoryblog@daum.net&amp;lt;/email&amp;gt;
  &amp;lt;/author&amp;gt;
  &amp;lt;default&amp;gt;
    &amp;lt;recentEntries&amp;gt;5&amp;lt;/recentEntries&amp;gt;
    &amp;lt;recentComments&amp;gt;5&amp;lt;/recentComments&amp;gt;
    &amp;lt;recentTrackbacks&amp;gt;5&amp;lt;/recentTrackbacks&amp;gt;
    &amp;lt;itemsOnGuestbook&amp;gt;10&amp;lt;/itemsOnGuestbook&amp;gt;
    &amp;lt;tagsInCloud&amp;gt;30&amp;lt;/tagsInCloud&amp;gt;
    &amp;lt;sortInCloud&amp;gt;3&amp;lt;/sortInCloud&amp;gt;
    &amp;lt;expandComment&amp;gt;0&amp;lt;/expandComment&amp;gt;
    &amp;lt;expandTrackback&amp;gt;0&amp;lt;/expandTrackback&amp;gt;
    &amp;lt;lengthOfRecentNotice&amp;gt;25&amp;lt;/lengthOfRecentNotice&amp;gt;
    &amp;lt;lengthOfRecentEntry&amp;gt;27&amp;lt;/lengthOfRecentEntry&amp;gt;
    &amp;lt;lengthOfRecentComment&amp;gt;30&amp;lt;/lengthOfRecentComment&amp;gt;
    &amp;lt;lengthOfRecentTrackback&amp;gt;30&amp;lt;/lengthOfRecentTrackback&amp;gt;
    &amp;lt;lengthOfLink&amp;gt;30&amp;lt;/lengthOfLink&amp;gt;
    &amp;lt;showListOnCategory&amp;gt;1&amp;lt;/showListOnCategory&amp;gt;
    &amp;lt;showListOnArchive&amp;gt;1&amp;lt;/showListOnArchive&amp;gt;
    &amp;lt;commentMessage&amp;gt;
      &amp;lt;none&amp;gt;댓글이 없습니다.&amp;lt;/none&amp;gt;
      &amp;lt;single&amp;gt;댓글 &amp;amp;lt;span class=&quot;cnt&quot;&amp;amp;gt;하나&amp;amp;lt;/span&amp;amp;gt; 달렸습니다.&amp;lt;/single&amp;gt;
    &amp;lt;/commentMessage&amp;gt;
    &amp;lt;trackbackMessage&amp;gt;
      &amp;lt;none&amp;gt;받은 트랙백이 없고&amp;lt;/none&amp;gt;
      &amp;lt;single&amp;gt;트랙백이 &amp;amp;lt;span class=&quot;cnt&quot;&amp;amp;gt;하나&amp;amp;lt;/span&amp;amp;gt;이고&amp;lt;/single&amp;gt;
    &amp;lt;/trackbackMessage&amp;gt;
    &amp;lt;tree&amp;gt;
      &amp;lt;color&amp;gt;000000&amp;lt;/color&amp;gt;
      &amp;lt;bgColor&amp;gt;ffffff&amp;lt;/bgColor&amp;gt;
      &amp;lt;activeColor&amp;gt;000000&amp;lt;/activeColor&amp;gt;
      &amp;lt;activeBgColor&amp;gt;eeeeee&amp;lt;/activeBgColor&amp;gt;
      &amp;lt;labelLength&amp;gt;27&amp;lt;/labelLength&amp;gt;
      &amp;lt;showValue&amp;gt;1&amp;lt;/showValue&amp;gt;
    &amp;lt;/tree&amp;gt;
    &amp;lt;contentWidth&amp;gt;500&amp;lt;/contentWidth&amp;gt;
  &amp;lt;/default&amp;gt;
&amp;lt;/skin&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; 파트:&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name: 스킨 이름&lt;/li&gt;
&lt;li&gt;version: 스킨 버전&lt;/li&gt;
&lt;li&gt;description: 상세 설명&lt;/li&gt;
&lt;li&gt;license: 저작권&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; 파트:&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name: 제작자 이름&lt;/li&gt;
&lt;li&gt;homepage: 제작자 웹사이트 주소&lt;/li&gt;
&lt;li&gt;email: 이메일&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; 파트:&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;recentEntries: 사이드바의 최근글 표시 갯수&lt;/li&gt;
&lt;li&gt;recentComments: 사이드바의 최근 댓글 표시 갯수&lt;/li&gt;
&lt;li&gt;recentTrackbacks: 사이드바의 최근 트랙백 표시 갯수&lt;/li&gt;
&lt;li&gt;itemsOnGuestbook: 한페이지에 표시될 방명록 갯수 *&lt;/li&gt;
&lt;li&gt;tagsInCloud: 사이드바에 표시될 태그 갯수&lt;/li&gt;
&lt;li&gt;sortInCloud: 태그 클라우드 표현 방식 (1:인기도순, 2:이름순, 3:랜덤)&lt;/li&gt;
&lt;li&gt;expandComment: 댓글영역 표현 방식 (0:감추기, 1:펼치기)&lt;/li&gt;
&lt;li&gt;expandTrackback: 트랙백영역 표현 방식 (0:감추기, 1:펼치기)&lt;/li&gt;
&lt;li&gt;lengthOfRecentNotice: 최근 공지 표현될 글자수&lt;/li&gt;
&lt;li&gt;lengthOfRecentEntry: 최근 글 표현될 글자수&lt;/li&gt;
&lt;li&gt;lengthOfRecentComment: 최근 댓글에 표현될 글자수&lt;/li&gt;
&lt;li&gt;lengthOfRecentTrackback: 최근 트랙백에 표현될 글자수&lt;/li&gt;
&lt;li&gt;lengthOfLink: 링크에 표현될 글자수&lt;/li&gt;
&lt;li&gt;entriesOnPage: 홈 화면 글 수&lt;/li&gt;
&lt;li&gt;entriesOnList: 글 목록 글 수&lt;/li&gt;
&lt;li&gt;showListOnCategory: 커버 미사용 홈에서 글 목록 표현(0:내용만, 1:목록만, 2: 내용+목록)&lt;/li&gt;
&lt;li&gt;showListLock : 홈 설정과 기본 설정에서 '목록 구성 요소' 항목의 노출 여부 결정 (0: 노출, 1: 노출 안 함)&lt;/li&gt;
&lt;li&gt;tree: 카테고리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;color: 카테고리 글자색&lt;/li&gt;
&lt;li&gt;bgColor: 카테고리 배경색&lt;/li&gt;
&lt;li&gt;activeColor: 선택시 글자색&lt;/li&gt;
&lt;li&gt;activeBgColor: 선택시 배경색&lt;/li&gt;
&lt;li&gt;labelLength: 표현될 카테고리 글자 수&lt;/li&gt;
&lt;li&gt;showValue: 카테고리 글 수 표현(0:숨김, 1:보임)&lt;/li&gt;
&lt;li&gt;contentWidth: 콘텐츠영역 가로 사이즈, 이 사이즈에 맞춰 에디터의 위지윅이 제대로 구현된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cover: 홈 커버 기본값이다. 추후 다시 설명한다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;스킨 옵션:&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킨 옵션이라는 게 있다는데 파일에 안보인다. 추후 설명을 보아야 할 듯?&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;리스트 스타일:&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용할 수 있는 글 목록 스타일을 정의한다고 한다. 추후에 수정하겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;치환자 구조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;티스토리 스킨 템플릿의 치환자는 그룹 치환자, 값 치환자로 두가지인데, 각각 다음과 같이 표현된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;lt;s_tag&amp;gt;&amp;lt;/s_tag&amp;gt;:&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹 치환자로, 내부의 다른 데이터들을 포함하여 렌더링된(HTML로 변환된) 값으로 변환된 값으로 치환될 부분입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[&amp;iexcl;##_tag_##]:&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값 치환자로, 해당하는 값으로 치환됩니다. 아마도 변수의 값을 읽어와 출력하는 느낌일 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(태그 안쪽의&amp;iexcl;는 티스토리에서 자동으로 치환해버리기에 추가한 문자이다. 실제로는 없이 작성해야 작동한다.)&lt;/p&gt;</description>
      <category>개발</category>
      <author>happytanuki</author>
      <guid isPermaLink="true">https://happytanuki.tistory.com/3</guid>
      <comments>https://happytanuki.tistory.com/3#entry3comment</comments>
      <pubDate>Mon, 9 Dec 2024 18:33:35 +0900</pubDate>
    </item>
    <item>
      <title>티스토리 스킨 개발환경 만들기 [#1]</title>
      <link>https://happytanuki.tistory.com/2</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;공부를 시작하기 전에 책상을 먼저 치워야 직성이 풀리는 나 답게 우선 스킨 커스터마이징 하는 툴을 만들어 블로그 스킨을 먼저 만들기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 제일 먼저 해야 할 일은 오픈소스 찾기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github: &lt;a title=&quot;티도리&quot; href=&quot;https://github.com/pronist/tidory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tidory&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733732058370&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - pronist/tidory:   티스토리 스킨 프레임워크, 티도리&quot; data-og-description=&quot;  티스토리 스킨 프레임워크, 티도리. Contribute to pronist/tidory development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/pronist/tidory&quot; data-og-url=&quot;https://github.com/pronist/tidory&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/LdwvR/hyXKwTF2kv/WrixnN5HvpeCUH03inbEmk/img.png?width=1200&amp;amp;height=600&amp;amp;face=976_161_1064_256,https://scrap.kakaocdn.net/dn/bCWeKK/hyXKqlBD0C/fQqgdJyvP3pUvvkujPZna0/img.png?width=1200&amp;amp;height=600&amp;amp;face=976_161_1064_256&quot;&gt;&lt;a href=&quot;https://github.com/pronist/tidory&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/pronist/tidory&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/LdwvR/hyXKwTF2kv/WrixnN5HvpeCUH03inbEmk/img.png?width=1200&amp;amp;height=600&amp;amp;face=976_161_1064_256,https://scrap.kakaocdn.net/dn/bCWeKK/hyXKqlBD0C/fQqgdJyvP3pUvvkujPZna0/img.png?width=1200&amp;amp;height=600&amp;amp;face=976_161_1064_256');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - pronist/tidory:   티스토리 스킨 프레임워크, 티도리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  티스토리 스킨 프레임워크, 티도리. Contribute to pronist/tidory development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;티도리라는 깃허브 오픈소스 프로젝트를 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 티스토리의 치환자 에 대한 설명이나 무슨무슨 파일이 있는지는 기본으로 가지고 가는 것 같아 티스토리 치환자 에 대한 기본 정보는 따로 찾아야 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;티스토리 스킨 가이드&quot; href=&quot;https://tistory.github.io/document-tistory-skin/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tistory.github.io/document-tistory-skin/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733732049175&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;소개 &amp;middot; GitBook&quot; data-og-description=&quot;No results matching &amp;quot;&amp;quot;&quot; data-og-host=&quot;tistory.github.io&quot; data-og-source-url=&quot;https://tistory.github.io/document-tistory-skin/&quot; data-og-url=&quot;https://tistory.github.io/document-tistory-skin/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://tistory.github.io/document-tistory-skin/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tistory.github.io/document-tistory-skin/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;소개 &amp;middot; GitBook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;No results matching &quot;&quot;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tistory.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 글을 찾았다. 이제 이 둘을 가지고 공부해야겠다.&lt;/p&gt;</description>
      <category>개발</category>
      <author>happytanuki</author>
      <guid isPermaLink="true">https://happytanuki.tistory.com/2</guid>
      <comments>https://happytanuki.tistory.com/2#entry2comment</comments>
      <pubDate>Mon, 9 Dec 2024 16:56:56 +0900</pubDate>
    </item>
    <item>
      <title>블로그 시작</title>
      <link>https://happytanuki.tistory.com/1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;포트폴리오 작성과 공부한 걸 나중에 찾아보고 다시 되새기기 위해 블로그 글로 정리하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 게임 개발, c/c++개발, 토이 프로젝트, 리눅스 서버 등등 생각나는대로 정리해서 꾸준히 작성해보려고 한다.&lt;/p&gt;</description>
      <category>뻘글</category>
      <author>happytanuki</author>
      <guid isPermaLink="true">https://happytanuki.tistory.com/1</guid>
      <comments>https://happytanuki.tistory.com/1#entry1comment</comments>
      <pubDate>Mon, 9 Dec 2024 16:17:01 +0900</pubDate>
    </item>
  </channel>
</rss>