Desenvolvendo um jogo no Svelte 3

O Svelte 3 foi lançado há pouco mais de um mês. Um bom momento para conhecê-lo, pensei, e fiz um excelente tutorial , que também foi traduzido para o russo.


Para consolidar o passado, fiz um pequeno projeto e compartilhei os resultados com você. Não é mais uma lista de tarefas, mas um jogo em que você precisa atirar em quadrados pretos.


imagem


0. Para impaciente


Repositório do tutorial
Repositório com extras
Demo


1. Preparação


Clonamos um modelo para desenvolvimento


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

Instale as dependências.


 cd template/ npm i 

Iniciamos o servidor de desenvolvimento.


 npm run dev 

Nosso modelo está disponível em
http: // localhost: 5000 . O servidor suporta recarga a quente, para que nossas alterações sejam visíveis no navegador conforme as alterações são salvas.


Se você não deseja implantar o ambiente localmente, pode usar as caixas de proteção codesandbox e stackblitz online que oferecem suporte ao Svelte.


2. O quadro do jogo


A pasta src consiste em dois arquivos main.js e App.svelte .
main.js é o ponto de entrada para nosso aplicativo. Durante o desenvolvimento, não vamos tocá-lo. Aqui, o componente App.svelte é montado no corpo do documento.
App.svelte é um componente do svelte. O modelo do componente consiste em três partes:


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

Os estilos de componente são isolados, mas é possível atribuir estilos globais com a diretiva: global (). Mais sobre estilos .
Adicione estilos comuns para o nosso componente.


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> 

Vamos criar a pasta src / components na qual nossos componentes serão armazenados
Nesta pasta, crie dois arquivos que conterão o campo de jogo e os controles.


src / components / GameField.svelte
 <div>GameField</div> 

src / components / Controls.svelte
 <div>Controls</div> 

O componente é importado pela diretiva


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

Para exibir um componente, basta inserir a etiqueta do componente na marcação. Saiba mais sobre tags .


 <Controls /> 

Agora importamos e exibimos nossos componentes no 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. Controles


O componente Controls.svelte consistirá em três botões: mover para a esquerda, mover para a direita, disparar. Os ícones de botão serão exibidos pelo elemento svg.
Crie a pasta src / asssets , à qual adicionamos nossos ícones svg.


src / assets / 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 / assets / 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 / assets / 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> 

Adicione o botão componente src / components / IconButton.svelte .
Aceitaremos manipuladores de eventos do componente pai. Para manter o botão pressionado, precisamos de dois manipuladores: o início do clique e o final do clique. Declaramos as variáveis start e release , onde aceitaremos os manipuladores de eventos para o início e o fim do clique. Também precisamos da variável ativa , que será exibida se o botão for pressionado ou não.


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

Nós estilizamos nosso componente


 <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> 

Um botão é um elemento do botão no qual o conteúdo transferido de um componente pai é exibido. O local onde o conteúdo transferido será montado é indicado pela tag <slot />. Saiba mais sobre o elemento <slot /> .


 <button> <slot /> </button> 

Os manipuladores de eventos são indicados usando a diretiva on :, por exemplo, on: click .
Lidaremos com eventos do mouse e tocaremos em cliques. Saiba mais sobre a associação de eventos .
A classe ativa será adicionada à classe base do componente se o botão for pressionado. Você pode atribuir uma classe pela propriedade da classe. Mais sobre aulas


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

Como resultado, nosso componente ficará assim:


src / components / 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> 

Agora importamos nossos ícones e o elemento button para src / components / Controls.svelte e criamos o layout.


src / components / 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> 

Nosso aplicativo deve ficar assim:
imagem


4. O campo de jogo


O campo de jogo é um componente svg, onde adicionaremos nossos elementos de jogo (arma, projéteis, oponentes).
Atualizar código src / components / GameField.svelte


src / components / 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> 

Crie a pistola src / components / Cannon.svelte . Alto para um retângulo, mas mesmo assim.


src / components / 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> 

Agora importamos nossa arma no campo de jogo.


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. Ciclo de Jogo


Nós temos a estrutura básica do jogo. O próximo passo é criar um ciclo de jogo que processará nossa lógica.
Vamos criar armazenamentos onde as variáveis ​​para nossa lógica estarão contidas. Vamos precisar do componente gravável do módulo svelte / store. Saiba mais sobre a loja .
A criação de um repositório simples se parece com isso:


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

Crie a pasta src / stores / , aqui todos os valores mutáveis ​​do nosso jogo serão armazenados.
Crie o arquivo src / stores / game.js no qual as variáveis ​​responsáveis ​​pelo estado geral do jogo serão armazenadas.


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

Crie o arquivo src / stores / cannon.js , no qual as variáveis ​​responsáveis ​​pelo estado da arma serão armazenadas


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

O Svelte permite criar repositórios personalizados que incluem a lógica do trabalho. Você pode ler mais sobre isso no livro . Não consegui encaixar isso perfeitamente no conceito do ciclo do jogo, portanto, apenas declaramos variáveis ​​no repositório. Executaremos todas as manipulações com eles na seção src / gameLoop .


O loop do jogo será planejado usando a função requestAnimationFrame . Uma matriz de funções que descreve a lógica do jogo será alimentada na entrada. No final do ciclo do jogo, se o jogo não terminar, a próxima iteração será planejada. No loop do jogo, acessaremos o valor da variável isPlaying para verificar se o jogo terminou.


Usando o armazenamento, você pode criar uma assinatura para um valor. Usaremos essa funcionalidade em componentes. Por enquanto, usaremos a função get para ler o valor da variável. Para definir o valor, usaremos o método .set () da variável
Você pode atualizar o valor chamando o método .update () , que assume uma função como entrada, cujo primeiro valor é passado no valor atual. Mais detalhes na documentação . Tudo o resto é JS puro.


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); } 

Agora descrevemos a lógica do comportamento de nossa arma.


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; } } 

Agora adicione nosso manipulador de rotação de armas ao loop do jogo.


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

Código do ciclo de jogo atual:


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); } 

Temos uma lógica que pode girar a arma. Mas ainda não o associamos ao apertar de botões. Está na hora de fazer isso. Os manipuladores de eventos de clique serão adicionados ao 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"); 

Adicione nossos manipuladores e o estado atual do clique aos elementos IconButton . Para fazer isso, basta passar os valores para os atributos criados anteriormente, start , release e active , conforme descrito na documentação .


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

Usamos a expressão $ para a variável $ direction . Essa sintaxe torna o valor reativo, assinando automaticamente as alterações. Mais detalhes na documentação .


src / components / 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> 

No momento, ao pressionar nosso botão, uma seleção ocorre, mas a arma ainda não gira. Precisamos importar o valor do ângulo para o componente Cannon.svelte e atualizar as regras de transformação de transformação


src / components / 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> 

Resta executar o loop do jogo no componente App.svelte .


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

App.svelte
 <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 /> 

Viva! Nossa arma começou a se mover.
imagem


6. Tiros


Agora ensine nossa arma a atirar. Precisamos armazenar os valores:


  • A arma dispara agora (o botão de disparo está pressionado);
  • O registro de data e hora do último tiro, você precisa calcular a taxa de tiro;
  • Matriz de conchas.

Adicione essas variáveis ​​ao nosso repositório src / stores / cannon.js .


src / stores / 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([]); 

Atualize as importações e a lógica do jogo em 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)); } 

Agora importamos nossos manipuladores para gameLoop.js e os adicionamos ao loop do jogo.


 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); } 

Agora só precisamos criar o processamento de pressionar o botão de disparo e adicionar a exibição de conchas no campo de jogo.
Edite src / components / Controls.svelte .


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

Agora adicione nossos manipuladores ao botão de controle de disparo, como fizemos com os botões de rotação


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

src / components / 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> 

Resta exibir as conchas no campo de jogo. Primeiro, crie o componente de projétil


src / components / 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> 

Como os shells que temos são armazenados em uma matriz, precisamos de um iterador para exibi-los. Em svelte, há uma diretiva Each para esses casos. Mais detalhes na documentação .


 //    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> 

.
imagem


7.


Ótimo. . 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> 

!
imagem


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); } 

, .
imagem


9.


, ToDo :


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

github .


Conclusão


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

Source: https://habr.com/ru/post/pt452684/


All Articles