| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654 |
- <!DOCTYPE html><html lang="ko"><head>
- <meta charset="utf-8">
- <title>๋ก ๊ฒ์ ๋ง๋ค๊ธฐ</title>
- <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
- <meta name="twitter:card" content="summary_large_image">
- <meta name="twitter:site" content="@threejs">
- <meta name="twitter:title" content="Three.js โ ๋ก ๊ฒ์ ๋ง๋ค๊ธฐ">
- <meta property="og:image" content="https://threejs.org/files/share.png">
- <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
- <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
- <link rel="stylesheet" href="../resources/lesson.css">
- <link rel="stylesheet" href="../resources/lang.css">
- <script type="importmap">
- {
- "imports": {
- "three": "../../build/three.module.js"
- }
- }
- </script>
- <link rel="stylesheet" href="/manual/ko/lang.css">
- </head>
- <body>
- <div class="container">
- <div class="lesson-title">
- <h1>๋ก ๊ฒ์ ๋ง๋ค๊ธฐ</h1>
- </div>
- <div class="lesson">
- <div class="lesson-main">
- <p>์ ๊ฐ ๊ฝค ๋ง์ด ๋ฐ์๋ ์ง๋ฌธ ์ค ํ๋๊ฐ Three.js๋ก ๊ฒ์์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๊ดํ ๊ฒ์ด์์ต๋๋ค. ๊ธฐ์ด์ ์ธ ๊ฒ์ด๊ธด ํด๋ ๋ถ๋ ์ด ๊ธ์ ์ฌ๋ฌ๋ถ์ด ์ํ๋ ๋ด์ฉ์ด ์๋ค๋ฉด ์ข๊ฒ ๋ค์.</p>
- <p>๊ธ์ ์ฐ๋ ํ์ฌ๋ฅผ ๊ธฐ์ค์ผ๋ก, ์๋ง ์ด ๊ธ์ด ์ด ์๋ฆฌ์ฆ์์ ๊ฐ์ฅ ๊ธด ๊ธ์ด ๋ ๊ฒ ๊ฐ์ต๋๋ค. ์์ ๋ก ์ด ์ฝ๋๊ฐ ์ง๋์น๊ฒ ์ ๋ฌธ์ ์ผ๋ก ๋ณด์ผ ์๋ ์์ง๋ง ๊ทธ๊ฑด ์์ ๋ฅผ ๋ง๋ค๋ฉฐ ๋ฌธ์ ๊ฐ ์๊ธธ ๋๋ง๋ค ์ด์ ์ ์ ๊ฐ ์ค์ ๋ก ๋ง๋ค์๋ ๊ฒ์์ ์ฝ๋๋ฅผ ๊ฐ์ ธ์์ ๊ทธ๋ ์ต๋๋ค. ๋ํ ์ ์ด๋ฐ ํด๊ฒฐ์ฑ
์ ์ผ๋์ง ์ต๋ํ ์ ์ผ๋ ค๊ณ ํ์ผ๋ ๊ธธ ์๋ฐ์ ์์ฃ . ๋ฌผ๋ก ๋ง๋ค๋ ค๋ ๊ฒ์์ ๊ท๋ชจ๊ฐ ์๋ค๋ฉด ์ด๋ฐ ํด๊ฒฐ์ฑ
์ด ์ ๋ถ ํ์ ์์ ์ ์์ต๋๋ค. ํ์ง๋ง ์์ ๋ก ๊ตฌํํ ๊ฒ๋ ๊ต์ฅํ ๊ฐ๋จํ ๊ฒ์์ ์ํฉ๋๋ค. ํต์์ ์ผ๋ก 3D ์บ๋ฆญํฐ๊ฐ 2D ์บ๋ฆญํฐ๋ณด๋ค ๋ ๋ณต์กํ๋ ์ฒ๋ฆฌํด์ค์ผ ํ ๊ฒ์ด ๋ง์ ์๋ฐ์ ์์ฃ .</p>
- <p>ํฉ๋งจ(PacMan)์ 2D๋ก ๊ตฌํํ๋ค๋ฉด ํฉ๋งจ์ด ์ฝ๋๋ฅผ ๋ ๋ ๋ฐ๋ก 90๋ ๊บพ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค. ํ๋ ์ ์ฌ์ด์ ๋ฐ๋ก ์ฒ๋ฆฌํด์ค์ผ ํ ๊ฒ์ด ์์ฃ . ํ์ง๋ง 3D์ ์ธ๊ณ์์๋ ๋ฐ๋ก ๋ฐฉํฅ์ ํ๊ธฐ๋ณด๋ค ๋ช ํ๋ ์์ ๊ฑธ์ณ ์์ํ ๋ฐฉํฅ์ ํธ๋ ๊ฒ ์ผ๋ฐ์ ์
๋๋ค. ์์ฃผ ๊ฐ๋จํ ์ฐจ์ด์ ์ด์ง๋ง, ์ด๊ฒ ๋๋ฌธ์ ์์
์ด ํจ์ฌ ๋ณต์กํด์ง๋๋ค.</p>
- <p>์ด ๊ธ์์ ๋ค๋ฃฐ ๋ด์ฉ์ Three.js์ ๊ดํ ๊ฒ์ด๋ผ๊ณ ๋ณด๊ธฐ ์ด๋ ต์ต๋๋ค. ์๋ํ๋ฉด <strong>Three.js๋ ๊ฒ์ ์์ง์ด ์๋๊ธฐ ๋๋ฌธ</strong>์ด์ฃ . Three.js๋ 3D ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค. 3D ์์๋ฅผ ๊ณ์ดํํ๋ <a href="scenegraph.html">์ฌ ๊ทธ๋ํ</a>์ 3D ์์๋ฅผ ๋ ๋๋งํ๋๋ก ๋์์ฃผ๋ ๊ธฐ๋ฅ ๋ฑ์ ์ ๊ณตํ์ฃ . ํ์ง๋ง ๊ฒ์๊ณผ ๊ด๋ จํ ๊ธฐ๋ฅ์ ์ง์ํ์ง ์์ต๋๋ค. ์ถฉ๋(collision), ๋ฌผ๋ฆฌ(physics), ์
๋ ฅ ์์คํ
, ํจ์ค ํ์ธ๋ฉ(path finding) ๋ฑ๋ฑ.. ์ด๋ฐ ๊ธฐ๋ฅ์ ์ง์ ๋ง๋ค์ด์ผ ํฉ๋๋ค.</p>
- <p>๊ฒฐ๊ตญ ์ด ๊ธ์ <em>๋ฏธ์์ฑ</em> ๊ฒ์์ ๋ง๋๋ ๋ฐ ๊ฝค ๋ง์ ์ฝ๋๋ฅผ ์ผ์ต๋๋ค. ์๊น ๋งํ๋ฏ ์ ๊ฐ ์ฝ๋๋ฅผ ๋๋ฌด ์ง๋์น๊ฒ ์งฐ์ ์๋ ์๊ณ , ๋ ๊ฐ๋จํ ํด๊ฒฐ์ฑ
์ด ์์ ์๋ ์์ผ๋, ์ ๋ ๊ธ์ ๋ง๋ฌด๋ฆฌํ ์ง๊ธ๋ ์ถฉ๋ถํ ๋ง์ ์ฝ๋๋ฅผ ์ผ๋์ง, ์ค๋ช
์ ๋น ๋จ๋ฆฐ ๊ฒ์ด ์๋์ง ๊ฑฑ์ ๋ฉ๋๋ค.</p>
- <p>์ด ๊ธ์์ ์ด ๋ฐฉ๋ฒ์ ๋๋ถ๋ถ <a href="https://unity.com">์ ๋ํฐ(Unity) ์์ง</a>์ ์ํฅ์ ํฌ๊ฒ ๋ฐ์์ต๋๋ค. ํ์ง๋ง ์ ๋ํฐ๋ฅผ ์ ๋ชจ๋ฅธ๋ค๊ณ ํด์ ์ด ๊ธ์ ์ฝ๋ ๊ฒ ์ด๋ ต์ง ์์ ๊ฒ๋๋ค. 1000๊ฐ์ ๊ธฐ๋ฅ์ด ์๋ค๋ฉด ๊ทธ ์ค 10๊ฐ ์ ๋ ๋ฐ์ ์ฐ์ง ์์๊ฑฐ๋ ์.</p>
- <p>๋จผ์ Three.js ๋ถ๋ถ๋ถํฐ ์์ํด๋ด
์๋ค. ๊ฒ์์ ์ธ ๋ชจ๋ธ๋ค๋ถํฐ ์ฐพ์๋ณด์ฃ .</p>
- <p><a href="https://opengameart.org">opengameart.org</a> ์ฌ์ดํธ์์ <a href="https://opengameart.org/users/quaternius">quaternius</a> ์๊ฐ์ <a href="https://opengameart.org/content/lowpoly-animated-knight">์์ง์ด๋ ๊ธฐ์ฌ ๋ชจ๋ธ</a>์ ์ฐพ์์ต๋๋ค.</p>
- <div class="threejs_center"><img src="../resources/images/knight.jpg" style="width: 375px;"></div>
- <p><a href="https://opengameart.org/users/quaternius">๊ฐ์ ์๊ฐ</a>๊ฐ ๋ง๋ ์ํ ์ค์ <a href="https://opengameart.org/content/lowpoly-animated-farm-animal-pack">์์ง์ด๋ ๋๋ฌผ๋ค</a>๋ ์๋๊ตฐ์.</p>
- <div class="threejs_center"><img src="../resources/images/animals.jpg" style="width: 606px;"></div>
- <p>์ด ๋ชจ๋ธ๋ค๋ก ๊ฝค ๊ด์ฐฎ์ ๊ฒ์์ ๋ง๋ค ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค. ๋ชจ๋ธ๋ค์ ๋ถ๋ฌ์๋ณด์ฃ .</p>
- <p><a href="load-gltf.html">glTF ํ์ผ ๋ถ๋ฌ์ค๊ธฐ</a>์ ๋ํด์๋ ์ด์ ์ ๋ค๋ค์์ต๋๋ค. ๋์ผํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ง๋ง ์ด๋ฒ์๋ ๋ชจ๋ธ์ด ์ฌ๋ฌ ๊ฐ์ด๊ธฐ๋ ํ๊ณ , ๋ชจ๋ธ์ ์ ๋ถ ๋ถ๋ฌ์ค๊ธฐ ์ ์ ๊ฒ์์ ์์ํด์ ์ ๋ฉ๋๋ค.</p>
- <p>์ด๋ฐ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด Three.js๋ <a href="/docs/#api/ko/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a>๋ฅผ ์ ๊ณตํฉ๋๋ค. <a href="/docs/#api/ko/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a>์ ์ธ์คํด์ค๋ฅผ ์์ฑํด ๋ค๋ฅธ ๋ก๋(loader)์ ๋๊ฒจ์ฃผ๊ธฐ๋ฉด ๋์ฃ . <a href="/docs/#api/ko/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a>์ <a href="/docs/#api/ko/loaders/managers/LoadingManager#onProgress"><code class="notranslate" translate="no">onProgress</code></a>์ <a href="/docs/#api/ko/loaders/managers/LoadingManager#onLoad"><code class="notranslate" translate="no">onLoad</code></a> ์์ฑ์ ์ฝ๋ฐฑ ํจ์๋ฅผ ์ง์ ํ๋ฉด ๋๋๋ฐ, <a href="/docs/#api/ko/loaders/managers/LoadingManager#onLoad"><code class="notranslate" translate="no">onLoad</code></a>๋ ๋ชจ๋ ํ์ผ์ ๋ถ๋ฌ์จ ๋ค ํธ์ถํ๊ณ , <a href="/docs/#api/ko/loaders/managers/LoadingManager#onProgress"><code class="notranslate" translate="no">onProgress</code></a>๋ ๊ฐ ํ์ผ์ ๋ถ๋ฌ์์ ๋ ํธ์ถํฉ๋๋ค. <a href="/docs/#api/ko/loaders/managers/LoadingManager#onProgress"><code class="notranslate" translate="no">onProgress</code></a>๋ฅผ ์ด์ฉํ๋ฉด ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ๋ณด์ฌ์ค ์ ์์ฃ .</p>
- <p><a href="load-gltf.html">glTF ํ์ผ ๋ถ๋ฌ์ค๊ธฐ</a> ์์ ๋ฅผ ๊ฐ์ ธ์ ์นด๋ฉ๋ผ ์ ๋์ฒด(frustum)๋ฅผ ์กฐ์ ํ๋ ์ฝ๋๋ฅผ ์ง์ฐ๊ณ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const manager = new THREE.LoadingManager();
- manager.onLoad = init;
- const models = {
- pig: { url: 'resources/models/animals/Pig.gltf' },
- cow: { url: 'resources/models/animals/Cow.gltf' },
- llama: { url: 'resources/models/animals/Llama.gltf' },
- pug: { url: 'resources/models/animals/Pug.gltf' },
- sheep: { url: 'resources/models/animals/Sheep.gltf' },
- zebra: { url: 'resources/models/animals/Zebra.gltf' },
- horse: { url: 'resources/models/animals/Horse.gltf' },
- knight: { url: 'resources/models/knight/KnightCharacter.gltf' },
- };
- {
- const gltfLoader = new GLTFLoader(manager);
- for (const model of Object.values(models)) {
- gltfLoader.load(model.url, (gltf) => {
- model.gltf = gltf;
- });
- }
- }
- function init() {
- // ๋์ค์ ์์ฑํ ์์
- }
- </pre>
- <p>์ ์ฝ๋๋ <code class="notranslate" translate="no">models</code> ๊ฐ์ฒด์ ์๋ ํ์ผ์ ๋ถ๋ฌ์ค๊ณ , <a href="/docs/#api/ko/loaders/managers/LoadingManager"><code class="notranslate" translate="no">LoadingManager</code></a>๊ฐ ํ์ผ์ ์ ๋ถ ๋ถ๋ฌ์์ ๋ <code class="notranslate" translate="no">init</code> ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. <code class="notranslate" translate="no">models</code> ๊ฐ์ฒด๋ฅผ ์ ์ญ์ผ๋ก ์ ์ธํ ๊ฑด ๋์ค์ <a href="/docs/#examples/loaders/GLTFLoader"><code class="notranslate" translate="no">GLTFLoader</code></a>์ ์ฝ๋ฐฑ์ ์ด์ฉํด ๊ฐ ๋ชจ๋ธ์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ ๋ ๋ถ๋ฌ์จ ๊ฐ ๋ชจ๋ธ์ ์ ๊ทผํ ์ ์๋๋ก ํ๊ธฐ ์ํจ์
๋๋ค.</p>
- <p>๋ชจ๋ ๋ชจ๋ธ๊ณผ ๋ชจ๋ธ์ ์ ๋๋ฉ์ด์
๋ฐ์ดํฐ๋ ํ์ฌ ์ฝ 6.6MB์
๋๋ค. ๊ฝค ์ฉ๋์ด ํฌ๋ค์. ์ฌ๋ฌ๋ถ์ ์๋ฒ๊ฐ ์์ถ์ ์ง์(์ด ์ฌ์ดํธ์ ์น ์๋ฒ๊ฐ ์ด ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค)ํ๋ค๋ฉด ์ฉ๋์ ์ฝ 1.4MB๊น์ง ์ค์ด๋ค ๊ฒ๋๋ค. 6.6MB์ ๋น๊ตํ๋ฉด ํ์คํ ์ ์ ๋ฐ์ดํฐ์ง๋ง ์ ๋ ์์ ๋ฐ์ดํฐ๋ ์๋๋๋ค. ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ๋ง๋ค์ด ์ฌ์ฉ์์๊ฒ ์ผ๋ง๋ ๊ธฐ๋ค๋ ค์ผ ํ๋์ง๋ฅผ ํ์ํด์ค๋ค๋ฉด ์ข๊ฒ ๋ค์.</p>
- <p><a href="/docs/#api/ko/loaders/managers/LoadingManager#onProgress"><code class="notranslate" translate="no">onProgress</code></a>์ ์ฝ๋ฐฑ ํจ์๋ฅผ ์ง์ ํด์ค์๋ค. ์ด ์ฝ๋ฐฑ ํจ์๋ ํธ์ถํ ๋ 3๊ฐ์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐ์ต๋๋ค. ๊ฐ๊ฐ ๋ง์ง๋ง์ ๋ถ๋ฌ์จ ํ์ผ์ <code class="notranslate" translate="no">url</code>, ๊ทธ๋ฆฌ๊ณ ๋ถ๋ฌ์จ ํ์ผ์ ๊ฐ์, ์ ์ฒด ํ์ผ์ ๊ฐ์์
๋๋ค.</p>
- <p>ํ๋ก๊ทธ๋์ค ๋ฐ๋ HTML๋ก ๊ฐ๋จํ ๊ตฌํํ๋๋ก ํ์ฃ .</p>
- <pre class="prettyprint showlinemods notranslate lang-html" translate="no"><body>
- <canvas id="c"></canvas>
- + <div id="loading">
- + <div>
- + <div>...loading...</div>
- + <div class="progress"><div id="progressbar"></div></div>
- + </div>
- + </div>
- </body>
- </pre>
- <p><code class="notranslate" translate="no">#progressbar</code>๋ฅผ ์ฐธ์กฐํ ๋ค <code class="notranslate" translate="no">width</code>๋ฅผ ํผ์ผํธ(%) ๋จ์๋ก ํ์ํด ํ์ฌ ์งํ์จ์ ๋ณด์ฌ์ค ๊ฒ๋๋ค. ์ฝ๋ฐฑ์์ ์ด ์คํ์ผ๋ง ์ฒ๋ฆฌํด์ฃผ๋ฉด ๋๊ฒ ๋ค์.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const manager = new THREE.LoadingManager();
- manager.onLoad = init;
- +const progressbarElem = document.querySelector('#progressbar');
- +manager.onProgress = (url, itemsLoaded, itemsTotal) => {
- + progressbarElem.style.width = `${ itemsLoaded / itemsTotal * 100 | 0 }%`;
- +};
- </pre>
- <p>์ด๋ฏธ ๋ชจ๋ ๋ชจ๋ธ์ ๋ถ๋ฌ์์ ๋ <code class="notranslate" translate="no">init</code> ํจ์๋ฅผ ํธ์ถํ๊ฒ ํด๋์์ผ๋, ์ฌ๊ธฐ์ <code class="notranslate" translate="no">#loading</code> ์์๋ฅผ ์จ๊ฒจ ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ์์ฑ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
- + // ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ์จ๊น๋๋ค.
- + const loadingElem = document.querySelector('#loading');
- + loadingElem.style.display = 'none';
- }
- </pre>
- <p>์๋๋ ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ๊พธ๋ฏธ๊ธฐ ์ํ CSS์
๋๋ค. <code class="notranslate" translate="no">#loading</code>์ ํ์ด์ง ์ ์ฒด๋ฅผ ๊ฝ ์ฑ์ฐ๊ณ ์์ ์์๋ฅผ ๊ฐ์ด๋ฐ ์ ๋ ฌ์ํต๋๋ค. ๊ทธ๋ฆฌ๊ณ <code class="notranslate" translate="no">.progress</code>๋ ํ๋ก๊ทธ๋์ค ๋ฐ๊ฐ ๋ค์ด๊ฐ ์์ญ์ ์ ์ํ์ฃ . ํ๋ก๊ทธ๋์ค ๋ฐ์ ๊ฐ๋จํ ์ ๋๋ฉ์ด์
๋ ๋ฃ์ด์คฌ์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-css" translate="no">#loading {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- text-align: center;
- font-size: xx-large;
- font-family: sans-serif;
- }
- #loading>div>div {
- padding: 2px;
- }
- .progress {
- width: 50vw;
- border: 1px solid black;
- }
- #progressbar {
- width: 0;
- transition: width ease-out .5s;
- height: 1em;
- background-color: #888;
- background-image: linear-gradient(
- -45deg,
- rgba(255, 255, 255, .5) 25%,
- transparent 25%,
- transparent 50%,
- rgba(255, 255, 255, .5) 50%,
- rgba(255, 255, 255, .5) 75%,
- transparent 75%,
- transparent
- );
- background-size: 50px 50px;
- animation: progressanim 2s linear infinite;
- }
- @keyframes progressanim {
- 0% {
- background-position: 50px 50px;
- }
- 100% {
- background-position: 0 0;
- }
- }
- </pre>
- <p>์ด์ ํ๋ก๊ทธ๋์ค ๋ฐ๊ฐ ์๊ฒผ์ผ๋ ๋ชจ๋ธ์ ์ฒ๋ฆฌํ ์ฐจ๋ก์
๋๋ค. ์ด ๋ชจ๋ธ๋ค์๋ ์ ๋๋ฉ์ด์
์ด ์๋๋ฐ, ์ด ์ ๋๋ฉ์ด์
์ ์ ์ดํ ์ ์๋ค๋ฉด ์ ๋๋ฉ์ด์
์ด ์๋ ์๋ฏธ๊ฐ ์๊ฒ ์ฃ . Three.js๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๋๋ฉ์ด์
๋ค์ ๋ฐฐ์ด ํํ๋ก ์ ์ฅํฉ๋๋ค. ํ์ง๋ง ๋ฐฐ์ด์ด ์๋ ์ด๋ฆ ํํ๋ก ์ ์ฅํ๋ ๊ฒ ๋์ค์ ์ฐ๊ธฐ์ ํธํ๋ ๊ฐ ๋ชจ๋ธ์ <code class="notranslate" translate="no">animation</code> ์์ฑ์ ๋ง๋ค๊ฒ ์ต๋๋ค. ๋ฌผ๋ก ๊ฐ ์ ๋๋ฉ์ด์
์ ์ด๋ฆ์ ๊ณ ์ ํ ๊ฐ์ด์ด์ผ ํ๊ฒ ์ฃ .</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+function prepModelsAndAnimations() {
- + Object.values(models).forEach(model => {
- + const animsByName = {};
- + model.gltf.animations.forEach((clip) => {
- + animsByName[clip.name] = clip;
- + });
- + model.animations = animsByName;
- + });
- +}
- function init() {
- // ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ์จ๊น๋๋ค.
- const loadingElem = document.querySelector('#loading');
- loadingElem.style.display = 'none';
- + prepModelsAndAnimations();
- }
- </pre>
- <p>์ด์ ์ ๋๋ฉ์ด์
์ด ๋ค์ด๊ฐ ๋ชจ๋ธ์ ํ๋ฉด์ ๋์๋ด
์๋ค.</p>
- <p><a href="load-gltf.html">์ด์ glTF ํ์ผ ์์ </a>์ ๋ฌ๋ฆฌ ์ด๋ฒ์๋ ๊ฐ ๋ชจ๋ธ์ ํ๋ ์ด์ ๋ฐฐ์นํ ๊ณํ์
๋๋ค. ๊ทธ๋ฌ๋ ํ์ผ์ ๋ถ๋ฌ์จ ๋ค ๋ฐ๋ก ์ฅ๋ฉด์ ๋ฃ๋ ๋์ ๊ฐ glTF์ ์ฌ ๊ทธ๋ํ(scene), ์ด ๊ฒฝ์ฐ์๋ ์์ง์ด๋ ์บ๋ฆญํฐ๋ฅผ ๋ณต์ฌํด์ผ ํฉ๋๋ค. ๋คํํ Three.js์๋ <code class="notranslate" translate="no">SkeletonUtil.clone</code>์ด๋ผ๋ ํจ์๊ฐ ์์ด ์ด๋ฅผ ์ฝ๊ฒ ๊ตฌํํ ์ ์์ฃ . ๋จผ์ ํด๋น ๋ชจ๋์ ๋ถ๋ฌ์ค๊ฒ ์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
- import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
- +import { SkeletonUtils } from 'three/addons/utils/SkeletonUtils.js';
- </pre>
- <p>๊ทธ๋ฆฌ๊ณ ์๊น ๋ถ๋ฌ์๋ ๋ชจ๋ธ์ ๋ณต์ฌํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
- // ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ์จ๊น๋๋ค.
- const loadingElem = document.querySelector('#loading');
- loadingElem.style.display = 'none';
- prepModelsAndAnimations();
- + Object.values(models).forEach((model, ndx) => {
- + const clonedScene = SkeletonUtils.clone(model.gltf.scene);
- + const root = new THREE.Object3D();
- + root.add(clonedScene);
- + scene.add(root);
- + root.position.x = (ndx - 3) * 3;
- + });
- }
- </pre>
- <p>์ ์ฝ๋์์๋ ๋ถ๋ฌ์จ ๊ฐ ๋ชจ๋ธ์ <code class="notranslate" translate="no">gltf.scene</code>์ ๋ณต์ฌํด ์๋ก์ด <a href="/docs/#api/ko/core/Object3D"><code class="notranslate" translate="no">Object3D</code></a>์ ์์์ผ๋ก ์ถ๊ฐํ์ต๋๋ค. ๋ถ๋ชจ๋ฅผ ๋ฐ๋ก ๋ง๋ ๊ฑด ๋ชจ๋ธ์ ์ ๋๋ฉ์ด์
์ด ๋ชจ๋ธ์ ๊ฐ ์์์ ์์น๊ฐ์ ์ํฅ์ ๋ฏธ์น๊ธฐ์ ์ฝ๋๋ก ์ง์ ์์น๊ฐ์ ์์ ํ๊ธฐ๋ ์ด๋ ต๊ณ , ์ ๋๋ก ๋ฐ์๋ ์ ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์
๋๋ค.</p>
- <p>๊ฐ ๋ชจ๋ธ์ ์ ๋๋ฉ์ด์
์ ์ฌ์ํ๋ ค๋ฉด <a href="/docs/#api/ko/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a>๋ฅผ ์จ์ผ ํฉ๋๋ค. <a href="/docs/#api/ko/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a>๋ ํ๋ ์ด์์ <a href="/docs/#api/ko/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a>์ผ๋ก ์ด๋ฃจ์ด์ง๊ณ , ๊ฐ <a href="/docs/#api/ko/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a>์๋ ํ๋์ <a href="/docs/#api/ko/animation/AnimationClip"><code class="notranslate" translate="no">AnimationClip</code></a>์ด ์์ต๋๋ค. <a href="/docs/#api/ko/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a>์๋ ์ฌ๋ฌ ์ก์
(action)์ ์ด์ด์ ์ฌ์ํ๊ฑฐ๋, ๋ค๋ฅธ ์ ๋๋ฉ์ด์
์ผ๋ก ๋ถ๋๋ฝ๊ฒ ์ ํํ๊ธฐ ๋ฑ ๋ค์ํ ์ค์ ์ด ์์ฃ . ๋น์ฅ์ ์ฒซ ๋ฒ์งธ <a href="/docs/#api/ko/animation/AnimationClip"><code class="notranslate" translate="no">AnimationClip</code></a>์ผ๋ก ์ก์
์ ๋ง๋ค์ด๋ด
์๋ค. ์ค์ ์ ๋ฐ๊พธ์ง ์๋๋ค๋ฉด ํด๋น ์ ๋๋ฉ์ด์
ํด๋ฆฝ(clip)์ ๋ฐ๋ณตํด ์ฌ์ํ ๊ฒ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const mixers = [];
- function init() {
- // ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ์จ๊น๋๋ค.
- const loadingElem = document.querySelector('#loading');
- loadingElem.style.display = 'none';
- prepModelsAndAnimations();
- Object.values(models).forEach((model, ndx) => {
- const clonedScene = SkeletonUtils.clone(model.gltf.scene);
- const root = new THREE.Object3D();
- root.add(clonedScene);
- scene.add(root);
- root.position.x = (ndx - 3) * 3;
- + const mixer = new THREE.AnimationMixer(clonedScene);
- + const firstClip = Object.values(model.animations)[0];
- + const action = mixer.clipAction(firstClip);
- + action.play();
- + mixers.push(mixer);
- });
- }
- </pre>
- <p>์ ๋๋ฉ์ด์
์ ์์ํ๊ธฐ ์ํด <a href="/docs/#api/ko/animation/AnimationAction#play"><code class="notranslate" translate="no">play</code></a> ๋ฉ์๋๋ฅผ ํธ์ถํ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์์ฑํ <a href="/docs/#api/ko/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a>๋ค์ ์ ๋ถ <code class="notranslate" translate="no">mixers</code> ๋ฐฐ์ด์ ๋ฃ์์ฃ . ๋ง์ง๋ง์ผ๋ก ๋ ๋๋ง ๋ฃจํ์์ ๊ฐ <a href="/docs/#api/ko/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a>์ <a href="/docs/#api/ko/animation/AnimationMixer.update"><code class="notranslate" translate="no">AnimationMixer.update</code></a> ๋ฉ์๋์ ๋ฐ๋ก ์ง์ ํ๋ ์๊ณผ ํ์ฌ ํ๋ ์์ ์๊ฐ๊ฐ์ ๋๊ฒจ์ฃผ์ด์ผ ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+let then = 0;
- function render(now) {
- + now *= 0.001; // ์ด ๋จ์๋ก ๋ณํ
- + const deltaTime = now - then;
- + then = now;
- if (resizeRendererToDisplaySize(renderer)) {
- const canvas = renderer.domElement;
- camera.aspect = canvas.clientWidth / canvas.clientHeight;
- camera.updateProjectionMatrix();
- }
- + for (const mixer of mixers) {
- + mixer.update(deltaTime);
- + }
- renderer.render(scene, camera);
- requestAnimationFrame(render);
- }
- </pre>
- <p>์ด์ ๊ฐ ๋ชจ๋ธ๊ณผ ๋ชจ๋ธ์ ์ฒซ ๋ฒ์งธ ์ ๋๋ฉ์ด์
์ด ๋ณด์ผ ๊ฒ๋๋ค.</p>
- <p></p><div translate="no" class="threejs_example_container notranslate">
- <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-load-models.html"></iframe></div>
- <a class="threejs_center" href="/manual/examples/game-load-models.html" target="_blank">์ ํญ์์ ๋ณด๊ธฐ</a>
- </div>
- <p></p>
- <p>๋ชจ๋ ์ ๋๋ฉ์ด์
์ ํ์ธํ ์ ์๋๋ก ์์ ๋ฅผ ์์ ํด๋ด
์๋ค. ์ ๋๋ฉ์ด์
ํด๋ฆฝ์ ์ ๋ถ ์ก์
์ผ๋ก ๋ง๋ค์ด ์ฌ์ํ ์ ์๋๋ก ๋ง๋ค๊ฒ ์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const mixers = [];
- +const mixerInfos = [];
- function init() {
- // ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ์จ๊น๋๋ค.
- const loadingElem = document.querySelector('#loading');
- loadingElem.style.display = 'none';
- prepModelsAndAnimations();
- Object.values(models).forEach((model, ndx) => {
- const clonedScene = SkeletonUtils.clone(model.gltf.scene);
- const root = new THREE.Object3D();
- root.add(clonedScene);
- scene.add(root);
- root.position.x = (ndx - 3) * 3;
- const mixer = new THREE.AnimationMixer(clonedScene);
- - const firstClip = Object.values(model.animations)[0];
- - const action = mixer.clipAction(firstClip);
- - action.play();
- - mixers.push(mixer);
- + const actions = Object.values(model.animations).map((clip) => {
- + return mixer.clipAction(clip);
- + });
- + const mixerInfo = {
- + mixer,
- + actions,
- + actionNdx: -1,
- + };
- + mixerInfos.push(mixerInfo);
- + playNextAction(mixerInfo);
- });
- }
- +function playNextAction(mixerInfo) {
- + const { actions, actionNdx } = mixerInfo;
- + const nextActionNdx = (actionNdx + 1) % actions.length;
- + mixerInfo.actionNdx = nextActionNdx;
- + actions.forEach((action, ndx) => {
- + const enabled = ndx === nextActionNdx;
- + action.enabled = enabled;
- + if (enabled) {
- + action.play();
- + }
- + });
- +}
- </pre>
- <p>์ ์ฝ๋์์๋ ๊ฐ ๋ชจ๋ธ๋ง๋ค ์ก์
๋ฐฐ์ด๊ณผ <a href="/docs/#api/ko/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a>๋ฅผ ๊ฐ์ฒด๋ก ๋ง๋ค์ด ์ ์ฅํ์ต๋๋ค. ์ก์
๋ฐฐ์ด์ ๋ชจ๋ธ์ ๊ฐ <a href="/docs/#api/ko/animation/AnimationClip"><code class="notranslate" translate="no">AnimationClip</code></a>๋ง๋ค <a href="/docs/#api/ko/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a>์ ํ๋์ฉ ์์ฑํด ๋ฐฐ์ด๋ก ๋ง๋ ๊ฒ์ด์ฃ . ๊ทธ๋ฆฌ๊ณ ํ๋์ ์ก์
์ ์ ์ธํ ๋๋จธ์ง ์ก์
์ <code class="notranslate" translate="no">enabled</code> ์์ฑ์ ๋๋ <code class="notranslate" translate="no">playNextAction</code>์ ํธ์ถํ์ต๋๋ค.</p>
- <p>๋ฐ์ดํฐ ํ์์ด ๋ฐ๋์์ผ๋ ๋ ๋๋ง ๋ฃจํ์ ์
๋ฐ์ดํธ ์ชฝ ์ฝ๋๋ ์์ ํด์ผ ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-for (const mixer of mixers) {
- +for (const { mixer } of mixerInfos) {
- mixer.update(deltaTime);
- }
- </pre>
- <p>์ซ์ํค 1-8๋ฒ์ ๋๋ฌ ๊ฐ ๋ชจ๋ธ์ ์ ๋๋ฉ์ด์
์ ์ ํํ ์ ์๋๋ก ๋ฆฌ์ค๋๋ฅผ ์ง์ ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">window.addEventListener('keydown', (e) => {
- const mixerInfo = mixerInfos[e.keyCode - 49];
- if (!mixerInfo) {
- return;
- }
- playNextAction(mixerInfo);
- });
- </pre>
- <p>์ด์ ์์ ๋ฅผ ํด๋ฆญํ ๋ค ์ซ์ํค 1-8๋ฒ์ ๋๋ฅด๋ฉด ๊ฐ ๋ฒํธ์ ํด๋นํ๋ ๋ชจ๋ธ์ ์ ๋๋ฉ์ด์
์ด ๋ฐ๋ ๊ฒ๋๋ค.</p>
- <p></p><div translate="no" class="threejs_example_container notranslate">
- <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-check-animations.html"></iframe></div>
- <a class="threejs_center" href="/manual/examples/game-check-animations.html" target="_blank">์ ํญ์์ ๋ณด๊ธฐ</a>
- </div>
- <p></p>
- <p>Three.js ๊ด๋ จ ๋ด์ฉ์ ์ฌ๊ธฐ๊น์ง์
๋๋ค. ์ฌํ๊น์ง ๋ค์์ ํ์ผ์ ๋ถ๋ฌ์ค๋ ๋ฒ, ํ
์ค์ฒ๊ฐ ์์์ง ๋ชจ๋ธ์ ๋ณต์ฌํ๋ ๋ฒ, ํด๋น ๋ชจ๋ธ์ ์ ๋๋ฉ์ด์
์ ์ฌ์ํ๋ ๋ฒ์ ์์๋ดค์ฃ . ์ค์ ๊ฒ์์์๋ <a href="/docs/#api/ko/animation/AnimationAction"><code class="notranslate" translate="no">AnimationAction</code></a> ๊ฐ์ฒด๋ก ๋ค์ํ ๋์์ ์ง์ ์ฒ๋ฆฌํด์ค์ผ ํฉ๋๋ค.</p>
- <p>์, ์ด์ ๊ฒ์์ ๊ธฐ๋ณธ ํ์ ๋ง๋ค์ด๋ด
์๋ค.</p>
- <p>์ต์ ๊ฒ์์ ๋ง๋ค ๋๋ ๋ณดํต <a href="https://www.google.com/search?q=entity+component+system">Entity Component System(ECS)</a>์ ๋ง์ด ์ฌ์ฉํฉ๋๋ค. Entity Component System์์๋ ๊ฒ์์ ์์๋ฅผ ์ฌ๋ฌ ๊ฐ์ <em>์ปดํฌ๋ํธ(component)</em>๋ก ์ด๋ฃจ์ด์ง <em>์ํฐํฐ(entity)</em>๋ผ ๋ถ๋ฅด์ฃ . ์๋ก์ด ์ํฐํฐ๋ฅผ ์์ฑํ ๋๋ ๋ชจ๋ ์ฝ๋๋ฅผ ์๋ก ์ฐ๋ ๊ฒ์ด ์๋, ๋ฏธ๋ฆฌ ๋ง๋ค์ด ๋์ ์ปดํฌ๋ํธ๋ค์ ์ฎ์ด ์์ฑํฉ๋๋ค.</p>
- <p>์์ ์์๋ ์ํฐํฐ๋ฅผ <code class="notranslate" translate="no">GameObject</code>๋ผ ๋ถ๋ฅด๊ฒ ์ต๋๋ค. ์ด๋ ๋จ์ํ ์ปดํฌ๋ํธ ๋ฐฐ์ด๊ณผ <a href="/docs/#api/ko/core/Object3D"><code class="notranslate" translate="no">THREE.Object3D</code></a>๋ฅผ ํฉ์น ๊ฒ์
๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function removeArrayElement(array, element) {
- const ndx = array.indexOf(element);
- if (ndx >= 0) {
- array.splice(ndx, 1);
- }
- }
- class GameObject {
- constructor(parent, name) {
- this.name = name;
- this.components = [];
- this.transform = new THREE.Object3D();
- parent.add(this.transform);
- }
- addComponent(ComponentType, ...args) {
- const component = new ComponentType(this, ...args);
- this.components.push(component);
- return component;
- }
- removeComponent(component) {
- removeArrayElement(this.components, component);
- }
- getComponent(ComponentType) {
- return this.components.find(c => c instanceof ComponentType);
- }
- update() {
- for (const component of this.components) {
- component.update();
- }
- }
- }
- </pre>
- <p><code class="notranslate" translate="no">GameObject.update</code> ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๊ฐ ์ปดํฌ๋ํธ์ <code class="notranslate" translate="no">update</code> ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.</p>
- <p>name ์์ฑ์ ๋จ์ํ ๋๋ฒ๊น
์ ์ํ ๊ฒ์
๋๋ค. ์ฝ์์์ <code class="notranslate" translate="no">GameObject</code>๋ฅผ ๋ดค์ ๋ ์ด๋ค ์์์ธ์ง ์ฝ๊ฒ ํ์ธํ ์ ์๊ฒ ์ฃ .</p>
- <p>์์ํด ๋ณด์ผ ์ ์๋ ๊ฒ๋ค ๋ช ๊ฐ์ง๋ง ์ง๊ณ ๋์ด๊ฐ๊ฒ ์ต๋๋ค.</p>
- <p><code class="notranslate" translate="no">GameObject.addComponent</code>๋ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ ๋ ์ฌ์ฉํฉ๋๋ค. GameObject ์์์ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ๋ ๊ฒ ์ต์ ์ธ์ง๋ ๋ชจ๋ฅด๊ฒ ์ผ๋, ๊ฐ์ธ์ ์ผ๋ก ์ปดํฌ๋ํธ๊ฐ GameObject ๋ฐ์ ์กด์ฌํ๋ ๊ฑด ์๋ฏธ๊ฐ ์์ด ๋ณด์์ต๋๋ค. ๊ทธ๋์ ์์ฑํ ์ปดํฌ๋ํธ๋ฅผ ์๋์ผ๋ก GameObject์ ๋ฐฐ์ด์ ์ถ๊ฐํ๊ณ , GameObject ์์ฒด๋ ์ปดํฌ๋ํธ์ constructor์ ๋๊ฒจ์ค ์ ์์ผ๋ฉด ํธํ๊ฒ ๋ค๊ณ ์๊ฐํ์ฃ . ์ฝ๊ฒ ๋งํด ์ง๊ธ์ ๋ค์์ฒ๋ผ ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ์ง๋ง,</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gameObject = new GameObject(scene, 'foo');
- gameObject.addComponent(TypeOfComponent);
- </pre>
- <p>์์ ๊ฐ์ ๋ฐฉ์์ ์ ํธํ์ง ์๋๋ค๋ฉด ๋ค์์ฒ๋ผ ์ถ๊ฐํ ์๋ ์์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gameObject = new GameObject(scene, 'foo');
- const component = new TypeOfComponent(gameObject);
- gameObject.addComponent(component);
- </pre>
- <p>์ฒซ ๋ฒ์งธ ์ฝ๋๊ฐ ์งง๊ณ ์๋ํ๋๋ค๋ ๋ฉด์์ ๋ ์ข์๊น์, ์๋๋ฉด ๊ธฐ์กด ํ์์ ํด์ณ์ ๋ ๋ณ๋ก์ผ๊น์? ์ ๋ ์ด๋ป๋ค๊ณ ํ๋จํ๊ธฐ๊ฐ ์ด๋ ต๋ค์.</p>
- <p><code class="notranslate" translate="no">GameObject.getComponent</code>๋ ์ปดํฌ๋ํธ์ ํ์
์ ์ด์ฉํด ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์ต๋๋ค. ์ด๋ ํ๋์ GameObject๊ฐ ๊ฐ์ ํ์
์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๊ฐ ์ด์ ์ฌ์ฉํ ์ ์๋ค๋ ์ด์ผ๊ธฐ์ฃ . ๋ฌผ๋ก ๋ ๊ฐ ์ด์ ์ฌ์ฉํ๋ค๊ณ ์๋ฌ๊ฐ ๋๊ฑฐ๋ ํ์ง ์๊ฒ ์ง๋ง, ๋ณ๋์ API๋ฅผ ์ถ๊ฐํ์ง ์๋ ํ ์ ๋ฉ์๋๋ ํญ์ ๊ฐ์ ํ์
์ค ์ฒซ ๋ฒ์งธ ์ปดํฌ๋ํธ๋ง์ ๋ฐํํ ๊ฒ๋๋ค.</p>
- <p>์ปดํฌ๋ํธ๊ฐ ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ฅผ ์ฐพ๋ ๊ฑด ํํ ์ผ์
๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์ ๋๋ ์๋ชป ์ฐธ์กฐํ๋ ์ผ์ด ์๋๋ก ํ์
์ ์ฒดํฌํด์ผ ํ์ฃ . ๊ทธ๋ฅ ๊ฐ ์ปดํฌ๋ํธ์ ๊ณ ์ ํ ์ด๋ฆ ์์ฑ์ ์ฃผ๊ณ ๊ทธ ์ด๋ฆ์ผ๋ก ํด๋น ์ปดํฌ๋ํธ๋ฅผ ์ฐพ์ ์๋ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๊ฐ์ ํ์
์ ์ปดํฌ๋ํธ๋ฅผ ์ฌ๋ฌ ๊ฐ ์ธ ์ ์์ผ๋ ํ์ฅ์ฑ ๋ฉด์์ ์ ๋ฆฌํ ๊ฒ๋๋ค. ํ์ง๋ง ์ด ๋ฐฉ๋ฒ์ ๊ทธ๋ค์ง ์ผ๊ด์ฑ์ด ์์ต๋๋ค. ์ด๋ฒ์๋ ์ด๋ค ์ชฝ์ด ๋ ์ข๋ค๊ณ ํ๋จํ๊ธฐ๊ฐ ์ด๋ ต๋ค์.</p>
- <p>์๋๋ ์ปดํฌ๋ํธ์ ๊ธฐ์ด ํด๋์ค์
๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">// ๋ชจ๋ ์ปดํฌ๋ํธ์ ๊ธฐ์ด
- class Component {
- constructor(gameObject) {
- this.gameObject = gameObject;
- }
- update() {
- }
- }
- </pre>
- <p>์ปดํฌ๋ํธ์ ๊ธฐ์ด ํด๋์ค๊ฐ ํ์ํ ๊น์? ์๋ฐ์คํฌ๋ฆฝํธ๋ ํ์
์ด ๋์จํ ์ธ์ด์ด๊ธฐ์ ๊ตณ์ด ๊ธฐ์ด ํด๋์ค๋ฅผ ์ธ ํ์๋ ์์ต๋๋ค. ๊ฐ ์ปดํฌ๋ํธ์ constructor์์ ์ฒซ ๋ฒ์งธ ์ธ์๊ฐ GameObject์ด๊ธฐ๋ง ํ๋ฉด ๋์ฃ . ๋ง์ฝ GameObject๋ฅผ ๋์ค์ ์ฐธ์กฐํ ํ์๊ฐ ์๋ค๋ฉด ๊ตณ์ด ์ ์ฅํ์ง ์์๋ ๋ ๊ฒ๋๋ค. ํ์ง๋ง ์ ๋ ์ ์ง ์ด ํ์์ด ๋ ์ข์ ๋ณด์ด๋ค์. ๊ธฐ์ด ํด๋์ค๋ฅผ ๋๋ฉด ๋ถ๋ชจ์ GameObject์ ์ฝ๊ฒ ์ ๊ทผํ ์ ์์ ๋ฟ๋ง ์๋๋ผ ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ฅผ ์ฝ๊ฒ ์ฐพ์ ์ ์๊ณ , ์ด๋ค ์ฐจ์ด์ ์ด ์๋์ง๋ ์ฝ๊ฒ ์ ์ ์์ ํ
๋๊น์.</p>
- <p>GameObject๋ฅผ ๋ค๋ฃจ๋ ค๋ฉด GameObject๋ฅผ ๊ด๋ฆฌํ๋ ํด๋์ค๋ฅผ ๋ง๋๋ ๊ฒ ์ข์ ๋ฏํฉ๋๋ค. ์ผํ GameObject๋ฅผ ๋ฐฐ์ด ํ์์ผ๋ก ๊ฐ๊ณ ์์ด๋ ๊ด์ฐฎ์ง ์๋ ์ถ์ ์ ์์ผ๋, ์ค์ ๋ก ๊ฒ์์ ํ๋ ์ดํ ๋๋ ์์๊ฐ ์ถ๊ฐ๋๊ธฐ๋ ํ๊ณ , ์์ด์ง๊ธฐ๋ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด ์ด GameObject๋ ์ด์ ๋ฐ์ฌํ ๋๋ง๋ค ์ด์ GameObject๋ฅผ ์ถ๊ฐํ ๊ฒ๋๋ค. ๋ชฌ์คํฐ GameObject๊ฐ ๋๊ตฐ๊ฐ์ ์ํด ์ฃฝ๋๋ค๋ฉด ํด๋น GameObject๋ ์ฌ๋ผ์ง๊ฒ ์ฃ . GameObject๋ฅผ ๋ฐฐ์ด๋ก ์ ์ฅํ๋ค๋ฉด ์ญ์คํ๊ตฌ ๋ค์๊ณผ ๊ฐ์ ์์ ์ฝ๋๋ฅผ ์ธ ๊ฒ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">for (const gameObject of globalArrayOfGameObjects) {
- gameObject.update();
- }
- </pre>
- <p>์ ๋ฐ๋ณต๋ฌธ์ <code class="notranslate" translate="no">globalArrayOfGameObjects</code>์ GameObject๊ฐ ์ถ๊ฐ๋๊ฑฐ๋ ์ ๊ฑฐ๋์ ๊ฒฝ์ฐ, ํน์ ์ปดํฌ๋ํธ์ <code class="notranslate" translate="no">update</code> ๋ฉ์๋์์ ์๋ฌ๋ฅผ ๋์ง๊ฑฐ๋ ์์ ๋ฐ์ ๋์์ ํ ์ ์์ต๋๋ค.</p>
- <p>์ด๋ฐ ์ผ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์์ ์ฅ์น๋ฅผ ์ถ๊ฐํด๋ณด๋๋ก ํ์ฃ .</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class SafeArray {
- constructor() {
- this.array = [];
- this.addQueue = [];
- this.removeQueue = new Set();
- }
- get isEmpty() {
- return this.addQueue.length + this.array.length > 0;
- }
- add(element) {
- this.addQueue.push(element);
- }
- remove(element) {
- this.removeQueue.add(element);
- }
- forEach(fn) {
- this._addQueued();
- this._removeQueued();
- for (const element of this.array) {
- if (this.removeQueue.has(element)) {
- continue;
- }
- fn(element);
- }
- this._removeQueued();
- }
- _addQueued() {
- if (this.addQueue.length) {
- this.array.splice(this.array.length, 0, ...this.addQueue);
- this.addQueue = [];
- }
- }
- _removeQueued() {
- if (this.removeQueue.size) {
- this.array = this.array.filter(element => !this.removeQueue.has(element));
- this.removeQueue.clear();
- }
- }
- }
- </pre>
- <p>์ ํด๋์ค๋ <code class="notranslate" translate="no">SafeArray</code>์ ์์๋ฅผ ๋ํ๊ฑฐ๋ ์ ๊ฑฐํ ์ ์๋๋ก ํด์ค๋๋ค. ์๋ณธ ๋ฐฐ์ด์ ๋ฐ๋ณต๋๋ ๋์ ์๋ณธ ๋ฐฐ์ด์ ๋ณ๊ฒฝํ์ง ์๋๋ค๋ ๊ฒ ์ฐจ์ด์ ์ด์ฃ . ๋์ ๋ฐ๋ณต ์ค๊ฐ์ ์ถ๊ฐ๋ ์์๋ <code class="notranslate" translate="no">addQueue</code>์, ์ ๊ฑฐ๋ ์์๋ <code class="notranslate" translate="no">removeQueue</code>์ ๋ค์ด๊ฐ ๋ค, ๋ฐ๋ณต๋ฌธ์ด ๋์๊ฐ์ง ์์ ๋ ์๋ณธ ๋ฐฐ์ด์ ์ ๊ฑฐ/์ถ๊ฐ๋ฉ๋๋ค.</p>
- <p>์๋๋ ์ ํด๋์ค๋ฅผ ์ด์ฉํ GameObject์ ๊ด๋ฆฌ ํด๋์ค์
๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class GameObjectManager {
- constructor() {
- this.gameObjects = new SafeArray();
- }
- createGameObject(parent, name) {
- const gameObject = new GameObject(parent, name);
- this.gameObjects.add(gameObject);
- return gameObject;
- }
- removeGameObject(gameObject) {
- this.gameObjects.remove(gameObject);
- }
- update() {
- this.gameObjects.forEach(gameObject => gameObject.update());
- }
- }
- </pre>
- <p>์ฌํ๊น์ง ๋ง๋ ์์๋ก ์ฒซ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด๋ด
์๋ค. ์ด ์ปดํฌ๋ํธ๋ ์๊น ๋ง๋ค์๋ ๊ฒ๊ณผ ๊ฐ์ Three.js glTF ๊ฐ์ฒด๋ฅผ ๊ด๋ฆฌํ ๊ฒ๋๋ค. ๊ฐ๋จํ ์ ๋๋ฉ์ด์
์ ์ด๋ฆ์ ๋ฐ์ ํด๋น ์ ๋๋ฉ์ด์
์ ์ฌ์ํ๋ <code class="notranslate" translate="no">setAnimation</code> ๋ฉ์๋๋ง ์๋ก ๋ง๋ค๋๋ก ํ๊ฒ ์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class SkinInstance extends Component {
- constructor(gameObject, model) {
- super(gameObject);
- this.model = model;
- this.animRoot = SkeletonUtils.clone(this.model.gltf.scene);
- this.mixer = new THREE.AnimationMixer(this.animRoot);
- gameObject.transform.add(this.animRoot);
- this.actions = {};
- }
- setAnimation(animName) {
- const clip = this.model.animations[animName];
- // ๋ชจ๋ ์ก์
์ ๋๋๋ค.
- for (const action of Object.values(this.actions)) {
- action.enabled = false;
- }
- // ํด๋น ํด๋ฆฝ์ ํด๋นํ๋ ์ก์
์ ์์ฑ ๋๋ ๊ฐ์ ธ์ต๋๋ค.
- const action = this.mixer.clipAction(clip);
- action.enabled = true;
- action.reset();
- action.play();
- this.actions[animName] = action;
- }
- update() {
- this.mixer.update(globals.deltaTime);
- }
- }
- </pre>
- <p>์ด ํด๋์ค๋ ์๊น ํ๋ ๊ฒ์ฒ๋ผ ๋ถ๋ฌ์จ ์ฌ ๊ทธ๋ํ๋ฅผ ๋ณต์ฌํ๊ณ , <a href="/docs/#api/ko/animation/AnimationMixer"><code class="notranslate" translate="no">AnimationMixer</code></a>๋ฅผ ๋ง๋ญ๋๋ค. ํด๋์ค์ <code class="notranslate" translate="no">setAnimation</code> ๋ฉ์๋๋ ํด๋น ํด๋ฆฝ์ ๋ํ ์ก์
์ด ์กด์ฌํ์ง ์๋๋ค๋ฉด ์๋ก ์์ฑํ๊ณ , ๋ค๋ฅธ ์ก์
์ ์ ๋ถ ๋๋ ์ญํ ์ ํฉ๋๋ค.</p>
- <p>์ด ์ฝ๋๋ <code class="notranslate" translate="no">globals.deltaTime</code>์ ์ฌ์ฉํฉ๋๋ค. ์ด ์ ์ญ ๊ฐ์ฒด๋ ๋ง๋ค์ด์ผ๊ฒ ์ฃ .</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const globals = {
- time: 0,
- deltaTime: 0,
- };
- </pre>
- <p>๊ทธ๋ฆฌ๊ณ ๋ ๋๋ง ๋ฃจํ์์ ์ ์ญ ๊ฐ์ฒด๋ฅผ ์
๋ฐ์ดํธํ๋๋ก ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">let then = 0;
- function render(now) {
- // ์ด ๋จ์๋ก ๋ณํ
- globals.time = now * 0.001;
- // ์๊ฐ๊ฐ์ด ๋๋ฌด ํฌ์ง ์๋๋ก ์ ํํฉ๋๋ค.
- globals.deltaTime = Math.min(globals.time - then, 1 / 20);
- then = globals.time;
- </pre>
- <p>์ ์ฝ๋์์๋ ์๊ฐ๊ฐ์ ๋ฒ์๊ฐ 1/20์ด๋ฅผ ๋์ง ์๋๋ก ํ์ต๋๋ค. ์ด๋ ์ฌ์ฉ์๊ฐ ํญ์ ์จ๊ธฐ๊ฑฐ๋ ํ์ ๊ฒฝ์ฐ ์๊ฐ๊ฐ์ด ๋๋ฌด ์ปค์ง์ง ์๊ฒ ํ๊ธฐ ์ํ ๊ฒ์ด์ฃ . ๋ง์ฝ ์ด๋ ๊ฒ ์ ํ์ ๋์ง ์๋๋ค๋ฉด ์ฌ์ฉ์๊ฐ ํญ์ ๋ช ์ด, ๋๋ ๋ช ๋ถ ์จ๊ฒผ๋ค๊ฐ ๋ค์ ํญ์ ์ด์์ ๋ ํ๋ ์ ๊ฐ ์๊ฐ๊ฐ์ด ๋๋ฌด ์ปค์ง ํ
๊ณ , ์๋์ ๊ฐ์ด ์๊ฐ์ผ๋ก ์๋ ฅ์ ๊ณ์ฐํ๋ ๊ฒฝ์ฐ ์บ๋ฆญํฐ๊ฐ ์๊ฐ์ด๋ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ผ ์ ์์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">position += velocity * deltaTime;
- </pre>
- <p><code class="notranslate" translate="no">deltaTime</code>์ ์ต๋๊ฐ์ ์ค์ ํ๋ฉด ์ด๋ฐ ๋ฌธ์ ๋ฅผ ๋ง์ ์ ์์ฃ .</p>
- <p>์ด์ ํ๋ ์ด์ด ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด๋ด
์๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player extends Component {
- constructor(gameObject) {
- super(gameObject);
- const model = models.knight;
- this.skinInstance = gameObject.addComponent(SkinInstance, model);
- this.skinInstance.setAnimation('Run');
- }
- }
- </pre>
- <p>ํ๋ ์ด์ด ์ปดํฌ๋ํธ๋ ์ด๊ธฐํ ์์ <code class="notranslate" translate="no">'Run'</code>์ ์ธ์๋ก <code class="notranslate" translate="no">setAnimation</code>์ ํธ์ถํฉ๋๋ค. ๊ฐ์ธ์ ์ผ๋ก ๋ฏธ๋ฆฌ ์ด๋ค ์ ๋๋ฉ์ด์
์ด ์๋์ง ๋ณด๋ ค๊ณ ์ด์ ์์ ๋ฅผ ์์ ํด ์ ๋๋ฉ์ด์
์ ์ด๋ฆ์ ์ถ๋ ฅํ๋๋ก ํ์ฃ .</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function prepModelsAndAnimations() {
- Object.values(models).forEach(model => {
- + console.log('------->:', model.url);
- const animsByName = {};
- model.gltf.animations.forEach((clip) => {
- animsByName[clip.name] = clip;
- + console.log(' ', clip.name);
- });
- model.animations = animsByName;
- });
- }
- </pre>
- <p>์๋๋ ์ค์ ๋ก <a href="https://developers.google.com/web/tools/chrome-devtools/console/javascript">์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ๋ฐ์ ์ฝ์</a>์ ์ถ๋ ฅ๋ ๊ฒฐ๊ณผ์
๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate notranslate" translate="no"> ------->: resources/models/animals/Pig.gltf
- Idle
- Death
- WalkSlow
- Jump
- Walk
- ------->: resources/models/animals/Cow.gltf
- Walk
- Jump
- WalkSlow
- Death
- Idle
- ------->: resources/models/animals/Llama.gltf
- Jump
- Idle
- Walk
- Death
- WalkSlow
- ------->: resources/models/animals/Pug.gltf
- Jump
- Walk
- Idle
- WalkSlow
- Death
- ------->: resources/models/animals/Sheep.gltf
- WalkSlow
- Death
- Jump
- Walk
- Idle
- ------->: resources/models/animals/Zebra.gltf
- Jump
- Walk
- Death
- WalkSlow
- Idle
- ------->: resources/models/animals/Horse.gltf
- Jump
- WalkSlow
- Death
- Walk
- Idle
- ------->: resources/models/knight/KnightCharacter.gltf
- Run_swordRight
- Run
- Idle_swordLeft
- Roll_sword
- Idle
- Run_swordAttack
- </pre><p>์ด ์ข๊ฒ๋ ๋๋ฌผ๋ค์ ์ ๋๋ฉ์ด์
์ด๋ฆ์ด ์ ๋ถ ๋๊ฐ๋ค์. ๋์ค์ ํธํ ๋ฏํฉ๋๋ค. ๋ญ, ๊ทธ๊ฑด ๋์ค ์๊ธฐ๊ณ , ์ง๊ธ์ ํ๋ ์ด์ด์ ์ ๋๋ฉ์ด์
์ค <code class="notranslate" translate="no">Run</code>๋ง ์ ๊ฒฝ์์๋ค.</p>
- <p>์ด์ ๋ง๋ ์ปดํฌ๋ํธ๋ฅผ ์จ ๋ณด๊ฒ ์ต๋๋ค. ๋จผ์ <code class="notranslate" translate="no">init</code> ํจ์๋ฅผ ์ฝ๊ฐ ์์ ํฉ๋๋ค. <code class="notranslate" translate="no">init</code> ํจ์๋ <code class="notranslate" translate="no">GameObject</code>๋ฅผ ๋ง๋ค๊ณ ๊ฑฐ๊ธฐ์ <code class="notranslate" translate="no">Player</code> ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๋ ์ญํ ์ ํ ๊ฒ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const globals = {
- time: 0,
- deltaTime: 0,
- };
- +const gameObjectManager = new GameObjectManager();
- function init() {
- // ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ์จ๊น๋๋ค.
- const loadingElem = document.querySelector('#loading');
- loadingElem.style.display = 'none';
- prepModelsAndAnimations();
- + {
- + const gameObject = gameObjectManager.createGameObject(scene, 'player');
- + gameObject.addComponent(Player);
- + }
- }
- </pre>
- <p>๋ ๋๋ง ๋ฃจํ์์ <code class="notranslate" translate="no">gameObjectManager.update</code>๋ฅผ ํธ์ถํ๋๋ก ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">let then = 0;
- function render(now) {
- // ์ด ๋จ์๋ก ๋ณํ
- globals.time = now * 0.001;
- // ์๊ฐ๊ฐ์ด ๋๋ฌด ํฌ์ง ์๋๋ก ์ ํํฉ๋๋ค.
- globals.deltaTime = Math.min(globals.time - then, 1 / 20);
- then = globals.time;
- if (resizeRendererToDisplaySize(renderer)) {
- const canvas = renderer.domElement;
- camera.aspect = canvas.clientWidth / canvas.clientHeight;
- camera.updateProjectionMatrix();
- }
- - for (const { mixer } of mixerInfos) {
- - mixer.update(deltaTime);
- - }
- + gameObjectManager.update();
- renderer.render(scene, camera);
- requestAnimationFrame(render);
- }
- </pre>
- <p>์์ ๋ฅผ ์คํํ๋ฉด ํ๋ ์ด์ด ํ๋๋ง ๋ณด์ผ ๊ฒ๋๋ค.</p>
- <p></p><div translate="no" class="threejs_example_container notranslate">
- <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-just-player.html"></iframe></div>
- <a class="threejs_center" href="/manual/examples/game-just-player.html" target="_blank">์ ํญ์์ ๋ณด๊ธฐ</a>
- </div>
- <p></p>
- <p>๋จ์ํ Entity Component System์ ๊ตฌํํ๋ ๋ฐ๋ง ๋๋ฌด ๋ง์ ์ฝ๋๋ฅผ ์ด ๊ฒ ์๋๊ฐ ์ถ์ง๋ง, ์ด ์ ๋๊ฐ ๋๋ถ๋ถ์ ๊ฒ์์ด ๊ฐ์ถฐ์ผํ ๊ธฐ๋ณธ์
๋๋ค.</p>
- <p>์ด์ ์ฌ์ฉ์ ์
๋ ฅ ์์คํ
์ ์ถ๊ฐํด๋ด
์๋ค. ๋จ์ํ ํค๋ณด๋ ์ด๋ฒคํธ์ ์ง์ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ๋ณด๋ค ํด๋์ค๋ฅผ ๋ง๋ค์ด ์ฝ๋์ ๋ค๋ฅธ ๋ถ๋ถ์์๋ <code class="notranslate" translate="no">์ผ์ชฝ</code>, <code class="notranslate" translate="no">์ค๋ฅธ์ชฝ</code>์ ํ์ธํ ์ ์๋๋ก ํ๊ฒ ์ต๋๋ค. ์ด๋ฌ๋ฉด <code class="notranslate" translate="no">์ผ์ชฝ</code>, <code class="notranslate" translate="no">์ค๋ฅธ์ชฝ</code> ๋ฑ ๋ค์ํ ํค๋ฅผ ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ์ง์ ํ ์ ์๊ฒ ์ฃ . ๋จผ์ ํค๋ณด๋ ์ด๋ฒคํธ๋ถํฐ ์ง์ ํฉ์๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">/**
- * ํค ๋๋ ๋ฒํผ์ ์ํ๋ฅผ ์ถ์ ํฉ๋๋ค.
- *
- * ์ผ์ชฝ ๋ฐฉํฅํค๊ฐ ๋๋ ธ๋์ง ํ์ธํ๋ ค๋ฉด
- *
- * inputManager.keys.left.down
- *
- * ์ ํ์ธํ๊ณ , ํ์ฌ ํ๋ ์์์ ์ผ์ชฝ ํค๋ฅผ ๋๋ ๋์ง ํ์ธํ๋ ค๋ฉด
- *
- * inputManager.keys.left.justPressed
- *
- * ๋ฅผ ํ์ธํ๋ฉด ๋ฉ๋๋ค.
- *
- * ํ์ฌ ๋ฑ๋ก๋ ํค๋ 'left', 'right', 'a', 'b', 'up', 'down' ์
๋๋ค.
- **/
- class InputManager {
- constructor() {
- this.keys = {};
- const keyMap = new Map();
- const setKey = (keyName, pressed) => {
- const keyState = this.keys[keyName];
- keyState.justPressed = pressed && !keyState.down;
- keyState.down = pressed;
- };
- const addKey = (keyCode, name) => {
- this.keys[name] = { down: false, justPressed: false };
- keyMap.set(keyCode, name);
- };
- const setKeyFromKeyCode = (keyCode, pressed) => {
- const keyName = keyMap.get(keyCode);
- if (!keyName) {
- return;
- }
- setKey(keyName, pressed);
- };
- addKey(37, 'left');
- addKey(39, 'right');
- addKey(38, 'up');
- addKey(40, 'down');
- addKey(90, 'a');
- addKey(88, 'b');
- window.addEventListener('keydown', (e) => {
- setKeyFromKeyCode(e.keyCode, true);
- });
- window.addEventListener('keyup', (e) => {
- setKeyFromKeyCode(e.keyCode, false);
- });
- }
- update() {
- for (const keyState of Object.values(this.keys)) {
- if (keyState.justPressed) {
- keyState.justPressed = false;
- }
- }
- }
- }
- </pre>
- <p>์ ์ฝ๋๋ ํค๊ฐ ๋๋ ธ๋์ง, ๋๋์ง๋ฅผ ์ถ์ ํฉ๋๋ค. ํน์ ํค๋ฅผ ๋๋ ๋์ง ํ์ธํ๋ ค๋ฉด ์๋ฅผ ๋ค์ด <code class="notranslate" translate="no">inputManager.keys.left.down</code>์ ์ฒดํฌํ๋ฉด ๋๊ณ , ํด๋น ๊ฐ์ฒด์ <code class="notranslate" translate="no">justPressed</code>๋ฅผ ์ฒดํฌํ๋ฉด ์ฌ์ฉ์๊ฐ ํด๋น ํ๋ ์์์ ํค๋ฅผ ๋๋ ๋์ง ํ์ธํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ์ ํ๋ฅผ ๊ตฌํํ ๊ฒฝ์ฐ ์ ์ ๊ฐ ํค๋ฅผ ๋๋ฅด๊ณ ์๋์ง๋ฅผ ์ถ์ ํ ์ด์ ๋ ์๊ฒ ์ฃ . ๋จ์ํ ํด๋น ํ๋ ์์์ ํค๋ฅผ ๋๋ ๋์ง๋ง ํ์ธํ๋ฉด ๋ ๊ฒ๋๋ค.</p>
- <p>์ด์ <code class="notranslate" translate="no">InputManager</code>์ ์ธ์คํด์ค๋ฅผ ์์ฑํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const globals = {
- time: 0,
- deltaTime: 0,
- };
- const gameObjectManager = new GameObjectManager();
- +const inputManager = new InputManager();
- </pre>
- <p>๊ทธ๋ฆฌ๊ณ ๋ ๋๋ง ๋ฃจํ์์ <code class="notranslate" translate="no">update</code> ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ก ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(now) {
- ...
- gameObjectManager.update();
- + inputManager.update();
- ...
- }
- </pre>
- <p><code class="notranslate" translate="no">gameObjectManager.update</code> ์ ์ ์ด ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด <code class="notranslate" translate="no">justPressed</code>๊ฐ ํญ์ <code class="notranslate" translate="no">false</code>์ผ ํ
๋ <code class="notranslate" translate="no">gameObjectManager.update</code> ๋ค์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ก ํ์ต๋๋ค.</p>
- <p>์ด์ <code class="notranslate" translate="no">Player</code> ์ปดํฌ๋ํธ์ ์ฌ์ฉ์ ์
๋ ฅ์ ์ถ๊ฐํด๋ด
์๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const kForward = new THREE.Vector3(0, 0, 1);
- const globals = {
- time: 0,
- deltaTime: 0,
- + moveSpeed: 16,
- };
- class Player extends Component {
- constructor(gameObject) {
- super(gameObject);
- const model = models.knight;
- this.skinInstance = gameObject.addComponent(SkinInstance, model);
- this.skinInstance.setAnimation('Run');
- + this.turnSpeed = globals.moveSpeed / 4;
- }
- + update() {
- + const { deltaTime, moveSpeed } = globals;
- + const { transform } = this.gameObject;
- + const delta = (inputManager.keys.left.down ? 1 : 0) +
- + (inputManager.keys.right.down ? -1 : 0);
- + transform.rotation.y += this.turnSpeed * delta * deltaTime;
- + transform.translateOnAxis(kForward, moveSpeed * deltaTime);
- + }
- }
- </pre>
- <p>์ ์ฝ๋์์๋ ํ๋ ์ด์ด๋ฅผ ์์ผ๋ก ์์ง์ด๊ธฐ ์ํด <a href="/docs/#api/ko/core/Object3D.transformOnAxis"><code class="notranslate" translate="no">Object3D.transformOnAxis</code></a>๋ฅผ ์ฌ์ฉํ์ต๋๋ค. <a href="/docs/#api/ko/core/Object3D.transformOnAxis"><code class="notranslate" translate="no">Object3D.transformOnAxis</code></a>๋ ์ง์ญ ๊ณต๊ฐ์ ๊ธฐ์ค์ผ๋ก ํ๊ธฐ์ ํด๋น ๊ฐ์ฒด๊ฐ ์ฅ๋ฉด์ ๋ฃจํธ(root) ์์์ ์ํ ๋๋ง ์ ์์ ์ผ๋ก ์๋ํฉ๋๋ค. <a class="footnote" href="#parented" id="parented-backref">1</a></p>
- <p>๋ํ ์ ์ญ ๊ฐ์ฒด์ <code class="notranslate" translate="no">moveSpeed</code>๋ฅผ ์ถ๊ฐํ๊ณ ์ด๋ฅผ <code class="notranslate" translate="no">turnSpeed</code>์ ๊ธฐ์ค์ผ๋ก ์ผ์์ต๋๋ค. ์ด๋ ์บ๋ฆญํฐ๊ฐ ๋ชฉํ๋ฅผ ํฅํด ์๋์ ์ผ๋ก ๋น ๋ฅด๊ฒ ๋๋๋ก ๋ง๋ ๊ฒ์ผ๋ก, ์ด <code class="notranslate" translate="no">turnSpeed</code>์ ๊ฐ์ด ๋๋ฌด ์๋ค๋ฉด ์บ๋ฆญํฐ๋ ๋ชฉํ ์ฃผ์๋ฅผ ๋น๋น ๋๊ธฐ๋ง ํ๊ณ ์ ๋ ๋ชฉํ์ ๋ฟ์ง๋ ๋ชปํ ๊ฒ๋๋ค. ๋ฌผ๋ก ์ ๊ฐ์ ์ด๋ค ์ํ์ ๊ณต์์ ์ฌ์ฉํ ๊ฒ์ด ์๋๋๋ค. ๊ทธ๋ฅ ๋์ถฉ ๋๋ ค ๋ฃ์ ๊ฒ์ด์ฃ .</p>
- <p>์ด๋๋ก๋ ์์ ๋ ์ ์๋ํ ํ
์ง๋ง ํ๋ ์ด์ด๊ฐ ํ๋ฉด์ ๋ฒ์ด๋๋ฉด ์บ๋ฆญํฐ๊ฐ ์ด๋ ์๋์ง ์ฐพ๊ธฐ๊ฐ ์ด๋ ค์ธ ๊ฒ๋๋ค. ์ผ๋จ ํ๋ฉด์์ ๋ฒ์ด๋ ๋ค ์ผ์ ์๊ฐ์ด ์ง๋๋ฉด ํ๋ ์ด์ด๋ฅผ ๋ค์ ์ค์ ์ผ๋ก ์๊ฐ์ด๋์ํค๊ธฐ๋ก ํฉ์๋ค. Three.js์ <a href="/docs/#api/ko/math/Frustum"><code class="notranslate" translate="no">Frustum</code></a> ํด๋์ค๋ฅผ ์ด์ฉํ๋ฉด ํน์ ์ ์ด ์นด๋ฉ๋ผ์ ์ ๋์ฒด(frustum) ์์ ์๋์ง ์ ์ ์์ต๋๋ค.</p>
- <p>๋จผ์ ์นด๋ฉ๋ผ๋ก ์ ๋์ฒด๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค. ํ๋ ์ด์ด ์ปดํฌ๋ํธ์์ ์ด๊ฑธ ์ฒ๋ฆฌํ ์๋ ์์ง๋ง, ๋ค๋ฅธ ์์๋ ์ด ๋ฐฉ๋ฒ์ ์จ์ผ ํ ์ ์์ผ๋ ์นด๋ฉ๋ผ์ ์ ๋์ฒด๋ฅผ ๊ด๋ฆฌํ๋ ์๋ก์ด ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ฒ ์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class CameraInfo extends Component {
- constructor(gameObject) {
- super(gameObject);
- this.projScreenMatrix = new THREE.Matrix4();
- this.frustum = new THREE.Frustum();
- }
- update() {
- const { camera } = globals;
- this.projScreenMatrix.multiplyMatrices(
- camera.projectionMatrix,
- camera.matrixWorldInverse);
- this.frustum.setFromProjectionMatrix(this.projScreenMatrix);
- }
- }
- </pre>
- <p>๋ค์์ผ๋ก <code class="notranslate" translate="no">init</code> ํจ์์์ ๋ฐฉ๊ธ ๋ง๋ ์ปดํฌ๋ํธ๋ก ์๋ก์ด GameObject๋ฅผ ์ถ๊ฐํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
- // ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ์จ๊น๋๋ค.
- const loadingElem = document.querySelector('#loading');
- loadingElem.style.display = 'none';
- prepModelsAndAnimations();
- + {
- + const gameObject = gameObjectManager.createGameObject(camera, 'camera');
- + globals.cameraInfo = gameObject.addComponent(CameraInfo);
- + }
- {
- const gameObject = gameObjectManager.createGameObject(scene, 'player');
- gameObject.addComponent(Player);
- }
- }
- </pre>
- <p><code class="notranslate" translate="no">Player</code> ์ปดํฌ๋ํธ์ ๋ฐฉ๊ธ ๋ง๋ GameObject๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player extends Component {
- constructor(gameObject) {
- super(gameObject);
- const model = models.knight;
- this.skinInstance = gameObject.addComponent(SkinInstance, model);
- this.skinInstance.setAnimation('Run');
- this.turnSpeed = globals.moveSpeed / 4;
- + this.offscreenTimer = 0;
- + this.maxTimeOffScreen = 3;
- }
- update() {
- - const { deltaTime, moveSpeed } = globals;
- + const { deltaTime, moveSpeed, cameraInfo } = globals;
- const { transform } = this.gameObject;
- const delta = (inputManager.keys.left.down ? 1 : 0) +
- (inputManager.keys.right.down ? -1 : 0);
- transform.rotation.y += this.turnSpeed * delta * deltaTime;
- transform.translateOnAxis(kForward, moveSpeed * deltaTime);
- + const { frustum } = cameraInfo;
- + if (frustum.containsPoint(transform.position)) {
- + this.offscreenTimer = 0;
- + } else {
- + this.offscreenTimer += deltaTime;
- + if (this.offscreenTimer >= this.maxTimeOffScreen) {
- + transform.position.set(0, 0, 0);
- + }
- + }
- }
- }
- </pre>
- <p>์์ ๋ฅผ ์คํํ๊ธฐ ์ ์ ๋ชจ๋ฐ์ผ ํ๊ฒฝ์ ์ํ ํฐ์น ์ธํฐํ์ด์ค๋ฅผ ์ถ๊ฐํ๊ฒ ์ต๋๋ค. ๋จผ์ ํฐ์น ์ด๋ฒคํธ๋ฅผ ๋ฐ์ HTML ์์๋ฅผ ๋ง๋ญ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-html" translate="no"><body>
- <canvas id="c"></canvas>
- + <div id="ui">
- + <div id="left"><img src="../resources/images/left.svg"></div>
- + <div style="flex: 0 0 40px;"></div>
- + <div id="right"><img src="../resources/images/right.svg"></div>
- + </div>
- <div id="loading">
- <div>
- <div>...loading...</div>
- <div class="progress"><div id="progressbar"></div></div>
- </div>
- </div>
- </body>
- </pre>
- <p>๋ฒํผ์ ์คํ์ผ๋ ์์ฑํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-css" translate="no">#ui {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- display: flex;
- justify-items: center;
- align-content: stretch;
- }
- #ui>div {
- display: flex;
- align-items: flex-end;
- flex: 1 1 auto;
- }
- .bright {
- filter: brightness(2);
- }
- #left {
- justify-content: flex-end;
- }
- #right {
- justify-content: flex-start;
- }
- #ui img {
- padding: 10px;
- width: 80px;
- height: 80px;
- display: block;
- }
- </pre>
- <p>์ ๊ฐ ์ฌ์ฉํ ๋ฐฉ๋ฒ์ ํ๋์ div ์์, <code class="notranslate" translate="no">#ui</code>๋ก ํ๋ฉด ์ ์ฒด๋ฅผ ์ฑ์ฐ๊ณ , ํด๋น ์์์ ์์์ผ๋ก ํ๋ฉด์ ๋๋ต ๋ฐ์ ์ฐจ์งํ๋ <code class="notranslate" translate="no">#left</code>์ <code class="notranslate" translate="no">#right</code>๋ฅผ ๊ฐ๊ฐ ์์ชฝ์, ๊ฐ์ด๋ฐ์๋ 40px์ง๋ฆฌ ๊ตฌ๋ถ์ ์ ๋ฃ์ด ํ๋ฉด ์ ์ฒด๊ฐ ์ด๋ฒคํธ๋ฅผ ๊ฐ์งํ๋๋ก ํ ๊ฒ์
๋๋ค. ์ด๋ฌ๋ฉด ์ฌ์ฉ์๊ฐ ์ผ์ชฝ ํ์ดํ๋ฅผ ๋๋ฅธ ๋ค ์ผ์ชฝ์์ ์ค๋ฅธ์ชฝ์ผ๋ก ์๊ฐ๋ฝ์ ์์ง์์ ๋ <code class="notranslate" translate="no">InputManager</code>์ <code class="notranslate" translate="no">keys.left</code>๊ณผ <code class="notranslate" translate="no">keys.right</code>๋ฅผ ์
๋ฐ์ดํธํ ์ ์๊ฒ ์ฃ . ๊ตณ์ด ์์ ํ์ดํ๋ฅผ ๋๋ฅด๋๋ผ ๊ณ ์ํ์ง ์์๋ ๋๋ ์ด ํธ์ด ํจ์ฌ ๋์ ๊ฒ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class InputManager {
- constructor() {
- this.keys = {};
- const keyMap = new Map();
- const setKey = (keyName, pressed) => {
- const keyState = this.keys[keyName];
- keyState.justPressed = pressed && !keyState.down;
- keyState.down = pressed;
- };
- const addKey = (keyCode, name) => {
- this.keys[name] = { down: false, justPressed: false };
- keyMap.set(keyCode, name);
- };
- const setKeyFromKeyCode = (keyCode, pressed) => {
- const keyName = keyMap.get(keyCode);
- if (!keyName) {
- return;
- }
- setKey(keyName, pressed);
- };
- addKey(37, 'left');
- addKey(39, 'right');
- addKey(38, 'up');
- addKey(40, 'down');
- addKey(90, 'a');
- addKey(88, 'b');
- window.addEventListener('keydown', (e) => {
- setKeyFromKeyCode(e.keyCode, true);
- });
- window.addEventListener('keyup', (e) => {
- setKeyFromKeyCode(e.keyCode, false);
- });
- + const sides = [
- + { elem: document.querySelector('#left'), key: 'left' },
- + { elem: document.querySelector('#right'), key: 'right' },
- + ];
- +
- + const clearKeys = () => {
- + for (const {key} of sides) {
- + setKey(key, false);
- + }
- + };
- +
- + const handleMouseMove = (e) => {
- + e.preventDefault();
- + // this is needed because we call preventDefault();
- + // we also gave the canvas a tabindex so it can
- + // become the focus
- + canvas.focus();
- + window.addEventListener('pointermove', handleMouseMove);
- + window.addEventListener('pointerup', handleMouseUp);
- +
- + for (const {elem, key} of sides) {
- + let pressed = false;
- + const rect = elem.getBoundingClientRect();
- + const x = e.clientX;
- + const y = e.clientY;
- + const inRect = x >= rect.left && x < rect.right &&
- + y >= rect.top && y < rect.bottom;
- + if (inRect) {
- + pressed = true;
- + }
- + setKey(key, pressed);
- + }
- + };
- +
- + function handleMouseUp() {
- + clearKeys();
- + window.removeEventListener('pointermove', handleMouseMove, {passive: false});
- + window.removeEventListener('pointerup', handleMouseUp);
- + }
- +
- + const uiElem = document.querySelector('#ui');
- + uiElem.addEventListener('pointerdown', handleMouseMove, {passive: false});
- +
- + uiElem.addEventListener('touchstart', (e) => {
- + // prevent scrolling
- + e.preventDefault();
- + }, {passive: false});
- }
- update() {
- for (const keyState of Object.values(this.keys)) {
- if (keyState.justPressed) {
- keyState.justPressed = false;
- }
- }
- }
- }
- </pre>
- <p>์ด์ ํ์ดํ ํค๋ ํ๋ฉด์ ํฐ์นํด ์บ๋ฆญํฐ๋ฅผ ์์ง์ผ ์ ์์ ๊ฒ๋๋ค.</p>
- <p></p><div translate="no" class="threejs_example_container notranslate">
- <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-player-input.html"></iframe></div>
- <a class="threejs_center" href="/manual/examples/game-player-input.html" target="_blank">์ ํญ์์ ๋ณด๊ธฐ</a>
- </div>
- <p></p>
- <p>๋ฌผ๋ก ํ๋ ์ด์ด๊ฐ ํ๋ฉด ๋ฐ์ผ๋ก ๋๊ฐ์ ๋ ์นด๋ฉ๋ผ๋ฅผ ์์ง์ด๊ฑฐ๋, "ํ๋ฉด ๋ฐ = ์ฃฝ์"์ด๋ผ๋ ์ค์ ์ ๋ฃ์ ์๋ ์์ต๋๋ค. ํ์ง๋ง ์ด๊ฒ๊น์ง ๋ค๋ฃฌ๋ค๋ฉด ์ ๊ทธ๋๋ ๊ธด ๊ธ์ด ๋ ๊ธธ์ด์ง ํ
๋ ์ด ๋ฐฉ๋ฒ์ผ๋ก ๋ง์กฑํ๊ฒ ์ต๋๋ค.</p>
- <p>์ด์ ๋๋ฌผ์ ์ถ๊ฐํด๋ด
์๋ค. <code class="notranslate" translate="no">Player</code>์ ๋น์ทํ ๋ฐฉ๋ฒ์ผ๋ก <code class="notranslate" translate="no">Animal</code> ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ญ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Animal extends Component {
- constructor(gameObject, model) {
- super(gameObject);
- const skinInstance = gameObject.addComponent(SkinInstance, model);
- skinInstance.mixer.timeScale = globals.moveSpeed / 4;
- skinInstance.setAnimation('Idle');
- }
- }
- </pre>
- <p>์ ์ฝ๋์์๋ <a href="/docs/#api/ko/animation/AnimationMixer.timeScale"><code class="notranslate" translate="no">AnimationMixer.timeScale</code></a>์ ์ค์ ํด ์ ๋๋ฉ์ด์
์๋๊ฐ ์ด๋ ์๋์ ๋น๋กํ๋๋ก ๋ง๋ค์์ต๋๋ค. ์ด๋ฌ๋ฉด ์ด๋ ์๋์ ๊ฐ์ด ์ ๋๋ฉ์ด์
์๋๊ฐ ๋นจ๋ผ์ง๊ณ ๋๋ ค์ง๊ฒ ์ฃ .</p>
- <p>๋ค์์ผ๋ก <code class="notranslate" translate="no">init</code> ํจ์์์ ๊ฐ ๋๋ฌผ์ ๋ฐฐ์นํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
- // ํ๋ก๊ทธ๋์ค ๋ฐ๋ฅผ ์จ๊น๋๋ค.
- const loadingElem = document.querySelector('#loading');
- loadingElem.style.display = 'none';
- prepModelsAndAnimations();
- {
- const gameObject = gameObjectManager.createGameObject(camera, 'camera');
- globals.cameraInfo = gameObject.addComponent(CameraInfo);
- }
- {
- const gameObject = gameObjectManager.createGameObject(scene, 'player');
- globals.player = gameObject.addComponent(Player);
- globals.congaLine = [gameObject];
- }
- + const animalModelNames = [
- + 'pig',
- + 'cow',
- + 'llama',
- + 'pug',
- + 'sheep',
- + 'zebra',
- + 'horse',
- + ];
- + animalModelNames.forEach((name, ndx) => {
- + const gameObject = gameObjectManager.createGameObject(scene, name);
- + gameObject.addComponent(Animal, models[name]);
- + gameObject.transform.position.x = (ndx + 1) * 5;
- + });
- }
- </pre>
- <p>๋๋ฌผ๋ค์ ๋ฐฐ์นํ๊ณ ๋๋ด๋ฉด ์ฌ์ฌํ๋ ๋ญ๊ฐ๋ฅผ ์ถ๊ฐํด์ผ๊ฒ ๋ค์.</p>
- <p>๋๋ฌผ์ด ํ๋ ์ด์ด๋ฅผ ๋ฐ๋ผ ๊ธฐ์ฐจ๋์ด๏ผ๋ฅผ ํ๊ฒ ํด๋ด
์๋ค. ํ๋ ์ด์ด๊ฐ ๋๋ฌผ์ ๊ฐ๊น์ด ๊ฐ์ ๋๋ง ๊ธฐ์ฐจ์ ํฉ๋ฅํ๋๋ก ํ๊ฒ ์ต๋๋ค. ์ด๋ฅผ ๊ตฌํํ๋ ค๋ฉด ์๋์ ๊ฐ์ ๋ชจ์
(์ํ, state)์ด ํ์ํ ๊ฒ๋๋ค.</p>
- <p>โป ์๋ฌธ์ "conga line"์
๋๋ค. ๊ธฐ์ฐจ๋์ด์ ์ ์ฌํ ๊ผฌ๋ฆฌ์๊ธฐ ๋์ด๋ก, ์ฐ๋ฆฌ์๊ฒ ๋ ์ต์ํ "๊ธฐ์ฐจ๋์ด"๋ก ์์ญํ์ต๋๋ค. ์ญ์ฃผ.</p>
- <ul>
- <li><p>๊ฐ๋งํ ์ ์๋ ๋ชจ์
(Idle):</p>
- <p>ํ๋ ์ด์ด๊ฐ ๊ฐ๊น์์ง๊ธฐ ์ ๊น์ง์ ๋ชจ์
์
๋๋ค.</p>
- </li>
- <li><p>๊ธฐ์ฐจ์ ๋์ ๊ฐ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋ชจ์
(Wait for End of Line):</p>
- <p>ํ๋ ์ด์ด๊ฐ ๋๋ฌผ๊ณผ ๋ฟ๋๋ผ๋ ๊ธฐ์ฐจ์ ๋์ ํฉ๋ฅํด์ผ ํ๋ฏ๋ก ๊ทธ ์ ๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋ชจ์
์
๋๋ค.</p>
- </li>
- <li><p>๋ฐ๋ผ๋ถ๊ธฐ(Go to Last):</p>
- <p>์์ ์ด ๋ฐ๋ผ๊ฐ ๋์์ด ์๋ ์์น๋ก ์ด๋ํจ๊ณผ ๋์์ ๋ฐ๋ผ๊ฐ ๋์์ด ์ด๋ ์๋์ง ๊ธฐ๋กํฉ๋๋ค.</p>
- </li>
- <li><p>๋ฐ๋ผ๊ฐ๊ธฐ(Follow):</p>
- <p>์์ ์ด ๋ฐ๋ผ๊ฐ๋ ๋์์ ํ์ฌ ์์น๋ฅผ ๊ธฐ๋กํจ๊ณผ ๋์์ ๋์์ด ์์๋ ์์น๋ก ์ด๋ํฉ๋๋ค.</p>
- </li>
- </ul>
- <p>์ด๋ฐ ์ํ๋ฅผ ๋ค๋ฃฐ ๋ฐฉ๋ฒ์ ์์ฃผ ๋ค์ํฉ๋๋ค. ๋ณดํต์ <a href="https://www.google.com/search?q=finite+state+machine">์ ํ ์ํ ๊ธฐ๊ณ(Finite State Machine)</a>์ ์ด๋ฐ ์ํ๋ฅผ ๋ค๋ฃฐ ํฌํผ ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฃ .</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class FiniteStateMachine {
- constructor(states, initialState) {
- this.states = states;
- this.transition(initialState);
- }
- get state() {
- return this.currentState;
- }
- transition(state) {
- const oldState = this.states[this.currentState];
- if (oldState && oldState.exit) {
- oldState.exit.call(this);
- }
- this.currentState = state;
- const newState = this.states[state];
- if (newState.enter) {
- newState.enter.call(this);
- }
- }
- update() {
- const state = this.states[this.currentState];
- if (state.update) {
- state.update.call(this);
- }
- }
- }
- </pre>
- <p>์ ํด๋์ค๋ ์์ ๋งํ ํฌํผ ํด๋์ค๋ฅผ ๊ฐ๋จํ ๊ตฌํํ ๊ฒ์
๋๋ค. ํด๋์ค๋ ์์ฑ ์ ์ํ๋ค์ ๊ฐ์ฒด๋ฅผ ๋ฐ๊ณ , ๊ฐ ์ํ์๋ <code class="notranslate" translate="no">enter</code>, <code class="notranslate" translate="no">update</code>, <code class="notranslate" translate="no">exit</code>์ด๋ผ๋ ๋ฉ์๋๊ฐ ์์ต๋๋ค. ์ํ๋ฅผ ๋ฐ๊พธ๋ ค๋ฉด <code class="notranslate" translate="no">FiniteStateMachine.transition</code>์ ํธ์ถํ ๋ ์๋ก์ด ์ด๋ฆ์ ๋๊ฒจ์ฃผ๋ฉด ๋์ฃ . ๋ง์ฝ ํ์ฌ ์ํ์ <code class="notranslate" translate="no">exit</code> ๋ฉ์๋๊ฐ ์๋ค๋ฉด ํด๋น ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์๋ก์ด ์ํ์ <code class="notranslate" translate="no">enter</code> ๋ฉ์๋๊ฐ ์์ ๊ฒฝ์ฐ <code class="notranslate" translate="no">enter</code> ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก ๋งค ํ๋ ์๋ง๋ค <code class="notranslate" translate="no">FiniteStateMachine.update</code>๋ฅผ ํธ์ถํ๋ฉด ๊ฐ ์ํ์ <code class="notranslate" translate="no">update</code> ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.</p>
- <p>์ด์ ์ด ํด๋์ค๋ฅผ ํ์ฉํด ๋๋ฌผ๋ค์ ์ํ๋ฅผ ๋ฐ๊ฟ๋ด
์๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">// ๋งค๊ฐ๋ณ์ obj1๊ณผ obj2์ด ๊ฐ๊น๋ค๋ฉด true๋ฅผ ๋ฐํํฉ๋๋ค.
- function isClose(obj1, obj1Radius, obj2, obj2Radius) {
- const minDist = obj1Radius + obj2Radius;
- const dist = obj1.position.distanceTo(obj2.position);
- return dist < minDist;
- }
- // v ์ ๊ฐ์ด -min๊ณผ +min ์ฌ์ด๊ฐ ๋๋๋ก ํฉ๋๋ค.
- function minMagnitude(v, min) {
- return Math.abs(v) > min
- ? min * Math.sign(v)
- : v;
- }
- const aimTowardAndGetDistance = function() {
- const delta = new THREE.Vector3();
- return function aimTowardAndGetDistance(source, targetPos, maxTurn) {
- delta.subVectors(targetPos, source.position);
- // ๋ฐ๋ผ๋ณผ ๋ฐฉํฅ์ ๊ณ์ฐํฉ๋๋ค.
- const targetRot = Math.atan2(delta.x, delta.z) + Math.PI * 1.5;
- // ๋ ๊ฐ๊น์ด ๋ฐฉํฅ์ผ๋ก ํ์ ํฉ๋๋ค.
- const deltaRot = (targetRot - source.rotation.y + Math.PI * 1.5) % (Math.PI * 2) - Math.PI;
- // maxTurn๋ณด๋ค ๋น ๋ฅธ ์๋๋ก ๋์ง ์๋๋ก ํฉ๋๋ค.
- const deltaRotation = minMagnitude(deltaRot, maxTurn);
- // rotation ๊ฐ์ 0์์ Math.PI * 2 ์ฌ์ด๋ก ์ ์งํฉ๋๋ค.
- source.rotation.y = THREE.MathUtils.euclideanModulo(
- source.rotation.y + deltaRotation, Math.PI * 2);
- // ๋ชฉํ๊น์ง์ ๊ฑฐ๋ฆฌ๋ฅผ ๋ฐํํฉ๋๋ค.
- return delta.length();
- };
- }();
- class Animal extends Component {
- constructor(gameObject, model) {
- super(gameObject);
- + const hitRadius = model.size / 2;
- const skinInstance = gameObject.addComponent(SkinInstance, model);
- skinInstance.mixer.timeScale = globals.moveSpeed / 4;
- + const transform = gameObject.transform;
- + const playerTransform = globals.player.gameObject.transform;
- + const maxTurnSpeed = Math.PI * (globals.moveSpeed / 4);
- + const targetHistory = [];
- + let targetNdx = 0;
- +
- + function addHistory() {
- + const targetGO = globals.congaLine[targetNdx];
- + const newTargetPos = new THREE.Vector3();
- + newTargetPos.copy(targetGO.transform.position);
- + targetHistory.push(newTargetPos);
- + }
- +
- + this.fsm = new FiniteStateMachine({
- + idle: {
- + enter: () => {
- + skinInstance.setAnimation('Idle');
- + },
- + update: () => {
- + // ํ๋ ์ด์ด๊ฐ ๊ทผ์ฒ์ ์๋์ง ํ์ธํฉ๋๋ค.
- + if (isClose(transform, hitRadius, playerTransform, globals.playerRadius)) {
- + this.fsm.transition('waitForEnd');
- + }
- + },
- + },
- + waitForEnd: {
- + enter: () => {
- + skinInstance.setAnimation('Jump');
- + },
- + update: () => {
- + // ๊ธฐ์ฐจ์ ๊ฐ์ฅ ๋ง์ง๋ง์ ์๋ gameObject๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- + const lastGO = globals.congaLine[globals.congaLine.length - 1];
- + const deltaTurnSpeed = maxTurnSpeed * globals.deltaTime;
- + const targetPos = lastGO.transform.position;
- + aimTowardAndGetDistance(transform, targetPos, deltaTurnSpeed);
- + // ๊ธฐ์ฐจ์ ๋ง์ง๋ง์ ์๋ ์์๊ฐ ๊ทผ์ฒ์ ์๋์ง ํ์ธํฉ๋๋ค.
- + if (isClose(transform, hitRadius, lastGO.transform, globals.playerRadius)) {
- + this.fsm.transition('goToLast');
- + }
- + },
- + },
- + goToLast: {
- + enter: () => {
- + // ๋ฐ๋ผ๊ฐ ๋์์ ๊ธฐ๋กํฉ๋๋ค. remember who we're following
- + targetNdx = globals.congaLine.length - 1;
- + // ๊ธฐ์ฐจ์ ๋ง์ง๋ง์ ์ค์ค๋ก๋ฅผ ์ถ๊ฐํฉ๋๋ค.
- + globals.congaLine.push(gameObject);
- + skinInstance.setAnimation('Walk');
- + },
- + update: () => {
- + addHistory();
- + // ๊ธฐ๋ก๋ ์์น ์ค ๊ฐ์ฅ ๋์ค ์์น๋ก ์ด๋ํฉ๋๋ค.
- + const targetPos = targetHistory[0];
- + const maxVelocity = globals.moveSpeed * globals.deltaTime;
- + const deltaTurnSpeed = maxTurnSpeed * globals.deltaTime;
- + const distance = aimTowardAndGetDistance(transform, targetPos, deltaTurnSpeed);
- + const velocity = distance;
- + transform.translateOnAxis(kForward, Math.min(velocity, maxVelocity));
- + if (distance <= maxVelocity) {
- + this.fsm.transition('follow');
- + }
- + },
- + },
- + follow: {
- + update: () => {
- + addHistory();
- + // ๊ฐ์ฅ ์ค๋๋ ์์น๊ฐ์ ์ง์ฐ๊ณ ์๊ธฐ ์์ ์ ์์น๊ฐ์ ์ถ๊ฐํฉ๋๋ค.
- + const targetPos = targetHistory.shift();
- + transform.position.copy(targetPos);
- + const deltaTurnSpeed = maxTurnSpeed * globals.deltaTime;
- + aimTowardAndGetDistance(transform, targetHistory[0], deltaTurnSpeed);
- + },
- + },
- + }, 'idle');
- + }
- + update() {
- + this.fsm.update();
- + }
- }
- </pre>
- <p>ํ ๋ฒ์ ๋๋ฌด ๋ง์ ์ฝ๋๋ฅผ ๋ณด์ฌ์ค ๋ฏํ์ง๋ง ์ ์ฝ๋๋ ๋ฐฉ๊ธ ์ธ๊ธํ ์ญํ ์ ํฉ๋๋ค. ๊ฐ ์ํ์ ๋ํ ์ฝ๋๋ฅผ ๋ณด๊ณ ์ด๋ค ์์ผ๋ก ์๋ํ๋์ง ๋ถ์ํด๋ณด๊ธฐ ๋ฐ๋๋๋ค.</p>
- <p>์ฌ๊ธฐ์ ๋ช ๊ฐ์ง ์์๋ฅผ ์ถ๊ฐํด์ผ ํฉ๋๋ค. ํ๋ ์ด์ด๊ฐ ์๊ธฐ ์์ ์ ์ ์ญ ๊ฐ์ฒด(globals)์ ์ถ๊ฐํด ๋ค๋ฅธ ๋๋ฌผ์ด ์์ ์ ์์น๋ฅผ ์ถ์ ํ๋๋ก ํด์ผ ํ๊ณ , ๋ ๊ธฐ์ฐจ์ ๋จธ๋ฆฌ๋ฅผ ํ๋ ์ด์ด์ <code class="notranslate" translate="no">GameObject</code>๋ก ์ง์ ํด์ผ ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
- ...
- {
- const gameObject = gameObjectManager.createGameObject(scene, 'player');
- + globals.player = gameObject.addComponent(Player);
- + globals.congaLine = [gameObject];
- }
- }
- </pre>
- <p>๊ฐ ๋ชจ๋ธ์ ํฌ๊ธฐ๋ ๊ณ์ฐํด์ผ ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function prepModelsAndAnimations() {
- + const box = new THREE.Box3();
- + const size = new THREE.Vector3();
- Object.values(models).forEach(model => {
- + box.setFromObject(model.gltf.scene);
- + box.getSize(size);
- + model.size = size.length();
- const animsByName = {};
- model.gltf.animations.forEach((clip) => {
- animsByName[clip.name] = clip;
- // ์ด๋ฐ ๋ถ๋ถ์ .blend ํ์ผ์์ ์์ ํ๋ ๊ฒ ์ข์ต๋๋ค.
- if (clip.name === 'Walk') {
- clip.duration /= 2;
- }
- });
- model.animations = animsByName;
- });
- }
- </pre>
- <p>๊ทธ๋ฆฌ๊ณ ํ๋ ์ด์ด๊ฐ ์๊ธฐ ์์ ์ ํฌ๊ธฐ๋ฅผ ๊ธฐ๋กํ๋๋ก ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player extends Component {
- constructor(gameObject) {
- super(gameObject);
- const model = models.knight;
- + globals.playerRadius = model.size / 2;
- </pre>
- <p>์ด์ ์ ์๊ฐํด๋ณด๋ ํ๋ ์ด์ด๊ฐ ์๋๋ผ ๊ธฐ์ฐจ์ ๋จธ๋ฆฌ๋ฅผ ๋ฐ๋ผ๋ณด๊ฒ ํ๋ ํธ์ด ๋ ๋์๊ฒ ๋ค์. ์ด๊ฑด ๋์ค์ ๋์์ ๊ณ ์น๋๋ก ํ๊ฒ ์ต๋๋ค.</p>
- <p>์์ ๋ฅผ ์ฒ์ ๋ง๋ค์์ ๋๋ ๋๋ฌผ๋ค์ด ๋ชจ๋ ๊ฐ์ ํฌ๊ธฐ์ ๊ฒฝ๊ณ ์(radius)์ ์ผ์ง๋ง, ์ด๋ ๊ฒ ํ๊ณ ๋ณด๋ ๋ง๊ณผ ํผ๊ทธ(๊ฐ์์ง)์ ํฌ๊ธฐ๊ฐ ๊ฐ์ ๊ฒ ๋ง์ด ์ ๋๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค. ๊ทธ๋์ ๊ฐ ๋ชจ๋ธ์ ํฌ๊ธฐ์ ๋ฐ๋ผ ๊ฒฝ๊ณ ์์ ๋ฐ๋ก ์ง์ ํ์ฃ . ๊ทธ๋ฆฌ๊ณ ์ํ๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด ์ข๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์ด ์ํ๋ฅผ ๋ณด์ฌ ์ค <code class="notranslate" translate="no">StatusDisplayHelper</code> ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ์ต๋๋ค.</p>
- <p>๋ํ <a href="/docs/#api/ko/helpers/PolarGridHelper"><code class="notranslate" translate="no">PolarGridHelper</code></a>๋ฅผ ์จ ๊ฐ ์บ๋ฆญํฐ์ ๊ฒฝ๊ณ ์์ด ๋ณด์ด๋๋ก ํ๊ณ , <a href="align-html-elements-to-3d.html">HTML ์์๋ฅผ 3D๋ก ์ ๋ ฌํ๊ธฐ</a>์์ ์ผ๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ฐ ์บ๋ฆญํฐ์ ์ํ๋ฅผ HTML๋ก ๋ณด์ฌ์ฃผ๋๋ก ํ์ต๋๋ค.</p>
- <p>๋จผ์ ๊ฐ ์์๋ฅผ ๋ด์ HTML์ ์ถ๊ฐํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-html" translate="no"><body>
- <canvas id="c"></canvas>
- <div id="ui">
- <div id="left"><img src="../resources/images/left.svg"></div>
- <div style="flex: 0 0 40px;"></div>
- <div id="right"><img src="../resources/images/right.svg"></div>
- </div>
- <div id="loading">
- <div>
- <div>...loading...</div>
- <div class="progress"><div id="progressbar"></div></div>
- </div>
- </div>
- + <div id="labels"></div>
- </body>
- </pre>
- <p>CSS๋ ์์ฑํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-css" translate="no">#labels {
- position: absolute; /* ๊ธฐ์ค ์์ ์๋ก ์ฌ๋ผ๊ฐ๋๋ก ํฉ๋๋ค. */
- left: 0; /* ๊ธฐ์ค ์์ ์ผ์ชฝ ์๋ก ์ ๋ ฌํฉ๋๋ค. */
- top: 0;
- color: white;
- width: 100%;
- height: 100%;
- overflow: hidden;
- pointer-events: none;
- }
- #labels>div {
- position: absolute; /* ๊ธฐ์ค ์์๋ฅผ ๊ธฐ์ค์ผ๋ก ํฉ๋๋ค. */
- left: 0; /* ๊ธฐ์ค ์์์ ์ผ์ชฝ ์๋ก ์ ๋ ฌํฉ๋๋ค. */
- top: 0;
- font-size: large;
- font-family: monospace;
- user-select: none; /* ํ
์คํธ๋ฅผ ์ ํํ ์ ์๋๋ก ํฉ๋๋ค. */
- text-shadow: /* ๊ธ์์ ๊ฒ์ ์ค๊ณฝ์ ์ ๋ฃ์ต๋๋ค. */
- -1px -1px 0 #000,
- 0 -1px 0 #000,
- 1px -1px 0 #000,
- 1px 0 0 #000,
- 1px 1px 0 #000,
- 0 1px 0 #000,
- -1px 1px 0 #000,
- -1px 0 0 #000;
- }
- </pre>
- <p>์๋๋ <code class="notranslate" translate="no">StateDisplayHelper</code> ์ปดํฌ๋ํธ์
๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const labelContainerElem = document.querySelector('#labels');
- class StateDisplayHelper extends Component {
- constructor(gameObject, size) {
- super(gameObject);
- this.elem = document.createElement('div');
- labelContainerElem.appendChild(this.elem);
- this.pos = new THREE.Vector3();
- this.helper = new THREE.PolarGridHelper(size / 2, 1, 1, 16);
- gameObject.transform.add(this.helper);
- }
- setState(s) {
- this.elem.textContent = s;
- }
- setColor(cssColor) {
- this.elem.style.color = cssColor;
- this.helper.material.color.set(cssColor);
- }
- update() {
- const { pos } = this;
- const { transform } = this.gameObject;
- const { canvas } = globals;
- pos.copy(transform.position);
- /**
- * ํด๋น ์์น๊ฐ์ ์ ๊ทํํ๋ฉด x์ y ๊ฐ์ -1์์ +1 ์ฌ์ด์ ๊ฐ์ด ๋ฉ๋๋ค.
- * x = -1 ์ด๋ฉด ์ผ์ชฝ, y = -1 ์ด๋ฉด ์ค๋ฅธ์ชฝ์ด์ฃ .
- **/
- pos.project(globals.camera);
- // ์ ๊ทํํ ์์น๊ฐ์ CSS ์์น๊ฐ์ผ๋ก ๋ณํํฉ๋๋ค.
- const x = (pos.x * .5 + .5) * canvas.clientWidth;
- const y = (pos.y * -.5 + .5) * canvas.clientHeight;
- // HTML ์์๋ฅผ ํด๋น ์์น๋ก ์ฎ๊น๋๋ค.
- this.elem.style.transform = `translate(-50%, -50%) translate(${ x }px, ${ y }px)`;
- }
- }
- </pre>
- <p>๋๋ฌผ ์ปดํฌ๋ํธ๋ฅผ ์์ฑํ ๋ ์ ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๋๋ก ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Animal extends Component {
- constructor(gameObject, model) {
- super(gameObject);
- + this.helper = gameObject.addComponent(StateDisplayHelper, model.size);
- ...
- }
- update() {
- this.fsm.update();
- + const dir = THREE.MathUtils.radToDeg(this.gameObject.transform.rotation.y);
- + this.helper.setState(`${ this.fsm.state }:${ dir.toFixed(0) }`);
- }
- }
- </pre>
- <p>์ถ๊ฐ๋ก lil-gui๋ฅผ ์ด์ฉํด ์ ๋๋ฒ๊น
์์๋ค๋ฅผ ์ผ๊ณ ๋ ์ ์๋๋ก ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
- import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
- import { SkeletonUtils } from 'three/addons/utils/SkeletonUtils.js';
- +import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
- </pre>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const gui = new GUI();
- +gui.add(globals, 'debug').onChange(showHideDebugInfo);
- +showHideDebugInfo();
- const labelContainerElem = document.querySelector('#labels');
- +function showHideDebugInfo() {
- + labelContainerElem.style.display = globals.debug ? '' : 'none';
- +}
- +showHideDebugInfo();
- class StateDisplayHelper extends Component {
- ...
- update() {
- + this.helper.visible = globals.debug;
- + if (!globals.debug) {
- + return;
- + }
- ...
- }
- }
- </pre>
- <p>๊ฒ์์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํ์ ์์ฑํ๋ค์.</p>
- <p></p><div translate="no" class="threejs_example_container notranslate">
- <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-conga-line.html"></iframe></div>
- <a class="threejs_center" href="/manual/examples/game-conga-line.html" target="_blank">์ ํญ์์ ๋ณด๊ธฐ</a>
- </div>
- <p></p>
- <p>์๋ ์ฒ์์๋ <a href="https://www.google.com/search?q=snake+game">์ง๋ ์ด ๊ฒ์</a>์ ๋ง๋ค๋ ค๊ณ ํ์ต๋๋ค. ๋๋ฌผ์ด ๊ธฐ์ฐจ์ ๋ถ์ด ๊ธฐ์ฐจ๊ฐ ๊ธธ์ด์ง์๋ก ์ฅ์ ๋ฌผ์ ํผํ๊ธฐ ์ด๋ ค์์ง๋ ๊ฒ์์ด์ฃ . ์์ ์ ๋ช ๊ฐ์ง ์ฅ์ ๋ฌผ ๋๊ฑฐ๋ ํ๋ฉด ๋๋ ์ ๋ฒฝ์ ์ธ์ฐ๊ธฐ๋ ํ์ต๋๋ค.</p>
- <p>ํ์ง๋ง ์์ ์์ ์ฌ์ฉํ ๋๋ฌผ์ ์ด์ ์ ํฉํ์ง ์์ต๋๋ค. ์์ ์ ๋๋ฌผ๋ค์ ๋๋ถ๋ถ ์์์ ๋ดค์ ๋ ๊ธธ๊ณ ํญ์ด ์๊ฑฐ๋ ์. ์๋๋ ์ผ๋ฃฉ๋ง์ ์์์ ๋ณธ ๊ฒ์
๋๋ค.</p>
- <div class="threejs_center"><img src="../resources/images/zebra.png" style="width: 113px;"></div>
- <p>์์ ๋ ์ ๋ชจ์์ ๊ฒฝ๊ณ๋ก ์์๋ผ๋ฆฌ์ ์ถฉ๋์ ๊ฐ์งํ๊ธฐ์ ์๋์ ๊ฐ์ด ์ธํ๋ฆฌ์ ๋ฟ๋ ๊ฒฝ์ฐ๋ ์ถฉ๋๋ก ๊ฐ์งํ ๊ฒ๋๋ค.</p>
- <div class="threejs_center"><img src="../resources/images/zebra-collisions.svg" style="width: 400px;"></div>
- <p>๋๋ฌผ๊ณผ ๋๋ฌผ์ด ๋ถ๋ชํ๋ ๊ฒฝ์ฐ์๋ ๋ง์ฐฌ๊ฐ์ง์
๋๋ค. ํนํ ๊ฒ์์์๋ ์ด๋์ ์ข์ ๊ฒ ์์ฃ .</p>
- <p>2D ์ฌ๊ฐํ์ ๋ง๋ค์ด ์ถฉ๋์ ๊ฐ์งํ๋ ๊ฒ๋ ์๊ฐํ์ผ๋, ๋ฐ๋ก ๋๋ฌด ๋ง์ ์ฝ๋๋ฅผ ์จ์ผ ํ๋ค๋ ๊ฑธ ๊นจ๋ฌ์์ต๋๋ค. ์์ ์ ๊ฐ ๋ชจ๋ธ์ ๋ค๋ฅธ ํฌ๊ธฐ์ ์ฌ๊ฐํ์ ์ถ๊ฐํ๋ ๋ฐ๋ ๊ทธ๋ค์ง ๋ง์ ์ฝ๋๊ฐ ๋ค์ด๊ฐ์ง ์์ต๋๋ค. ํ์ง๋ง ์ด๋ ๊ฒ ๋ช ๊ฐ์ง ๋ชจ๋ธ์ ์ฌ๊ฐํ์ ์ถ๊ฐํด๋ณด๋ฉด ๊ณง ์ถฉ๋์ ๊ฐ์งํ๋ ์ฝ๋๋ฅผ ์๋ด์ผ ํ ํ์๊ฐ ์๊ธธ ๊ฒ๋๋ค. ๋จผ์ ๊ฐ ๋ชจ๋ธ์ด ์๋ก ์ถฉ๋ํ๋์ง ํ์ธํด์ผ ํ๋ ๊ฐ ๋ชจ๋ธ์ ๊ฒฝ๊ณ ์ ์ก๋ฉด์ฒด๋ ๊ฒฝ๊ณ ๊ตฌ์ฒด, ๋๋ ๋ชจ๋ธ๊ณผ ๊ฐ์ ๋ฐฉํฅ์ผ๋ก ์ ๋ ฌ๋ ๊ฒฝ๊ณ ์ก๋ฉด์ฒด๋ฅผ ๊ฒ์ฌํด์ผ ํฉ๋๋ค. ๊ฐ ๋ชจ๋ธ์ ๊ฒฝ๊ณ๊ฐ ์ถฉ๋ํ๋ค๋ ๊ฑด ๋ ๋ชจ๋ธ์ด <em>์ด์ฉ๋ฉด</em> ์๋ก ์ถฉ๋ํ์ ์๋ ์๋ค๋ ์ด์ผ๊ธฐ์ด๊ธฐ์, ๊ฐ ๋ชจ๋ธ์ด <em>์ค์ ๋ก</em> ์ถฉ๋ํ๋์ง ๊ฒ์ฌํ๊ธฐ ์ํด ํด๋น ๋ชจ๋ธ๋ค์ ๋ค์ ๊ฒ์ฌํด์ผ ํฉ๋๋ค. ๋์ฒด๋ก ๊ฒฝ๊ณ ๊ตฌ์ฒด๋ฅผ ๊ฒ์ฌํ๋ ๊ฒ๋ง ํด๋ ๊ฝค ๋ง์ ์์
์ด ํ์ํฉ๋๋ค. ๊ฐ๋ฅํ๋ค๋ฉด ๊ฐ ์์๊ฐ ๊ทผ์ ํ๋์ง์ ์ฌ๋ถ๋ง ๊ฒ์ฌํ๋ ๋ฑ ๋ ํน์ํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ ๊ฒ ๋ ๊ฒฝ์ ์ ์ด์ฃ .</p>
- <p>๋ํ ์ถฉ๋ ์ฌ๋ถ๋ฅผ ๊ฒ์ฌํ๊ธฐ๋ง ํ๋ ๊ฒ์ผ๋ก ๋๋๋ ๊ฒ์ด ์๋๋ผ ์ถฉ๋ ์์คํ
๋ ๊ตฌ์ถํด์ผ ํฉ๋๋ค. ๊ทธ๋ ๊ทธ๋ ๊ฐ ๋ชจ๋ธ์๊ฒ "๋ ๋ค๋ฅธ ์ ๋ ์ถฉ๋ํ๋?" ์ด๋ ๊ฒ ๋ฌผ์ด๋ณด๋ ๊ฒ๋ณด๋ค ์์คํ
์ด ์ง์ ์ถฉ๋ ์ฌ๋ถ๋ฅผ ์ด๋ฒคํธ ๋ฑ์ผ๋ก ์๋ ค์ฃผ๋ ๊ฒ ๋ ํธํ ํ
๋๊น์. ์ถฉ๋ ์์คํ
์ ์ถฉ๋๊ณผ ๊ด๋ จํ ์ด๋ฒคํธ๋ ์ฝ๋ฐฑ์ ์ฌ์ฉํฉ๋๋ค. ์ด ๋ฐฉ๋ฒ์ ์ฅ์ ์ ๋ชจ๋ ์ถฉ๋์ ํ ๋ฒ๋ง ๊ฒ์ฌํ๊ธฐ์ ๊ฐ ๋ชจ๋ธ์ด "๋ด๊ฐ ๋ค๋ฅธ ์ ๋ ์ถฉ๋ํ๋?" ์ด๋ ๊ฒ ๊ฒ์ฌ๋ฅผ ๋ฐ๋ก ํ ํ์๊ฐ ์๋ค๋ ๊ฑฐ์ฃ . ์ฐ์ฐ๋์ ํจ์ฌ ์ค์ผ ์ ์์ต๋๋ค.</p>
- <p>์ฌ๊ฐํ์ ํ์ธํ๋ ์ ๋์ ๊ฐ๋จํ ์ถฉ๋ ์์คํ
์ ๋ง๋๋ ์ฝ๋๋ 100-300 ์ค ์ ๋๋ฅผ ๋์ง ์์ ๊ฒ๋๋ค. ํ์ง๋ง ์์ ์ ๋น๊ตํ๋ฉด ์ฌ์ ํ ๋ง์ ์ฝ๋์ด๋ ์ง๊ธ์ ์ด๋๋ก ๋จ๊ฒจ ๋๊ฒ ์ต๋๋ค.</p>
- <p>์๋ํด๋ด์งํ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ๋ค๋ฅธ ์บ๋ฆญํฐ ์ค ์์์ ๋ฐ๋ผ๋ดค์ ๋ ๊ฐ์ฅ ์ํ์ ๊ฐ๊น์ด ์บ๋ฆญํฐ๋ฅผ ์ฐพ๋ ๊ฒ๋๋ค๏ผ. ์ธ๊ฐํ ์บ๋ฆญํฐ์ ๊ฒฝ์ฐ๋ ๋๋ถ๋ถ ์ ์๋ํ ํ
๊ณ , ๋๋ฌผ๊ณผ ๋๋ฌผ์ ๊ฒฝ์ฐ๋ ์ผ๋ถ ๊ฒฝ์ฐ๋ ์ ์๋ํ ๊ฒ๋๋ค. ํ์ง๋ง ๋๋ฌผ๊ณผ ์ธํ๋ฆฌ์ ๊ฒฝ์ฐ๋ ๊ฐ์งํ์ง ๋ชปํ๊ฒ ์ฃ . ์๋ ํ๋ฉด ์ฃผ์์ ์ธํ๋ฆฌ๋ ๋ค๋ถ, ๋ฅ๊ทผ ๋ง๋๋ฅผ ๋๋ฌ๋ณด๋ ค๊ณ ํ์ผ๋ ์ด๋ฌ๋ ค๋ฉด 120์์ 200๊ฐ ์ ๋์ ์์๋ฅผ ๋ ๋ง๋ค์ด์ผ ํ๊ณ , ์์์ ์ธ๊ธํ ์ต์ ํ ๋ฌธ์ ์ ๋ถ๋ช์ณค์ ๊ฒ๋๋ค.</p>
- <p>โป ์์ผ๊ฐ ๋๋ฌธ์ ์นด๋ฉ๋ผ์ ์ค์ฌ์์ ๋ฒ์ด๋ ์๋ก ๋จธ๋ฆฌ ์๊ฐ ์๋ ์์ด ๋ณด์ด๋ ๊ฑธ ์ด์ฉํ ๋ฐฉ๋ฒ. ์ญ์ฃผ.</p>
- <p>์ด๋ฐ ์ฌ๋ฌ ๋ฌธ์ ๋๋ฌธ์ ๋๋ถ๋ถ์ ๊ฒ์๋ค์ด ๊ธฐ์กด์ ์ฐ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ๋ฐฉ๋ฒ๋ค ์ค์๋ ๋ฌผ๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ฐ๋ ๊ฒ๋ค๋ ์์ฃ . ๋ฌผ๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์์๊ฐ ์๋ก ์ถฉ๋ํ๋์ง ํ์ธํ๋ ๊ธฐ๋ฅ์ด ํ์๊ธฐ์, ์ ๊ฐ ์์์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๊ธฐ๋ ํฉ๋๋ค.</p>
- <p>Three.js์ ์์ ์ค <a href="https://github.com/kripken/ammo.js/">ammo.js</a>๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๋ณด๋ฉด ์ด๋ฐ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ฐพ๋ ๋ฐ ๋์์ด ๋ ์ง๋ ๋ชจ๋ฅด๊ฒ ๋ค์.</p>
- <p>๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ ์ฅ์ ๋ฌผ์ ์ผ์ ํ ๊ฒฉ์(grid)์ ๋๊ณ ํ๋ ์ด์ด์ ๋๋ฌผ์ด ํด๋น ๊ฒฉ์๋ง ์ฐธ์กฐํ๊ฒ ํ๋ ๊ฒ๋๋ค. ์ฑ๋ฅ ๋ฉด์์ ๊ต์ฅํ ์ข์ ๋ฐฉ๋ฒ์ธ๋ฐ, ์ด ๋ํ ์ฌ๋ฌ๋ถ์ด ์ง์ ์ฐ์ตํ ์ ์๋ ๐ ์์๋ก ๋จ๊ฒจ ๋๋ฉด ์ข๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค๋๊ตฐ์.</p>
- <p>๋ง๋ถ์ฌ ๋๋ถ๋ถ์ ๊ฒ์ ์์คํ
์๋ <a href="https://www.google.com/search?q=coroutines">์ฝ๋ฃจํด(coroutine)</a>์ด๋ผ๋ ๊ฒ์ด ์์ต๋๋ค. ์ฝ๋ฃจํด์ ํน์ ์์
์ ํ๋ ๋์ ๋ฉ์ท๋ค๊ฐ ๋์ค์ ๋ค์ ์์ํ๋ ๋ฃจํด(routine)์ ๋งํ์ฃ .</p>
- <p>ํ๋ ์ด์ด ์์ ์ํ๋ฅผ ๋์ ๋
ธ๋๋ก ๋๋ฌผ๋ค์ ๊ผฌ์๋ ๊ฒ์ฒ๋ผ ํด๋ณด๊ฒ ์ต๋๋ค. ๊ตฌํํ ์ ์๋ ๋ฐฉ๋ฒ์ ์์ฃผ ๋ง์ง๋ง, ์์ ์์๋ ์ฝ๋ฃจํด์ ์ฌ์ฉํด ์ด๋ฅผ ๊ตฌํํ๊ฒ ์ต๋๋ค.</p>
- <p>๋จผ์ ์ฝ๋ฃจํด์ ๊ด๋ฆฌํ๋ ํด๋์ค๋ฅผ ๋ง๋ญ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function* waitSeconds(duration) {
- while (duration > 0) {
- duration -= globals.deltaTime;
- yield;
- }
- }
- class CoroutineRunner {
- constructor() {
- this.generatorStacks = [];
- this.addQueue = [];
- this.removeQueue = new Set();
- }
- isBusy() {
- return this.addQueue.length + this.generatorStacks.length > 0;
- }
- add(generator, delay = 0) {
- const genStack = [generator];
- if (delay) {
- genStack.push(waitSeconds(delay));
- }
- this.addQueue.push(genStack);
- }
- remove(generator) {
- this.removeQueue.add(generator);
- }
- update() {
- this._addQueued();
- this._removeQueued();
- for (const genStack of this.generatorStacks) {
- const main = genStack[0];
- // ๋ค๋ฅธ ์ฝ๋ฃจํด์ด ํด๋น ์์๋ฅผ ์ ๊ฑฐํ์ ๊ฒฝ์ฐ
- if (this.removeQueue.has(main)) {
- continue;
- }
- while (genStack.length) {
- const topGen = genStack[genStack.length - 1];
- const { value, done } = topGen.next();
- if (done) {
- if (genStack.length === 1) {
- this.removeQueue.add(topGen);
- break;
- }
- genStack.pop();
- } else if (value) {
- genStack.push(value);
- } else {
- break;
- }
- }
- }
- this._removeQueued();
- }
- _addQueued() {
- if (this.addQueue.length) {
- this.generatorStacks.splice(this.generatorStacks.length, 0, ...this.addQueue);
- this.addQueue = [];
- }
- }
- _removeQueued() {
- if (this.removeQueue.size) {
- this.generatorStacks = this.generatorStacks.filter(genStack => !this.removeQueue.has(genStack[0]));
- this.removeQueue.clear();
- }
- }
- }
- </pre>
- <p>์ ํด๋์ค๋ ๋ค๋ฅธ ์ฝ๋ฃจํด์ด ์คํ๋๋ ๋์ ์์๋ฅผ ์์ ํ๊ฒ ์ ๊ฑฐ/์ถ๊ฐํ๋๋ก <code class="notranslate" translate="no">SafeArray</code>์ ๋น์ทํ ๊ตฌ์กฐ๋ก ๋ง๋ค์์ต๋๋ค. ๋ํ ์ด ํด๋์ค๋ ์ค์ฒฉ๋ ์ฝ๋ฃจํด๋ ์ฒ๋ฆฌํฉ๋๋ค.</p>
- <p>์ฝ๋ฃจํด์ ๋ง๋ค๋ ค๋ฉด ์๋ฐ์คํฌ๋ฆฝํธ์ <a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/function*">์ ๋๋ ์ดํฐ ํจ์</a>๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค. ์ ๋๋ ์ดํฐ ํจ์๋ <code class="notranslate" translate="no">function*</code>์ด๋ผ๋ ํค์๋๋ก ์์ฑํ์ฃ (๋ณํ๋ฅผ ๋ถ์ฌ์ผ ํฉ๋๋ค!).</p>
- <p>์ ๋๋ ์ดํฐ ํจ์๋ <code class="notranslate" translate="no">yield</code> ํค์๋๋ก ์คํ ์์๋ฅผ <strong>์๋ณด(yield)</strong>ํ ์ ์์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function* countOTo9() {
- for (let i = 0; i < 10; ++i) {
- console.log(i);
- yield;
- }
- }
- </pre>
- <p>์ด ํจ์๋ฅผ ์๊น ๋ง๋ <code class="notranslate" translate="no">CoroutineRunner</code>์ ์ถ๊ฐํ๋ฉด ํ ํ๋ ์, ๋๋ <code class="notranslate" translate="no">runner.update</code>๋ฅผ ํธ์ถํ ๋๋ง๋ค 0๋ถํฐ 9๊น์ง์ ์ซ์๋ฅผ ์ฐจ๋ก๋๋ก ์ถ๋ ฅํ ๊ฒ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const runner = new CoroutineRunner();
- runner.add(count0To9);
- while(runner.isBusy()) {
- runner.update();
- }
- </pre>
- <p>์ฝ๋ฃจํด์ ๋์์ด ๋๋ฌ์ ๋ ์๋์ผ๋ก ์ ๊ฑฐ๋ฉ๋๋ค. ์ฝ๋ฃจํด์ด ๋๋๊ธฐ ์ ์ ์ ๊ฑฐํ๋ ค๋ฉด ์ ๋๋ ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ์ฐธ์กฐํ ๋ค <code class="notranslate" translate="no">remove</code> ๋ฉ์๋๋ฅผ ํธ์ถํด์ผ ํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const gen = count0To9();
- runner.add(gen);
- // ์ผ๋ง ํ
- runner.remove(gen);
- </pre>
- <p>์ด์ ํ๋ ์ด์ด๊ฐ 0.5์์ 1์ด ์ฌ์ด๋ง๋ค ํ ๋ฒ์ฉ ์ํ๋ฅผ ๋ฑ๋๋ก ํด๋ด
์๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player extends Component {
- constructor(gameObject) {
- ...
- + this.runner = new CoroutineRunner();
- +
- + function* emitNotes() {
- + for (;;) {
- + yield waitSeconds(rand(0.5, 1));
- + const noteGO = gameObjectManager.createGameObject(scene, 'note');
- + noteGO.transform.position.copy(gameObject.transform.position);
- + noteGO.transform.position.y += 5;
- + noteGO.addComponent(Note);
- + }
- + }
- +
- + this.runner.add(emitNotes());
- }
- update() {
- + this.runner.update();
- ...
- }
- }
- function rand(min, max) {
- if (max === undefined) {
- max = min;
- min = 0;
- }
- return Math.random() * (max - min) + min;
- }
- </pre>
- <p>์ ์ฝ๋์์๋ <code class="notranslate" translate="no">CoroutineRunner</code>๋ฅผ ๋ง๋ค๊ณ <code class="notranslate" translate="no">emitNotes</code> ์ฝ๋ฃจํด์ ์ถ๊ฐํ์ต๋๋ค. ์ด ํจ์๋ 0.5์ด์์ 1์ด ์ฌ์ด๋ง๋ค ๊ณ์ํด์ <code class="notranslate" translate="no">Note</code> ์ปดํฌ๋ํธ๋ฅผ ์์ฑํฉ๋๋ค.</p>
- <p><code class="notranslate" translate="no">Note</code> ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๋ ค๋ฉด ๋จผ์ ํ
์ค์ฒ๊ฐ ํ์ํฉ๋๋ค. ์ํ ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ฌ ์๋ ์์ง๋ง, <a href="canvas-textures.html">์บ๋ฒ์ค๋ก ํ
์ค์ฒ ๋ง๋ค๊ธฐ</a>์์ ๋ค๋ค๋ ๊ฒ์ฒ๋ผ ์บ๋ฒ์ค๋ฅผ ์ด์ฉํด ์ง์ ์ํ๋ฅผ ๋ง๋ค๊ฒ ์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeTextTexture(str) {
- const ctx = document.createElement('canvas').getContext('2d');
- ctx.canvas.width = 64;
- ctx.canvas.height = 64;
- ctx.font = '60px sans-serif';
- ctx.textAlign = 'center';
- ctx.textBaseline = 'middle';
- ctx.fillStyle = '#FFF';
- ctx.fillText(str, ctx.canvas.width / 2, ctx.canvas.height / 2);
- return new THREE.CanvasTexture(ctx.canvas);
- }
- const noteTexture = makeTextTexture('โช');
- </pre>
- <p>์์์ ๋ง๋ ํ
์ค์ฒ๋ ํ์์์ผ๋ก, ๋์ค์ ํ
์ค์ฒ๋ฅผ ์ฌ์ฉํ ๋ ์์ ๋ฐ๋ก ์ง์ ํด ์ํ๋ ์์ ์ํ๋ฅผ ๊ทธ๋ฆด ์ ์์ต๋๋ค.</p>
- <p>์ด์ ์ํ ํ
์ค์ฒ๋ฅผ ๋ง๋ค์์ผ๋ <code class="notranslate" translate="no">Note</code> ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ฐจ๋ก์
๋๋ค. ์ํ ์ปดํฌ๋ํธ๋ <a href="billboards.html">๋น๋ณด๋์ ๊ดํ ๊ธ</a>์์ ๋ค๋ค๋ <a href="/docs/#api/ko/materials/SpriteMaterial"><code class="notranslate" translate="no">SpriteMaterial</code></a>๊ณผ <a href="/docs/#api/ko/objects/Sprite"><code class="notranslate" translate="no">Sprite</code></a>๋ฅผ ์ฌ์ฉํฉ๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Note extends Component {
- constructor(gameObject) {
- super(gameObject);
- const { transform } = gameObject;
- const noteMaterial = new THREE.SpriteMaterial({
- color: new THREE.Color().setHSL(rand(1), 1, 0.5),
- map: noteTexture,
- side: THREE.DoubleSide,
- transparent: true,
- });
- const note = new THREE.Sprite(noteMaterial);
- note.scale.setScalar(3);
- transform.add(note);
- this.runner = new CoroutineRunner();
- const direction = new THREE.Vector3(rand(-0.2, 0.2), 1, rand(-0.2, 0.2));
- function* moveAndRemove() {
- for (let i = 0; i < 60; ++i) {
- transform.translateOnAxis(direction, globals.deltaTime * 10);
- noteMaterial.opacity = 1 - (i / 60);
- yield;
- }
- transform.parent.remove(transform);
- gameObjectManager.removeGameObject(gameObject);
- }
- this.runner.add(moveAndRemove());
- }
- update() {
- this.runner.update();
- }
- }
- </pre>
- <p>์ด ์ปดํฌ๋ํธ๋ <a href="/docs/#api/ko/objects/Sprite"><code class="notranslate" translate="no">Sprite</code></a>๋ฅผ ๋ง๋ค๊ณ ๋ฌด์์๋ก ์๋๋ฅผ ์ ํด 60ํ๋ ์ ๋์ ๊ทธ ์๋๋ก ์ด๋ํ๊ฒ ํฉ๋๋ค. ๋์์ ์ฌ์ง์ <a href="/docs/#api/ko/materials/Material#opacity"><code class="notranslate" translate="no">opacity</code></a> ์์ฑ์ ๋ฐ๊ฟ ํ์ด๋-์์ ํจ๊ณผ๋ ์ฃผ์ฃ . ๋ฐ๋ณต๋ฌธ์ด ๋๋๋ฉด ์ด ์ปดํฌ๋ํธ๋ ์์น๊ฐ๊ณผ ์ํ๋ฅผ ํด๋น GameObject์์ ์ ๊ฑฐํฉ๋๋ค.</p>
- <p>์ ๋ง ๋ง์ง๋ง์ผ๋ก, ๋๋ฌผ์ ์๋ฅผ ์ข ๋๋ ค๋ณด๊ฒ ์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function init() {
- ...
- const animalModelNames = [
- 'pig',
- 'cow',
- 'llama',
- 'pug',
- 'sheep',
- 'zebra',
- 'horse',
- ];
- + const base = new THREE.Object3D();
- + const offset = new THREE.Object3D();
- + base.add(offset);
- +
- + // ์์ฉ๋์ด ํํ๋ก ๋๋ฌผ๋ค์ ๋ฐฐ์นํฉ๋๋ค.
- + const numAnimals = 28;
- + const arc = 10;
- + const b = 10 / (2 * Math.PI);
- + let r = 10;
- + let phi = r / b;
- + for (let i = 0; i < numAnimals; ++i) {
- + const name = animalModelNames[rand(animalModelNames.length) | 0];
- const gameObject = gameObjectManager.createGameObject(scene, name);
- gameObject.addComponent(Animal, models[name]);
- + base.rotation.y = phi;
- + offset.position.x = r;
- + offset.updateWorldMatrix(true, false);
- + offset.getWorldPosition(gameObject.transform.position);
- + phi += arc / r;
- + r = b * phi;
- }
- </pre>
- <p></p><div translate="no" class="threejs_example_container notranslate">
- <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/game-conga-line-w-notes.html"></iframe></div>
- <a class="threejs_center" href="/manual/examples/game-conga-line-w-notes.html" target="_blank">์ ํญ์์ ๋ณด๊ธฐ</a>
- </div>
- <p></p>
- <p>๋๊ตฐ๊ฐ <code class="notranslate" translate="no">setTimeout</code>์ ์ฐ๋ฉด ์ ๋๋๊ณ ๋ฌผ์์ง๋ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. <code class="notranslate" translate="no">setTimeout</code>์ ์ฐ์ง ์์ ๊ฑด <code class="notranslate" translate="no">setTimeout</code>์ ๊ฒ์์ ํ๋ ์ ์ฃผ๊ธฐ์ ๋ฌด๊ดํ๊ธฐ ๋๋ฌธ์
๋๋ค. ์๋ฅผ ๋ค์ด ์์ ์์๋ ํ๋ ์ ๊ฐ ์๊ฐ๊ฐ์ ์ต๋ 1/20์ด๋ก ์ ํํ์ฃ . ๋ฐฉ๊ธ ๊ตฌ์ถํ ์ฝ๋ฃจํด ์์คํ
๋ ์ด ์ ํ์ ๋ฐ๋ฅผ ํ
์ง๋ง, <code class="notranslate" translate="no">setTimeout</code>์ ์ฐ๋ฉด ๊ทธ๋ ์ง ์์ ๊ฒ๋๋ค.</p>
- <p>๋ฌผ๋ก ์ข ๋ ๊ฐ๋จํ ํ์ด๋จธ๋ฅผ ๋ง๋ค ์๋ ์์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">class Player ... {
- update() {
- this.noteTimer -= globals.deltaTime;
- if (this.noteTimer <= 0) {
- // ํ์ด๋จธ๋ฅผ ์ด๊ธฐํํฉ๋๋ค.
- this.noteTimer = rand(0.5, 1);
- // GameObject๋ก ์ํ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ญ๋๋ค.
- }
- }
- </pre>
- <p>ํน์ ๊ฒฝ์ฐ์์ผ ์ด ๋ฐฉ๋ฒ์ด ๋ ์ข์ ์๋ ์์ง๋ง, ๋ ๋ง์ ์์๋ฅผ ์ถ๊ฐํ๋ฉด ๊ทธ๋งํผ ๋ ๋ง์ ๋ณ์์ ์ฝ๋ฃจํด์ ์ถ๊ฐํด์ผ ํ ํ
๊ณ , ๊ทธ๋ด์๋ก <code class="notranslate" translate="no">setTimeout</code>์ <em>์ค์ ํ๊ณ ๊น๋จน์</em> ํ๋ฅ ์ด ๋์์ง ๊ฒ๋๋ค.</p>
- <p>๋๋ฌผ๋ค์ ์ํ๋ฅผ ์ค์ ํ ๋๋ ์๋์ ๊ฐ์ด ์ฝ๋ฃจํด์ ์ฌ์ฉํ ์ ์์ต๋๋ค.</p>
- <pre class="prettyprint showlinemods notranslate lang-js" translate="no">// ์ค์ ๋ก ์ฌ์ฉํ์ง ์๋ ํจ์
- function* animalCoroutine() {
- setAnimation('Idle');
- while(playerIsTooFar()) {
- yield;
- }
- const target = endOfLine;
- setAnimation('Jump');
- while(targetIsTooFar()) {
- aimAt(target);
- yield;
- }
- setAnimation('Walk')
- while(notAtOldestPositionOfTarget()) {
- addHistory();
- aimAt(target);
- yield;
- }
- for(;;) {
- addHistory();
- const pos = history.unshift();
- transform.position.copy(pos);
- aimAt(history[0]);
- yield;
- }
- }
- </pre>
- <p>์ด ๋ฐฉ๋ฒ์ ์จ๋ ๋ฑํ ๋ฌธ์ ๋ ์์๊ฒ ์ง๋ง, ์ํ๊ฐ ์ผ์ ํ์ง ์์ ๋ค์ <code class="notranslate" translate="no">FiniteStateMachine</code>์ ์ฐพ๊ฒ ๋ ๊ฒ๋๋ค.</p>
- <p>๋ ์ ๋ ์ฝ๋ฃจํด์ ํด๋น ์ปดํฌ๋ํธ์ ๋
๋ฆฝ์ ์ผ๋ก ์คํํ๋ ๊ฒ ์ข์์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ๋ฌผ๋ก ๊ทธ๋ฅ ์ ์ญ์ <code class="notranslate" translate="no">CoroutineRunner</code>๋ฅผ ๋ง๋ค์ด ๋ชจ๋ ์ฝ๋ฃจํด์ ์ฌ๊ธฐ์ ์ง์ด ๋ฃ์ ์๋ ์์ฃ . ํ์ง๋ง ์ด๋ฌ๋ฉด ์ฝ๋ฃจํด์ ์์ ๊ธฐ๊ฐ ํ๋ค์ด์ง ๊ฒ๋๋ค. ์ง๊ธ ์์ ๋ GameObject๋ฅผ ์ ๊ฑฐํ๋ฉด ํด๋น ์ปดํฌ๋ํธ๋ ์ ๊ฑฐ๋๊ณ , ๊ทธ๋ฌ๋ฉด ์์ฑํ <code class="notranslate" translate="no">CoroutineRunner</code>์ ๋ฉ์๋๋ฅผ ํธ์ถํ ์ผ๋ ์์ผ๋ ์ฝ๋ฃจํด๋ ์ ๋ถ ๊ฐ๋น์ง ์ปฌ๋ ์
์ ๋ค์ด๊ฐ ๊ฒ๋๋ค. ์ ์ญ์ <code class="notranslate" translate="no">CoroutineRunner</code>๋ฅผ ๋๋ฉด ์ปดํฌ๋ํธ์์ ์ง์ ์ด ์ ์ญ ๊ฐ์ฒด์ ์ฝ๋ฃจํด์ ์ ๊ฑฐํ๊ฑฐ๋ ์๋์ผ๋ก ์ฝ๋ฃจํด์ ์ ๊ฑฐํ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด ํ์ํ ๊ฒ๋๋ค.</p>
- <p>์ค์ ๊ฒ์ ์์ง์ด๋ผ๋ฉด ๋ ๊ณ ๋ คํด์ผ ํ ๋ฌธ์ ๊ฐ ๋ง์ต๋๋ค. ์ง๊ธ์ GameObject๋ ์ปดํฌ๋ํธ์ ๋ฐ๋ก ์์๋ฅผ ์ง์ ํ ์ ์์ฃ . ๊ทธ๋ฅ ์ถ๊ฐํ ์์๊ฐ ํด๋น ์์์ ์์๊ฐ ๋ฉ๋๋ค. ๋๋ถ๋ถ์ ๊ฒ์ ์์ง์ ์ฐ์ ์์๋ฅผ ์ ํด ์์๋ฅผ ๋ฐ๊ฟ ์ ์์ต๋๋ค.</p>
- <p>๋ค๋ฅธ ๋ฌธ์ ๋ <code class="notranslate" translate="no">Note</code> ์ปดํฌ๋ํธ๊ฐ ์ฅ๋ฉด ์ GameObject์ transform ์์ฑ์ ๋ณ๊ฒฝํ๋ค๋ ๊ฒ๋๋ค. ์ ์ด์ <code class="notranslate" translate="no">GameObject</code>๊ฐ transform ์์ฑ์ ๋ณ๊ฒฝํ์ผ๋ ์ข ๋ ์ ๋๋ก ๊ตฌํํ๋ ค๋ฉด <code class="notranslate" translate="no">GameObject</code>๊ฐ ๊ณ์ transform ์์ฑ์ ๊ด๋ฆฌํ๋ ๊ฒ ๋ง๊ฒ ์ฃ . <code class="notranslate" translate="no">GameObject</code>์ <code class="notranslate" translate="no">dispose</code> ๊ฐ์ ๋ฉ์๋๋ฅผ ๋๊ณ <code class="notranslate" translate="no">GameObjectManager.removeGameObject</code>์์ ์ด ๋ฉ์๋๋ฅผ ํธ์ถํ๋ค๋ฉด ์ด๋จ๊น์?</p>
- <p>๋ <code class="notranslate" translate="no">gameObjectManager.update</code>๋ <code class="notranslate" translate="no">inputManager.update</code>๋ฅผ ์ง์ ํธ์ถํ๋ ๋์ <code class="notranslate" translate="no">SystemManager</code>๋ฅผ ๋ง๋ค์ด <code class="notranslate" translate="no">update</code>๋ฉ์๋๋ฅผ ๊ฐ์ง ์์๋ฅผ ์ ๋ถ ์ถ๊ฐํด ์ด ํด๋์ค๊ฐ ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ก ํ๋ ๊ฒ ๋ ๋์ ์๋ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด <code class="notranslate" translate="no">CollisionManager</code> ๋ฑ ์๋ก์ด ์์คํ
์ ๋ง๋ค์์ ๋ <code class="notranslate" translate="no">render</code> ํจ์๋ฅผ ์์ ํ๋ ๊ฒ ์๋๋ผ <code class="notranslate" translate="no">SystemManager</code>์ ์ด ์์คํ
์ ์ถ๊ฐํ๊ธฐ๋ง ํ๋ฉด ๋ ๊ฒ๋๋ค.</p>
- <p>์ ๋ ์ด๋ฐ ๋ฌธ์ ๋ค์ ์ ๋ถ ๋ค๋ฃจ๊ธฐ๋ณด๋ค ์ฌ๋ฌ๋ถ์ ๋ชซ์ผ๋ก ๋จ๊ฒจ ๋๊ณ ์ ํฉ๋๋ค. ๋ถ๋ ์ด ๊ธ์ด ์ฌ๋ฌ๋ถ๋ง์ ๊ฒ์ ์์ง์ ๋ง๋๋ ๋ฐ ๋์์ด ๋์๋ค๋ฉด ์ข๊ฒ ๋ค์.</p>
- <p>์ด์ฉ๋ฉด ์ ๊ฐ ๊ฒ์ ์ผ(game jam)๏ผ์ ์ด ์๋ ์๊ฒ ๋ค์. ์ ์์ ์ <em>jsfiddle</em>์ด๋ <em>codepen</em>์ ํด๋ฆญํด๋ณด๋ฉด ์ฝ๋๋ฅผ ๋ฐ๋ก ํธ์งํด ๋ณผ ์ ์๋ ์ฌ์ดํธ๊ฐ ์ด๋ฆด ๊ฒ๋๋ค. ํน์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ฑฐ๋ ํด์ ์์ ๋ฅผ ํผ๊ทธ๊ฐ ๊ธฐ์ฌ(knight)์ ๋๊ณ ๋ค๋๋ ๊ฒ์์ ๋ง๋ค๊ฑฐ๋, ๊ธฐ์ฌ์ ๊ตฌ๋ฅด๊ธฐ ์ ๋๋ฉ์ด์
์ ๋ณผ๋ง๊ณต์ผ๋ก์จ ๋๋ฌผ ๋ณผ๋ง ๊ฒ์์ ๋ง๋ค ์๋ ์๊ฒ ์ฃ . ๋๋ ๋๋ฌผ ์ด์ด ๋ฌ๋ฆฌ๊ธฐ ๊ฒ์์ด๋ผ๋ ๊ฐ์. ๊ด์ฐฎ์ ๊ฒ์์ ๋ง๋ค์๋ค๋ฉด ์๋์ ๋๊ธ๋ก ๋งํฌ๋ฅผ ๋จ๊ฒจ์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.</p>
- <p>โป ๊ฒ์ ์ผ: ๋ณดํต 24์๊ฐ์์ 72์๊ฐ ์ ๋์ ์งง์ ๊ธฐ๊ฐ ๋ด์ ํ, ๋๋ ๊ฐ์ธ์ด ๊ฒ์์ ๋ง๋๋ ๋ํ. ์ญ์ฃผ.</p>
- <div class="footnotes">
- [<a id="parented">1</a>]: ๋ฌผ๋ก ๋ถ๋ชจ์ ์ด๋ค ์์๋ translation, rotation, scale ์์ฑ์ ๋ฐ๊พธ์ง ์์๋ค๋ฉด ์ ์์ ์ผ๋ก ์๋ํฉ๋๋ค.<a href="#parented-backref">[๋์๊ฐ๊ธฐ]</a>
- </div>
- </div>
- </div>
- </div>
- <script src="../resources/prettify.js"></script>
- <script src="../resources/lesson.js"></script>
- </body></html>
|