Svelte 3 рдкрд░ рдПрдХ рдЧреЗрдо рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдирд╛

Svelte 3 рдХреЛ рдПрдХ рдорд╣реАрдиреЗ рдкрд╣рд▓реЗ рдереЛрдбрд╝рд╛ рд░рд┐рд▓реАрдЬ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред рдЖрдкрд╕реЗ рдорд┐рд▓рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрдЪреНрдЫрд╛ рдкрд▓, рдореИрдВрдиреЗ рд╕реЛрдЪрд╛, рдФрд░ рдПрдХ рдЙрддреНрдХреГрд╖реНрдЯ рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рднрд╛рдЧ рдЧрдпрд╛, рдЬрд┐рд╕рдХрд╛ рд░реВрд╕реА рдореЗрдВ рдЕрдиреБрд╡рд╛рдж рднреА рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред


рдЕрддреАрдд рдХреЛ рдордЬрдмреВрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рдПрдХ рдЫреЛрдЯреА рд╕реА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдмрдирд╛рдИ рдФрд░ рдкрд░рд┐рдгрд╛рдо рдЖрдкрдХреЗ рд╕рд╛рде рд╕рд╛рдЭрд╛ рдХрд┐рдпрд╛ред рдпрд╣ рдПрдХ-рдЕрдзрд┐рдХ-рдЯреВрдбреВ-рд╕реВрдЪреА рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рдПрдХ рдЧреЗрдо рд╣реИ рдЬрд┐рд╕рдореЗрдВ рдЖрдкрдХреЛ рдХрд╛рд▓реЗ рд╡рд░реНрдЧреЛрдВ рд╕реЗ рд╢реВрдЯ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред


рдЫрд╡рд┐


0. рдЕрдзреАрд░ рдХреЗ рд▓рд┐рдП


рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА
рдПрдХреНрд╕реНрдЯреНрд░рд╛рд╕ рдХреЗ рд╕рд╛рде рд░рд┐рдкреЛрдЬрд┐рдЯрд░реА
рдбреЗрдореЛ


1. рддреИрдпрд╛рд░реА


рд╣рдо рд╡рд┐рдХрд╛рд╕ рдХреЗ рд▓рд┐рдП рдПрдХ рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреНрд▓реЛрди рдХрд░рддреЗ рд╣реИрдВ


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

рдирд┐рд░реНрднрд░рддрд╛ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВред


 cd template/ npm i 

рд╣рдо рджреЗрд╡ рд╕рд░реНрд╡рд░ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВред


 npm run dev 

рд╣рдорд╛рд░реЗ рдЯреЗрдореНрдкрд▓реЗрдЯ рдкрд░ рдЙрдкрд▓рдмреНрдз рд╣реИ
http: // рд▓реЛрдХрд▓рд╣реЛрд╕реНрдЯ: 5000 ред рд╕рд░реНрд╡рд░ рдЧрд░реНрдо рдкреБрдирдГ рд▓реЛрдб рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╣рдорд╛рд░реЗ рдкрд░рд┐рд╡рд░реНрддрди рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рджрд┐рдЦрд╛рдИ рджреЗрдВрдЧреЗ рдХреНрдпреЛрдВрдХрд┐ рдкрд░рд┐рд╡рд░реНрддрди рд╕рд╣реЗрдЬреЗ рдЧрдП рд╣реИрдВред


рдпрджрд┐ рдЖрдк рд╕реНрдерд╛рдиреАрдп рд░реВрдк рд╕реЗ рдкрд░реНрдпрд╛рд╡рд░рдг рдХреЛ рдкрд░рд┐рдирд┐рдпреЛрдЬрд┐рдд рдирд╣реАрдВ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рддреЛ рдЖрдк рдХреЛрдбреИрдВрдбрдмреЙрдХреНрд╕ рдФрд░ рд╕реНрдЯреИрдХрдмреНрд▓рд┐рдЯреНрдЬрд╝ рд╕реИрдВрдбрдмреЙрдХреНрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдСрдирд▓рд╛рдЗрди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ рдЬреЛ Svelte рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддреЗ рд╣реИрдВред


2. рдЦреЗрд▓ рдХрд╛ рдврд╛рдБрдЪрд╛


Src рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рджреЛ рдлрд╛рдЗрд▓реЗрдВ рд╣реЛрддреА рд╣реИрдВ main.js рдФрд░ App.svelte ред
main.js рд╣рдорд╛рд░реЗ рдЖрд╡реЗрджрди рдХрд╛ рдкреНрд░рд╡реЗрд╢ рдмрд┐рдВрджреБ рд╣реИред рд╡рд┐рдХрд╛рд╕ рдХреЗ рджреМрд░рд╛рди, рд╣рдо рдЗрд╕реЗ рдирд╣реАрдВ рдЫреВрдПрдВрдЧреЗред рдпрд╣рд╛рдВ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдХреЗ рд╢рд░реАрд░ рдореЗрдВ App.svelte рдШрдЯрдХ рдорд╛рдЙрдВрдЯ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред
App.svelte svelte рдХрд╛ рдПрдХ рдШрдЯрдХ рд╣реИред рдШрдЯрдХ рдЯреЗрдореНрдкрд▓реЗрдЯ рдореЗрдВ рддреАрди рднрд╛рдЧ рд╣реЛрддреЗ рд╣реИрдВ:


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

рдШрдЯрдХ рд╢реИрд▓рд┐рдпреЛрдВ рдХреЛ рдЕрд▓рдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдирд┐рд░реНрджреЗрд╢ рдХреЗ рд╕рд╛рде рд╡реИрд╢реНрд╡рд┐рдХ рд╢реИрд▓рд┐рдпреЛрдВ рдХреЛ рдЕрд╕рд╛рдЗрди рдХрд░рдирд╛ рд╕рдВрднрд╡ рд╣реИ: рд╡реИрд╢реНрд╡рд┐рдХ ()ред рд╢реИрд▓рд┐рдпреЛрдВ рдкрд░ рдЕрдзрд┐рдХ ред
рд╣рдорд╛рд░реЗ рдШрдЯрдХ рдХреЗ рд▓рд┐рдП рд╕рд╛рдорд╛рдиреНрдп рд╢реИрд▓рд┐рдпрд╛рдБ рдЬреЛрдбрд╝реЗрдВред


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 / Components / 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 рдЬреЛрдбрд╝реЗрдВ ред
рд╣рдо рдореВрд▓ рдШрдЯрдХ рд╕реЗ рдИрд╡реЗрдВрдЯ рд╣реИрдВрдбрд▓рд░ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░реЗрдВрдЧреЗред рдмрдЯрди рдХреЛ рджрдмрд╛рдП рд░рдЦрдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реЛрдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рджреЛ рд╣реИрдВрдбрд▓рд░ рдЪрд╛рд╣рд┐рдП: рдХреНрд▓рд┐рдХ рдХреА рд╢реБрд░реБрдЖрдд рдФрд░ рдХреНрд▓рд┐рдХ рдХрд╛ рдЕрдВрддред рд╣рдо рдЪрд░реЛрдВ рдХреА рд╢реБрд░реБрдЖрдд рдФрд░ рд╡рд┐рдореЛрдЪрди рдХреА рдШреЛрд╖рдгрд╛ рдХрд░рддреЗ рд╣реИрдВ, рдЬрд╣рд╛рдВ рд╣рдо рдХреНрд▓рд┐рдХ рдХреА рд╢реБрд░реБрдЖрдд рдФрд░ рдЕрдВрдд рдХреЗ рд▓рд┐рдП рдИрд╡реЗрдВрдЯ рд╣реИрдВрдбрд▓рд░ рд╕реНрд╡реАрдХрд╛рд░ рдХрд░реЗрдВрдЧреЗред рд╣рдореЗрдВ рд╕рдХреНрд░рд┐рдп рдЪрд░ рдХреА рднреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рдЬреЛ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░реЗрдЧрд╛ рдХрд┐ рдмрдЯрди рджрдмрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ рдпрд╛ рдирд╣реАрдВред


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

рдПрдХ рдмрдЯрди рдПрдХ рдмрдЯрди рддрддреНрд╡ рд╣реИ рдЬрд┐рд╕рдХреЗ рднреАрддрд░ рдореВрд▓ рдШрдЯрдХ рд╕реЗ рд╕реНрдерд╛рдирд╛рдВрддрд░рд┐рдд рд╕рд╛рдордЧреНрд░реА рдкреНрд░рджрд░реНрд╢рд┐рдд рд╣реЛрддреА рд╣реИред рдЬрд┐рд╕ рд╕реНрдерд╛рди рдкрд░ рд╣рд╕реНрддрд╛рдВрддрд░рд┐рдд рд╕рд╛рдордЧреНрд░реА рдХреЛ рдорд╛рдЙрдВрдЯ рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛, рд╡рд╣ <рд╕реНрд▓реЙрдЯ /> рдЯреИрдЧ рджреНрд╡рд╛рд░рд╛ рдЗрдВрдЧрд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред <рд╕реНрд▓реЙрдЯ /> рддрддреНрд╡ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрдзрд┐рдХ рдЬрд╛рдиреЗрдВ ред


 <button> <slot /> </button> 

рдИрд╡реЗрдВрдЯ рд╣реИрдВрдбрд▓рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХрд╛ рд╕рдВрдХреЗрдд рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ : рдирд┐рд░реНрджреЗрд╢, рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП, рдкрд░: рдХреНрд▓рд┐рдХ рдХрд░реЗрдВ ред
рд╣рдо рдорд╛рдЙрд╕ рдШрдЯрдирд╛рдУрдВ рдХреЛ рд╕рдВрднрд╛рд▓реЗрдВрдЧреЗ рдФрд░ рдХреНрд▓рд┐рдХреЛрдВ рдХреЛ рд╕реНрдкрд░реНрд╢ рдХрд░реЗрдВрдЧреЗред рдЗрд╡реЗрдВрдЯ рдмрд╛рдЗрдВрдбрд┐рдВрдЧ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрдзрд┐рдХ рдЬрд╛рдиреЗрдВ ред
рдпрджрд┐ рдмрдЯрди рджрдмрд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рддреЛ рд╕рдХреНрд░рд┐рдп рд╡рд░реНрдЧ рдХреЛ рдШрдЯрдХ рдХреЗ рдЖрдзрд╛рд░ рд╡рд░реНрдЧ рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЬрд╛рдПрдЧрд╛ред рдЖрдк рдХреНрд▓рд╛рд╕ рдкреНрд░реЙрдкрд░реНрдЯреА рдХреЗ рд╣рд┐рд╕рд╛рдм рд╕реЗ рдХреНрд▓рд╛рд╕ рдЕрд╕рд╛рдЗрди рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рд╡рд░реНрдЧреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрдзрд┐рдХ


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

рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк, рд╣рдорд╛рд░рд╛ рдШрдЯрдХ рдЗрд╕ рддрд░рд╣ рджрд┐рдЦреЗрдЧрд╛:


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> 

рдЕрдм рд╣рдо рдЕрдкрдиреЗ рдЖрдЗрдХрди рдФрд░ рдмрдЯрди рддрддреНрд╡ рдХреЛ src / Components / Controls.svelte рдореЗрдВ рдЖрдпрд╛рдд рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рд▓реЗрдЖрдЙрдЯ рдмрдирд╛рддреЗ рд╣реИрдВред


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> 

рд╣рдорд╛рд░рд╛ рдЖрд╡реЗрджрди рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрдирд╛ рдЪрд╛рд╣рд┐рдП:
рдЫрд╡рд┐


4. рдЦреЗрд▓ рдХрд╛ рдореИрджрд╛рди


рдЦреЗрд▓ рдХрд╛ рдореИрджрд╛рди рдПрдХ svg рдШрдЯрдХ рд╣реИ, рдЬрд╣рд╛рдВ рд╣рдо рдЕрдкрдиреЗ рдЦреЗрд▓ рддрддреНрд╡реЛрдВ (рдмрдВрджреВрдХ, рдЧреЛрд▓реЗ, рд╡рд┐рд░реЛрдзрд┐рдпреЛрдВ) рдХреЛ рдЬреЛрдбрд╝реЗрдВрдЧреЗред
рдЕрдкрдбреЗрдЯ src / Components / 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 / 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> 

рдЕрдм рд╣рдо рдЕрдкрдиреА рдмрдВрджреВрдХ рдХреЛ рдЦреЗрд▓ рдореИрджрд╛рди рдкрд░ рдЖрдпрд╛рдд рдХрд░рддреЗ рд╣реИрдВред


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 / store / folder рдмрдирд╛рддреЗ рд╣реИрдВ, рд╣рдорд╛рд░реЗ рдЧреЗрдо рдХреЗ рд╕рднреА рдЙрддреНрдкрд░рд┐рд╡рд░реНрддрд┐рдд рдорд╛рдиреЛрдВ рдХреЛ рдпрд╣рд╛рдБ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдпрд╛ рдЬрд╛рдПрдЧрд╛ред
Src / store / game.js рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдПрдВ рдЬрд┐рд╕рдореЗрдВ рдЧреЗрдо рдХреА рд╕рд╛рдорд╛рдиреНрдп рд╕реНрдерд┐рддрд┐ рдХреЗ рд▓рд┐рдП рдЬрд┐рдореНрдореЗрджрд╛рд░ рдЪрд░ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдП рдЬрд╛рдПрдВрдЧреЗред


src / рд╕реНрдЯреЛрд░ / game.js
 //     import { writable } from "svelte/store"; //        ,    true/false export const isPlaying = writable(false); 

Src / store / cannon.js рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдПрдВ, рдЬрд┐рд╕рдореЗрдВ рдмрдВрджреВрдХ рдХреА рд╕реНрдерд┐рддрд┐ рдХреЗ рд▓рд┐рдП рдЬрд┐рдореНрдореЗрджрд╛рд░ рдЪрд░ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдП рдЬрд╛рдПрдВрдЧреЗ


src / store / 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 рд╡реИрд░рд┐рдПрдмрд▓ рдХреЗ рдорд╛рди рдХреЛ рдПрдХреНрд╕реЗрд╕ рдХрд░реЗрдВрдЧреЗ рдХрд┐ рдХреНрдпрд╛ рдЧреЗрдо рд╕рдорд╛рдкреНрдд рд╣реЛ рдЧрдпрд╛ рд╣реИред


рднрдВрдбрд╛рд░рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ, рдЖрдк рдореВрд▓реНрдп рдХреЗ рд▓рд┐рдП рдПрдХ рд╕рджрд╕реНрдпрддрд╛ рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВред рд╣рдо рдЗрд╕ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдШрдЯрдХреЛрдВ рдореЗрдВ рдХрд░реЗрдВрдЧреЗред рдЕрднреА рдХреЗ рд▓рд┐рдП, рд╣рдо рд╡реЗрд░рд┐рдПрдмрд▓ рдХреЗ рдорд╛рди рдХреЛ рдкрдврд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдлрдВрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред рдорд╛рди рд╕реЗрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдЪрд░ рдХреА .set () рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред
рдЖрдк .update () рд╡рд┐рдзрд┐ рдХреЛ рдХреЙрд▓ рдХрд░рдХреЗ рдорд╛рди рдХреЛ рдЕрдкрдбреЗрдЯ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдЬреЛ рдПрдХ рдлрд╝рдВрдХреНрд╢рди рдХреЛ рдЗрдирдкреБрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рд▓реЗрддрд╛ рд╣реИ, рд╡рд░реНрддрдорд╛рди рдорд╛рди рдХреЛ рдкрд╣рд▓реЗ рддрд░реНрдХ рдореЗрдВ рдкрд╛рд╕ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдкреНрд░рд▓реЗрдЦрди рдореЗрдВ рдЕрдзрд┐рдХ рдЬрд╛рдирдХрд╛рд░реА ред рдмрд╛рдХреА рд╕рдм рд╢реБрджреНрдз рдЬреЗрдПрд╕ рд╣реИред


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

рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рддрд░реНрдХ рд╣реИ рдЬреЛ рдмрдВрджреВрдХ рдХреЛ рдШреБрдорд╛ рд╕рдХрддрд╛ рд╣реИред рд▓реЗрдХрд┐рди рд╣рдордиреЗ рдЕрднреА рддрдХ рдЗрд╕реЗ рдмрдЯрди рдХреЗ рдкреБрд╢ рд╕реЗ рдирд╣реАрдВ рдЬреЛрдбрд╝рд╛ рд╣реИред рдпрд╣ рдХрд░рдиреЗ рдХрд╛ рд╕рдордп рд╣реИред рдИрд╡реЗрдВрдЯ рд╣реИрдВрдбрд▓рд░реНрд╕ рдХреЛ src / рдШрдЯрдХреЛрдВ / Controls.svelte рдореЗрдВ рдЬреЛрдбрд╝рд╛ рдЬрд╛рдПрдЧрд╛ред


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

IconButton рддрддреНрд╡реЛрдВ рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╣рдорд╛рд░реЗ рд╣реИрдВрдбрд▓рд░ рдФрд░ рд╡рд░реНрддрдорд╛рди рд╕реНрдерд┐рддрд┐ рдХреЛ рдЬреЛрдбрд╝реЗрдВред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдмрд╕ рдкрд╣рд▓реЗ рд╕реЗ рдмрдирд╛рдИ рдЧрдИ рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рдХреЗ рдореВрд▓реНрдпреЛрдВ рдХреЛ рдкрд╛рд╕ рдХрд░реЗрдВ, рдЬрд╛рд░реА рдХрд░реЗрдВ рдФрд░ рд╕рдХреНрд░рд┐рдп рдХрд░реЗрдВ , рдЬреИрд╕рд╛ рдХрд┐ рдкреНрд░рд▓реЗрдЦрди рдореЗрдВ рд╡рд░реНрдгрд┐рдд рд╣реИред


 <IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </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 } 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 / 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> 

рдпрд╣ 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 /> 

рд╣реБрд░реНрд░реЗ! рд╣рдорд╛рд░реА рдмрдВрджреВрдХ рдЪрд▓рдиреЗ рд▓рдЧреАред
рдЫрд╡рд┐


6. рд╢реЙрдЯреНрд╕


рдЕрдм рд╣рдорд╛рд░реА рдмрдВрджреВрдХ рдХреЛ рдЧреЛрд▓реА рдорд╛рд░рдирд╛ рд╕рд┐рдЦрд╛рдПрдВред рд╣рдореЗрдВ рдореВрд▓реНрдпреЛрдВ рдХреЛ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:


  • рдХреНрдпрд╛ рдЕрдм рдмрдВрджреВрдХ рдХреА рдЧреЛрд▓реА рдЪрд▓рддреА рд╣реИ (рдлрд╛рдпрд░ рдмрдЯрди рджрдмрд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ);
  • рдЕрдВрддрд┐рдо рд╢реЙрдЯ рдХреЗ рдЯрд╛рдЗрдорд╕реНрдЯреИрдореНрдк рдХреЛ рдЖрдЧ рдХреА рджрд░ рдХреА рдЧрдгрдирд╛ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ;
  • рдЧреЛрд▓реЗ рдХреА рдРрд░реЗред

рдЗрди рдЪрд░реЛрдВ рдХреЛ рд╣рдорд╛рд░реЗ src / store / cannon.js рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдореЗрдВ рдЬреЛрдбрд╝реЗрдВ ред


src / store / 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)); } 

рдЕрдм рд╣рдо рдЕрдкрдиреЗ рд╣реИрдВрдбрд▓рд░ рдХреЛ рдЧреЗрдорд▓реЛрдк.рдЬреЗрдПрд╕ рдореЗрдВ рдЖрдпрд╛рдд рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдЙрдиреНрд╣реЗрдВ рдЧреЗрдо рд▓реВрдк рдореЗрдВ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВред


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

рдпрд╣ рдЦреЗрд▓ рдХреЗ рдореИрджрд╛рди рдкрд░ рдЧреЛрд▓реЗ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдиреА рд╣реБрдИ рд╣реИред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдкреНрд░рдХреНрд╖реЗрдкреНрдп рдШрдЯрдХ рдмрдирд╛рдПрдВ


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> 

рдЪреВрдВрдХрд┐ рдЧреЛрд▓реЗ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ рд╕рд░рдгреА рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рдЙрдиреНрд╣реЗрдВ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдкреБрдирд░рд╛рд╡реГрддреНрддрд┐ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред Svelte рдореЗрдВ рдРрд╕реЗ рдорд╛рдорд▓реЛрдВ рдХреЗ рд▓рд┐рдП рдкреНрд░рддреНрдпреЗрдХ рдирд┐рд░реНрджреЗрд╢ рд╣реИред рдкреНрд░рд▓реЗрдЦрди рдореЗрдВ рдЕрдзрд┐рдХ рдЬрд╛рдирдХрд╛рд░реА ред


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


All Articles