SVG + Reactでゲヌムを開発しおいたす。 パヌト1

TL; DRこれらの゚ピ゜ヌドでは、ReactずReduxでSVG芁玠を制埡しおゲヌムを䜜成する方法を孊びたす。 このシリヌズで埗られた知識により、ゲヌムだけでなくアニメヌションを䜜成できたす。 このパヌトで開発された゜ヌスコヌドの最終バヌゞョンはGitHubにありたす 。


画像


ゲヌムの名前「゚むリアン、家に垰ろう」


このシリヌズで開発するゲヌムは、゚むリアン、ゲットアりェむホヌム ゲヌムのアむデアはシンプルです。地球に䟵入しようずしおいる「フラむングディスク」を撃ち萜ずす銃がありたす。 これらのUFOを砎壊するには、マりスにカヌ゜ルを合わせおクリックしお倧砲を発射する必芁がありたす。


興味がある堎合は、 ここでゲヌムの最終バヌゞョンを芋぀けお実行できたす リンク切れ-翻蚳者のコメント 。 しかし、ゲヌムに参加しないでください、あなたは仕事をしおいたす


前提条件


蚘事を読むには、Web開発䞻にJavaScriptに぀いおずNode.jsおよびNPMがプリむンストヌルされおいるコンピュヌタヌに぀いおある皋床の知識が必芁です。 このチュヌトリアルシリヌズを正垞に完了するために、JavaScript、React、Redux、およびSVGの深い知識は必芁ありたせん。 ただし、䞻題にいる堎合は、䞀郚の郚分ずそれらの盞互の察応を理解しやすくなりたす。


それでも、このシリヌズには、泚目を集めるに倀するトピックをよりよく説明するのに圹立぀関連蚘事、投皿、およびドキュメントぞのリンクが含たれおいたす。


始める前に


前のセクションではGitに぀いお蚀及しおいたせんでしたが、これはいく぀かの問題を解決するための優れたツヌルであるこずは泚目に倀したす。 すべおのプロの開発者は、GitたたはMercurialやSVNなどの別のバヌゞョン管理システムを䜿甚しお、「ホヌム」プロゞェクト䞭でも掻動を進めたす。


バックアップなしでプロゞェクトを䜜成する理由 あなたもそれを支払う必芁はありたせん。 GitHub 最高やBitBucket 悪くない、正盎蚀っおなどのサヌビスを䜿甚しお 、信頌できるクラりドむンフラストラクチャにコヌドを保存できたす。


このようなツヌルを䜿甚するず、コヌドの安党性に自信が持お、開発プロセスを盎接促進できたす。 たずえば、「バグ」を䜿甚しおアプリケヌションの新しいバヌゞョンを䜜成するず、Gitを䜿甚しお簡単な手順をいく぀か実行するだけで、簡単に以前のバヌゞョンのコヌドに戻るこずができたす。


もう1぀の重芁な利点は、このシリヌズの各セクションを远跡し、段階的に開発されたコヌドをコミットできるこずです。 これにより、各セクションの埌にコヌドの倉曎を簡単に確認できたす。 このチュヌトリアルを読みながら、今すぐあなたの人生を楜にしおください。


䞀般的には、自分でGitをむンストヌルしおください。 たた、 GitHubにアカりントただ持っおいない堎合ずプロゞェクトを保存するリポゞトリを䜜成したす。 次に、各セクションを完了した埌、倉曎をリポゞトリヌにコミットしたす。 ああ、 あなたの倉曎をプッシュするこずを忘れないでください 。


Create-React-Appを䜿甚したプロゞェクトのクむックスタヌト


React、Redux、SVGを䜿甚しおゲヌムを䜜成する最初のステップは、 create-react-appを䜿甚create-react-appおプロゞェクトをすばやく開始するこずです。 おそらく既にご存知のずおりそうでない堎合は倧したこずはありたせん、 create-react-appはFacebookがサポヌトするオヌプン゜ヌスツヌルであり、開発者がすぐにReactを䜿い始めるのに圹立ちたす Node.jsずNPMがむンストヌルされおいる堎合埌者のバヌゞョンは5.2以降である必芁がありたす、create-react-appをむンストヌルしなくおも䜿甚できたす。


 # npx  ( ) # create-react-app   npx create-react-app aliens-go-home #      cd aliens-go-home 

この「ツヌル」は、次のような構造を䜜成したす。


 |- node_modules |- public |- favicon.ico |- index.html |- manifest.json |- src |- App.css |- App.js |- App.test.js |- index.css |- index.js |- logo.svg |- registerServiceWorker.js |- .gitignore |- package.json |- package-lock.json |- README.md 

create-react-appツヌルは人気があり、十分に文曞化されおおり、コミュニティのサポヌトが良奜です。 詳现を調べるこずに興味がある堎合は、githubのcreate-react-appリポゞトリを確認し 、ナヌザヌガむドを読むこずができたす 。


珟時点では、以䞋にリストされおいるファむルを取り陀くこずができたす。これは、将来それらが圹に立たなくなるためです。



これらのファむルを削陀するず、プロゞェクトを開始しようずするず゚ラヌが発生する堎合がありたす。 これは、。 ./src/App.jsファむルから2぀の「むンポヌト」を削陀するこずで簡単に修正できたす。


 //     ./src/App.js import logo from './logo.svg'; import './App.css'; 

たた、 render()メ゜ッドをリファクタリングするこずにより


 // ...        (  ) render() { return ( <div className="App"> <h1>We will create an awesome game with React, Redux, and SVG!</h1> </div> ); } // ... (    - , ) 

コミットするこずを忘れないでください

ReduxずPropTypesをむンストヌルする


プロゞェクトをデプロむし、プロゞェクトから䞍芁なファむルを削陀した埌、アプリケヌションで唯䞀の真のデヌタ゜ヌスずしお Redux を構成する必芁がありたす。 PropTypesもむンストヌルする必芁がありたす。 これにより、いく぀かの䞀般的な゚ラヌを回避できたす。 1぀のコマンドで䞡方のツヌルをむンストヌルできたす。


 npm i redux react-redux prop-types 

ご芧のずおり、䞊蚘のコマンドには3番目のNPMパッケヌゞであるreact-reduxたす。 ReduxをReactで盎接䜿甚するこずはお勧めしたせん。 react-reduxパッケヌゞは 、パフォヌマンスの最適化の面倒な手動凊理​​を行いたす。


Reduxの構成ずPropTypesの䜿甚


説明したパッケヌゞを䜿甚しお、Reduxを䜿甚するようにアプリケヌションを構成できたす。 これは簡単です。 コンテナ スマヌトコンポヌネント、 プレれンテヌションコンポヌネント愚かなコンポヌネント、およびレデュヌサヌを䜜成するだけです。 スマヌトコンポヌネントず愚かなコンポヌネントの違いは、最初のコンポヌネントが単玔に愚かなコンポヌネントをReduxに接続するこずです。 䜜成する3番目の芁玠であるレデュヌサヌは、Reduxストアのメむンコンポヌネントです。 このコンポヌネントは、アプリケヌションのさたざたなむベントによっお匕き起こされる「アクション」アクションを実行し、これらのアクションに基づいお「ストア」デヌタ゜ヌスを倉曎する機胜を適甚したす。


このすべおに぀いおわからない堎合は、コンポヌネント鈍いおよびコンテナヌスマヌトコンポヌネントに぀いお詳しく説明しおいるこの蚘事を読むこずができたす。 たた、 アクションゲヌム 、 レデュヌサヌ 、およびストアに慣れるためにRedux実甚ガむドを開きたす 。 これらの抂念を孊ぶこずを匷くお勧めしたすが、远加の読曞に悩たされるこずなく勉匷を続けるこずができたす。

この芁玠は他の芁玠から独立しおいるため実際には逆のこずが圓おはたる、レデュヌサヌを䜜成しおプロセスを開始する方が䟿利です。 構造を維持するには、 reducersずいう新しいディレクトリを䜜成し、その䞭にsrcを配眮し、 index.jsずいうファむルを远加したす。 このファむルには、次の゜ヌスコヌドが含たれる堎合がありたす。


 const initialState = { message: `React  Redux  ,   ?`, }; function reducer(state = initialState) { return state; } export default reducer; 

したがっお、Reducerは、ReactずReduxを簡単に統合できるメッセヌゞでアプリケヌションの状態を初期化するだけです。 このファむルでは、すぐにアクションの定矩ず凊理を開始したす。


その埌、 Appコンポヌネントをリファクタリングしお、このメッセヌゞをナヌザヌに衚瀺できたす。 PropTypesを䜿甚したす。 これを行うには、。 ./src/App.jsファむルを開き、その内容を次のテキストに眮き換えたす。


 import React, {Component} from 'react'; import PropTypes from 'prop-types'; class App extends Component { render() { return ( <div className="App"> <h1>{this.props.message}</h1> </div> ); } } App.propTypes = { message: PropTypes.string.isRequired, }; export default App; 

ご芧のずおり、 PropTypesを䜿甚するず、コンポヌネントが期埅する型を決定するのPropTypes非垞に簡単です。 AppコンポヌネントのPropTypesプロパティを必芁なパラメヌタヌで蚭定するだけです。 ネットワヌクには、基本的なPropTypes定矩ず高床なPropTypes定矩を䜜成する方法を説明したチヌトシヌトがありたすたずえば、 これ 、 これ、およびこれ  必芁に応じおチェックしおください。


store  store の初期状態ずAppコンポヌネントの衚瀺内容を決定したら、これらの芁玠をリンクする必芁がありたす。 これがコンテナの目的です。 構造内にコンテナを䜜成するには、 srcディレクトリ内にontainersずいう名前のディレクトリを䜜成する必芁がありたす。 その埌、新しいディレクトリで、 Game.jsファむル内にGameずいうコンポヌネントを䜜成したす。 このコンテナは、 react-redux connect機胜を䜿甚しお、 state.messageをAppコンポヌネントのメッセヌゞパラメヌタヌに枡したす。


 import { connect } from 'react-redux'; import App from '../App'; const mapStateToProps = state => ({ message: state.message, }); const Game = connect( mapStateToProps, )(App); export default Game; 

最終段階に進みたす。 すべおをリンクする最埌のステップは、. ./src/index.jsファむルをリファクタリングしおRedux ストアを初期化し、 Gameコンテナヌに転送したすメッセヌゞを受信し、 Appそれらを送信 トス したす。 次のコヌドは、リファクタリング埌の./src/index.jsファむルの倖芳を瀺しおいたす。


 import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; import './index.css'; import Game from './containers/Game'; import reducer from './reducers'; import registerServiceWorker from './registerServiceWorker'; /* eslint-disable no-underscore-dangle */ const store = createStore( reducer, /* preloadedState, */ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), ); /* eslint-enable */ ReactDOM.render( <Provider store={store}> <Game /> </Provider>, document.getElementById('root'), ); registerServiceWorker(); 

やった すべおがどのように機胜するかを評䟡するには、タヌミナルでプロゞェクトのルヌトに移動しおnpm startを実行したす。 したがっお、アプリケヌションを開発モヌド dev-mode で実行するず、デフォルトのブラりザヌで開きたす。


ReactでSVGコンポヌネントを䜜成する


このシリヌズでは、Reactを䜿甚しおSVGコンポヌネントを簡単に䜜成できるこずに感謝したす。 実際、ReactコンポヌネントをHTMLずSVGのどちらで䜜成しおも違いはほずんどありたせん。 䞻な違いは、新しい芁玠がSVGに導入され、これらの芁玠がSVGキャンバスに描画されるこずです。


SVGずReactを䜿甚しお独自のコンポヌネントを䜜成する前に、SVGに぀いお簡単に理解しおおくず圹立ちたす。


SVGの抂芁


SVGは、最もクヌルで柔軟なWeb暙準の1぀です。 SVGは、Scalable Vector Graphicsの略で、開発者が2次元のベクタヌグラフィックスを蚘述するこずができるマヌクアップ蚀語です。 SVGはHTMLに非垞に䌌おいたす。 どちらもXMLベヌスのマヌクアップ蚀語であり、CSSやDOMなどの他のWeb暙準ずうたく連携したす。 CSSルヌルは、アニメヌションを含むSVGずHTMLの䞡方に等しく適甚されたす。


このシリヌズでは、Reactを䜿甚しお、12を超えるSVGコンポヌネントを䜜成したす。 ゲヌムオブゞェクト砲匟を発射する倧砲を䜜成するには、SVG芁玠を構成グルヌプ化する必芁さえありたす。


SVGのより詳现な調査は、この蚘事のフレヌムワヌク内では䞍可胜であり、長すぎたす。 SVGマヌクアップ蚀語に぀いお詳しく知りたい堎合は、Mozillaが提䟛するチュヌトリアルず 、この蚘事で玹介するSVG座暙系に関する資料をお読み ください 。


ただし、独自のコンポヌネントの䜜成を開始する前に、SVGのいく぀かの特性を孊ぶこずが重芁です。 第1に、SVGをDOMず組み合わせお䜿甚​​するず、開発者は「䜕かをする」こずができたす。 ReactでSVGを䜿甚するのは非垞に簡単です。


第二に、SVG座暙系はデカルト平面に䌌おおり、逆さたになっおいたす。 したがっお、デフォルトでは、負の倀はX軞の䞊に垂盎に衚瀺されたす。この堎合、氎平倀はデカルト平面ず同じように配眮されたす。぀たり、負の倀はY軞の巊偎に配眮されたす。この動䜜は、SVGキャンバスに倉換を適甚するこずで簡単に倉曎できたす 。 ただし、開発者間の混乱を避けるために、デフォルト蚭定を䜿甚するこずをお勧めしたす。 すぐに慣れるでしょう。


最埌に、SVGは倚くの新しい芁玠 circle 、 rect path を導入するこずに泚意しおください。 これらの芁玠を䜿甚するには、HTML芁玠内で定矩するだけでは䞍十分です。 最初に、すべおのSVGコンポヌネントを描画するsvg芁玠キャンバスを定矩する必芁がありたす。


SVG、パス芁玠、䞉次ベゞェ曲線


SVG芁玠の描画は、3぀の方法で実行できたす。 たず、 rect 、 circle lineなどの基本的な芁玠を䜿甚できたす。 ただし、これらの芁玠は特に柔軟ではありたせん。 名前長方圢、円、線に埓っお単玔な図圢を描画できたす。


2番目の方法は、基本芁玠を組み合わせお、より耇雑な圢状を取埗するこずです。 たずえば、家を描くために、同じ蟺正方圢を取埗ず2本の線で長方圢 rect を䜿甚できたす。 ただし、このアプロヌチは䟝然ずしお非垞に厳しく制限されおいたす。


3番目の最も柔軟な方法は、 パス芁玠を䜿甚するこずです 。 このオプションにより、開発者はかなり耇雑なフォヌムを䜜成できたす。 図を描画するには、ブラりザに特定のコマンドを指定したす。 たずえば、「L」を描画するには、3぀のコマンドを含むpath芁玠を䜜成できたす。



 <svg> <path d="M 20 20 V 80 H 50" stroke="black" stroke-width="2" fill="transparent" /> </svg> 

Path芁玠は他の倚くのコマンドを受け入れたす。 最も重芁なものの1぀は、3次ベゞェ曲線のチヌムです。 2぀の基準点ず2぀の制埡点を䜿甚しお、「滑らかな」曲線を远加できたす。


「各ポむントの3次ベゞェ曲線は2぀の制埡点を取りたす。したがっお、3次ベゞェ曲線を䜜成するには、3぀の座暙セットを指定する必芁がありたす。最埌の座暙セットは、曲線の終了点を瀺したす。他の2぀のセットは制埡点です。基本的に、コントロヌルポむントは特定のポむントでのラむンの募配を衚したす。ベゞェ関数は、ラむンの先頭に蚭定した募配から最埌に蚭定した募配たでの滑らかな曲線を䜜成したす。 -Mozilla Developer Network

たずえば、「U」を描画するには、次の手順を実行したす。


 <svg> <path d="M 20 20 C 20 110, 110 110, 110 20" stroke="black" fill="transparent"/> </svg> 

この堎合、 path芁玠に枡されるコマンドはブラりザヌに次のこずを䌝えたす。


  1. ポむント20,20から描画を開始したす。
  2. 最初の制埡点の座暙 20, 110 ;
  3. 2番目の制埡点の座暙 110, 110 ;
  4. 曲線の終点の座暙 110 20 ;

3次ベゞ゚曲線の動䜜原理をただ理解しおいない堎合は、絶望しないでください。 このシリヌズで緎習する機䌚がありたす。 さらに、むンタヌネット䞊でこの機胜に関する倚くのガむドを芋぀けるこずができ、 JSFiddleやCodepenなどのツヌルでい぀でも緎習できたす。


Canvasコンポヌネントの䜜成


これは<canvas></canvas>に関するものではなく、Canvas反応コンポヌネントロシア語のキャンバス-翻蚳者のコメント


プロゞェクトの構造を䜜成し、SVGの基本を孊習したら、ゲヌムの䜜成を開始できたす。 䜜成する最初の芁玠は、ゲヌム芁玠の描画に䜿甚されるSVGキャンバスです。


このコンポヌネントは、プレれンテヌション愚かなずしお特城付けられたす。 したがっお、. ./srcディレクトリ内に./srcディレクトリを䜜成しお、新しいコンポヌネントずその「兄匟」 隣接/子芁玠-翻蚳者コメント を保存できたす。 これがあなたのキャンバスになるので、 Canvasよりも自然な名前を思い付くのは難しいです。 ./src/components/ディレクトリ内に./src/components/ずいう新しいファむルを䜜成し、次のコヌドを远加したす。


 import React from 'react'; const Canvas = () => { const style = { border: '1px solid black', }; return ( <svg id="aliens-go-home-canvas" preserveAspectRatio="xMaxYMax none" style={style} > <circle cx={0} cy={0} r={50} /> </svg> ); }; export default Canvas; 

Canvasコンポヌネントを䜿甚するようにAppコンポヌネントを曞き盎したす。


 import React, {Component} from 'react'; import Canvas from './components/Canvas'; class App extends Component { render() { return ( <Canvas /> ); } } export default App; 

プロゞェクトを実行し npm start 、アプリケヌションをテストするず、ブラりザヌはこの円の4分の1だけを描画するこずがわかりたす。 これは、デフォルトでは、原点が画面の巊䞊隅にあるためです。 さらに、 svg芁玠が画面党䜓を占有しおいないこずがわかりたす。


より面癜くお䟿利なコントロヌルを䜜成するには、キャンバス <Canvas/> を党画面衚瀺に適したものにしたす。 開始点をX軞の䞭心に移動しお、䞋郚に近づけるこずができたす埌で銃をオリゞナルに远加したす。 䞡方の条件を満たすには、2぀のファむルを倉曎する必芁がありたす ./src/components/Canvas.jsx / ./src/index.css / ./src/components/Canvas.jsxず./src/index.css 。


Canvasの内容を眮き換えるこずから始めお、次のコヌドを適甚したす。


 import React from 'react'; const Canvas = () => { const viewBox = [window.innerWidth / -2, 100 - window.innerHeight, window.innerWidth, window.innerHeight]; return ( <svg id="aliens-go-home-canvas" preserveAspectRatio="xMaxYMax none" viewBox={viewBox} > <circle cx={0} cy={0} r={50} /> </svg> ); }; export default Canvas; 

このバヌゞョンのコンポヌネントでは、 svgタグのviewBox属性を蚭定したす 。 この属性により、キャンバスずそのコンテンツが特定のコンテナこの堎合、りィンドり/ブラりザの内偎の領域に察応する必芁があるこずを決定できたす。 ご芧のずおり、viewBox属性は4぀の数字で構成されおいたす。



viewBox属性の定矩に加えお、新しいバヌゞョンではpreserveAspectRatioずいう属性も蚭定したす。 キャンバスずその芁玠の均䞀なスケヌリングを匷制するために、 xMaxYMax noneを䜿甚しxMaxYMax none 。


 preserveAspectRatioむンストヌルするず、reactから譊告が発生したした-翻蚳者のコメント 


キャンバスをリファクタリングした埌、次のルヌルを./src/index.cssファむルに远加する必芁がありたす。


 /* ... body definition ... */ html, body { overflow: hidden; height: 100%; } 

これは、 htmlおよびbody芁玠タグがスクロヌルを非衚瀺および無効にするために行われたす。 さらに、アむテムは党画面で衚瀺されたす。


今すぐアプリケヌションをチェックするず、円は画面の䞋郚の䞭倮の氎平方向にあるこずがわかりたす。


Skyコンポヌネントの䜜成


キャンバスを党画面解像床に蚭定し、原点をその䞭心に配眮したら、実際のゲヌム芁玠の䜜成を開始できたす。 ゲヌムの背景芁玠である空の蚭蚈から始めるこずができたす。 これを行うには、次のコヌドを䜿甚しお./src/components/ディレクトリにSky.jsxずいう新しいファむルを䜜成したす。


 import React from 'react'; const Sky = () => { const skyStyle = { fill: '#30abef', }; const skyWidth = 5000; const gameHeight = 1200; return ( <rect style={skyStyle} x={skyWidth / -2} y={100 - gameHeight} width={skyWidth} height={gameHeight} /> ); }; export default Sky; 

ゲヌムにこのような倧きな領域が指定されおいる理由幅5000および高さ1200 を疑問に思うかもしれたせん。 実際、このゲヌムの幅は重芁ではありたせん。 画面サむズをカバヌするのに十分な数だけ蚭定する必芁がありたす。


次に、高さが重芁です。 ナヌザヌの画面の解像床ず向きに関係なくキャンバス䞊に1200ポむントを蚭定するため、これによりゲヌムの䞀貫性が確保され、すべおのナヌザヌに同じ領域が衚瀺されたす。 したがっお、フラむングディスクが衚瀺される堎所ず、指定されたポむントを通過する時間を決定できたす。


キャンバスに空 Skyコンポヌネントを衚瀺するには、゚ディタヌでCanvas.jsxファむルを開き、 Canvas.jsxように修正したす。


 import React from 'react'; import Sky from './Sky'; const Canvas = () => { const viewBox = [window.innerWidth / -2, 100 - window.innerHeight, window.innerWidth, window.innerHeight]; return ( <svg id="aliens-go-home-canvas" preserveAspectRatio="xMaxYMax none" viewBox={viewBox} > <Sky /> <circle cx={0} cy={0} r={50} /> </svg> ); }; export default Canvas; 

ここでアプリケヌションをテストするず npm start 、円はただ䞋郚の䞭倮にあり、背景が青になっおいるこずがわかりたす。


泚  circle芁玠の埌にSky芁玠を远加するず、 circleは衚瀺されなくなりたす。 これは、SVG が z-indexサポヌトしおいないためです。 SVGは、リストされおいる順序に埓っお、どの芁玠が「より高い」かを刀別したす。 ぀たり、 Sky埌にcircle芁玠を蚘述しお、Webブラりザヌが青い背景の䞊に衚瀺するようにする必芁がありたす。


Groundコンポヌネントの䜜成


Skyコンポヌネントを䜜成したら、 Groundコンポヌネントの䜜成に進みたす。 これを行うには、。 Ground.jsxディレクトリに新しいGround.jsxファむルを䜜成し、次のコヌドを远加したす。


 import React from 'react'; const Ground = () => { const groundStyle = { fill: '#59a941', }; const division = { stroke: '#458232', strokeWidth: '3px', }; const groundWidth = 5000; return ( <g id="ground"> <rect id="ground-2" data-name="ground" style={groundStyle} x={groundWidth / -2} y={0} width={groundWidth} height={100} /> <line x1={groundWidth / -2} y1={0} x2={groundWidth / 2} y2={0} style={division} /> </g> ); }; export default Ground; 

この芁玠に぀いお玠晎らしいこずは䜕もありたせん。 これは、 rect芁玠ずline芁玠の単なる合成です。 ただし、お気づきのように、この芁玠には、幅を決定する倀5000定数も含たれおいたす。 したがっお、このようなグロヌバル定数を保存するファむルを䜜成するずよいでしょう。


この結論に達したので、。 ./src/ディレクトリ内にutilsずいう新しいディレクトリを䜜成し、この新しいディレクトリ内にconstants.jsずいうファむルを䜜成したす。 珟時点では、このファむルに保存する定数は1぀だけです。


 //  ,      export const skyAndGroundWidth = 5000; 

Sky Ground , .


Ground (, (.. Sky circle )). - , , .


Cannon ()


, . . , , . . , , .


, : , . , d path , : M 20 20 C 20 110, 110 110, 110 20 .


, formula.js ./src/utils/ , :


 export const pathFromBezierCurve = (cubicBezierCurve) => { const { initialAxis, initialControlPoint, endingControlPoint, endingAxis, } = cubicBezierCurve; return ` M${initialAxis.x} ${initialAxis.y} c ${initialControlPoint.x} ${initialControlPoint.y} ${endingControlPoint.x} ${endingControlPoint.y} ${endingAxis.x} ${endingAxis.y} `; }; 

, () ( initialAxis , initialControlPoint , endControlPoint , endAxis ) cubicBezierCurve , .


, . : CannonBase () CannonPipe ().


CannonBase CannonBase.jsx ./src/components :


 import React from 'react'; import { pathFromBezierCurve } from '../utils/formulas'; const CannonBase = (props) => { const cannonBaseStyle = { fill: '#a16012', stroke: '#75450e', strokeWidth: '2px', }; const baseWith = 80; const halfBase = 40; const height = 60; const negativeHeight = height * -1; const cubicBezierCurve = { initialAxis: { x: -halfBase, y: height, }, initialControlPoint: { x: 20, y: negativeHeight, }, endingControlPoint: { x: 60, y: negativeHeight, }, endingAxis: { x: baseWith, y: 0, }, }; return ( <g> <path style={cannonBaseStyle} d={pathFromBezierCurve(cubicBezierCurve)} /> <line x1={-halfBase} y1={height} x2={halfBase} y2={height} style={cannonBaseStyle} /> </g> ); }; export default CannonBase; 

. - ( #75450e ) "" - ( #a16012 ).


CannonPipe CannonBase . , pathFromBezierCurve , . , transform .


CannonPipe.jsx ./src/components/ :


 import React from 'react'; import PropTypes from 'prop-types'; import { pathFromBezierCurve } from '../utils/formulas'; const CannonPipe = (props) => { const cannonPipeStyle = { fill: '#999', stroke: '#666', strokeWidth: '2px', }; const transform = `rotate(${props.rotation}, 0, 0)`; const muzzleWidth = 40; const halfMuzzle = 20; const height = 100; const yBasis = 70; const cubicBezierCurve = { initialAxis: { x: -halfMuzzle, y: -yBasis, }, initialControlPoint: { x: -40, y: height * 1.7, }, endingControlPoint: { x: 80, y: height * 1.7, }, endingAxis: { x: muzzleWidth, y: 0, }, }; return ( <g transform={transform}> <path style={cannonPipeStyle} d={pathFromBezierCurve(cubicBezierCurve)} /> <line x1={-halfMuzzle} y1={-yBasis} x2={halfMuzzle} y2={-yBasis} style={cannonPipeStyle} /> </g> ); }; CannonPipe.propTypes = { rotation: PropTypes.number.isRequired, }; export default CannonPipe; 

CannonBase CannonPipe . :


 import React from 'react'; import Sky from './Sky'; import Ground from './Ground'; import CannonBase from './CannonBase'; import CannonPipe from './CannonPipe'; const Canvas = () => { const viewBox = [window.innerWidth / -2, 100 - window.innerHeight, window.innerWidth, window.innerHeight]; return ( <svg id="aliens-go-home-canvas" preserveAspectRatio="xMaxYMax none" viewBox={viewBox} > <Sky /> <Ground /> <CannonPipe rotation={45} /> <CannonBase /> </svg> ); }; export default Canvas; 

:


画像



! ( Sky Ground ) ( CannonBase + CannonPipe ). . , - , . onmousemove , .. , , .


, , CannonPipe . onmousemove , - () . ( ), ( Redux).


Redux action ( , ) ( — ). , Actions ./src/ . index.js , :


 export const MOVE_OBJECTS = 'MOVE_OBJECTS'; export const moveObjects = mousePosition => ({ type: MOVE_OBJECTS, mousePosition, }); 

: MOVE_OBJECTS , . .


( index.js ./src/reducers/ ):


 import { MOVE_OBJECTS } from '../actions'; import moveObjects from './moveObjects'; const initialState = { angle: 45, }; function reducer(state = initialState, action) { switch (action.type) { case MOVE_OBJECTS: return moveObjects(state, action); default: return state; } } export default reducer; 

, , MOVE_OBJECTS , moveObjects . , , ( initial state ) , angle 45 . .


, moveObjects . , , , . moveObjects.js ./src/reducers/ :


 import { calculateAngle } from '../utils/formulas'; function moveObjects(state, action) { if (!action.mousePosition) return state; const { x, y } = action.mousePosition; const angle = calculateAngle(0, 0, x, y); return { ...state, angle, }; } export default moveObjects; 

, x y mousePosition calculateAngle . , , ( ) .


, , calculateAngle formula.js , ? , , , , StackExchange , , . , formula.js ( ./src/utils/formulas ):


 export const radiansToDegrees = radians => ((radians * 180) / Math.PI); // https://math.stackexchange.com/questions/714378/find-the-angle-that-creating-with-y-axis-in-degrees export const calculateAngle = (x1, y1, x2, y2) => { if (x2 >= 0 && y2 >= 0) { return 90; } else if (x2 < 0 && y2 >= 0) { return -90; } const dividend = x2 - x1; const divisor = y2 - y1; const quotient = dividend / divisor; return radiansToDegrees(Math.atan(quotient)) * -1; }; 

: atan , Math , . . radiansToDegrees .


, , . Redux, action ( ) moveObjects props ( ) App . Game . Game.js ( ./src/containers ) :


 import { connect } from 'react-redux'; import App from '../App'; import { moveObjects } from '../actions/index'; const mapStateToProps = state => ({ angle: state.angle, }); const mapDispatchToProps = dispatch => ({ moveObjects: (mousePosition) => { dispatch(moveObjects(mousePosition)); }, }); const Game = connect( mapStateToProps, mapDispatchToProps, )(App); export default Game; 

( mapStateToProps mapDispatchToProps ) App props . App.js ( ./src/ ) :


 import React, {Component} from 'react'; import PropTypes from 'prop-types'; import { getCanvasPosition } from './utils/formulas'; import Canvas from './components/Canvas'; class App extends Component { componentDidMount() { const self = this; setInterval(() => { self.props.moveObjects(self.canvasMousePosition); }, 10); } trackMouse(event) { this.canvasMousePosition = getCanvasPosition(event); } render() { return ( <Canvas angle={this.props.angle} trackMouse={event => (this.trackMouse(event))} /> ); } } App.propTypes = { angle: PropTypes.number.isRequired, moveObjects: PropTypes.func.isRequired, }; export default App; 

, . :



App formula.js :


 export const getCanvasPosition = (event) => { // mouse position on auto-scaling canvas // https://stackoverflow.com/a/10298843/1232793 const svg = document.getElementById('aliens-go-home-canvas'); const point = svg.createSVGPoint(); point.x = event.clientX; point.y = event.clientY; const { x, y } = point.matrixTransform(svg.getScreenCTM().inverse()); return {x, y}; }; 

, , StackOverflow .


, — Canvas . Canvas.jsx ( ./src/components ) :


 import React from 'react'; import PropTypes from 'prop-types'; import Sky from './Sky'; import Ground from './Ground'; import CannonBase from './CannonBase'; import CannonPipe from './CannonPipe'; const Canvas = (props) => { const viewBox = [window.innerWidth / -2, 100 - window.innerHeight, window.innerWidth, window.innerHeight]; return ( <svg id="aliens-go-home-canvas" preserveAspectRatio="xMaxYMax none" onMouseMove={props.trackMouse} viewBox={viewBox} > <Sky /> <Ground /> <CannonPipe rotation={props.angle} /> <CannonBase /> </svg> ); }; Canvas.propTypes = { angle: PropTypes.number.isRequired, trackMouse: PropTypes.func.isRequired, }; export default Canvas; 

:



! . npm start ( ). http://localhost:3000/ - . .


, ?!



, . create-react-app , , . , . , .


, , . .


!


!


翻蚳者から


, "" . どう思いたすか




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


All Articles