<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Henry Tutorial</title>
    <link>https://rlg1133.tistory.com/</link>
    <description>https://github.com/Gyeony95  
</description>
    <language>ko</language>
    <pubDate>Wed, 5 Aug 2026 19:51:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Henry95</managingEditor>
    <image>
      <title>Henry Tutorial</title>
      <url>https://tistory1.daumcdn.net/tistory/3162437/attach/6c655fdcfd3e47679d03a9bb5de1424d</url>
      <link>https://rlg1133.tistory.com</link>
    </image>
    <item>
      <title>[플러터] Flutter Web 을 디버깅하는 3가지 방법</title>
      <link>https://rlg1133.tistory.com/147</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter는 크로스 플랫폼을 지원하는 프레임워크이며 많은 개발자들이 Andorid, IOS 모바일 앱을 만들기 위해 사용하지만 데스크탑 앱과 웹에서도 꽤 괜찮은 성능으로 동작합니다. 하지만 익숙한 모바일앱과는 달리 플러터를 웹으로 빌드하고 또 그 웹사이트를 모바일앱에서 확인해야 한다고 했을때 어떻게 하면 좋을까요?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;DALL&amp;amp;middot;E_2024-09-08_21.44.15_-_A_developer_sitting_at_a_desk,_looking_puzzled_while_deciding_between_web_development,_Android_phon.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSA4ka/btsJv7tE1Q0/0Lo0TiMa3mZIhkC2FkzK8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSA4ka/btsJv7tE1Q0/0Lo0TiMa3mZIhkC2FkzK8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSA4ka/btsJv7tE1Q0/0Lo0TiMa3mZIhkC2FkzK8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSA4ka%2FbtsJv7tE1Q0%2F0Lo0TiMa3mZIhkC2FkzK8k%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;682&quot; height=&quot;682&quot; data-filename=&quot;DALL&amp;middot;E_2024-09-08_21.44.15_-_A_developer_sitting_at_a_desk,_looking_puzzled_while_deciding_between_web_development,_Android_phon.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&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;아시다시피 Flutter 에는 웹을 렌더링하는 두가지 방식이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. HTML : HTML/CSS를 기반으로 위젯 트리를 렌더링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. CanvasKit : Flutter의 기본 렌더링 엔진인 skia와 WebGL을 사용하여 렌더링&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;이렇게 봤을때 HTML 렌더링을 통해 보통 웹처럼 디버깅 해볼 수 있지 않을까? 하는 생각이 들지만 말만 HTML 렌더링이지 생각보다 쓸만한 디버깅을 하기에는 데이터가 많이 부족합니다.&lt;br /&gt;그 이유는 Flutter 에서 html로 렌더링시 Flutter의 위젯이 대응되는 html 코드로 치환되게 되는데 이 과정에서 위젯트리와 상태관리가 복잡하게 변환되어 개발자도구(Dev Tools) 에서 쉽게 확인하고 수정할 수 있는 수준이 아니기 때문이고 또하나의 이유는 Flutter 의 html 렌더링은 Shadow DOM 이라는걸 사용하여 DOM을 캡슐화 하고 보호하며 브라우저의 개발자 도구에서 접근 및 수정을 제한하게되고 html 트리의 일부부은 아예 숨겨져 보일수도 있기 때문입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725800802426&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// html로 렌더링
 flutter run -d chrome --web-port=8080 --web-renderer html

// canvasKit으로 렌더링
 flutter run -d chrome --web-port=8080 --web-renderer canvaskit&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위 명령을 통해 각각의 렌더링을 해볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;canvasKit 렌더링의 경우 웹상에서의 디버깅 상황은 더 안좋은데요&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3158&quot; data-origin-height=&quot;2112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRrDSP/btsJuRkYa6r/tNImLkfQ7K3jGrk4N6ejgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRrDSP/btsJuRkYa6r/tNImLkfQ7K3jGrk4N6ejgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRrDSP/btsJuRkYa6r/tNImLkfQ7K3jGrk4N6ejgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRrDSP%2FbtsJuRkYa6r%2FtNImLkfQ7K3jGrk4N6ejgK%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;3158&quot; height=&quot;2112&quot; data-origin-width=&quot;3158&quot; data-origin-height=&quot;2112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 &amp;lt;canvas/&amp;gt; 태그 안에서 모든 위젯이 그려지고 있기 때문입니다. 따라서 웹상에서 디버깅을 하는 작업은 거의 포기하게 되는 상태가 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Flutter 웹에서의 디버깅은 어떻게 진행해야할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Flutter PC Web 디버깅&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC 웹에서의 디버깅은 비교적 간단합니다. 모바일 웹에서와 똑같이 Android Studio 혹은 Visual Studio Code 등 IDE 에서 디버거를 찍고 작업하는거죠&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3148&quot; data-origin-height=&quot;2112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mg7nu/btsJvUBjnio/pEhsrIXZJsGrEFD4jx7CfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mg7nu/btsJvUBjnio/pEhsrIXZJsGrEFD4jx7CfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mg7nu/btsJvUBjnio/pEhsrIXZJsGrEFD4jx7CfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmg7nu%2FbtsJvUBjnio%2FpEhsrIXZJsGrEFD4jx7CfK%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;3148&quot; height=&quot;2112&quot; data-origin-width=&quot;3148&quot; data-origin-height=&quot;2112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 보시는 분들도 PC 웹에서의 디버깅은 어렵지 않게 하고 계실거라고 생각합니다. 하지만 우리의 서비스가 만약 모바일 웹을 제공해야 한다면 우리는 PC 웹에서의 디버깅만으로는 충분하지 않고 Android 디바이스의 삼성인터넷, 크롬 브라우저 혹은 IOS 디바이스의 사파리 브라우저에서 내 작업을 확인하고 싶을겁니다. 그럴때 어떻게 해야 하는지 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 아이폰 모바일 웹 디버깅 (IOS)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 를 개발하시는 개발자분들은 대부분 MacOS를 쓰고계실거라 생각합니다. 그리고 IOS 앱을 개발하실때 사용하는 IOS Simulator를 통해 웹 디버깅을 해볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IOS 시뮬레이터에서는 다행히 우리 pc에서 로컬빌드한 사이트에 접근이 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;2072&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JI0Y2/btsJvTWElyC/IuH3D3l3Sh7OadMKkb2sjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JI0Y2/btsJvTWElyC/IuH3D3l3Sh7OadMKkb2sjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JI0Y2/btsJvTWElyC/IuH3D3l3Sh7OadMKkb2sjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJI0Y2%2FbtsJvTWElyC%2FIuH3D3l3Sh7OadMKkb2sjK%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;368&quot; height=&quot;702&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;2072&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지처럼 locahost 에 접근해 내 코드의 결과물을 바로 바로 확인이 가능하게 되는데 우리는 pc의 사파리 브라우저를 통해 더 많은 정보를 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9ufJ2/btsJt5khu7c/NDcj0T805YQnpOHEAsDWj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9ufJ2/btsJt5khu7c/NDcj0T805YQnpOHEAsDWj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9ufJ2/btsJt5khu7c/NDcj0T805YQnpOHEAsDWj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9ufJ2%2FbtsJt5khu7c%2FNDcj0T805YQnpOHEAsDWj0%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;531&quot; height=&quot;110&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;234&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6JTAg/btsJu8zWE0s/R17D5feUs74Vhknpwqnoqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6JTAg/btsJu8zWE0s/R17D5feUs74Vhknpwqnoqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6JTAg/btsJu8zWE0s/R17D5feUs74Vhknpwqnoqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6JTAg%2FbtsJu8zWE0s%2FR17D5feUs74Vhknpwqnoqk%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;528&quot; height=&quot;107&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;우선 default 상태의 사파리 메뉴에서는 불가능 하며, 사파리의 개발자 전용 메뉴를 ON 시켜줘야 하는데 하단 링크의 설명을 참고하여 위와 같이 '개발자용' 메뉴를 추가해줍니다.&lt;br /&gt;&lt;a href=&quot;https://support.apple.com/ko-kr/guide/safari/sfri20948/mac&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://support.apple.com/ko-kr/guide/safari/sfri20948/mac&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1725802778445&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Mac용 Safari에서 개발자용 메뉴의 개발자용 도구 사용하기&quot; data-og-description=&quot;Mac의 Safari에서, 개발자용 도구를 사용하여 사용자가 생성한 웹 사이트가 모든 표준 웹 브라우저에서 잘 동작하는지 확인할 수 있습니다.&quot; data-og-host=&quot;support.apple.com&quot; data-og-source-url=&quot;https://support.apple.com/ko-kr/guide/safari/sfri20948/mac&quot; data-og-url=&quot;https://support.apple.com/ko-kr/guide/safari/sfri20948/mac&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://support.apple.com/ko-kr/guide/safari/sfri20948/mac&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://support.apple.com/ko-kr/guide/safari/sfri20948/mac&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;Mac용 Safari에서 개발자용 메뉴의 개발자용 도구 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Mac의 Safari에서, 개발자용 도구를 사용하여 사용자가 생성한 웹 사이트가 모든 표준 웹 브라우저에서 잘 동작하는지 확인할 수 있습니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;support.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1902&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zJdlG/btsJwa4ZEII/fSyBqMukRgaJ0mzoVSHjp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zJdlG/btsJwa4ZEII/fSyBqMukRgaJ0mzoVSHjp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zJdlG/btsJwa4ZEII/fSyBqMukRgaJ0mzoVSHjp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzJdlG%2FbtsJwa4ZEII%2FfSyBqMukRgaJ0mzoVSHjp0%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;1902&quot; height=&quot;612&quot; data-origin-width=&quot;1902&quot; data-origin-height=&quot;612&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;개발자용 메뉴를 활성화 했다면 우리가 아까 빌드한 localhost를 보고있는 IOS 시물레이터가 보이게 됩니다. 여기서 저 메뉴를 클릭하면 크롬의 개발자 도구와 같은 메뉴를 확인할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceF261/btsJwacROcE/Oqk91QQSFR42UNoh3PPDWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceF261/btsJwacROcE/Oqk91QQSFR42UNoh3PPDWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceF261/btsJwacROcE/Oqk91QQSFR42UNoh3PPDWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceF261%2FbtsJwacROcE%2FOqk91QQSFR42UNoh3PPDWk%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;2224&quot; height=&quot;1524&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;1524&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhQlyR/btsJueOTBFY/GmHKCmNGyjnEQjJkKmzLr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhQlyR/btsJueOTBFY/GmHKCmNGyjnEQjJkKmzLr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhQlyR/btsJueOTBFY/GmHKCmNGyjnEQjJkKmzLr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhQlyR%2FbtsJueOTBFY%2FGmHKCmNGyjnEQjJkKmzLr1%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;2224&quot; height=&quot;1524&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;1524&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;2224&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wFBD9/btsJvr68v7y/oWNm2OY1fVCalk0D1zWEY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wFBD9/btsJvr68v7y/oWNm2OY1fVCalk0D1zWEY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wFBD9/btsJvr68v7y/oWNm2OY1fVCalk0D1zWEY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwFBD9%2FbtsJvr68v7y%2FoWNm2OY1fVCalk0D1zWEY1%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;2224&quot; height=&quot;1524&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;1524&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BM6jC/btsJuwuVhFy/F94zycVlTyrc9axhbjngak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BM6jC/btsJuwuVhFy/F94zycVlTyrc9axhbjngak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BM6jC/btsJuwuVhFy/F94zycVlTyrc9axhbjngak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBM6jC%2FbtsJuwuVhFy%2FF94zycVlTyrc9axhbjngak%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;2224&quot; height=&quot;1524&quot; data-origin-width=&quot;2224&quot; data-origin-height=&quot;1524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이 Dev Tool을 사용해서 아래와 같은 디버깅이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 콘솔에 찍히는 로그 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 모바일 웹에서 호출하는 api 혹은 리소스를 불러오는 시간 및 순서 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 의도적으로 이벤트를 일으키고 해당 액션이 어느정도의 CPU를 차지하는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 내 앱의 레이어를 확인&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;이 외에도 다양하게 활용해 볼 수 있지만 위 4가지만 체크해도 모바일 웹상에서의 디버깅을 더 원활하게 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 IOS 사파리에서의 디버깅을 알아봤으니 다음은 Android 의 디버깅을 알아볼 차례입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Android 모바일 웹 디버깅&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드의 경우 어찌보면 상황이 조금 더 까다롭습니다. 왜냐면 localhost에 바로 접근되지 않기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 조금 편법을 쓰게 되는데 그건 바로 로컬 ip로 웹서버를 열어서 확인하는 방법입니다.&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;우선 내 컴퓨터의 내부 ip를 알아야합니다. macOs를 사용한다면 터미널에 아래와 같은 명령어를 입력해 간단하게 알아낼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725803896067&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ipconfig getifaddr en0&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;br /&gt;192.168.xxx.xxx:8080 &amp;lt;- 이런식으로 에뮬레이터의 주소창에 입력해주면 정상적으로 접근 가능하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1725803725456&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 아이피 영역에는 본인의 ip를 입력해주세요!
flutter run -d web-server --web-hostname 192.168.xxx.xxx --web-port 8080&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;이미지 2024. 9. 8. 오후 11.03.jpg&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v0388/btsJvSXJg48/UVMPl7W5lPKOBMVYkGpvtK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v0388/btsJvSXJg48/UVMPl7W5lPKOBMVYkGpvtK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v0388/btsJvSXJg48/UVMPl7W5lPKOBMVYkGpvtK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv0388%2FbtsJvSXJg48%2FUVMPl7W5lPKOBMVYkGpvtK%2Fimg.jpg&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;429&quot; height=&quot;905&quot; data-filename=&quot;이미지 2024. 9. 8. 오후 11.03.jpg&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진과 같이 잘 접근된걸 확인할 수 있습니다. 하지만 위 상태까지만 보면 그냥 빌드된걸 모바일 웹에서 확인만 했을 뿐 어떤 데이터도 확인할 수 없는 상태입니다. 이럴 때 사용할 수 있는 방법으로 '크롬 인스펙터' 가 있습니다.&lt;br /&gt;pc의 크롬 브라우저에서 하단의 링크에 접근합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chrome://inspect/#devices&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷_2024-09-08_오후_11.10.51 2.png&quot; data-origin-width=&quot;3162&quot; data-origin-height=&quot;2112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cK7Bs5/btsJuVVgTzM/WcNj5nes465Xg1t9LKuikK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cK7Bs5/btsJuVVgTzM/WcNj5nes465Xg1t9LKuikK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cK7Bs5/btsJuVVgTzM/WcNj5nes465Xg1t9LKuikK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcK7Bs5%2FbtsJuVVgTzM%2FWcNj5nes465Xg1t9LKuikK%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;3162&quot; height=&quot;2112&quot; data-filename=&quot;스크린샷_2024-09-08_오후_11.10.51 2.png&quot; data-origin-width=&quot;3162&quot; data-origin-height=&quot;2112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 위와같은 화면을 확인할 수 있고 형광펜으로 칠해진 영역을 클릭시 아래와 같은 Dev Tool을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-08 오후 11.20.37.png&quot; data-origin-width=&quot;3152&quot; data-origin-height=&quot;2112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bosSiS/btsJuhLwGSG/f6YFt8vEbDq0az6XX0EzFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bosSiS/btsJuhLwGSG/f6YFt8vEbDq0az6XX0EzFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bosSiS/btsJuhLwGSG/f6YFt8vEbDq0az6XX0EzFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbosSiS%2FbtsJuhLwGSG%2Ff6YFt8vEbDq0az6XX0EzFk%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;3152&quot; height=&quot;2112&quot; data-filename=&quot;스크린샷 2024-09-08 오후 11.20.37.png&quot; data-origin-width=&quot;3152&quot; data-origin-height=&quot;2112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-08 오후 11.23.57.png&quot; data-origin-width=&quot;3152&quot; data-origin-height=&quot;2112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBPJ39/btsJuWmnQGb/MfcFFcKsrYbOuqrY4kCrwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBPJ39/btsJuWmnQGb/MfcFFcKsrYbOuqrY4kCrwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBPJ39/btsJuWmnQGb/MfcFFcKsrYbOuqrY4kCrwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBPJ39%2FbtsJuWmnQGb%2FMfcFFcKsrYbOuqrY4kCrwk%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;3152&quot; height=&quot;2112&quot; data-filename=&quot;스크린샷 2024-09-08 오후 11.23.57.png&quot; data-origin-width=&quot;3152&quot; data-origin-height=&quot;2112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같이 크롬 인스펙터를 사용하면&amp;nbsp; 평소 웹을 디버깅할때 크롬에서 확인하던 기능들을 거의 그대로 사용할 수 있게됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;마무리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 Flutter 개발자들이 모바일 앱을 개발하는 빈도가 높지만 크로스 플랫폼의 장점을 최대한 활용하기 위해서는 웹과 데스크탑 앱을 모두 개발하고 디버깅해보며 경험을 쌓는게 중요하다고 생각합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 현재 아직까지 Flutter 프레임워크에는 해결되지 않은 고질적인 문제들이 많이 있습니다. 그래서 개발을 하는 도중 예상대로 동작하지 않거나 충돌이 나는경우가 많은데 단지 개발자의 코드문제였다면 고치면 되는 문제이기 때문에 다행이지만 Flutter 자체의 결함인 경우가 상당히 많고 찾아보면 Github 이슈에 등록이 되어있으나 아직 해결이 되지 않은경우 혹은 아직 이슈에 등록조차 안된경우에 최대한 데이터를 수집하여 공유하려는 노력이 필요합니다.&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;</description>
      <category>플러터</category>
      <category>debug</category>
      <category>devtools</category>
      <category>flutter</category>
      <category>Safari</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/147</guid>
      <comments>https://rlg1133.tistory.com/147#entry147comment</comments>
      <pubDate>Sun, 8 Sep 2024 23:37:20 +0900</pubDate>
    </item>
    <item>
      <title>[플러터] 관리하기 좋은 다국어 모듈</title>
      <link>https://rlg1133.tistory.com/146</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;DALL&amp;amp;middot;E_2024-07-02_21.53.53_-_A_diverse_group_of_people_engaged_in_conversation._The_group_includes_individuals_of_different_race 2.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfemPe/btsIkArdEY6/adZK5x93hCvQJq30rykKGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfemPe/btsIkArdEY6/adZK5x93hCvQJq30rykKGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfemPe/btsIkArdEY6/adZK5x93hCvQJq30rykKGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfemPe%2FbtsIkArdEY6%2FadZK5x93hCvQJq30rykKGK%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;1024&quot; height=&quot;688&quot; data-filename=&quot;DALL&amp;middot;E_2024-07-02_21.53.53_-_A_diverse_group_of_people_engaged_in_conversation._The_group_includes_individuals_of_different_race 2.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 앱을 개발하다보면 외국인을 대상으로 서비스를 운영하기도 합니다.&amp;nbsp; 외국인 클라이언트들에게 한국어를 강요할 수는 없기때문에 우리 개발자들은 정해진 locale 에 따라서 언어를 분기해주어야 하죠&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;제가 소속된 팀에서는 GetX를 사용하는데 이 상태관리툴에는 기본적으로 다국어를 지원할 수 있끔 기능이 포함되어있습니다. 아래 예시를 보시죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-07-02 오후 10.25.13.png&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzWbBn/btsIjhTKbFT/1IegXT9jZXAgbwyLMO7Gkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzWbBn/btsIjhTKbFT/1IegXT9jZXAgbwyLMO7Gkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzWbBn/btsIjhTKbFT/1IegXT9jZXAgbwyLMO7Gkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzWbBn%2FbtsIjhTKbFT%2F1IegXT9jZXAgbwyLMO7Gkk%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;1636&quot; height=&quot;1570&quot; data-filename=&quot;스크린샷 2024-07-02 오후 10.25.13.png&quot; data-origin-width=&quot;1636&quot; data-origin-height=&quot;1570&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;위 내용은 Getx 패키지 Readme에 작성 되어있는 다국어의 예시입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 국가별 구분을 최상위 key로 가지고 value 로는 같은 하위 key에 다른 하위 value 를 가진 json 형태로 만들어져 있죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이렇게 정해준 내용들을 가지고&amp;nbsp; 아래와 같은 형태로 사용을 하게 되는겁니다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;Text('title'.tr);
&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;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;하지만 위와 같은 방식으로 사용하게 되면 부각되는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;단점&lt;/span&gt;이 있습니다. 바로 &lt;b&gt;유지보수가 어렵다는 점&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;예들들어 아래와 같은 json 내용물이 있다고 가정하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1719927614320&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;conversation_person_1_1&quot; : &quot;난 대학시절 묵찌빠를 전공했단 사실!&quot;,
	&quot;conversation_person_1_2&quot; : &quot;니놈을 이겨 눈물, 콧물 쏙다빼주마!&quot;,
	&quot;conversation_person_2_1&quot; : &quot;난 묵찌빠로 유학까지 다녀왔단 사실!&quot;,
	&quot;conversation_person_2_2&quot; : &quot;니놈을 이겨 가문의 이름 높이리!&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1719927692898&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Text('conversation_person_1_1'.tr);
Text('conversation_person_1_2'.tr);
Text('conversation_person_2_1'.tr);
Text('conversation_person_2_2'.tr);&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;여기서 만약에 위 json에 &lt;b&gt;'conversation_person_1_2' 항목을 지워버린다면&lt;/b&gt; 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;DALL&amp;amp;middot;E_2024-07-02_22.45.17_-_A_person_holding_a_whiteboard_eraser,_erasing_the_words_'conversation_person_1_2'_from_a_whiteboard.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;694&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRULhx/btsIlzylRQ9/Yk95zmSCdrpDp9nhL3EJsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRULhx/btsIlzylRQ9/Yk95zmSCdrpDp9nhL3EJsk/img.png&quot; data-alt=&quot;몰래 json 행지우는 김AI씨&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRULhx/btsIlzylRQ9/Yk95zmSCdrpDp9nhL3EJsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRULhx%2FbtsIlzylRQ9%2FYk95zmSCdrpDp9nhL3EJsk%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;559&quot; height=&quot;379&quot; data-filename=&quot;DALL&amp;middot;E_2024-07-02_22.45.17_-_A_person_holding_a_whiteboard_eraser,_erasing_the_words_'conversation_person_1_2'_from_a_whiteboard.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;694&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;몰래 json 행지우는 김AI씨&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 우리 개발자들이 json 행을 의도적으로 지우는 만행을 하지는 않을겁니다. 그렇지만 코드를 작성하다가 json 작업중 confilct가 나고 휴먼에러를 일으켜 정상적으로 병합이 되지 않는일이 발생할지도 모르죠&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 다국어 key 값이 존재하지 않는다면 공백으로 노출되게 됩니다. 이 텍스트가 노출되는 부분이 눈에 잘 띄는 부분이라 금방 발견하면 좋지만 만약 눈에 띄지 않는 부분이고 그게 그대로 심사를 받고 스토어에 올라간다면,,?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 스스로의 코드를 믿지 못하고 항상 불안하게 코드를 작성하게 될지도 모르는 일입니다. 그렇다면 어떻게 하면 이러한 문제를 해결할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 &lt;b&gt;Type Safe 코드로 작성한다&lt;/b&gt; 입니다. 이 모든 문제는 위 GetX의 다국어 예제와 같이 String 값을 그대로 사용하려 했기 때문에 발생하게 됩니다. 그렇다면 우리는 String 값이 아니라 다국어 json 을 기반으로 Model 을 구축해 그 Model 에서 변수로 값을 불러와 사용을 하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가만든 플러그인인 &lt;a href=&quot;https://pub.dev/packages/gh_i18n&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;gh_i18n&lt;/a&gt; 에서는 asset으로 지정해놓은 json 데이터를 토대로 자동으로 model 만들고 만들어진 model 을 기반으로 다국어 처리를 손쉽게 할 수 있게 작업이 되어있습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1719929294799&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;gh_i18n | Flutter package&quot; data-og-description=&quot;This is an easy plugin to implement i18n functions in flutter.&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/gh_i18n&quot; data-og-url=&quot;https://pub.dev/packages/gh_i18n&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bKWTYI/hyWvI1SH83/au4MISoUKRNMnPTcjGCqg0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/blAByu/hyWvIU7VFK/DjLjq1OQodQQ1YD1K8BUx1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/gh_i18n&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/gh_i18n&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bKWTYI/hyWvI1SH83/au4MISoUKRNMnPTcjGCqg0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/blAByu/hyWvIU7VFK/DjLjq1OQodQQ1YD1K8BUx1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&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;gh_i18n | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This is an easy plugin to implement i18n functions in flutter.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목_없는_아트워크.png&quot; data-origin-width=&quot;2021&quot; data-origin-height=&quot;1700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6keeS/btsIlPVfEUF/ZQTshEtk9Jv7nqFzfQMYO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6keeS/btsIlPVfEUF/ZQTshEtk9Jv7nqFzfQMYO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6keeS/btsIlPVfEUF/ZQTshEtk9Jv7nqFzfQMYO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6keeS%2FbtsIlPVfEUF%2FZQTshEtk9Jv7nqFzfQMYO0%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;545&quot; data-filename=&quot;제목_없는_아트워크.png&quot; data-origin-width=&quot;2021&quot; data-origin-height=&quot;1700&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;위 그림과 같이 각 언어별 같은 폼을 가진 json 에셋들을 준비하고 json 파일의 경로와 같은 환경 변수들을 기입한 Yaml 파일을 생성합니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1719930333346&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dart run gh_i18n&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;p data-ke-size=&quot;size16&quot;&gt;작성된 json 파일을 기반으로한 model 이 생성이 되고 그 모델을 기반으로 한 Helper 와 Translator 가 생성이 되는데 이 클래스들을 통해 Type Safe 한 방식으로 다국어를 사용할 수 있게 됩니다. 이 후 json이 수정되면 다시 위 명령어를 호출해 model 을 업데이트 시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이제 안전한 다국어를&amp;nbsp; 사용할 준비가 되었는데 그 예시는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1719930543836&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//기존
Text('conversation_person_1_1'.tr);

//수정된 형태
Text(tl.common.conversationPerson11);&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;tl 이라는 객체는 아래와 같이 생긴 json을 바탕으로 만들어진 모델의 shortCut 입니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1719930584682&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GhI18nLanguage get tl =&amp;gt; GhTranslatorHelper()._translateLanguage;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 tl 객체를 전역에서 불러와 사용하면 되는데 여기서 기존에 가지고있던 문제점인 유지보수가 어려운 점을 해결할 수 있습니다.&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;기존에 json에 등록되어있던 'conversation_person_2_1' 값을 실수로 삭제한 경우 명령어 입력시 model 생성과정에서 현재 기준 json 을 바탕으로 모델을 만들기 때문에 만약 기존에 tl.common.conversationPerson21 을 사용하는곳이 있었다면 &lt;b&gt;해당 row에는 에러가 발생해 빌드를 할 수 없게됩니다.&lt;/b&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;이와같이 String 기반 다국어 처리가 아닌 Model을 통한 다국어처리를 하고 거기에 json 과 Model 의 동기화를 명령어 한줄로 수행함으로써 불필요하게 늘어날 코드를 방지하고 미래에 발생할 유지보수의 어려움을 사전에 막아 더 효율적인 결과물을 도출할 수 있게 됩니다.&lt;/p&gt;</description>
      <category>플러터</category>
      <category>flutter</category>
      <category>getx</category>
      <category>i18n</category>
      <category>translation</category>
      <category>다국어</category>
      <category>다국어 처리</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/146</guid>
      <comments>https://rlg1133.tistory.com/146#entry146comment</comments>
      <pubDate>Tue, 2 Jul 2024 23:38:36 +0900</pubDate>
    </item>
    <item>
      <title>시뮬레이티드 어닐링(Simulated Annealing) 알고리즘으로 대학원생 친구 구원해주기</title>
      <link>https://rlg1133.tistory.com/145</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;약 2달 전쯤 친구들이 모여있는 단톡방에서 포스텍 박사과정을 진행중인 친구가 여느때와 같이 우는소리를 하기 시작했습니다.&lt;br&gt;얘기를 들어보니 자기가 하는 연구가 대충 단백질 만드는거라는데 만들어진 단백질의 분자량만 가지고 그 단백질을 구성하는 아미노산들의 조합을 뽑아보고 싶다는겁니다.&amp;nbsp;&lt;br&gt;저런 화학지식에 대해 아는게 전혀없는 저에게는 뭔소린가 싶었지만 설명을 듣다보니 생각보다 할만하다? 라는 느낌이 들었습니다.&lt;br&gt;(물론 처음에 말한 조건과 나중에 말한 조건이 달라서 낭패를 보긴했습니다.. )&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;조건은?&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 저한테말해줬던 조건은&amp;nbsp;&lt;br&gt;1. 단백질 분자량 약 500~ 10000 정도의 값을 입력할 수 있어야한다. (double 값)&lt;br&gt;2. 아미노산의 종류와 각 분자량은 이미 20개 정도로 정해져있다.&lt;br&gt;3. 필수로 들어가는 아미노산을 입력할 수 있으면 좋다.&lt;br&gt;4. 아미노산의 조합을 목표값과 가까운 순서대로 20개 정도 뽑아줬으면 좋겠다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이정도로 생각보다 간단해 보이는 주문이었습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;final aminoMap = {
&amp;nbsp;&amp;nbsp;'G': 75.03,
&amp;nbsp;&amp;nbsp;'A': 89.05,
&amp;nbsp;&amp;nbsp;'S': 105.04,
&amp;nbsp;&amp;nbsp;'T': 119.06,
&amp;nbsp;&amp;nbsp;'C': 121.02,
&amp;nbsp;&amp;nbsp;'V': 117.08,
&amp;nbsp;&amp;nbsp;'L': 131.09,
&amp;nbsp;&amp;nbsp;'I': 131.09,
&amp;nbsp;&amp;nbsp;'M': 149.05,
&amp;nbsp;&amp;nbsp;'P': 115.06,
&amp;nbsp;&amp;nbsp;'F': 165.08,
&amp;nbsp;&amp;nbsp;'Y': 181.07,
&amp;nbsp;&amp;nbsp;'W': 204.09,
&amp;nbsp;&amp;nbsp;'D': 133.04,
&amp;nbsp;&amp;nbsp;'E': 147.05,
&amp;nbsp;&amp;nbsp;'N': 132.05,
&amp;nbsp;&amp;nbsp;'Q': 146.07,
&amp;nbsp;&amp;nbsp;'H': 155.07,
&amp;nbsp;&amp;nbsp;'K': 146.11,
&amp;nbsp;&amp;nbsp;'R': 174.11,
};&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위 20개가 계산에 쓰이는 아미노산의 고유 Seq 와 분자량을 의미한다. 즉, 입력값을 750 입력하면 GGGGGGGGGG (분자량 : 750.3) 이런식으로 출력이 되어야 한다는 얘기로 이해했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;어떻게 구현할까?&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 해주기로 한 이상 어떤식으로 로직을 짤까 생각을 했는데, 이왕 하는거 저는 제 공부도 할겸, 그 연구실 학생들도 편하게쓸겸 MacOs, Windows 데스크탑 앱 배포를 결심했고 비지니스 로직을 수행해줄 주체를 고르기만하면 됐다.&lt;br&gt;1. dart 코드보다는 성능이 좋다는 python 코드로 짜고 Django로 서버를 띄워 요청을 보내고 response로 받는다.&lt;br&gt;2. flutter local 에서 파이썬 코드를 실행하고 결과를 받아오는 방법을 찾아본다.&lt;br&gt;3. dart code로 알고리즘을 구현한다.&lt;br&gt;&amp;nbsp;&lt;br&gt;솔직히 학창시절 알고리즘 공부는 java 아니면 python 으로만 짜봤고 flutter 개발자가 된 이후로 dart 언어를 통해 별다른 알고리즘을 해볼 기회는 없어서 고민을 하던중 아래와 같이 소거법으로 진행했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;1번 방법은 서버를 띄워줘야 하는데 ec2 프리티어 서버는 만료된지 오래고 자원봉사하는데 서버까지 띄워주는건 투머치라고 판단.&lt;br&gt;2번 방법은 파이썬 코드를 실행하는 플러그인은 있지만 연산의 결과를 유연하게 받아오는건 안되는것으로 보임.&lt;br&gt;&amp;nbsp;&lt;br&gt;즉 그냥 생짜 dart 언어로 알고리즘을 짜보기로 했고 속도가 안나오면 그때 서버를 띄워도 늦지 않을거라 생각해 dart로 진행했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;알고리즘을 골라보자!&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;로직의 주체는 골랐으니 이제 어떤 로직을 채택할지가 문제였는데 당연히 모든 경우의수를 계산한다는 단순한 해결책은 통할리가 없었습니다. 입력값이 10000쯤 되면 아미노산 시퀀스의 평균을 150정도로 잡아도&amp;nbsp; 아미노산 조합의 길이가 60~70개는 될거고 그것은 2^60 ~ 2^70개 만큼의 경우의수가 있다는 얘긴데 심지어 가장 분자량이 작은 G의 경우에는 130개정도 되는 길이가 나오게 되고 이를 메모리 공간에 다 저장해두고 있다가 마지막에 한바퀴 쭉 돌면서 가장 근사치 20개 찾아줘! 할수는 없는노릇이었습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;효율적으로 답을 찾아올 방법을 고민했는데 듣도 보도 못한 알고리즘들을 비교하다가 결국 고른게 시뮬레이티드 어닐링(Simulated Annealing) 알고리즘 이었습니다. 이 개념이 꽤나 단순하면서 신기했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;시뮬레이티드 어닐링..? 그게뭐야?&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우선 어닐링의 의미부터 알아야 하는데 흔히 드라마나 영화 또는 만화에서 대장장이들이 칼을 만들때&lt;br&gt;1. 뜨거운 용광로에 철을 달구고&lt;br&gt;2. 망치로 두들기고&lt;br&gt;3. 찬물로 식히는&lt;br&gt;위 step을 계속해서 반복하면 원하는 칼의 형태가 만들어지는데 이 과정을 어닐링이라고 합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그렇다면 시뮬레이티드 어닐링이란? 위 과정을 코드로 구현해 최적의 해(올바른 칼의 형태) 를 구해내는 과정인 것이지요&lt;br&gt;이 방법을 간략하게 설명하면 우선 초고온상태에 있다고 가정을 하고 얼마나 낮은 온도가 되어야 어닐링을 멈출지 미리 지정을 합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;const double initialTemperature = 10000.0; // 초기 온도
const double absoluteTemperature = 0.00001; // 최소 온도
const double coolingRate = 0.99; // 냉각률&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;우 세 값을 통해 초기 온도 부터 시작해 냉각률을 곱해가면서 최소온도가 될때까지가 한번의 싸이클입니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;const int saIterations = 100; // 시뮬레이티드 어닐링 반복 횟수&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;하지만 여기서 우리는 최적의 해를 1개가 아닌 20개를 뽑아와야하기 때문에 중복을 고려해 넉넉하게 100번 시행하도록 합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; randomSolution(double targetMass) {
&amp;nbsp;&amp;nbsp;List&amp;lt;String&amp;gt; solution = [];
&amp;nbsp;&amp;nbsp;double mass = 0.0;
&amp;nbsp;&amp;nbsp;while (mass &amp;lt; targetMass) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String aminoAcid = dataMap.keys.elementAt(random.nextInt(dataMap.length));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;double aminoAcidMass = dataMap[aminoAcid]!;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (mass + aminoAcidMass &amp;gt; targetMass) break;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;solution.add(aminoAcid);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mass += aminoAcidMass;
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;return solution;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;가장 처음에는 위 코드와 같이 목표가 되는 분자량과 멀어도 좋습니다. 대장장이가 창고에서 대충 철괴를 꺼내오듯 아무값이나 도출을 합니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;List&amp;lt;String&amp;gt; currentSolution = randomSolution(targetMass);

&amp;nbsp;&amp;nbsp;List&amp;lt;String&amp;gt; bestSolution = List.from(currentSolution);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;그리고는 대충만든값을 최적을 해 라고 판단하고 bestSolution 에 넣어놓습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;while (temperature &amp;gt; absoluteTemperature) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List&amp;lt;String&amp;gt; newSolution = neighborSolution(currentSolution, targetMass);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;double newEnergy = evaluate(newSolution, targetMass);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (acceptanceProbability(currentEnergy, newEnergy, temperature) &amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;random.nextDouble()) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;currentSolution = newSolution;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;currentEnergy = newEnergy;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (currentEnergy &amp;lt; bestEnergy) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bestSolution = List.from(currentSolution);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;bestEnergy = currentEnergy;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;temperature *= coolingRate;
&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;그리고 나서는 10000.0(최고온도) 부터 시작된 두들김이 0.99를 반복해 곱해서 0.00001(최저온도) 가 될때까지 로직을 반복하며 망치를 두들깁니다.&lt;br&gt;현재 코드에서는 acceptanceProbability 함수가 1번의 망치 두들김이라고 보시면 되고 neighborSolution 함수는 기존 bestSolution 에서 하나의 아미노산 조합에서 랜덤으로 하나를 교체해 비슷하지만 다른 아미노산 조합을 만드는 함수입니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;double acceptanceProbability(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;double currentEnergy, double newEnergy, double temperature) {
&amp;nbsp;&amp;nbsp;return newEnergy &amp;lt; currentEnergy;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;새로 만들어진 값과 목표 분자량과의 차이가 기존 bestSolution에 비교해서 적다면 새로운 값을 bestSolution에 넣어주는 것입니다.&lt;br&gt;여기서 이 방법의 장점이 나오는데 그것은 메모리에 하나의 값만 저장하고 있으면 된다는것입니다.&lt;br&gt;계산을 수도없이 하지만 메모리에 1개의 값만 가지고 비교해서 더 나은값을 찾으면 기존값은 몰라도 되고 새로운값만 알면 되기때문에 이점이 있다고 볼 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이런식으로 어닐링을 100회정도 하고나면 그때그때 다른 100개의 최적의 해가 나오는데 여기서 중복을 제거하고&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;&amp;nbsp;&amp;nbsp;// 에너지(오차)에 따라 해들을 정렬하고 상위 20개를 선택
&amp;nbsp;&amp;nbsp;bestSolutions.sort((a, b) =&amp;gt; a.values.single.compareTo(b.values.single));
&amp;nbsp;&amp;nbsp;bestSolutions = bestSolutions.take(topSolutionsCount).toList();&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위 코드를 통해 20개를 잘라서 최종 결과를 도출해냅니다.&lt;br&gt;.&lt;br&gt;.&lt;br&gt;.&lt;br&gt;근데 여기서 잠깐.. 항상 그렇듯 기획자는 개발자에게 숨기는게 있기 마련이죠&lt;br&gt;대충 다 만들고 나니 한다는 말이 &lt;b&gt;'어 근데 거기서 물 증발량 빼야해 그건 금방하지?'&lt;/b&gt; 라더군요 얘기를 들어보니 아미노산의 갯수 * 18.01 만큼은 최초 입력값에 포함 되어있지 않다는겁니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;즉, 'G': 75.03 로 예시를 들면&lt;br&gt;입력값을 750.3 을 입력했으면 75.03개 10개인 GGGGGGGGGG 가 나오는게 아닌겁니다.&lt;br&gt;GGGGGGGGGG 의 분자량은 750.3 - (18.01 * 10) = 570.2 가 되어버리는겁니다.&lt;br&gt;여태 작업했던 코드 기준으로는 생뚱맞은 결과값밖에 얻을수가 없는거였습니다. 여기서 더 문제인건 아미노산조합의 길이는 아미노산마다 분자량이 다르기때문에 결과가 도출될때까지 알 수 가 없습니다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;분자량 1000으로 따지면 G 기준으로는 10개가 넘지만 W 기준으로는 5개밖에 되지 않기때문에 들쭉날쭉하여 가장 분자량이 적은 아미노산, 가장 큰 아미노산으로 가능한 최소 길이와 최대 길이를 지정해서&lt;br&gt;만약 길이가 5~ 10이라면 물 증발량은 각각 90.05, 108.06, 126.07, 144.08, 162.09, 180.1 이 되고&amp;nbsp;&lt;br&gt;최초 입력값에서 위 6개 값을 순서대로 뺀 값을 기준으로 알고리즘을 실행해 결과를 구해야 했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;사실 위 문제 말고도 이후에 다양한 추가 요구사항이 나왔지만 본문엔 안적는거로 하겠습니다...&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;결과는?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이런 우여곡절 끝에 포항공대 바이오 연구실에서 사용될 아미노산 시퀀스 유추 툴이 완성되었습니다.&lt;br&gt;현재도 업그레이드 중이고&amp;nbsp; 친구 연구실 교수님이 제가 만든 프로그램을 보시더니 친구한테 저랑 같이 논문하나 내보는거 어떠냐고 요구사항을 몇가지 준비하시고 있계시다던데 벌써 두렵습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;만들어진 계산기는 현재 아래 링크에서 업데이트 되고있습니다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;a href=&quot;https://github.com/Gyeony95/Mass-Finder/releases&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/Gyeony95/Mass-Finder/releases&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Releases · Gyeony95/Mass-Finder&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;단백질의 총량을 입력하고 단백질을 구성하는 아미노산 종류를 유추하는 프로그램. Contribute to Gyeony95/Mass-Finder development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Gyeony95/Mass-Finder/releases&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RPDWC/hyUE3utWVA/BPyIzPspD5KJOno2fJvJCk/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_121_1057_211&quot; data-og-url=&quot;https://github.com/Gyeony95/Mass-Finder/releases&quot;&gt;&lt;a href=&quot;https://github.com/Gyeony95/Mass-Finder/releases&quot; target=&quot;_blank&quot; data-source-url=&quot;https://github.com/Gyeony95/Mass-Finder/releases&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RPDWC/hyUE3utWVA/BPyIzPspD5KJOno2fJvJCk/img.png?width=1200&amp;amp;height=600&amp;amp;face=975_121_1057_211')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Releases · Gyeony95/Mass-Finder&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;단백질의 총량을 입력하고 단백질을 구성하는 아미노산 종류를 유추하는 프로그램. Contribute to Gyeony95/Mass-Finder development by creating an account on GitHub.&lt;/p&gt;&lt;p class=&quot;og-host&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS(Computer  Science)/기타</category>
      <category>simulated annealing</category>
      <category>시뮬레이티드 어닐링</category>
      <category>알고리즘</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/145</guid>
      <comments>https://rlg1133.tistory.com/145#entry145comment</comments>
      <pubDate>Sat, 2 Dec 2023 22:53:48 +0900</pubDate>
    </item>
    <item>
      <title>[플러터] 이미지 Pre-Cache를 통해 성능을 올려보자</title>
      <link>https://rlg1133.tistory.com/144</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;플러터 로고.png&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vay9H/btsBfxvDAfu/ZQjJrbaqFOQKbGJYN8Gpc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vay9H/btsBfxvDAfu/ZQjJrbaqFOQKbGJYN8Gpc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vay9H/btsBfxvDAfu/ZQjJrbaqFOQKbGJYN8Gpc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvay9H%2FbtsBfxvDAfu%2FZQjJrbaqFOQKbGJYN8Gpc0%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;318&quot; height=&quot;159&quot; data-filename=&quot;플러터 로고.png&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;앱의 성능을 올려볼 방법이 없을까?&lt;/b&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;최근 작업을 하면서 성능을 올릴만한게 뭐 없을까? 라는 고민을 하며 회사에서 작업을 하던중 앱의 화면에 asset이 많이 들어가면 화면이 버벅이는거 같은 느낌을 받았습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 느낌은 아이폰에서 특히 더 많이 느껴지는거같았고 어떻게 해결할 수 있는 방법이 없을지 찾아보던 중 이미지를 precache 할 수 있다는것을 알아냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;이미지 Precache는 Flutter에서 제공하는 기능 중 하나로, 화면에 표시되기 전에 이미지를 미리 로드하여 캐시에 저장하는 과정입니다. 이를 통해 사용자가 이미지를 요청할 때마다 실시간으로 다운로드하는 대신, 이미지가 사전에 로드되어 빠르게 표시될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;그래서 어떻게 쓰는건데?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 사용법은 아래와 같습니다.&lt;br /&gt;이미지를 사용하기 전 precacheImage 메서드를 통해 ImageProvider의 구현체인 NetworkImage 혹은 AssetImage 객체를 생성해 ImageCache 내에 등록하는겁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701449415554&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // precacheImage(NetworkImage('https://example.com/image.jpg'), context); // or
    precacheImage(AssetImage('assets/image.jpg'), context);
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Image Precache Example'),
      ),
      body: Center(
        child: Image.network('https://example.com/image.jpg'),
      ),
    );
  }
}
&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;p data-ke-size=&quot;size16&quot;&gt;그때부터 바로 뚝딱뚝딱 작업에 돌입!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;플러그인으로 만들어보자!&lt;/b&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;메인 페이지에서 캐시된 이미지와 캐시되지 않은 이미지를 구분해서 보기위해 각 페이지를 나누고 assets 폴더에 들어있는 모든 이미지를 캐시하게끔 작업했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 들었던 생각이 '그러면 에셋의 모든 경로를 하드코딩으로 입력해줘야하나? 그럼 너무 비효율적인데?' 라고 생각하고 로컬 파일주소에 접근할 수 있는 방법을 찾다가 아래와 같은 방법을 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1701449415554&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final manifestJson = await DefaultAssetBundle.of(context).loadString('AssetManifest.json');
final Map&amp;lt;String, dynamic&amp;gt; manifestMap = json.decode(manifestJson);
final assetList = manifestMap.keys.toList();
&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;AssetManifest.json&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt; 파일은 Flutter 앱에서 사용되는 에셋들의 정보를 포함하는 파일입니다. 이 파일은 앱이 빌드될 때 생성되며, 앱이 어떤 에셋을 포함하고 있는지에 대한 메타데이터를 담고 있습니다. &lt;/span&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;AssetManifest.json 파일을 까보면 이런식으로 생겼고 각 경로의 key 값을 모아 내 로컬의 에셋의 경로를 찾아올 수 있었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701449415554&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;assets/test_cached.jpg&quot;: [
    &quot;assets/test_cached.jpg&quot;
  ],
  &quot;packages/cupertino_icons/assets/CupertinoIcons.ttf&quot;: [
    &quot;packages/cupertino_icons/assets/CupertinoIcons.ttf&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_1701449621110&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; List&amp;lt;String&amp;gt; extensionList = ['svg', 'png', 'jpg', 'jpeg'];
 imageList.removeWhere((e) =&amp;gt; !extensionList.contains(e.split('.').last));&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;위에서 얻은 경로를 바탕으로 svg 파일과 기타 이미지 파일을 나눠서 precache를 진행하면 거의 완성입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1701449415554&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  /// 이미지 전달받아서 캐싱함
  void _imageCache(String path, BuildContext context) {
    bool isSvg = path.contains('.svg');
    if(isSvg){
      final loader = SvgAssetLoader(path);
      svg.cache.putIfAbsent(loader.cacheKey(null), () =&amp;gt; loader.loadBytes(null));
    }else{
      precacheImage(AssetImage(path), context);
    }
  }&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;마지막으로 위 작업들을 진행하기 위해서는 BuildContext 를 전달 받아야하는 유저 입장에서의 불편함이 있는데 이를 해결하기위해&lt;/p&gt;
&lt;pre id=&quot;code_1701449876495&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  void startImageCache(){
    final binding = WidgetsFlutterBinding.ensureInitialized();
    binding.addPostFrameCallback((_) async {
      BuildContext? context = binding.rootElement;
      if(context != null) _startImageCache(context);
    });
  }&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;이런식으로 플러그인 내부에서 위젯이 그려지고 context가 생기면 받아서 호출해주게끔 작업을 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래와 같이 main 함수 내에서도 별다른 제약없이 호출할 수 있게되었습니다!&lt;/p&gt;
&lt;pre id=&quot;code_1701449930420&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() {
  GhAssetPreCache().startImageCache();
  runApp(const MyApp());
}&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;size18&quot;&gt;&lt;b&gt;그래서 결과물은?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2023-12-02 오전 1.05.41.gif&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;1574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c455hE/btsBjpbCXge/Jils7M6praPxyTw0TObnak/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c455hE/btsBjpbCXge/Jils7M6praPxyTw0TObnak/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c455hE/btsBjpbCXge/Jils7M6praPxyTw0TObnak/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c455hE/btsBjpbCXge/Jils7M6praPxyTw0TObnak/img.gif&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;324&quot; height=&quot;664&quot; data-filename=&quot;화면 기록 2023-12-02 오전 1.05.41.gif&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;1574&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;약 2mb 정도 되는 이미지를 카피해서 하나는 캐시를 진행하고 하나는 캐시를 시키지 않고 테스트 한 영상입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'image cache' 버튼에서는 별다른 로딩없이 페이지가 열릴때부터 이미지가 잘 보이는 반면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'image non cache' 버튼에서는 흰바탕이었다가 화면이 어느정도 노출된 이후 이미지가 보이게 되는걸 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/gh_asset_pre_cache&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://pub.dev/packages/gh_asset_pre_cache&lt;/a&gt;&lt;/p&gt;</description>
      <category>플러터</category>
      <category>flutter</category>
      <category>precache</category>
      <category>성능</category>
      <category>성능개선</category>
      <category>플러터</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/144</guid>
      <comments>https://rlg1133.tistory.com/144#entry144comment</comments>
      <pubDate>Sat, 2 Dec 2023 02:05:26 +0900</pubDate>
    </item>
    <item>
      <title>[플러터] 플러터의 멀티스레드? Isolate!</title>
      <link>https://rlg1133.tistory.com/143</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;플러터 로고.png&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eAtpXK/btsd1jzhgEA/bNunnTQKdsay937lD1EIvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eAtpXK/btsd1jzhgEA/bNunnTQKdsay937lD1EIvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eAtpXK/btsd1jzhgEA/bNunnTQKdsay937lD1EIvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeAtpXK%2Fbtsd1jzhgEA%2FbNunnTQKdsay937lD1EIvk%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;318&quot; height=&quot;159&quot; data-filename=&quot;플러터 로고.png&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Isolate 가 뭐길래?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dart는 기본적으로 싱글 스레드를 기반으로 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 스트리밍, 백그라운드 작업을 지원합니다. 그 이유는 Dart의 모든 코드는 Isolate 기반으로 동작을 하기 떄문인데요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글스레드가 격리된 메모리 공간에서 Isolate를 사용해 _&lt;b&gt;이벤트 루프&lt;/b&gt;_를 실행시켜 멀티스레드 처럼 동작시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/// Event Roop는 Event Queue에 담겨 주어진 작업을 순서대로 처리해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OqhaD/btsd1iUCNdx/i7SCVCvP41wnXcYAqJAuqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OqhaD/btsd1iUCNdx/i7SCVCvP41wnXcYAqJAuqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OqhaD/btsd1iUCNdx/i7SCVCvP41wnXcYAqJAuqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOqhaD%2Fbtsd1iUCNdx%2Fi7SCVCvP41wnXcYAqJAuqK%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;628&quot; height=&quot;330&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Isolate를 코드에서 적절히 사용해주면 개인적으로 Flutter의 큰 단점중 하나라고 생각되는 화면의 버벅거림을 줄여 UX를 좋게 만들어 줄 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;그렇다면 Isolate가 Thread 인가요??&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히는 아이솔레이트는 스레드를 감싸는 wrapper입니다! 스레드는 다른 스레드와 메모리를 공유할 수 있어서 개발할때 여러모로 편하다고 하지만 여러 스레드에서 하나의 변수를 참조하다보면 오류가 생기기 마련입니다. 따라서 아이솔레이트에서는 메모리를 공유할 수 없습니다. 대신에 값이담긴 메세지를 주고받으며 대화를 하는데 그건 아래 코드에서 알아보겠습니다!&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Isolate를 이해해보자!&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;  String? loadedData;
  late Isolate _isolate;
  final _receivePort = ReceivePort();

  @override
  void initState() {
    super.initState();
    _receivePort.listen((message) {
      setState(() {
        loadedData = message.toString();
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return MainFrame(
      route: FeatureEnum.isolate,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Builder(builder: (_) {
            if(loadedData == null){
              return const SizedBox();
            }
            if(loadedData!.isEmpty){
              return const CircularProgressIndicator();
            }
            return Text(loadedData!);
          },),
          const SizedBox(height: 10,),
          ElevatedButton(onPressed: onTapMethodCall, child: const Text('Call Method')),
          const SizedBox(height: 10,),
          ElevatedButton(onPressed: onTapMethodCallWithIsolate, child: const Text('Call Method With Isolate')),
          const SizedBox(height: 10,),
          ElevatedButton(onPressed: onTapCancelIsolate, child: const Text('Cancel Isolate')),
        ],
      ),
    );
  }&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;loadedData 라는 String 값이 null 이면 빈화면, 빈값이면 로딩, 유의미한 값이면 Text를 보여주는 위젯입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에는 ElevatedButton 이 세개가 있네요! 각각 하는 일을 알아봅시다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;/// 아이솔레이트 없이 sleep을 호출함 -&amp;gt; 화면 멈춤
  Future&amp;lt;void&amp;gt; onTapMethodCall() async {
    setState(() {
      loadedData = '';
    });
    sleep(const Duration(milliseconds: 5000));
    setState(() {
      loadedData = 'just sleep over';
    });
  }

  /// 아이솔레이트를 통해 별도 메모리를 할당시켜 UI에 영향을 주지 않고 로직 실행
  Future&amp;lt;void&amp;gt; onTapMethodCallWithIsolate() async {
    setState(() {
      loadedData = '';
    });

    _isolate = await Isolate.spawn&amp;lt;SendPort&amp;gt;(
        sleepApp, _receivePort.sendPort);
  }

  /// 작업 중단
  void onTapCancelIsolate(){
    _isolate.kill(priority: Isolate.immediate);

    setState(() {
      loadedData = 'Task canceled';
    });
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 첫번째 버튼의 옵션인 onTapMethodCall 입니다.&lt;br /&gt;코드를 보면 앱을 로딩상태로 만들어주고 5초간 대기 후 로딩을 풀어주는 식으로 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 두번째 버튼은 onTapMethodCallWithIsolate 함수를 호출하는데 이는 위 함수와 똑같이 앱을 로딩상태로 만들어주고 아래와 같은 sleepApp 함수를 통해 5초간 대기시킨 후 로딩을 풀어주게 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째 함수는 아이솔레이트가 돌고있는 상태에서 kill 함수를 통해 작업을 중단시키는 함수입니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;void sleepApp(SendPort sendPort) {
  sleep(const Duration(seconds: 5));
  sendPort.send(&quot;Completion&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼핏보면 onTapMethodCall 함수와 onTapMethodCallWithIsolate는 하는 역할이 비슷하게 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차이점이라고는 Isolate.spawn 함수를 통해 sleepApp 함수를 실행시켰다는 점인데요 실제 동작이 어떻게 이루어지는지 한번 보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결과는 어떻게 되었나?&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면-기록-2023-05-07-오전-1.37.20.gif&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;1636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmFNLk/btsd3dS3q6u/G9Isji65khV238IxMTUAJ0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmFNLk/btsd3dS3q6u/G9Isji65khV238IxMTUAJ0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmFNLk/btsd3dS3q6u/G9Isji65khV238IxMTUAJ0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cmFNLk/btsd3dS3q6u/G9Isji65khV238IxMTUAJ0/img.gif&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;330&quot; height=&quot;831&quot; data-filename=&quot;화면-기록-2023-05-07-오전-1.37.20.gif&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;1636&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;앱이 멈춰버리며 5초가 지나면 'just sleep over' 라는 메세지가 나타납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 결과는 나왔지만 그 과정에 ui 에 영향을 주고말았습니다. 그렇다면 Isolate를 사용한 작업은 어떨까요?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면-기록-2023-05-07-오전-1.38.56.gif&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;1636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHgO1n/btsd4dkUagL/XFT0bdXuGVhljlHK6gtzXk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHgO1n/btsd4dkUagL/XFT0bdXuGVhljlHK6gtzXk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHgO1n/btsd4dkUagL/XFT0bdXuGVhljlHK6gtzXk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cHgO1n/btsd4dkUagL/XFT0bdXuGVhljlHK6gtzXk/img.gif&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;336&quot; height=&quot;846&quot; data-filename=&quot;화면-기록-2023-05-07-오전-1.38.56.gif&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;1636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와같이 정상적으로 로딩이 실행되고 5초가 지나 'Completion' 이라는 문구가 나타나게 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Isolate 사용법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는&lt;/p&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;_isolate = await Isolate.spawn&amp;lt;SendPort&amp;gt;(
    sleepApp, _receivePort.sendPort);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 Isolate.spawn() 함수를 사용하는데 이 함수는 인자로 사용할 함수와 파라미터를 전달받아 실행시켜 줍니다.&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;_receivePort는 이미 initState에서&lt;/p&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;@override
void initState() {
  super.initState();
  _receivePort.listen((message) {
    setState(() {
      loadedData = message.toString();
    });
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 리스너를 등록해준 상태이고, _receivePort.sendPort를 인자로 전달해줘서 등록해준 리스너에 응답을 줄 수 있게끔 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cs&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;void sleepApp(SendPort sendPort) {
  sleep(const Duration(seconds: 5));
  sendPort.send(&quot;isolate over!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 sleepApp 함수는 Isolate.spawn() 함수로 호출해준 함수인데 sendPort.send() 함수를 통해 원하는 메세지를 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의해야 할 점은 Isolate.spawn()에 등록해줄 함수는 Top-Level의 함수이거나 static 함수여야 한다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;_isolate.kill(priority: Isolate.immediate);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업을모두 마친 상태라면 위처럼 kill을 호출해줘서 메모리에서 지워줘야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주의할점&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;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>플러터</category>
      <category>DART</category>
      <category>flutter</category>
      <category>isolate</category>
      <category>memory</category>
      <category>thread</category>
      <category>아이솔레이트</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/143</guid>
      <comments>https://rlg1133.tistory.com/143#entry143comment</comments>
      <pubDate>Sun, 7 May 2023 02:20:56 +0900</pubDate>
    </item>
    <item>
      <title>Open Ai) Flutter에서 Chat GPT 의 API를 사용해보자!</title>
      <link>https://rlg1133.tistory.com/142</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 chat gpt의 인기가 핫해지면서 chat gpt를 통한 외부 프로그램도 많이 생기고있다.&lt;br /&gt;현재 내가 쓰는것만 해도 구글 검색시 오른쪽에 chat gpt의 답변을 같이 보여주는 확장프로그램, 외국어를 마우스로 드래그 하면 한국어로 번역해서 보여주는 확장프로그램을 사용하고있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이런 좋은 기능들을 만들 수 있게 open ai의 gpt api를 사용하는 방법을 알아보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://platform.openai.com/account/api-keys&quot;&gt;API 키 발급 사이트&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1677913833989&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;OpenAI API&quot; data-og-description=&quot;An API for accessing new AI models developed by OpenAI&quot; data-og-host=&quot;platform.openai.com&quot; data-og-source-url=&quot;https://platform.openai.com/account/api-keys&quot; data-og-url=&quot;https://platform.openai.com&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://platform.openai.com/account/api-keys&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://platform.openai.com/account/api-keys&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;OpenAI API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An API for accessing new AI models developed by OpenAI&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;platform.openai.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 위 사이트에서 자신만의 api key를 발급받자, &lt;b&gt;주의해야할 점은 처음에 한번만 key를 복사해올 수 있다는 점이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2916&quot; data-origin-height=&quot;1616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RbQ4A/btr1QxiGOm2/wFxFxxQkX1blg9xhep9xKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RbQ4A/btr1QxiGOm2/wFxFxxQkX1blg9xhep9xKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RbQ4A/btr1QxiGOm2/wFxFxxQkX1blg9xhep9xKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRbQ4A%2Fbtr1QxiGOm2%2FwFxFxxQkX1blg9xhep9xKK%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;757&quot; height=&quot;420&quot; data-origin-width=&quot;2916&quot; data-origin-height=&quot;1616&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 화면을 볼 수 있다. api 키는 복사를 하고 따로 보관해준다.&lt;br /&gt;&lt;br /&gt;그리고 무료 평가판 계정에서는 api를 정상적으로 이용할 수 없으므로&lt;br /&gt;&lt;a href=&quot;https://platform.openai.com/account/billing/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://platform.openai.com/account/billing/overview&lt;/a&gt;&lt;br /&gt;위 링크에서 로그인해서 free trial로 되어있다면 카드를 등록해준다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1677915202342&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;OpenAI API&quot; data-og-description=&quot;An API for accessing new AI models developed by OpenAI&quot; data-og-host=&quot;platform.openai.com&quot; data-og-source-url=&quot;https://platform.openai.com/account/billing/overview&quot; data-og-url=&quot;https://platform.openai.com&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://platform.openai.com/account/billing/overview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://platform.openai.com/account/billing/overview&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;OpenAI API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;An API for accessing new AI models developed by OpenAI&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;platform.openai.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2908&quot; data-origin-height=&quot;1686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLplzm/btr1T6E9rhk/7uGqlKfE0Ni4X3tgZsqNtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLplzm/btr1T6E9rhk/7uGqlKfE0Ni4X3tgZsqNtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLplzm/btr1T6E9rhk/7uGqlKfE0Ni4X3tgZsqNtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLplzm%2Fbtr1T6E9rhk%2F7uGqlKfE0Ni4X3tgZsqNtk%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;588&quot; height=&quot;341&quot; data-origin-width=&quot;2908&quot; data-origin-height=&quot;1686&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;1288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y6hST/btr1POLTLkg/NXdVlF0ScoGRsRbbKO5qx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y6hST/btr1POLTLkg/NXdVlF0ScoGRsRbbKO5qx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y6hST/btr1POLTLkg/NXdVlF0ScoGRsRbbKO5qx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy6hST%2Fbtr1POLTLkg%2FNXdVlF0ScoGRsRbbKO5qx0%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;601&quot; height=&quot;541&quot; data-origin-width=&quot;1432&quot; data-origin-height=&quot;1288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 플러터 프로젝트를 생성해준다.&lt;br /&gt;이름은 간단하게 gpt_app 이라고 지어줬다.&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;2410&quot; data-origin-height=&quot;1804&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tklBQ/btr1WmADQnQ/K6813m3itYMQXxtVwWkvg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tklBQ/btr1WmADQnQ/K6813m3itYMQXxtVwWkvg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tklBQ/btr1WmADQnQ/K6813m3itYMQXxtVwWkvg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtklBQ%2Fbtr1WmADQnQ%2FK6813m3itYMQXxtVwWkvg1%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;540&quot; height=&quot;404&quot; data-origin-width=&quot;2410&quot; data-origin-height=&quot;1804&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 간단하게 gpt를 사용할 수 있는 코드로 바꿔보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 코드는 이제 직접 짤 필요 없이 gpt에게 물어보면 알아서 짜준다.&lt;br /&gt;아래는 내가 질문한 내용에 대해 chat gpt 가 대답해준것인데 저것을 일부만 바꿔서 그대로 사용해보도록 하겠다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #292c32;&quot;&gt;프롬프트: 새 텍스트를 생성할 때 시작점으로 사용할 초기 텍스트 프롬프트입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #292c32;&quot;&gt;최대_토큰: 출력 텍스트에 생성할 토큰(즉, 단어)의 최대 개수입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #292c32;&quot;&gt;온도: 생성된 텍스트의 '창의성' 또는 무작위성을 측정하는 값입니다. 온도가 높을수록 더 다양하고 예측할 수 없는 텍스트가 생성되고, 온도가 낮을수록 더 보수적이고 예측 가능한 텍스트가 생성됩니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #292c32;&quot;&gt;n: 생성할 독립적인 완성문의 수입니다. 각 완성은 온도 매개변수에 의해 도입된 무작위성에 따라 조금씩 달라집니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #292c32;&quot;&gt;stop: 모델이 토큰을 발견할 때 텍스트 생성을 중지하도록 하는 하나 이상의 토큰 문자열입니다. 특정 단어나 구문으로 끝나는 텍스트를 생성할 때 유용할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;1518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blqmOP/btr1Xy11L3p/Jpx8l1AUw9h5v5WlCFpajK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blqmOP/btr1Xy11L3p/Jpx8l1AUw9h5v5WlCFpajK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blqmOP/btr1Xy11L3p/Jpx8l1AUw9h5v5WlCFpajK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblqmOP%2Fbtr1Xy11L3p%2FJpx8l1AUw9h5v5WlCFpajK%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;559&quot; height=&quot;493&quot; data-origin-width=&quot;1720&quot; data-origin-height=&quot;1518&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;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() =&amp;gt; runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() =&amp;gt; _MyAppState();
}

class _MyAppState extends State&amp;lt;MyApp&amp;gt; {
  String _generatedText = &quot;&quot;;

  Future&amp;lt;void&amp;gt; generateText() async {
    String prompt = &quot;The quick brown fox jumps over the lazy dog&quot;;
    String model = &quot;text-davinci-002&quot;;
    String apiKey = &quot;&amp;lt;YOUR_API_KEY&amp;gt;&quot;;

    var response = await http.post(
      Uri.parse('https://api.openai.com/v1/engines/$model/completions'),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $apiKey',
      },
      body: jsonEncode({
        'prompt': prompt,
        'max_tokens': 100,
        'temperature': 0.5,
        'n': 1,
        'stop': '.'
      }),
    );

    if (response.statusCode == 200) {
      var data = jsonDecode(response.body);
      setState(() {
        _generatedText = data['choices'][0]['text'];
      });
    } else {
      setState(() {
        _generatedText = &quot;Error: ${response.reasonPhrase}&quot;;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('GPT-3 Text Generation'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: generateText,
                child: Text('Generate Text'),
              ),
              SizedBox(height: 20),
              Text(_generatedText),
            ],
          ),
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;
    body: jsonEncode({
        'prompt': prompt, // 새 텍스트를 생성할 때 시작점으로 사용할 초기 텍스트 프롬프트.
        'max_tokens': 1000, // 출력 텍스트에 생성할 토큰(즉, 단어)의 최대 개수입니다.
        'temperature': 0.5, // 생성된 텍스트의 '창의성' 또는 무작위성을 측정하는 값, 값이 높을수록 더 다양하고 예측할 수 없는 텍스트가 생성되고, 낮을수록 더 보수적이고 예측 가능한 텍스트가 생성됨
        // 'n': 1, // 생성할 독립적인 완성문의 수입니다. 각 완성문은 temperature 에 의해 도입된 무작위성에 따라 조금씩 달라짐.
        // 'stop': '.' // 모델이 토큰을 발견할 때 텍스트 생성을 중지하도록 하는 하나 이상의 토큰 문자열 특정 단어나 구문으로 끝나는 텍스트를 생성할 때 유용할 수 있습니다.
      }),
&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;원하는 결과를 얻기 위해 위 body에서 문장의 수를 제한하는 'n' 파라미터와, 문장을 중간에 끊어주는 'stop' 파라미터를 주석처리 해주고 단어의 갯수를 1000으로 늘려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀 소스는 위와 같고 &amp;lt;YOUT_API_KEY&amp;gt; 부분에는 아까전에 발급받았던 키를 넣어준다.&lt;br /&gt;통신을 위해 http 플러그인이 필요한듯 하니 pubspec.yaml 파일에&amp;nbsp;&lt;br /&gt;http: ^0.13.5 &amp;lt;-- 요 플러그인을 추가해준다.&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;마지막으로 'prompt'에는 chat gpt에게 질문하는것처럼 원하는 질문을 넣어준다.&lt;br /&gt;나는 '서울대 입구역 근처 맛집을 세개만 추천해줘' 라고 질문을 넣었다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;String prompt = &quot;Please recommend only three restaurants near Seoul National University Station&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2408&quot; data-origin-height=&quot;1800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDO6jP/btr114TEonR/acbaIFnSPYk1GfwUikhLWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDO6jP/btr114TEonR/acbaIFnSPYk1GfwUikhLWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDO6jP/btr114TEonR/acbaIFnSPYk1GfwUikhLWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDO6jP%2Fbtr114TEonR%2FacbaIFnSPYk1GfwUikhLWK%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;689&quot; height=&quot;515&quot; data-origin-width=&quot;2408&quot; data-origin-height=&quot;1800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Generate Text 버튼을 누르면 위 질문에 대한 대답이 아래에 출력되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1892&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqMjxr/btr1Wm1LoiQ/kVTZji3SNSpEF9g3U2P8p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqMjxr/btr1Wm1LoiQ/kVTZji3SNSpEF9g3U2P8p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqMjxr/btr1Wm1LoiQ/kVTZji3SNSpEF9g3U2P8p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqMjxr%2Fbtr1Wm1LoiQ%2FkVTZji3SNSpEF9g3U2P8p0%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;681&quot; height=&quot;426&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;1892&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝! (저 맛집들은 나중에 꼭 가보는걸로 ㅎㅎ)&lt;/p&gt;</description>
      <category>API</category>
      <category>AI</category>
      <category>chat-gpt</category>
      <category>DART</category>
      <category>flutter</category>
      <category>gpt</category>
      <category>OpenAI</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/142</guid>
      <comments>https://rlg1133.tistory.com/142#entry142comment</comments>
      <pubDate>Sat, 4 Mar 2023 16:53:40 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter Flame] Flame 엔진을 사용한 게임 예제 만들어보기 (4. 完) 펫 넣어주기</title>
      <link>https://rlg1133.tistory.com/141</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;flame_logo.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAUspl/btrPfmVaenN/kFrUXqdGOqtn8gHI0oNzd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAUspl/btrPfmVaenN/kFrUXqdGOqtn8gHI0oNzd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAUspl/btrPfmVaenN/kFrUXqdGOqtn8gHI0oNzd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAUspl%2FbtrPfmVaenN%2FkFrUXqdGOqtn8gHI0oNzd0%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;410&quot; height=&quot;123&quot; data-filename=&quot;flame_logo.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;✍️ 이 포스팅은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://nomadcoders.notion.site/55fe963f890c41ddbe09f7f7e29f3e3d&quot;&gt;노마드 개발자 글쓰기 스터디&lt;/a&gt;&lt;span style=&quot;color: #555555;&quot;&gt;의 일환으로 자유 주제를 공부한 내용으로 포스팅 하고있습니다.&lt;br /&gt;&lt;br /&gt;지난번 포스팅에서 바텀시트를 통해 게임에 객체를 추가해주는 부분을 진행했습니다. 이번 포스팅에서는 펫을 추가해 벽에 닿으면 자동으로 방향을 바꿔 움직이는 기능을 구현해보도록 하겠습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;1. 펫 이미지 추가&lt;br /&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKQNgX/btrPg1oMOSi/dKWpYxKA5qhb9mP8ncXeY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKQNgX/btrPg1oMOSi/dKWpYxKA5qhb9mP8ncXeY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKQNgX/btrPg1oMOSi/dKWpYxKA5qhb9mP8ncXeY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKQNgX%2FbtrPg1oMOSi%2FdKWpYxKA5qhb9mP8ncXeY0%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;434&quot; height=&quot;265&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;assets/image 하위에 pet_sheet.png 파일을 추가해 주었습니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EU1Mt/btrPjj3kQVT/0xvCFR03jBLpbBEzX1PUn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EU1Mt/btrPjj3kQVT/0xvCFR03jBLpbBEzX1PUn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EU1Mt/btrPjj3kQVT/0xvCFR03jBLpbBEzX1PUn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEU1Mt%2FbtrPjj3kQVT%2F0xvCFR03jBLpbBEzX1PUn1%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;366&quot; height=&quot;94&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 인터넷에서 아무거나 가져와 사용했고 글을 보시는 분들도 아무 sheet를 구해서 추가해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 pet_component.dart 라는 이름으로 펫 객체로 사용할 클래스를 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 펫 코드 작성&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUBbGl/btrPiuKHjUV/SVMgtGH9FcUsn6NSv3VwWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUBbGl/btrPiuKHjUV/SVMgtGH9FcUsn6NSv3VwWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUBbGl/btrPiuKHjUV/SVMgtGH9FcUsn6NSv3VwWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUBbGl%2FbtrPiuKHjUV%2FSVMgtGH9FcUsn6NSv3VwWK%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;548&quot; height=&quot;380&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;pet_component.dart&amp;gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import 'dart:math';
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';

class PetComponent extends SpriteAnimationComponent
    with CollisionCallbacks, HasGameRef {
  final Vector2 velocity;

  PetComponent(
      this.velocity,
      Vector2 position,
      Vector2 size, {
        double angle = -pi / 4,
      }) : super(
    position: position,
    size: size,
    angle: angle,
    anchor: Anchor.center,
  );

  @override
  Future&amp;lt;void&amp;gt; onLoad() async {
    add(RectangleHitbox());
    animation = await gameRef.loadSpriteAnimation(
      'pet_sheet.png',
      SpriteAnimationData.sequenced(
        amount: 4,
        stepTime: 0.2,
        textureSize: Vector2.all(16),
      ),
    );
  }

  @override
  void update(double dt) {
    super.update(dt);
    position += velocity * dt;
  }

  @override
  void onCollisionStart(
      Set&amp;lt;Vector2&amp;gt; intersectionPoints,
      PositionComponent other,
      ) {
    super.onCollisionStart(intersectionPoints, other);
    velocity.negate();
    flipHorizontally();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;펫 컴포넌트는 움직이는 이미지 이며, 충돌감지를 해야하므로 SpriteAnimationComponent와 CollisionCallbacks 를 추가해줍니다.&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;PetComponent의 인자로 받는 velocity는 펫이 움직일 방향 및 속도에 대한 값이며 , position은 초기 위치, size는 펫의 크기를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;@override
Future&amp;lt;void&amp;gt; onLoad() async {
  add(RectangleHitbox());
  animation = await gameRef.loadSpriteAnimation(
    'pet_sheet.png',
    SpriteAnimationData.sequenced(
      amount: 4,
      stepTime: 0.2,
      textureSize: Vector2.all(16),
    ),
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 사용할 pet_sheet.png는 168 x 16 짜리 이미지이고 8칸으로 나누어져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 8칸 중 앞의 4칸만 사용할 것이며 각 칸의 이미지 크기는 16 x 16 이므로 위와 같이 셋팅해주겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@override
void update(double dt) {
  super.update(dt);
  position += velocity * dt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수는 객체를 업데이트 시켜주는 함수입니다. Flame은 모든 컴포넌트들을 동시에 업데이트를 시켜주고 dt 값은 delta라고 읽는데 마지막으로 업데이트 시켜준 후 경과된 시간을 의미합니다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;position += velocity * dt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 위 코드는 &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;'컴포넌트의 방향 및 속도'&lt;/span&gt;&amp;nbsp;x '이전 업데이트 후 경과된 시간 ' 이라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 충돌을 감지하기 위한 부분을 살펴보겠습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@override
void onCollisionStart(
    Set&amp;lt;Vector2&amp;gt; intersectionPoints,
    PositionComponent other,
    ) {
  super.onCollisionStart(intersectionPoints, other);
  velocity.negate();
  flipHorizontally();
}&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 class=&quot;abnf&quot;&gt;&lt;code&gt;velocity.negate();
&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;예를 들어 velocity가 Vector2(100, 0); 이었다면 Vector2(-100, 0); 으로 바꿔준다는 의미입니다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;flipHorizontally();
&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 화면에 추가&lt;/h4&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;henry_some_one.dart&amp;gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;class MyStaticGame extends FlameGame with HasCollisionDetection {

  List&amp;lt;int&amp;gt; list;
  MyStaticGame({required this.list});

  @override
  Color backgroundColor() =&amp;gt; const Color(0x00000000);

  final GameBackGround _backGround = GameBackGround();
  // 플레이어의 사이즈
  final playerSize = Vector2(150, 100);
  // 플레이어의 위치
  final playerPosition = Vector2(200, 600);
  // 펫의 사이즈
  final petSize = Vector2(64, 64);
  // 펫의 위치
  final petPosition = Vector2(200, 700);

  @override
  Future&amp;lt;void&amp;gt; onLoad() async {
    // 화면 고정
    // await add(ScreenHitbox());
    await add(_backGround);
    // 플레이어의 위치 및 사이즈 input
    await add(AnimatedPlayer(playerPosition, playerSize));
    await add(
      PetComponent(Vector2(-100, 0), petPosition, petSize, angle: pi),
    );
  }

  @override
  void update(double dt) {
    super.update(dt);
    if(list.isEmpty) return;
    Vector2 position = Vector2(list.last * 50, 100);
    add(Item(position, index: list.last));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 중복 충돌을 막기위해&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;// await add(ScreenHitbox());&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;game_background.dart 에서 이미 ScreenHitbox()를 적용해 놨기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;await add(
  PetComponent(Vector2(-100, 0), petPosition, petSize, angle: pi),
);&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;velocity 값을 Vector2(-100, 0) 으로 주었기 때문에 이 펫은 x 축을 기준으로만 움직입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 왼쪽으로 출발했다가 벽을만나면 오른쪽으로 가게 될것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 실행한 결과는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-10-22 오후 3.42.40.gif&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;1602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HIfYa/btrPgVB2BUx/JNMvsFvEZBkj9S7ePIQA1K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HIfYa/btrPgVB2BUx/JNMvsFvEZBkj9S7ePIQA1K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HIfYa/btrPgVB2BUx/JNMvsFvEZBkj9S7ePIQA1K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/HIfYa/btrPgVB2BUx/JNMvsFvEZBkj9S7ePIQA1K/img.gif&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;423&quot; height=&quot;889&quot; data-filename=&quot;화면 기록 2022-10-22 오후 3.42.40.gif&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;1602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 정상적으로 움직이는 것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.&lt;/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;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4개의 포스팅에 걸쳐 간단한 게임을 만들어 보았습니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 기획과 비교하면 상당히 간소해졌지만 여기까지만 문제없이 구현한다면 나머지는 제 포스팅을 봐주시는 분들이 추가해 보셔도 좋을거같습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 게임을 만들어 본다는 개념이 많이 생소했고 막상 구현을 하면서도 익숙하지 않은 개념들이 많아 힘들었지만 어찌됐건 마무리를 지을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 Flame에 관련해서 좀 더 공부하겠지만 포스팅으로 올리게 될지는 모르겠습니다  &amp;nbsp;&lt;/p&gt;</description>
      <category>플러터</category>
      <category>flame</category>
      <category>flutter</category>
      <category>게임</category>
      <category>플러터</category>
      <category>플레임</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/141</guid>
      <comments>https://rlg1133.tistory.com/141#entry141comment</comments>
      <pubDate>Sat, 22 Oct 2022 15:50:45 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter Flame] Flame 엔진을 사용한 게임 예제 만들어보기 (3) 바텀시트를 통한 게임에 아이템 추가</title>
      <link>https://rlg1133.tistory.com/140</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;flame_logo.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0J1SM/btrPhpP6Vu9/mRNd1rwgjAKrBk9vY2gLY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0J1SM/btrPhpP6Vu9/mRNd1rwgjAKrBk9vY2gLY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0J1SM/btrPhpP6Vu9/mRNd1rwgjAKrBk9vY2gLY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0J1SM%2FbtrPhpP6Vu9%2FmRNd1rwgjAKrBk9vY2gLY1%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;410&quot; height=&quot;123&quot; data-filename=&quot;flame_logo.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;✍️ 이 포스팅은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://nomadcoders.notion.site/55fe963f890c41ddbe09f7f7e29f3e3d&quot;&gt;노마드 개발자 글쓰기 스터디&lt;/a&gt;&lt;span style=&quot;color: #555555;&quot;&gt;의 일환으로 자유 주제를 공부한 내용으로 포스팅 하고있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;지난주 포스팅에서 화면의 캐릭터를 자동으로 움직이는 부분까지 구현했는데 이번에는 flutter의 바텀시트를 통해 배경인 벽지에 아이템을 놓는 작업을 해보겠습니다.&lt;b&gt;(원래는 캐릭터 옷을 갈아입혀야 하지만 디자인 리소스를 직접 만들어야 할거같아서 바꿔 버렸습니다 &lt;span style=&quot;color: #555555;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #555555;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #555555;&quot;&gt; &lt;/span&gt;)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 바텀시트 추가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 바텀시트로 사용할 sliding_up_panel 패키지를 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;henry_some_one/pubspec.yaml 파일에 추가해 주시면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;380&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIWLn0/btrPeULkudQ/APHD9k31h1PhhKykMg0GB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIWLn0/btrPeULkudQ/APHD9k31h1PhhKykMg0GB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIWLn0/btrPeULkudQ/APHD9k31h1PhhKykMg0GB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIWLn0%2FbtrPeULkudQ%2FAPHD9k31h1PhhKykMg0GB1%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;281&quot; height=&quot;201&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;380&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;550&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxcLbn/btrPfEubPA1/b0W6UFCGALtA4Gi6ek8m4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxcLbn/btrPfEubPA1/b0W6UFCGALtA4Gi6ek8m4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxcLbn/btrPfEubPA1/b0W6UFCGALtA4Gi6ek8m4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxcLbn%2FbtrPfEubPA1%2Fb0W6UFCGALtA4Gi6ek8m4K%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;263&quot; height=&quot;153&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 item_list_bottom_sheet.dart 파일과 item.dart 파일을 추가해줍니다.&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;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1NkM2/btrPf3mZuWY/c23KaeD9LPdOrULqsWkH9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1NkM2/btrPf3mZuWY/c23KaeD9LPdOrULqsWkH9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1NkM2/btrPf3mZuWY/c23KaeD9LPdOrULqsWkH9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1NkM2%2FbtrPf3mZuWY%2Fc23KaeD9LPdOrULqsWkH9k%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;282&quot; height=&quot;327&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;그리고 아무런 이미지를 추가해 줄건데 같은 확장자만 가지고 있다면 어떤 이미지던 상관없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;위처럼 assets/images/items 폴더 안에 item_0 ~ item_9 까지의 이미지를 추가해 줬습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;br /&gt;이후 바텀시트를 구현해 줍니다.&lt;br /&gt;&amp;lt;item_list_bottom_sheet.dart&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';

class ItemListBottomSheet extends StatelessWidget {
  final Function(int) onTapItem;
  const ItemListBottomSheet({Key? key, required this.onTapItem}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(top: 20),
      child: GridView.builder(
        shrinkWrap: true,
        physics: const NeverScrollableScrollPhysics(),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          crossAxisSpacing: 20.0,
          mainAxisSpacing: 10.0,
          childAspectRatio: 100.0 / 50.0,
        ),
        itemCount: 10,
        itemBuilder: (context, index) {
          return InkWell(
            onTap: () =&amp;gt; onTapItem.call(index),
            child: Container(
              decoration: BoxDecoration(
                border: Border.all(color: Colors.black12),
              ),
              alignment: Alignment.center,
              child: Image.asset('assets/images/items/item_$index.png'),
            ),
          );
        },
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;lt;item.dart&amp;gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flame/collisions.dart';
import 'package:flame/components.dart';

class Item extends SpriteComponent with HasGameRef {
  int index;
  Item( Vector2 position,
      {required this.index}) :super( position: position,);

  @override
  Future&amp;lt;void&amp;gt; onLoad() async {
    super.onLoad();
    add(RectangleHitbox());
    sprite = await gameRef.loadSprite('items/item_$index.png');
    size = Vector2(40, 40);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ItemListBottomSheet 객체를 통해 게임에 추가해줄 아이템을 선택할거고 Item 객체는 실제로 게임에 추가되는 역할을 담당합니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 henry_some_one.dart 파일로 돌아와줍니다.&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;h4 data-ke-size=&quot;size20&quot;&gt;2. 아이템 받아서 보여주기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;henry_some_one_dart&amp;gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class HenrySomeOneGame extends StatefulWidget {
  const HenrySomeOneGame({Key? key}) : super(key: key);

  @override
  State&amp;lt;HenrySomeOneGame&amp;gt; createState() =&amp;gt; _HenrySomeOneGameState();
}

class _HenrySomeOneGameState extends State&amp;lt;HenrySomeOneGame&amp;gt; {

  List&amp;lt;int&amp;gt; list = [];

  @override
  Widget build(BuildContext context) {
    return SlidingUpPanel(
      minHeight: 40,
      borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
      panel: ItemListBottomSheet(onTapItem: onTapItem ,),
      body: GameWidget.controlled(
        gameFactory: () =&amp;gt; MyStaticGame(list : list),
        loadingBuilder: (context) =&amp;gt; const Material(
          child: Center(
            child: CircularProgressIndicator(),
          ),
        ),
        errorBuilder: (context, ex) =&amp;gt; const Material(
          child: Center(
            child: Text('에러났슈'),
          ),
        ),
      ),
    );
  }

  void onTapItem(int index){
    debugPrint(index.toString());
    if(list.contains(index)) return;
    list.add(index);
    setState(() {});
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HenrySomeOneGame 객체를 위처럼 바꿔줍니다.&lt;/p&gt;
&lt;pre class=&quot;zephir&quot;&gt;&lt;code&gt;List&amp;lt;int&amp;gt; list = [];
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 객체에는 선택된 아이템들의 index 리스트가 담기게 될것입니다.&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;이제 저 list를 넣어줄 부분을 만들어 줍니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class MyStaticGame extends FlameGame with HasCollisionDetection {

  List&amp;lt;int&amp;gt; list;
  MyStaticGame({required this.list});

  @override
  Color backgroundColor() =&amp;gt; const Color(0x00000000);

  final GameBackGround _backGround = GameBackGround();
  @override
  Future&amp;lt;void&amp;gt; onLoad() async {
    // 화면 고정
    add(ScreenHitbox());
    await add(_backGround);

    // 플레이어의 사이즈
    final playerSize = Vector2(150, 100);
    // 플레이어의 위치
    final playerPosition = Vector2(200, 600);
    // 플레이어의 위치 및 사이즈 input
    add(AnimatedPlayer(playerPosition, playerSize));
  }

  @override
  void update(double dt) {
    super.update(dt);
    if(list.isEmpty) return;
    Vector2 position = Vector2(list.last * 50, 100);
    add(Item(position, index: list.last));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;required로 선언된 list를 받는 부분을 만들어 줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@override
void update(double dt) {
  super.update(dt);
  if(list.isEmpty) return;
  Vector2 position = Vector2(list.last * 50, 100);
  add(Item(position, index: list.last));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고는 리스트가 변했을때 어떤 동작을 수행할지에 대해 update 함수에 정의해 두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;update 함수는 게임 요소에 변화가 있을때 수행됩니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;gameFactory: () =&amp;gt; MyStaticGame(list : list),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 게임을 생성할때 list를 인자로 넘겨줍니다.&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;SlidingUpPanel 에는 위에서 만들어준 ItemListBottomSheet 를 넣어주고 onTapItem 함수를 인자로 넘겨주어 각 아이템을 터치 했을때 어떤 동작을 수행할지 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;void onTapItem(int index){
  debugPrint(index.toString());
  if(list.contains(index)) return;
  list.add(index);
  setState(() {});
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 함수는 단순히 '중복이 아닐때 선택된 int 값을 리스트에 추가해준다.' 라는 의미를 가지고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고는 setState 함수를 통해 게임의 상태를 새로고침 해줍니다. 그렇게 되면 게임에 적용되는 list의 값이 바뀌게 되는것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 아래에서 결과를 확인해 봅시다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 결과 확인!&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-10-22 오전 2.12.43.gif&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;1602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r53fm/btrPgXlZgKP/BqepSwFfkc3Vs6jqYdJSo1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r53fm/btrPgXlZgKP/BqepSwFfkc3Vs6jqYdJSo1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r53fm/btrPgXlZgKP/BqepSwFfkc3Vs6jqYdJSo1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/r53fm/btrPgXlZgKP/BqepSwFfkc3Vs6jqYdJSo1/img.gif&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;344&quot; height=&quot;723&quot; data-filename=&quot;화면 기록 2022-10-22 오전 2.12.43.gif&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;1602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(정말 아무 이미지나 넣었습니다 ㅎㅎ,,)&lt;br /&gt;위 처럼 정상적으로 동작하는것을 볼 수 있습니다.&lt;/p&gt;</description>
      <category>플러터</category>
      <category>flame</category>
      <category>flutter</category>
      <category>게임</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/140</guid>
      <comments>https://rlg1133.tistory.com/140#entry140comment</comments>
      <pubDate>Sat, 22 Oct 2022 02:17:22 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter Flame] Flame 엔진을 사용한 게임 예제 만들어보기 (2) 배경과 자동으로 움직이는 캐릭터 넣기</title>
      <link>https://rlg1133.tistory.com/139</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;flame_logo.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7s1a8/btrN5wJ3eF2/HdHgwjTePH1Pm2Gj1QoMx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7s1a8/btrN5wJ3eF2/HdHgwjTePH1Pm2Gj1QoMx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7s1a8/btrN5wJ3eF2/HdHgwjTePH1Pm2Gj1QoMx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7s1a8%2FbtrN5wJ3eF2%2FHdHgwjTePH1Pm2Gj1QoMx1%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;410&quot; height=&quot;123&quot; data-filename=&quot;flame_logo.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;✍️ 이 포스팅은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://nomadcoders.notion.site/55fe963f890c41ddbe09f7f7e29f3e3d&quot;&gt;노마드 개발자 글쓰기 스터디&lt;/a&gt;&lt;span style=&quot;color: #555555;&quot;&gt;의 일환으로 자유 주제를 공부한 내용으로 포스팅 하고있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/span&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;h4 data-ke-size=&quot;size20&quot;&gt;1. 게임 화면 띄우기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 앞서 게임 프로젝트를 패키지로 생성했기 때문에 example 에서 사용을 해보려면 export 과정을 거쳐야 할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 패키지 프로젝트 하위에 lib/index.dart 파일을 생성하고 아래와 같이 원하는 파일을 export 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;79&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u1r6S/btrN8BcW0lV/KyOH94HJAU7c1KbbNu2QV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u1r6S/btrN8BcW0lV/KyOH94HJAU7c1KbbNu2QV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u1r6S/btrN8BcW0lV/KyOH94HJAU7c1KbbNu2QV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu1r6S%2FbtrN8BcW0lV%2FKyOH94HJAU7c1KbbNu2QV0%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;293&quot; height=&quot;79&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;79&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;index.dart&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm5KxX/btrN32XGDtG/arRAa45ctir3nn824jp4ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm5KxX/btrN32XGDtG/arRAa45ctir3nn824jp4ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm5KxX/btrN32XGDtG/arRAa45ctir3nn824jp4ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm5KxX%2FbtrN32XGDtG%2FarRAa45ctir3nn824jp4ok%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;462&quot; height=&quot;42&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 실제 게임 화면에 보여질 부분을 만들어 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;henry_some_one_dart&amp;gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;brush:sql scala&quot;&gt;&lt;code&gt;library henry_some_one;

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';

class HenrySomeOneGame extends StatefulWidget {
  const HenrySomeOneGame({Key? key}) : super(key: key);

  @override
  State createState() =&amp;gt; _HenrySomeOneGameState();
}

class _HenrySomeOneGameState extends State {
  @override
  Widget build(BuildContext context) {
    return const GameWidget.controlled(
      gameFactory: MyStaticGame.new,
    );
  }
}

class MyStaticGame extends FlameGame with HasCollisionDetection {
  @override
  Color backgroundColor() =&amp;gt; const Color(0x00000000);

  @override
  Future onLoad() async {
    // 화면 고정
    add(ScreenHitbox());
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 진행했다면 이제 게임을 띄워줄 준비는 다 된것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 example 프로젝트 하위의 main.dart 코드로 돌아가 위에서 작성한 HenrySomeOne 화면을 호출 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjeKqI/btrN5bzmp4M/4uMkLw4R8xGbKOdzltGAjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjeKqI/btrN5bzmp4M/4uMkLw4R8xGbKOdzltGAjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjeKqI/btrN5bzmp4M/4uMkLw4R8xGbKOdzltGAjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjeKqI%2FbtrN5bzmp4M%2F4uMkLw4R8xGbKOdzltGAjk%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;223&quot; height=&quot;63&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;main.dart&amp;gt;&lt;/p&gt;
&lt;pre class=&quot;brush:sql scala&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:henry_some_one/index.dart';
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: SomeOneGameApp(),
    );
  }
}

class SomeOneGameApp extends StatelessWidget {
  const SomeOneGameApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: HenrySomeOneGame(),
    );
  }
}

&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;포스팅 초반에 export 해줬던 index.dart 파일을 여기서 import 함으로써 henry_some_one 패키지에서 정의한 위젯을 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-08 오후 5.43.38.png&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zat9o/btrN6nMOZB6/dhyjhSdemgKir4jk56vLkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zat9o/btrN6nMOZB6/dhyjhSdemgKir4jk56vLkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zat9o/btrN6nMOZB6/dhyjhSdemgKir4jk56vLkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZat9o%2FbtrN6nMOZB6%2FdhyjhSdemgKir4jk56vLkk%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;411&quot; height=&quot;763&quot; data-filename=&quot;스크린샷 2022-10-08 오후 5.43.38.png&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;1852&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 배경화면 그려주기&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beDWUJ/btrN4ZTpUhl/ZKpdIOVVx9WSwflgWHQd31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beDWUJ/btrN4ZTpUhl/ZKpdIOVVx9WSwflgWHQd31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beDWUJ/btrN4ZTpUhl/ZKpdIOVVx9WSwflgWHQd31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeDWUJ%2FbtrN4ZTpUhl%2FZKpdIOVVx9WSwflgWHQd31%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;245&quot; height=&quot;98&quot; data-origin-width=&quot;245&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지 프로젝트 lib 하위에 game_background.dart 파일을 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flame/collisions.dart';
import 'package:flame/components.dart';

class GameBackGround extends SpriteComponent with HasGameRef {
  @override
  Future&amp;lt;void&amp;gt; onLoad() async {
    super.onLoad();
    add(RectangleHitbox());
    sprite = await gameRef.loadSprite('room.jpeg');
    // var origin = sprite!.originalSize; // &amp;lt;-- 실제사이즈
    // size = origin;

    final gameSize = gameRef.size; // &amp;lt;-- 화면 사이즈
    size = Vector2(gameSize.x, gameSize.y);
  }
}
&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;SpriteComponent는 spriteSheet(게임 ui 리소스) 의 기본이 되는 컴포넌트이다.&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;pre class=&quot;ini&quot;&gt;&lt;code&gt;sprite = await gameRef.loadSprite('room.jpeg');
&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;위 코드를 통해 이미지를 불러올 수 있는데 .loadSprite는 기본적으로 {root}/assets/images 폴더가 있다고 가정하기 때문에 아래와 같이 폴더 구조를 만들어 준 후 이미지를 넣어줘야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;184&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRG1qw/btrN6mUIC5M/xHk7kMiL3hYkFxNRUxKmN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRG1qw/btrN6mUIC5M/xHk7kMiL3hYkFxNRUxKmN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRG1qw/btrN6mUIC5M/xHk7kMiL3hYkFxNRUxKmN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRG1qw%2FbtrN6mUIC5M%2FxHk7kMiL3hYkFxNRUxKmN0%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;184&quot; height=&quot;149&quot; data-origin-width=&quot;184&quot; data-origin-height=&quot;149&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;flutter 에서는 이미지를 넣어주는것 만으로는 이미지를 불러올 수 없고 pubspec.yaml 에서 flutter 하위에 경로를 추가 한 뒤 pub get 을 해줘야 이미지를 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;room.jpeg는 필자가 아무 이미지나 가져와서 넣은것으로 실제로 그냥 그럴듯한 이미지라면 어떤 이미지든 상관없이 넣으면 된다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;238&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FtNBy/btrN4wYiByI/sc4Q0oldLdYgcNj0ngTsT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FtNBy/btrN4wYiByI/sc4Q0oldLdYgcNj0ngTsT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FtNBy/btrN4wYiByI/sc4Q0oldLdYgcNj0ngTsT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFtNBy%2FbtrN4wYiByI%2Fsc4Q0oldLdYgcNj0ngTsT0%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;238&quot; height=&quot;82&quot; data-origin-width=&quot;238&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 실제 사이즈로 넣어줄 수도 있으나 화면 사이즈에 딱 맞는 이미지를 구하기 어려우므로 화면 사이즈만큼 이미지를 늘려주는 코드를 작성한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;final gameSize = gameRef.size; // &amp;lt;-- 화면 사이즈
size = Vector2(gameSize.x, gameSize.y);&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위에서 생성해준 객체를 게임 화면에 올릴 차례이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;henry_some_one.dart&amp;gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class MyStaticGame extends FlameGame with HasCollisionDetection {
  @override
  Color backgroundColor() =&amp;gt; const Color(0x00000000);

  final GameBackGround _backGround = GameBackGround();

  @override
  Future&amp;lt;void&amp;gt; onLoad() async {
    // 화면 고정
    add(ScreenHitbox());
    await add(_backGround);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;henry_some_one 파일에&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;final GameBackGround _backGround = GameBackGround();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 객체를 생성해서&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;await add(_backGround);
&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;p data-ke-size=&quot;size16&quot;&gt;게임 내의 모든 구성요소는 저 add() 함수를 통해 input 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시한번 앱을 구동해보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-10-08 오후 5.57.22.png&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7dWUx/btrN4Zy9SfT/o9U4Ks8d1OKMpbl1nTLvS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7dWUx/btrN4Zy9SfT/o9U4Ks8d1OKMpbl1nTLvS1/img.png&quot; data-alt=&quot;출처 :&amp;amp;nbsp;https://www.pinterest.co.kr/pin/750834569143146719/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7dWUx/btrN4Zy9SfT/o9U4Ks8d1OKMpbl1nTLvS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7dWUx%2FbtrN4Zy9SfT%2Fo9U4Ks8d1OKMpbl1nTLvS1%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;406&quot; height=&quot;753&quot; data-filename=&quot;스크린샷 2022-10-08 오후 5.57.22.png&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;nbsp;https://www.pinterest.co.kr/pin/750834569143146719/&lt;/figcaption&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;이제 위 배경에다가 캐릭터를 넣어보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 캐릭터 넣기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 ui가 필요한 사이드 프로젝트를 하면서 가장 어려운게 바로 리소스 구하기이다 ㅜㅜ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://opengameart.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://opengameart.org/&lt;/a&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사이트 에서는 저작권이 없는 리소스를 가져와 사용할 수 있다. (근데 쓸만한게 별로 없,,읍읍)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://opengameart.org/content/simple-character-base-16x16&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://opengameart.org/content/simple-character-base-16x16&lt;/a&gt; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 위 링크에서 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 플레이어를 만들어주기 위해 animated_player 라는 클래스를 생성했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GTV39/btrN4OLacKB/g8VWXm8UWj2p0knkzhFark/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GTV39/btrN4OLacKB/g8VWXm8UWj2p0knkzhFark/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GTV39/btrN4OLacKB/g8VWXm8UWj2p0knkzhFark/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGTV39%2FbtrN4OLacKB%2Fg8VWXm8UWj2p0knkzhFark%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;267&quot; height=&quot;108&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;108&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;그리고 나서 아래와 같이 생긴 이미지를 assets/images/ 폴더에 넣어준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;271&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUUngT/btrN4jLEboL/4KxSpKzGdFmc4WcBPbyEnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUUngT/btrN4jLEboL/4KxSpKzGdFmc4WcBPbyEnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUUngT/btrN4jLEboL/4KxSpKzGdFmc4WcBPbyEnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUUngT%2FbtrN4jLEboL%2F4KxSpKzGdFmc4WcBPbyEnK%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;433&quot; height=&quot;271&quot; data-origin-width=&quot;433&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;289&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsKrvf/btrN4yhxnsa/9TeBalHLazVinK1eA8FEr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsKrvf/btrN4yhxnsa/9TeBalHLazVinK1eA8FEr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsKrvf/btrN4yhxnsa/9TeBalHLazVinK1eA8FEr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsKrvf%2FbtrN4yhxnsa%2F9TeBalHLazVinK1eA8FEr0%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;289&quot; height=&quot;107&quot; data-origin-width=&quot;289&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;animated_player.dart&amp;gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import 'package:flame/collisions.dart';
import 'package:flame/components.dart';

class AnimatedPlayer extends SpriteAnimationComponent
    with CollisionCallbacks, HasGameRef {

  AnimatedPlayer(
    Vector2 position,
    Vector2 size,
  ) : super(
          position: position,
          size: size,
          anchor: Anchor.center,
        );

  @override
  Future&amp;lt;void&amp;gt;? onLoad() async {
    animation = await gameRef.loadSpriteAnimation(
      'animated_player.png',
      SpriteAnimationData.sequenced(
        amount: 4,
        stepTime: 0.5,
        textureSize: Vector2(16, 16),
      ),
    );
    return super.onLoad();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;SpriteAnimationComponent&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;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;animation = await gameRef.loadSpriteAnimation(
  'animated_player.png',
  SpriteAnimationData.sequenced(
    amount: 4,
    stepTime: 0.5,
    textureSize: Vector2(16, 16),
  ),
);&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;amount 는 위에서 가져온 sprite sheet를 split할 총 량이다. 위에서 필자가 가져온 이미지는 4 열로 이루어져 있기에 4를 입력해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stemTime은 움직여줄 시간차를 의미한다. 즉 이 애니메이션은 0.5초마다 바뀐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;textureSize는 위에서 가져온 이미지의 사이즈를 amount만큼 나눈 값이다. 위 이미지를 우클릭 하고 정보 가져오기로 사이즈를 봤을때 아래와 같이 64 x 64 가 나온다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;377&quot; data-origin-height=&quot;758&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2DtFm/btrN8A6dRpj/IXKq0xRDT7Gc5Mgd1e6APk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2DtFm/btrN8A6dRpj/IXKq0xRDT7Gc5Mgd1e6APk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2DtFm/btrN8A6dRpj/IXKq0xRDT7Gc5Mgd1e6APk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2DtFm%2FbtrN8A6dRpj%2FIXKq0xRDT7Gc5Mgd1e6APk%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;377&quot; height=&quot;758&quot; data-origin-width=&quot;377&quot; data-origin-height=&quot;758&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 4행 4열 이므로 이미지 한칸은 16 x 16 이라는 결론이 나오기 때문에 Vector2(16,16) 이라고 작성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 이미지를 게임화면에 추가해주면 끝이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;henry_some_one.dart&amp;gt;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;class MyStaticGame extends FlameGame with HasCollisionDetection {
  @override
  Color backgroundColor() =&amp;gt; const Color(0x00000000);

  final GameBackGround _backGround = GameBackGround();

  @override
  Future&amp;lt;void&amp;gt; onLoad() async {
    // 화면 고정
    add(ScreenHitbox());
    await add(_backGround);

    // 플레이어의 사이즈
    final playerSize = Vector2(150, 100);
    // 플레이어의 위치
    final playerPosition = Vector2(200, 600);
    // 플레이어의 위치 및 사이즈 input
    add(AnimatedPlayer(playerPosition, playerSize));
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;add(AnimatedPlayer(playerPosition, playerSize));
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 add() 함수를 통해 게임의 구성요소를 추가한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 후 앱을 실행시켜보면&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 기록 2022-10-08 오후 6.31.07.gif&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;1572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZaVqj/btrN3CdXqev/hQjk542OS9mkK6Dh98kPxk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZaVqj/btrN3CdXqev/hQjk542OS9mkK6Dh98kPxk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZaVqj/btrN3CdXqev/hQjk542OS9mkK6Dh98kPxk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/ZaVqj/btrN3CdXqev/hQjk542OS9mkK6Dh98kPxk/img.gif&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;308&quot; height=&quot;634&quot; data-filename=&quot;화면 기록 2022-10-08 오후 6.31.07.gif&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;1572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 자동으로 움직이는 캐릭터가 생성된다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 포스팅은 여기까지 이며, 다음 포스팅에서는 플러터 바텀시트를 통해 게임으로 구성요소를 input 하는 방법에 대해 알아보겠다.&lt;/p&gt;</description>
      <category>플러터</category>
      <category>flame</category>
      <category>flutter</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/139</guid>
      <comments>https://rlg1133.tistory.com/139#entry139comment</comments>
      <pubDate>Sat, 8 Oct 2022 18:36:43 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter Flame] Flame 엔진을 사용한 게임 예제 만들어보기 (1) 프로젝트 생성 및 계획</title>
      <link>https://rlg1133.tistory.com/138</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;flame_logo.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0gyp/btrNkB5ThwU/73XOBtJ04WVMPguvLNA091/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0gyp/btrNkB5ThwU/73XOBtJ04WVMPguvLNA091/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0gyp/btrNkB5ThwU/73XOBtJ04WVMPguvLNA091/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0gyp%2FbtrNkB5ThwU%2F73XOBtJ04WVMPguvLNA091%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;410&quot; height=&quot;123&quot; data-filename=&quot;flame_logo.png&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✍️ 이 포스팅은 &lt;a title=&quot;노마드 개발자 글쓰기 스터디&quot; href=&quot;https://nomadcoders.notion.site/55fe963f890c41ddbe09f7f7e29f3e3d&quot;&gt;노마드 개발자 글쓰기 스터디&lt;/a&gt;의 일환으로 자유 주제를 공부한 내용으로 포스팅 하고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러터 개발자로 첫 취직을 하고 1년 반정도가 지났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 앱개발을 진행했고 때때로는 플러터 웹을 다뤄보기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러는 와중에 이 Flame 이라는 게임 엔진을 알게된지는 오래되었는데, 플러터로 게임을 만들 수 있다는 사실이 놀라웠고 공부해보고 싶었지만 현생이 바쁘고 다른 공부할게 쌓여있기 때문에 구석에 미뤄두고 잊고 살았었다.&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;size18&quot;&gt;1. 프로젝트 구조&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 상태관리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태관리 관련 패키지는 되도록 사용하지 않는다. Flame 자체적으로 지원하는 기능들을 사용하여 만들 예정이며 일부 Flame 이 아닌 Flutter 위젯이 존재 하겠지만 이 또한 상태관리 없이 간결하게 만들어 최대한 다른 상태관리툴에 영향을 받지 않게 할것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. 기획&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠깐 아이패드로 끄적끄적 대본 결과이다. 모티브는 회사사람이 보여준 SomeOne(썸원) 이라는 어플이며, 요구조건을 간단하게 정리하면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1) 중앙에 캐릭터 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 1-1) 위아래로 바운스 하듯 움직인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2) 상단에 액션 버튼이 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 2-1) 밥먹이기, 씻기기 등 버튼을 누르면 캐릭터가 액션을 취한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 3) 캐릭터의 옆에는 펫이 존재&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 3-1) 좌, 우로 조금씩 움직이고 방향은 랜덤함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 3-2) 벽에 부딪히면 반대로 움직여야함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 4) 하단에 바텀시트의 Header 부분이 존재함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 4-1) 끌어서 위로 올리면 바텀시트의 Body 부분이 노출되고 그 안에는 캐릭터를 꾸밀 수 있는 아이템이 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 4-2) 바텀시트 내부의 아이템을 터치하면 캐릭터의 ui 가 바뀜&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 다 만들수 있을지 없을지도 모르겠다. 약 한달정도 진행되는 글쓰기 스터디에서 매주 진행상황을 올리겠지만 구상만 하고 구현하지 못할 수도 있고 생각보다 빠르게 구현해 추가 기능을 넣을수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Flame 공부-1.jpg&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;2154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q2u8Z/btrNi5GvXXV/WmuIXL9a0ufvU9jG8Ck7g1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q2u8Z/btrNi5GvXXV/WmuIXL9a0ufvU9jG8Ck7g1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q2u8Z/btrNi5GvXXV/WmuIXL9a0ufvU9jG8Ck7g1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq2u8Z%2FbtrNi5GvXXV%2FWmuIXL9a0ufvU9jG8Ck7g1%2Fimg.jpg&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;509&quot; height=&quot;657&quot; data-filename=&quot;Flame 공부-1.jpg&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;2154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 프로젝트 생성&lt;/p&gt;
&lt;pre class=&quot;d&quot;&gt;&lt;code&gt;flutter create --template=package [만들고자하는 패키지명]&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;나는 henry_some_one 이라는 이름으로 생성을 해보겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WmrHC/btrNjpSp5bu/96GiJXxxxQ8XxTZ5A5NBv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WmrHC/btrNjpSp5bu/96GiJXxxxQ8XxTZ5A5NBv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WmrHC/btrNjpSp5bu/96GiJXxxxQ8XxTZ5A5NBv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWmrHC%2FbtrNjpSp5bu%2F96GiJXxxxQ8XxTZ5A5NBv1%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;255&quot; height=&quot;371&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 위와 같이 프로젝트가 생성되는데 우리는 이 패키지를 사용해서 실제 구동 가능한 프로젝트여야 하므로 예시를 보여주기 위해 example 을 추가해야한다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;flutter create example&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 입력하면 아래처럼 내부에 example 이라는 이름의 프로젝트가 하나 생기게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;1366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BpSI2/btrNjAfc2GP/K4V7s6k8yIsE7b6Z3lpH01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BpSI2/btrNjAfc2GP/K4V7s6k8yIsE7b6Z3lpH01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BpSI2/btrNjAfc2GP/K4V7s6k8yIsE7b6Z3lpH01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBpSI2%2FbtrNjAfc2GP%2FK4V7s6k8yIsE7b6Z3lpH01%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;426&quot; height=&quot;609&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;1366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 안쪽의 pubspec.yaml 에다가 henry_some_one 디펜던시를 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더 구조상 1 뎁스 바깥쪽이기 때문에 ../ 를 통해 추가해줄 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEIA6G/btrNhuHetFA/immb2y9LTEr3CCRVNNLeL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEIA6G/btrNhuHetFA/immb2y9LTEr3CCRVNNLeL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEIA6G/btrNhuHetFA/immb2y9LTEr3CCRVNNLeL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEIA6G%2FbtrNhuHetFA%2Fimmb2y9LTEr3CCRVNNLeL1%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;341&quot; height=&quot;468&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 바깥쪽의 pubspec.yaml 부분에 &lt;a title=&quot;flame 패키지&quot; href=&quot;https://pub.dev/packages/flame&quot;&gt;flame 패키지&lt;/a&gt;를 추가해준다. 글 작성일 기준 최신 버전은 1.3.0 이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;566&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IE5mZ/btrNkagiJcl/AwarYkc50uqlunhYICHwj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IE5mZ/btrNkagiJcl/AwarYkc50uqlunhYICHwj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IE5mZ/btrNkagiJcl/AwarYkc50uqlunhYICHwj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIE5mZ%2FbtrNkagiJcl%2FAwarYkc50uqlunhYICHwj1%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;381&quot; height=&quot;447&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;566&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;</description>
      <category>플러터</category>
      <category>flame</category>
      <category>flutter</category>
      <category>package</category>
      <category>노마드코더</category>
      <author>Henry95</author>
      <guid isPermaLink="true">https://rlg1133.tistory.com/138</guid>
      <comments>https://rlg1133.tistory.com/138#entry138comment</comments>
      <pubDate>Thu, 29 Sep 2022 00:18:00 +0900</pubDate>
    </item>
  </channel>
</rss>