在Svelte 3上开发游戏

Svelte 3在一个多月前发布。 我想是个很高兴认识您的好时机,并完成了一个出色的教程 ,该教程也被翻译成了俄语。


为了巩固过去,我做了一个小项目,并与您分享成果。 这不仅仅是一个待办事项清单,而是一款您需要从黑色方块射击的游戏。


图片


0.不耐烦


教程库
带有额外功能的存储库
演示版


1.准备


我们克隆一个开发模板


git clone https://github.com/sveltejs/template.git 

安装依赖项。


 cd template/ npm i 

我们启动开发服务器。


 npm run dev 

我们的模板位于
http://本地主机:5000 该服务器支持热重装,因此在保存更改后,我们的更改将在浏览器中可见。


如果您不想在本地部署环境,则可以在线使用支持Svelte的codeandboxstackblitz沙箱。


2.游戏框架


src文件夹由两个文件main.jsApp.svelte组成
main.js是我们应用程序的入口。 在开发过程中,我们不会碰它。 这里,App.svelte组件安装在文档主体中。
App.svelte是svelte的组件。 组件模板包括三个部分:


 <script> // JS   export let name; </script> <style> /* CSS   */ h1 { color: purple; } </style> <!--   --> <h1>Hello {name}!</h1> 

组件样式是孤立的,但是可以使用以下指令分配全局样式:global()。 有关样式的更多信息
为我们的组件添加通用样式。


src / App.svelte
 <script> export let name; </script> <style> :global(html) { height: 100%; /*     100% */ } :global(body) { height: 100%; /*     100% */ overscroll-behavior: none; /*  pull to refresh*/ user-select: none; /*        */ margin: 0; /*  */ background-color: #efefef; /*    */ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; /*   */ } </style> <h1>Hello {name}!</h1> 

让我们创建src / components文件夹,其中将存储我们的组件
在此文件夹中,创建两个文件,其中将包含运动场和控件。


src /组件/ GameField.svelte
 <div>GameField</div> 

src /组件/ Controls.svelte
 <div>Controls</div> 

组件是通过指令导入的


 import Controls from "./components/Controls.svelte"; 

要显示组件,只需将组件标签插入标记中。 了解有关标签的更多信息


 <Controls /> 

现在,我们将组件导入并显示在App.svelte中。


src / App.svelte
 <script> //   import Controls from "./components/Controls.svelte"; import GameField from "./components/GameField.svelte"; </script> <style> :global(html) { height: 100%; } :global(body) { height: 100%; overscroll-behavior: none; user-select: none; margin: 0; background-color: #efefef; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } </style> <!--  . ,     , , ,  react --> <Controls /> <GameField /> 

3.控制


Controls.svelte组件将包含三个按钮:向左移动,向右移动,射击。 svg元素将显示按钮图标。
创建src / asssets文件夹 ,在其中添加svg图标。


src /资产/ Bullet.svelte
 <svg height="40px" viewBox="0 0 427 427.08344" width="40px"> <path d="m341.652344 38.511719-37.839844 37.839843 46.960938 46.960938 37.839843-37.839844c8.503907-8.527344 15-18.839844 19.019531-30.191406l19.492188-55.28125-55.28125 19.492188c-11.351562 4.019531-21.664062 10.515624-30.191406 19.019531zm0 0" /> <path d="m258.65625 99.078125 69.390625 69.390625 14.425781-33.65625-50.160156-50.160156zm0 0" /> <path d="m.0429688 352.972656 28.2812502-28.285156 74.113281 74.113281-28.28125 28.28125zm0 0" /> <path d="m38.226562 314.789062 208.167969-208.171874 74.113281 74.113281-208.171874 208.171875zm0 0" /> </svg> 

src /资产/ LeftArrow.svelte
 <svg width="40px" height="40px" viewBox="0 0 292.359 292.359" transform="translate(-5 0)"> <path d="M222.979,5.424C219.364,1.807,215.08,0,210.132,0c-4.949,0-9.233,1.807-12.848,5.424L69.378,133.331 c-3.615,3.617-5.424,7.898-5.424,12.847c0,4.949,1.809,9.233,5.424,12.847l127.906,127.907c3.614,3.617,7.898,5.428,12.848,5.428 c4.948,0,9.232-1.811,12.847-5.428c3.617-3.614,5.427-7.898,5.427-12.847V18.271C228.405,13.322,226.596,9.042,222.979,5.424z" /> </svg> 

src /资产/ RightArrow.svelte
 <svg width="40px" height="40px" viewBox="0 0 292.359 292.359" transform="translate(5 0) rotate(180)"> <path d="M222.979,5.424C219.364,1.807,215.08,0,210.132,0c-4.949,0-9.233,1.807-12.848,5.424L69.378,133.331 c-3.615,3.617-5.424,7.898-5.424,12.847c0,4.949,1.809,9.233,5.424,12.847l127.906,127.907c3.614,3.617,7.898,5.428,12.848,5.428 c4.948,0,9.232-1.811,12.847-5.428c3.617-3.614,5.427-7.898,5.427-12.847V18.271C228.405,13.322,226.596,9.042,222.979,5.424z" /> </svg> 

添加按钮组件src / components / IconButton.svelte
我们将接受来自父组件的事件处理程序。 为了能够按住按钮,我们需要两个处理程序:单击的开始和单击的结束。 我们声明变量startrelease ,我们将在其中接受事件处理程序,以进行单击的开始和结束。 我们还需要活动变量,无论是否按下按钮,该变量都会显示。


 <script> export let start; export let release; export let active; </script> 

我们风格化我们的组件


 <style> .iconButton { /*   flex     */ display: flex; align-items: center; justify-content: center; /*    60px */ width: 60px; height: 60px; /*   */ border: 1px solid black; /*    */ border-radius: 50px; /*     */ outline: none; background: transparent; } .active { /*    ,    */ background-color: #bdbdbd; } </style> 

按钮是一个按钮元素,在其中显示从父组件传输的内容。 <slot />标记指示要安装传输的内容的位置。 了解有关<slot />元素的更多信息


 <button> <slot /> </button> 

事件处理程序使用on:指令指示,例如on:click
我们将处理鼠标事件和触摸点击。 了解有关事件绑定的更多信息
如果按下按钮, 活动类将被添加到组件的基类中。 您可以通过class属性分配一个类。 有关课程的更多信息


 <button on:mousedown={start} on:touchstart={start} on:mouseup={release} on:touchend={release} class={`iconButton ${active ? 'active' : ''}`}> <slot /> </button> 

结果,我们的组件将如下所示:


src /组件/ IconButton.svelte
 <script> export let start; export let release; export let active; </script> <style> .iconButton { display: flex; align-items: center; justify-content: center; width: 60px; height: 60px; border: 1px solid black; border-radius: 50px; outline: none; background: transparent; } .active { background-color: #bdbdbd; } </style> <button on:mousedown={start} on:touchstart={start} on:mouseup={release} on:touchend={release} class={`iconButton ${active ? 'active' : ''}`}> <slot /> </button> 

现在,我们将图标和按钮元素导入src / components / Controls.svelte并组成布局。


src /组件/ Controls.svelte
 <script> //      import IconButton from "./IconButton.svelte"; import LeftArrow from "../assets/LeftArrow.svelte"; import RightArrow from "../assets/RightArrow.svelte"; import Bullet from "../assets/Bullet.svelte"; </script> <style> /*    ,   */ .controls { position: fixed; bottom: 0; left: 0; width: 100%; } /*          */ .container { display: flex; justify-content: space-between; margin: 1rem; } /*     */ .arrowGroup { display: flex; justify-content: space-between; width: 150px; } </style> <div class="controls"> <div class="container"> <div class="arrowGroup"> <IconButton> <LeftArrow /> </IconButton> <IconButton> <RightArrow /> </IconButton> </div> <IconButton> <Bullet /> </IconButton> </div> </div> 

我们的应用程序应如下所示:
图片


4.比赛场地


比赛场地是svg组件,我们将在其中添加游戏元素(枪支,炮弹,对手)。
更新src /组件/ GameField.svelte代码


src /组件/ GameField.svelte
 <style> /*  ,         */ .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: flex-start; max-height: 100%; } </style> <div class="container"> <!--    viewBox          --> <svg viewBox="0 0 480 800"> </svg> </div> 

创建src / components / Cannon.svelte枪 。 大声喊出一个矩形,但是。


src /组件/ Cannon.svelte
 <style> /*   ,        */ .cannon { transform-origin: 4px 55px; } </style> <!--      svg .   <g>     --> <g class="cannon" transform={`translate(236, 700)`}> <rect width="8" height="60" fill="#212121" /> </g> 

现在,我们在战场上进口枪支。


src / GameField.svelte
 <script> //    import Cannon from "./Cannon.svelte"; </script> <style> .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: flex-start; max-height: 100%; } </style> <div class="container"> <svg viewBox="0 0 480 800"> <!--    --> <Cannon /> </svg> </div> 

5.游戏周期


我们有游戏的基本框架。 下一步是创建一个处理我们的逻辑的游戏循环。
让我们创建包含逻辑变量的存储。 我们将需要svelte / store模块中的可写组件。 了解有关商店的更多信息
创建一个简单的存储库如下所示:


 //     import { writable } from "svelte/store"; //      null export const isPlaying = writable(null); 

让我们创建src / stores /文件夹,我们游戏的所有可变值都将存储在这里。
创建src / stores / game.js文件 ,其中将存储负责游戏一般状态的变量。


src /商店/game.js
 //     import { writable } from "svelte/store"; //        ,    true/false export const isPlaying = writable(false); 

创建src / stores / cannon.js文件 ,在其中存储负责枪支状态的变量


src /商店/ cannon.js
 //     import { writable } from "svelte/store"; //    ,     . //    'left', 'right', null,    export const direction = writable(null); //     export const angle = writable(0); 

Svelte允许您创建包含工作逻辑的自定义存储库。 您可以在教科书中阅读有关此内容的更多信息。 我无法完美地将其融入游戏周期的概念,因此我们仅在存储库中声明变量。 我们将在src / gameLoop部分中对它们进行所有操作。


将使用requestAnimationFrame函数计划游戏循环。 描述游戏逻辑的函数数组将输入到输入中。 在游戏周期结束时,如果游戏尚未结束,则计划进行下一次迭代。 在游戏循环中,我们将访问isPlaying变量的值以检查游戏是否结束。


使用存储,您可以订阅一个值。 我们将在组件中使用此功能。 现在,我们将使用get函数来读取变量的值。 我们将使用变量的.set()方法来设置值。
您可以通过调用.update()方法来更新值,该方法将一个函数作为输入,该函数的第一个值将传递给当前值。 文档中有更多详细信息 。 其他所有内容都是纯JS。


src / gameLoop / gameLoop.js
 //     import { isPlaying } from '../stores/game'; //    get     ,  . import { get } from 'svelte/store'; //      function startLoop(steps) { window.requestAnimationFrame(() => { //      steps.forEach(step => { //    -  if (typeof step === 'function') step(); }); //    ,    if (get(isPlaying)) startLoop(steps); }); } //       export const startGame = () => { //  ,      true isPlaying.set(true); //   .     startLoop([]); }; //       export function stopGame() { //  ,      false isPlaying.set(false); } 

现在,我们描述枪支行为的逻辑。


src / gameLoop / cannon.js
 //    get     ,  . import { get } from 'svelte/store'; //      cannon import { angle, direction } from '../stores/cannon.js'; //      export function rotateCannon() { //     const currentAngle = get(angle); //    ,   ,    switch (get(direction)) { //    ""     -45°, //      0.4 case 'left': if (currentAngle > -45) angle.update(a => a - 0.4); break; //    ""     45°, //      0.4 case 'right': if (currentAngle < 45) angle.update(a => a + 0.4); break; default: break; } } 

现在,将我们的枪支旋转处理程序添加到游戏循环中。


 import { rotateCannon } from "./cannon"; /* ... */ export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon]); }; 

当前游戏周期代码:


src / gameLoop / gameLoop.js
 import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon } from './cannon'; //     function startLoop(steps) { window.requestAnimationFrame(() => { steps.forEach(step => { if (typeof step === 'function') step(); }); if (get(isPlaying)) startLoop(steps); }); } export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon]); //      }; export function stopGame() { isPlaying.set(false); } 

我们有可以旋转枪支的逻辑。 但是我们还没有将其与按钮关联。 是时候这样做了。 Click事件处理程序将添加到src / components / Controls.svelte中


 import { direction } from "../stores/cannon.js"; //       //    const resetDirection = () => direction.set(null); const setDirectionLeft = () => direction.set("left"); const setDirectionRight = () => direction.set("right"); 

将处理程序和单击的当前状态添加到IconButton元素。 为此,只需将值传递到先前创建的属性startreleaseactive即可 ,如文档中所述。


 <IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton> 

我们将$表达式用作$方向变量。 这种语法使该值成为反应性的,并自动订阅更改。 文档中有更多详细信息


src /组件/ Controls.svelte
 <script> import IconButton from "./IconButton.svelte"; import LeftArrow from "../assets/LeftArrow.svelte"; import RightArrow from "../assets/RightArrow.svelte"; import Bullet from "../assets/Bullet.svelte"; //     import { direction } from "../stores/cannon.js"; //    const resetDirection = () => direction.set(null); const setDirectionLeft = () => direction.set("left"); const setDirectionRight = () => direction.set("right"); </script> <style> .controls { position: fixed; bottom: 0; left: 0; width: 100%; } .container { display: flex; justify-content: space-between; margin: 1rem; } .arrowGroup { display: flex; justify-content: space-between; width: 150px; } </style> <div class="controls"> <div class="container"> <div class="arrowGroup"> <!--        --> <IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton> </div> <IconButton> <Bullet /> </IconButton> </div> </div> 

此刻,当按下我们的按钮时,会进行选择,但喷枪仍不会旋转。 我们需要将角度值导入Cannon.svelte组件并更新变换变换规则


src /组件/ Cannon.svelte
 <script> //      import { angle } from "../stores/cannon.js"; </script> <style> .cannon { transform-origin: 4px 55px; } </style> <!--    rotate(${$angle})--> <g class="cannon" transform={`translate(236, 700) rotate(${$angle})`}> <rect width="8" height="60" fill="#212121" /> </g> 

仍然需要在App.svelte组件中运行我们的游戏循环。


 import { startGame } from "./gameLoop/gameLoop"; startGame(); 

瘦身
 <script> import Controls from "./components/Controls.svelte"; import GameField from "./components/GameField.svelte"; //     import { startGame } from "./gameLoop/gameLoop"; //  startGame(); </script> <style> :global(html) { height: 100%; } :global(body) { height: 100%; overscroll-behavior: none; user-select: none; margin: 0; background-color: #efefef; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } </style> <Controls /> <GameField /> 

万岁! 我们的枪开始移动。
图片


6.射击


现在教我们的枪射击。 我们需要存储值:


  • 现在是否开枪(按下开火按钮);
  • 最后一枪的时间戳,您需要计算射速;
  • 壳数组。

将这些变量添加到我们的src / stores / cannon.js存储库中


src /商店/ cannon.js
 import { writable } from 'svelte/store'; export const direction = writable(null); export const angle = writable(0); //   export const isFiring = writable(false); export const lastFireAt = writable(0); export const bulletList = writable([]); 

src / gameLoop / cannon.js中更新导入和游戏逻辑。


src / gameLoop / cannon.js
 import { get } from 'svelte/store'; //   import { angle, direction, isFiring, lastFireAt, bulletList } from '../stores/cannon.js'; export function rotateCannon() { const currentAngle = get(angle); switch (get(direction)) { case 'left': if (currentAngle > -45) angle.update(a => a - 0.4); break; case 'right': if (currentAngle < 45) angle.update(a => a + 0.4); break; default: break; } } //   export function shoot() { //           800 , //          if (get(isFiring) && Date.now() - get(lastFireAt) > 800) { lastFireAt.set(Date.now()); //         . //  id   Math.random    bulletList.update(bullets => [...bullets, { x: 238, y: 760, angle: get(angle), id: () => Math.random() + Date.now() }]); } } //    export function moveBullet() { //    ,      y  -20, //          . //    , ,     . //      , ? bulletList.update(bullets => bullets.map(bullet => ({ ...bullet, y: bullet.y - 20, x: (780 - bullet.y) * Math.tan((bullet.angle * Math.PI) / 180) + 238, })), ); } //    ,     . export function clearBullets() { bulletList.update(bullets => bullets.filter(bullet => bullet.y > 0)); } //     Id. ,        export function removeBullet(id) { bulletList.update(bullets => bullets.filter(bullet => bullet.id !== id)); } 

现在,将处理程序导入gameLoop.js并将它们添加到游戏循环中。


 import { rotateCannon, shoot, moveBullet, clearBullets } from "./cannon"; /* ... */ export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon, shoot, moveBullet, clearBullets ]); }; 

src / gameLoop / gameLoop.js
 import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; //        import { rotateCannon, shoot, moveBullet, clearBullets } from "./cannon"; function startLoop(steps) { window.requestAnimationFrame(() => { steps.forEach(step => { if (typeof step === 'function') step(); }); if (get(isPlaying)) startLoop(steps); }); } export const startGame = () => { isPlaying.set(true); //      startLoop([rotateCannon, shoot, moveBullet, clearBullets ]); }; export function stopGame() { isPlaying.set(false); } 

现在,我们只需要创建按下“火”按钮的过程,并在运动场上添加贝壳的显示即可。
编辑src / components / Controls.svelte


 //  ,       import { direction, isFiring } from "../stores/cannon.js"; //      const startFire = () => isFiring.set(true); const stopFire = () => isFiring.set(false); 

现在,像旋转按钮一样,将处理程序添加到火控按钮


 <IconButton start={startFire} release={stopFire} active={$isFiring}> <Bullet /> </IconButton> 

src /组件/ Controls.svelte
 <script> import IconButton from "./IconButton.svelte"; import LeftArrow from "../assets/LeftArrow.svelte"; import RightArrow from "../assets/RightArrow.svelte"; import Bullet from "../assets/Bullet.svelte"; //  ,       import { direction, isFiring } from "../stores/cannon.js"; const resetDirection = () => direction.set(null); const setDirectionLeft = () => direction.set("left"); const setDirectionRight = () => direction.set("right"); //      const startFire = () => isFiring.set(true); const stopFire = () => isFiring.set(false); </script> <style> .controls { position: fixed; bottom: 0; left: 0; width: 100%; } .container { display: flex; justify-content: space-between; margin: 1rem; } .arrowGroup { display: flex; justify-content: space-between; width: 150px; } </style> <div class="controls"> <div class="container"> <div class="arrowGroup"> <IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton> </div> <!--     --> <IconButton start={startFire} release={stopFire} active={$isFiring}> <Bullet /> </IconButton> </div> </div> 

它仍然可以在运动场上显示贝壳。 首先,创建弹丸组件


src /组件/ Bullet.svelte
 <script> //   bullet  ,    export let bullet; </script> <!--  -  svg  --> <g transform={`translate(${bullet.x}, ${bullet.y}) rotate(${bullet.angle})`}> <rect width="3" height="5" fill="#212121" /> </g> 

由于我们拥有的外壳存储在数组中,因此我们需要一个迭代器来显示它们。 实际上,对于这种情况有一个Every指令。 文档中有更多详细信息


 //    bulletList,      bullet. //      id  ,  svelte       ,   . //  key   React {#each $bulletList as bullet (bullet.id)} <Bullet {bullet}/> {/each} 

src/components/GameField.svelte
 <script> import Cannon from "./Cannon.svelte"; //    import Bullet from "./Bullet.svelte"; //      import { bulletList } from "../stores/cannon"; </script> <style> .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: flex-start; max-height: 100%; } </style> <div class="container"> <svg viewBox="0 0 480 800"> <!--       --> {#each $bulletList as bullet (bullet.id)} <Bullet {bullet} /> {/each} <Cannon /> </svg> </div> 

.
图片


7.


太好了 . src/stores/enemy.js .


src/stores/enemy.js
 import { writable } from "svelte/store"; //   export const enemyList = writable([]); //      export const lastEnemyAddedAt = writable(0); 

src/gameLoop/enemy.js


src/gameLoop/enemy.js
 import { get } from 'svelte/store'; //      import { enemyList, lastEnemyAddedAt } from '../stores/enemy.js'; //    export function addEnemy() { //         2500 , //     if (Date.now() - get(lastEnemyAddedAt) > 2500) { //      lastEnemyAddedAt.set(Date.now()); //        1  499 // (   ) enemyList.update(enemies => [ ...enemies, { x: Math.floor(Math.random() * 449) + 1, y: 0, id: () => Math.random() + Date.now(), }, ]); } } //   .       0.5 export function moveEnemy() { enemyList.update(enemyList => enemyList.map(enemy => ({ ...enemy, y: enemy.y + 0.5, })), ); } //      id,     export function removeEnemy(id) { enemyList.update(enemies => enemies.filter(enemy => enemy.id !== id)); } 

.


src/gameLoop/gameLoop.js
 import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon, shoot, moveBullet, clearBullets } from './cannon'; //      import { addEnemy, moveEnemy } from './enemy'; function startLoop(steps) { window.requestAnimationFrame(() => { steps.forEach(step => { if (typeof step === 'function') step(); }); if (get(isPlaying)) startLoop(steps); }); } export const startGame = () => { isPlaying.set(true); //      startLoop([rotateCannon, shoot, moveBullet, clearBullets, addEnemy, moveEnemy]); }; export function stopGame() { isPlaying.set(false); } 

src/components/Enemy.js .


src/components/Enemy.js
 <script> //   enemy   ,   export let enemy; </script> //    ,     . <g transform={`translate(${enemy.x}, ${enemy.y})`} > <rect width="30" height="30" fill="#212121" /> </g> 

, Each


src/components/GameField.svelte
 <script> import Cannon from "./Cannon.svelte"; import Bullet from "./Bullet.svelte"; //    import Enemy from "./Enemy.svelte"; import { bulletList } from "../stores/cannon"; //      import { enemyList } from "../stores/enemy"; </script> <style> .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: flex-start; max-height: 100%; } </style> <div class="container"> <svg viewBox="0 0 480 800"> <!--       --> {#each $enemyList as enemy (enemy.id)} <Enemy {enemy} /> {/each} {#each $bulletList as bullet (bullet.id)} <Bullet {bullet} /> {/each} <Cannon /> </svg> </div> 

!
图片


8.


, .
. src/gameLoop/game.js . MDN


src/gameLoop/game.js
 import { get } from 'svelte/store'; //    import { bulletList } from '../stores/cannon'; //    import { enemyList } from '../stores/enemy'; //     import { removeBullet } from './cannon'; //     import { removeEnemy } from './enemy'; //       . //     ,   svg,   , //        . const enemyWidth = 30; const bulletWidth = 5; const enemyHeight = 30; const bulletHeight = 8; //    export function checkCollision() { get(bulletList).forEach(bullet => { get(enemyList).forEach(enemy => { if ( bullet.x < enemy.x + enemyWidth && bullet.x + bulletWidth > enemy.x && bullet.y < enemy.y + enemyHeight && bullet.y + bulletHeight > enemy.y ) { //   ,         removeBullet(bullet.id); removeEnemy(enemy.id); } }); }); } 

.


src/gameLoop/gameLoop.js
 import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon, shoot, moveBullet, clearBullets } from './cannon'; //    import { checkCollision } from './game'; import { addEnemy, moveEnemy } from './enemy'; function startLoop(steps) { window.requestAnimationFrame(() => { steps.forEach(step => { if (typeof step === 'function') step(); }); if (get(isPlaying)) startLoop(steps); }); } export const startGame = () => { isPlaying.set(true); //      startLoop([rotateCannon, shoot, moveBullet, clearBullets, addEnemy, moveEnemy, checkCollision]); }; export function stopGame() { isPlaying.set(false); } 

, .
图片


9.


, ToDo :


  • , ;
  • ;
  • ;
  • . svelte ;
  • ;
  • . .

github .


结论


, , React. 60 FPS, Svelte .
Svelte , .

Source: https://habr.com/ru/post/zh-CN452684/


All Articles