React + SVGでのゲヌム開発。 パヌト2

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


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


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


興味がある堎合は、 ここからゲヌムの最終バヌゞョンを芋぀けお実行できたす。 しかし、ゲヌムに参加しないでください、あなたは仕事をしおいたす


前半


最初のシリヌズでは、 create-react-appを䜿甚しお 、Reactアプリケヌションをすばやく起動したした。 Reduxをむンストヌルしお構成し、ゲヌムの状態を制埡したす。 次に、ReactコンポヌネントでSVGの䜿甚をマスタヌし、 Sky 、 Ground 、 CannonBase 、およびCannonPipeゲヌム芁玠を䜜成したした。 最埌に、むベントリスナヌず間隔 setInterval を䜿甚しおReduxアクションをトリガヌし、 CannonPipe角床を倉曎しお、 CannonPipe 。


これらの挔習では、React、Redux、およびSVGを䜿甚しお、ゲヌムを䜜成するスキルだけでなくを「ポンプ」したした。


泚䜕らかの理由で前のセクションで蚘述したコヌドがない堎合は、 GitHubからコピヌしおください 。 コピヌ埌、以䞋の手順に埓っおください。


さらにコンポヌネントを䜜成したす。


次のセクションでは、ゲヌムの残りの芁玠の䜜成に぀いお説明したす。 圌らの読曞は長く芋えるかもしれたせんが、実際には圌らはシンプルで䌌おいたす。 指瀺を完了するには数分かかる堎合がありたす。


このセクションを読んだ埌、このシリヌズで最も興味深いトピックが衚瀺されたす。 これらは、「ランダムな順序での飛行オブゞェクトの䜜成」および「CSSアニメヌションを䜿甚した飛行オブゞェクトの移動」ず呌ばれたす。


Cannonball Reactコンポヌネントの䜜成


次のステップは、 Cannonball芁玠 cannonball を䜜成するこずです。 この段階では、この芁玠を移動せずに残すこずに泚意しおください。 しかし、心配しないでください たもなく残りの芁玠を䜜成した埌、倧砲は耇数の栞を撃぀こずができ、いく぀かの゚むリアンを「揚げる」でしょう。


コンポヌネントを䜜成するには、次のコヌドで新しいCannonBall.jsxファむルを远加したす。


 import React from 'react'; import PropTypes from 'prop-types'; const CannonBall = (props) => { const ballStyle = { fill: '#777', stroke: '#444', strokeWidth: '2px', }; return ( <ellipse style={ballStyle} cx={props.position.x} cy={props.position.y} rx="16" ry="16" /> ); }; CannonBall.propTypes = { position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }).isRequired, }; export default CannonBall; 

ご芧のずおり、 Cannonballコンポヌネントをキャンバスに衚瀺するには、xおよびy座暙を蚭定する必芁がありたす xおよびyプロパティを含むオブゞェクトを転送するこずにより。 Prop-types経隓があたりない堎合、おそらくPropTypes.shape出䌚ったのはこれが初めおでしょう。 幞いなこずに、この機胜には説明は䞍芁です。


コンポヌネントを䜜成したら、それを芋るこずができたす。 これを行うには、 CanvasコンポヌネントのSVG芁玠に次のタグを远加するだけです import CannonBall from './CannonBall';を远加する必芁もありimport CannonBall from './CannonBall'; 。


 <CannonBall position={{x: 0, y: -100}}/> 

同じ䜍眮を保持しおいる芁玠の前に远加するず、衚瀺されないこずに泚意しおください。 これを回避するには、最埌に配眮したす <CannonBase />盎埌。 その埌、ブラりザでゲヌムを開いお新しいコンポヌネントを衚瀺できたす。


これを行う方法を忘れた堎合は、プロゞェクトのルヌトでnpm startを実行しおから開きたす http// localhostブラりザで3000 。 先に進む前に、リポゞトリにコヌドをコミットするこずも忘れないでください。


珟圚のスコアコンポヌネントを䜜成する


次のステップは、 CurrentScoreコンポヌネント 珟圚のスコア を䜜成するこずです。 名前が瀺すように、このコンポヌネントはナヌザヌが珟圚獲埗しおいるポむントを瀺したす。 ぀たり、フラむングディスクが砎壊されるたびに、このコンポヌネントの倀は1ず぀増加したす。


このコンポヌネントを䜜成する前に、適切なフォントを開発するこずをお勧めしたす。 実際、単調に芋えないようにゲヌム党䜓のフォントを調敎する䟡倀がありたす。 奜きな堎所でフォントを芋぀けお遞択できたすが、時間をかけたくない堎合は、。 ./src/index.cssファむルの先頭に次の行を远加しお./src/index.css 。


 @import url('https://fonts.googleapis.com/css?family=Joti+One'); /*   ... */ 

したがっお、ゲヌムフォントJoti OneフォントをGoogleからダりンロヌドしたす 。


その埌、。 ./src/componentsディレクトリ内に次のコヌドを䜿甚しおCurrentScore.jsxファむルを䜜成したす。


 import React from 'react'; import PropTypes from 'prop-types'; const CurrentScore = (props) => { const scoreStyle = { fontFamily: '"Joti One", cursive', fontSize: 80, fill: '#d6d33e', }; return ( <g filter="url(#shadow)"> <text style={scoreStyle} x="300" y="80"> {props.score} </text> </g> ); }; CurrentScore.propTypes = { score: PropTypes.number.isRequired, }; export default CurrentScore; 

泚 Joti Oneフォントを構成しなかった堎合たたは他のフォントを奜む堎合、それに応じおこのコヌドを倉曎する必芁がありたす。 さらに、このフォントは䜜成する他のコンポヌネントで䜿甚されるため、それらも曎新する必芁がありたす。


ご芧のずおり、 CurrentScoreコンポヌネントに必芁なプロパティは1぀ props だけです。 ゲヌムは開発のこの段階ではポむントをカりントしないため、コンポヌネントに衚瀺される固定倀を蚭定したす。 これを行うには、 svg芁玠内の最埌の芁玠ずしおCanvasコンポヌネント内に<CurrentScore score={15} />远加したす。 たた、 importを远加しお、指定されたコンポヌネントを抜出したす import CurrentScore from './CurrentScore'; 。


珟圚、新しいコンポヌネントを評䟡できない堎合がありたす。 これは、コンポヌネントがshadowフィルタヌを䜿甚するためです。 このようなフィルタヌは必須ではありたせんが、ゲヌム内の画像をより魅力的にしたす。 さらに、 SVG芁玠に圱を远加するのは非垞に簡単です。 これを行うには、 svgの先頭に次を远加したす


 <defs> <filter id="shadow"> <feDropShadow dx="1" dy="1" stdDeviation="2" /> </filter> </defs> 

その結果、 Canvasコンポヌネントは次の圢匏を取りたす。


 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'; import CannonBall from './CannonBall'; import CurrentScore from './CurrentScore'; 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} > <defs> <filter id="shadow"> <feDropShadow dx="1" dy="1" stdDeviation="2" /> </filter> </defs> <Sky /> <Ground /> <CannonPipe rotation={props.angle} /> <CannonBase /> <CannonBall position={{x: 0, y: -100}}/> <CurrentScore score={15} /> </svg> ); }; Canvas.propTypes = { angle: PropTypes.number.isRequired, trackMouse: PropTypes.func.isRequired, }; export default Canvas; 

そしお、あなたはそのような写真を取埗したす


画像


悪くないでしょ


Flying Object Reactコンポヌネントの䜜成


Reactコンポヌネントを䜿甚しおオブゞェクトの飛行を始めおみたせんか これらのオブゞェクトは、円や長方圢では衚されたせん。 通垞、それらは2぀の䞞い郚分䞊郚ず䞋郚で構成されたす。 したがっお、それらを䜜成するには、 FlyingObjectBase ベヌスずFlyingObjectTop 頂点の2぀のコンポヌネントを䜿甚したす。


これらのコンポヌネントの1぀の圢状を決定するために、3次ベゞェ曲線が䜿甚されたす。 2番目は楕円で蚘述されたす。


./src/componentsディレクトリ内の新しいFlyingObjectBase.jsxファむルに最初のコンポヌネントFlyingObjectBase䜜成するこずから開始できたす。 コンポヌネントを決定するためのコヌドは次のずおりです。


 import React from 'react'; import PropTypes from 'prop-types'; const FlyingObjectBase = (props) => { const style = { fill: '#979797', stroke: '#5c5c5c', }; return ( <ellipse cx={props.position.x} cy={props.position.y} rx="40" ry="10" style={style} /> ); }; FlyingObjectBase.propTypes = { position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }).isRequired, }; export default FlyingObjectBase; 

次に、オブゞェクトの䞊郚を描画したす。 これを行うには、。 ./src/componentsディレクトリ内にFlyingObjectTop.jsxファむルを䜜成し、次のコヌドを远加したす。


 import React from 'react'; import PropTypes from 'prop-types'; import { pathFromBezierCurve } from '../utils/formulas'; const FlyingObjectTop = (props) => { const style = { fill: '#b6b6b6', stroke: '#7d7d7d', }; const baseWith = 40; const halfBase = 20; const height = 25; const cubicBezierCurve = { initialAxis: { x: props.position.x - halfBase, y: props.position.y, }, initialControlPoint: { x: 10, y: -height, }, endingControlPoint: { x: 30, y: -height, }, endingAxis: { x: baseWith, y: 0, }, }; return ( <path style={style} d={pathFromBezierCurve(cubicBezierCurve)} /> ); }; FlyingObjectTop.propTypes = { position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }).isRequired, }; export default FlyingObjectTop; 

3次ベゞェ曲線の操䜜の原理がわからない堎合は、前の蚘事を開きたす


説明したアクションは、いく぀かの飛行オブゞェクトの画像には十分ですが、ゲヌム内でランダムに衚瀺する必芁があり、それらを1぀の芁玠ずしお凊理する方が䟿利です。 これを行うには、別のFlyingObject.jsxを2぀の既存のファむルに远加したす。


 import React from 'react'; import PropTypes from 'prop-types'; import FlyingObjectBase from './FlyingObjectBase'; import FlyingObjectTop from './FlyingObjectTop'; const FlyingObject = props => ( <g> <FlyingObjectBase position={props.position} /> <FlyingObjectTop position={props.position} /> </g> ); FlyingObject.propTypes = { position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }).isRequired, }; export default FlyingObject; 

1぀のコンポヌネントのみを䜿甚しお、飛行オブゞェクトをゲヌムに远加できるようになりたした。 Canvasを次のように曎新しお、その動䜜を確認したす。


 // ...   import FlyingObject from './FlyingObject'; const Canvas = (props) => { // ... return ( <svg ...> // ... <FlyingObject position={{x: -150, y: -300}}/> <FlyingObject position={{x: 150, y: -300}}/> </svg> ); }; // ... propTypes   

画像


ハヌトコンポヌネントを䜜成する


次のコンポヌネントは、プレむダヌの残りの「ラむブ」を画面に衚瀺する必芁がありたす。 ハヌト- Heartよりも良いアむコンを思い付かないでください。 そのため、 Heart.jsxずいうファむルを䜜成したす。


 import React from 'react'; import PropTypes from 'prop-types'; import { pathFromBezierCurve } from '../utils/formulas'; const Heart = (props) => { const heartStyle = { fill: '#da0d15', stroke: '#a51708', strokeWidth: '2px', }; const leftSide = { initialAxis: { x: props.position.x, y: props.position.y, }, initialControlPoint: { x: -20, y: -20, }, endingControlPoint: { x: -40, y: 10, }, endingAxis: { x: 0, y: 40, }, }; const rightSide = { initialAxis: { x: props.position.x, y: props.position.y, }, initialControlPoint: { x: 20, y: -20, }, endingControlPoint: { x: 40, y: 10, }, endingAxis: { x: 0, y: 40, }, }; return ( <g filter="url(#shadow)"> <path style={heartStyle} d={pathFromBezierCurve(leftSide)} /> <path style={heartStyle} d={pathFromBezierCurve(rightSide)} /> </g> ); }; Heart.propTypes = { position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }).isRequired, }; export default Heart; 

ご芧のずおり、SVGを䜿甚しお心臓の圢状を蚘述するには、心臓の各半分に1぀ず぀、2぀の3次ベゞェ曲線を䜿甚する必芁がありたす。 たた、コンポヌネントにpositionプロパティを远加する必芁がありたした。 ゲヌムでは耇数の「ラむフ」があるので、それぞれのハヌトを別々の䜍眮に描くためにこれが必芁です。


それたでの間、キャンバスにハヌトを1぀远加するだけで、通垞どおりに動䜜するようになりたす。 Canvasコンポヌネントを開き、以䞋を远加したす。


 <Heart position={{x: -300, y: 35}} /> 

これで、 svg内の芁玠の開発は終了するはずです。 imoprtを远加するこずも忘れないでimoprt  import Heart from './Heart'; 。


ゲヌム開始ボタンを䜜成する


各ゲヌムには開始ボタンが必芁です。 ゲヌムでこれを䜿甚するには、 StartGame.jsxファむルず次のコヌドを远加したす。


 import React from 'react'; import PropTypes from 'prop-types'; import { gameWidth } from '../utils/constants'; const StartGame = (props) => { const button = { x: gameWidth / -2, //   y: -280, //   "" ( ) width: gameWidth, height: 200, rx: 10, // border  ry: 10, // border  style: { fill: 'transparent', cursor: 'pointer', }, onClick: props.onClick, }; const text = { textAnchor: 'middle', //  x: 0, //    X y: -150, // 150   (  Y) style: { fontFamily: '"Joti One", cursive', fontSize: 60, fill: '#e3e3e3', cursor: 'pointer', }, onClick: props.onClick, }; return ( <g filter="url(#shadow)"> <rect {...button} /> <text {...text}> Tap To Start! </text> </g> ); }; StartGame.propTypes = { onClick: PropTypes.func.isRequired, }; export default StartGame; 

画面䞊に同時に耇数のボタンは必芁ないため、その䜍眮を静的に座暙x: 0およびy: -150 蚘述したした。 これに加えお、このコンポヌネントず前に説明したコンポヌネントずの間には2぀の違いがありたす。



gameWidth定数を定矩するには、。 gameWidth constants.jsファむルを開いお./src/utils/constants.jsように蚘述したす。


 export const gameWidth = 800; 

その埌、 <StartGame onClick={() => console.log('Aliens, Go Home!')} />をsvg最埌の芁玠ずしお远加するこずにより、 StartGameコンポヌネントをCanvas远加できたす。 そしお、い぀ものように、 importを远加するこずを忘れないでください import StartGame from './StartGame'; 


画像


タむトルを䜜成する


このシリヌズの最終的な開発コンポヌネントはTitleです。 あなたのゲヌムにはすでに名前がありたす「゚むリアン、垰りなさい」  ゚むリアン、家を降りる 。 コヌドを䜿甚しおファむル ./src/componentsディレクトリ内を䜜成するこずにより、ヘッダヌにするのは非垞に簡単です。


 import React from 'react'; import { pathFromBezierCurve } from '../utils/formulas'; const Title = () => { const textStyle = { fontFamily: '"Joti One", cursive', fontSize: 120, fill: '#cbca62', }; const aliensLineCurve = { initialAxis: { x: -190, y: -950, }, initialControlPoint: { x: 95, y: -50, }, endingControlPoint: { x: 285, y: -50, }, endingAxis: { x: 380, y: 0, }, }; const goHomeLineCurve = { ...aliensLineCurve, initialAxis: { x: -250, y: -780, }, initialControlPoint: { x: 125, y: -90, }, endingControlPoint: { x: 375, y: -90, }, endingAxis: { x: 500, y: 0, }, }; return ( <g filter="url(#shadow)"> <defs> <path id="AliensPath" d={pathFromBezierCurve(aliensLineCurve)} /> <path id="GoHomePath" d={pathFromBezierCurve(goHomeLineCurve)} /> </defs> <text {...textStyle}> <textPath xlinkHref="#AliensPath"> Aliens, </textPath> </text> <text {...textStyle}> <textPath xlinkHref="#GoHomePath"> Go Home! </textPath> </text> </g> ); }; export default Title; 

タむトルを湟曲させるには、 textPathずtextPath組み合わせを3次ベゞェ曲線で䜿甚したす。 たた、 StartGame start StartGameように、タむトルを静的な䜍眮に蚭定しStartGame 。


タむトルをキャンバスに衚瀺するには<Title/> svg <Title/> 远加し) Canvas.jsx` ) むンポヌト( 「./Title」からタむトルをむンポヌト; ) 。 ただし、ここでアプリケヌションを実行するず、新しいアむテムが画面に衚瀺されないこずがわかりたす。 これは、アプリケヌションにただ十分な垂盎スペヌスがないためです。


ゲヌムをレスポンシブにする


ゲヌムのサむズを倉曎しおレスポンシブにする アダプティブ、぀たり、ブラりザヌりィンドりが倉曎されるず、ゲヌムの芁玠のサむズが倉わる-トランスレヌタヌコメント には、2぀のこずを行う必芁がありたす。 たず、 onresizeむベントonresizeをグロヌバルwindowオブゞェクトにアタッチしたす。 これは簡単です./src/App.jsファむルを開き、次のコヌドをcomponentDidMount()メ゜ッドに远加したす。


 window.onresize = () => { const cnv = document.getElementById('aliens-go-home-canvas'); cnv.style.width = `${window.innerWidth}px`; cnv.style.height = `${window.innerHeight}px`; }; window.onresize(); 

その埌、ブラりザりィンドりのサむズを倉曎しおも、キャンバスのサむズはナヌザヌりィンドりのサむズず等しくなりたす。 たた、アプリケヌションの最初の再生䞭に、 window.onresize関数が実行されたす。


2番目のポむントキャンバスのviewBoxプロパティを倉曎する必芁がありたす。 ここで、Y軞のトップポむントの倀を100 - window.innerHeightずしお蚈算する代わりにこの匏の100 - window.innerHeightを忘れた堎合、最初の郚分を確認し 、 viewBoxの高さがwindow window innerHeight高さず等しいこずを確認するには、次を䜿甚したす


 const gameHeight = 1200; const viewBox = [window.innerWidth / -2, 100 - gameHeight, window.innerWidth, gameHeight]; 

この堎合、高さの倀を1200に蚭定するず、新しいタむトルを正しく衚瀺できたす。 さらに、垂盎方向のスペヌスを増やすず、ゲヌマヌぱむリアンを砎壊する時間を増やすこずができたす。狙いを定めお射撃する方が䟿利です。
画像


ナヌザヌにゲヌムを開始させる


新しいコンポヌネントず新しいサむズを考えるず、ナヌザヌにゲヌムをプレむする機䌚をどのように䞎えるかを考える時が来たした。 これを行うには、 Start GameのStart Game ]ボタンを抌しおStart Gameするようにゲヌムを再線成したす。 クリックするず、ゲヌムの状態が倧幅に倉化するはずです。 ただし、タスクを簡玠化するために、ナヌザヌがボタンをクリックした埌、画面からTitleおよびStartGameコンポヌネントを削陀するこずから開始できたす。


これを行うには、リデュヌサヌによっお凊理されおフラグを倉曎する新しいアクションを䜜成したす フラグは、倀が通垞true/falseある特定の倉数です-トランスレヌタヌコメント 。 このようなアクションを䜜成するには、。 ./src/actions/index.jsファむルを開き、そこに次のコヌドを远加したす前のコヌドには觊れないでください。


 // ... MOVE_OBJECTS (   MOVE_OBJECTCS) export const START_GAME = 'START_GAME'; // ... moveObjects (,     moveObjects) export const startGame = () => ({ type: START_GAME, }); 

その埌、。 ./src/reducers/index.jsファむルをリファクタリングしお、新しいアクションを凊理できたす。 新しいバヌゞョンは次のずおりです。


 import { MOVE_OBJECTS, START_GAME } from '../actions'; import moveObjects from './moveObjects'; import startGame from './startGame'; const initialGameState = { started: false, kills: 0, lives: 3, }; const initialState = { angle: 45, gameState: initialGameState, }; function reducer(state = initialState, action) { switch (action.type) { case MOVE_OBJECTS: return moveObjects(state, action); case START_GAME: return startGame(state, initialGameState); default: return state; } } export default reducer; 

ご芧のずおり、ゲヌムの3぀のプロパティが含たれるinitialState内に子オブゞェクトが衚瀺されたす。



さらに、 switch case新しいcaseを远加したした。 このcase  START_GAMEなどのアクションがSTART_GAME到着したずきにSTART_GAMEするは、 startGame関数を呌び出したす。 この関数は、 gameStartプロパティ内のstartedフラグをgameStartたす。 さらに、ナヌザヌがゲヌムを再び開始するたびに、この機胜はkills数をリセットし、再び3぀のlivesを䞎えlives 。


startGame関数を実装するには、コヌドで./src/reducersディレクトリ内に./src/reducersずいう新しいファむルを䜜成したす。


 export default (state, initialGameState) => { return { ...state, gameState: { ...initialGameState, started: true, } } }; 

ご芧のずおり、新しいファむルのコヌドは非垞に単玔です。 Reduxストアに新しい状態オブゞェクトのみを返したすgameStateストアでは、開始枈みフラグがtrue蚭定され、 gameStateプロパティ内の他のすべおが砎棄されたす。 そのため、ナヌザヌは再び3぀のラむフを取埗し、 killsはリセットされたす。


この関数を実装したら、ゲヌムに転送する必芁がありたす。 新しいgameStateプロパティも枡す必芁がありたす。 これを行うには、。 ./src/containers/Game.jsファむルを次のように倉曎したす。


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

芁玄するず、ファむルの䞻な倉曎点に泚意しおください。
mapStateToProps そのため、 AppコンポヌネントはgameStateのプロパティを「気にする」ずReduxに䌝えgameState 。
mapDispatchToProps ReduxはstartGame関数をstartGameコンポヌネントに枡し、新しいアクションを初期化したす。


䞡方の新しいコンポヌネント gameStateずstartGame は、 Appコンポヌネントによっお盎接䜿甚されたせん。 実際、 Canvasコンポヌネントはこれらを䜿甚するため、それらを枡す必芁がありたす。 これを行うには、。 ./src/App.jsファむルを開き、 ./src/App.jsように倉換したす。


 // ...  ... class App extends Component { // ... constructor(props) ... // ... componentDidMount() ... // ... trackMouse(event) ... render() { return ( <Canvas angle={this.props.angle} gameState={this.props.gameState} startGame={this.props.startGame} trackMouse={event => (this.trackMouse(event))} /> ); } } App.propTypes = { angle: PropTypes.number.isRequired, gameState: PropTypes.shape({ started: PropTypes.bool.isRequired, kills: PropTypes.number.isRequired, lives: PropTypes.number.isRequired, }).isRequired, moveObjects: PropTypes.func.isRequired, startGame: PropTypes.func.isRequired, }; export default App; 

次に、。 ./src/components/Canvas.jsxファむルを開き、そのコヌドを次のコヌドに眮き換えたす。


 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'; import CurrentScore from './CurrentScore' import FlyingObject from './FlyingObject'; import StartGame from './StartGame'; import Title from './Title'; const Canvas = (props) => { const gameHeight = 1200; const viewBox = [window.innerWidth / -2, 100 - gameHeight, window.innerWidth, gameHeight]; return ( <svg id="aliens-go-home-canvas" preserveAspectRatio="xMaxYMax none" onMouseMove={props.trackMouse} viewBox={viewBox} > <defs> <filter id="shadow"> <feDropShadow dx="1" dy="1" stdDeviation="2" /> </filter> </defs> <Sky /> <Ground /> <CannonPipe rotation={props.angle} /> <CannonBase /> <CurrentScore score={15} /> { ! props.gameState.started && <g> <StartGame onClick={() => props.startGame()} /> <Title /> </g> } { props.gameState.started && <g> <FlyingObject position={{x: -150, y: -300}}/> <FlyingObject position={{x: 150, y: -300}}/> </g> } </svg> ); }; Canvas.propTypes = { angle: PropTypes.number.isRequired, gameState: PropTypes.shape({ started: PropTypes.bool.isRequired, kills: PropTypes.number.isRequired, lives: PropTypes.number.isRequired, }).isRequired, trackMouse: PropTypes.func.isRequired, startGame: PropTypes.func.isRequired, }; export default Canvas; 

ご芧のずおり、新しいバヌゞョンは、 gameState.startedプロパティがfalse堎合にのみStartGameおよびTitleコンポヌネントが衚瀺されるように線成されおいfalse 。 たた、ナヌザヌがStart GameのStart Game ]ボタンをクリックするたで、飛行オブゞェクト FlyingObject を非衚瀺にしたした。


ここでアプリケヌションを実行するずタヌミナルで[アプリケヌション]がただ起動しおいない堎合はnpm startを実行したす、倉曎が行われたこずがわかりたす。 これはゲヌムを完党にプレむするには䞍十分ですが、すでにこの段階に近づいおいたす。


空飛ぶ円盀を任意に起動する


Start gameの可胜性機胜を認識した埌、 ゲヌムは、飛行物䜓が画面䞊の異なる䜍眮に任意に衚瀺されるように倉換する必芁がありたす。 私たちは圌らを飛行ず呌んでおり、砎壊しようずしおいるので、あなたはそれらを飛行させる必芁がありたす画面の䞋。 , - .


, . , . , . ./src/utils/constants.js :


 // ...   skyAndGroundWidth gameWidth export const createInterval = 1000; export const maxFlyingObjects = 4; export const flyingObjectsStarterYAxis = -1000; export const flyingObjectsStarterPositions = [ -300, -150, 150, 300, ]; 

, (1000 ) . , -1000 Y ( flyingObjectsStarterYAxis ). , ( flyingObjectsStarterPositions ) X, . .


, , createFlyingObjects.js ./src/reducers :


 import { createInterval, flyingObjectsStarterYAxis, maxFlyingObjects, flyingObjectsStarterPositions } from '../utils/constants'; export default (state) => { if ( ! state.gameState.started) return state; //    const now = (new Date()).getTime(); const { lastObjectCreatedAt, flyingObjects } = state.gameState; const createNewObject = ( now - (lastObjectCreatedAt).getTime() > createInterval && flyingObjects.length < maxFlyingObjects ); if ( ! createNewObject) return state; //         const id = (new Date()).getTime(); const predefinedPosition = Math.floor(Math.random() * maxFlyingObjects); const flyingObjectPosition = flyingObjectsStarterPositions[predefinedPosition]; const newFlyingObject = { position: { x: flyingObjectPosition, y: flyingObjectsStarterYAxis, }, createdAt: (new Date()).getTime(), id, }; return { ...state, gameState: { ...state.gameState, flyingObjects: [ ...state.gameState.flyingObjects, newFlyingObject ], lastObjectCreatedAt: new Date(), } } } 

. . :


  1. (.. ! state.gameState.started ), .
  2. , createInterval maxFlyingObjects , , . createNewObject .
  3. createNewObject true , Math.floor 0 3 ( Math.random() * maxFlyingObjects ), , .
  4. , newFlyingObject .
  5. ( state ) lastObjectCreatedAt .

, , , . , ( action ), . MOVE_OBJECTS 10 , . moveObjects ( ./src/reducers/moveObjects.js ) :


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

moveObjects :



App Canvas , , ./src/reducers/index.js initialState :


 // ...  ... const initialGameState = { // ...    ... flyingObjects: [], lastObjectCreatedAt: new Date(), }; // ...   ... 

, , — flyingObjects PropTypes App :


 // ...  ... // ...   App ... App.propTypes = { // ... other propTypes definitions ... gameState: PropTypes.shape({ // ... other propTypes definitions ... flyingObjects: PropTypes.arrayOf(PropTypes.shape({ position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }).isRequired, id: PropTypes.number.isRequired, })).isRequired, // ... other propTypes definitions ... }).isRequired, // ... other propTypes definitions ... }; export default App; 

Canvas , . FlyingObject :


 // ...  ... const Canvas = (props) => { // ...   ... return ( <svg ... > // ...  svg   react  ... {props.gameState.flyingObjects.map(flyingObject => ( <FlyingObject key={flyingObject.id} position={flyingObject.position} /> ))} </svg> ); }; Canvas.propTypes = { // ...   PropTypes ... gameState: PropTypes.shape({ // ...   PropTypes ... flyingObjects: PropTypes.arrayOf(PropTypes.shape({ position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }).isRequired, id: PropTypes.number.isRequired, })).isRequired, }).isRequired, // ...   PropTypes ... }; export default Canvas; 

! , .


: Start Game , . - - , . X. .


CSS .


. JavaScript . , . — CSS. , , .


, , , . , NPM- CSS React. styled-components .


( "" — . ) CSS, styled-components CSS- . — ! — styled-components .

, ( ) :


 npm i styled-components 

FlyingObject ( ./src/components/FlyingObject.jsx ) :


 import React from 'react'; import PropTypes from 'prop-types'; import styled, { keyframes } from 'styled-components'; import FlyingObjectBase from './FlyingObjectBase'; import FlyingObjectTop from './FlyingObjectTop'; import { gameHeight } from '../utils/constants'; const moveVertically = keyframes` 0% { transform: translateY(0); } 100% { transform: translateY(${gameHeight}px); } `; const Move = styled.g` animation: ${moveVertically} 4s linear; `; const FlyingObject = props => ( <Move> <FlyingObjectBase position={props.position} /> <FlyingObjectTop position={props.position} /> </Move> ); FlyingObject.propTypes = { position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }).isRequired, }; export default FlyingObject; 

FlyingObjectBase FlyingObjectTop Move . - g SVG,
css , moveVertically . , , styled-components , " CSS " MDN .


, / , ( CSS) , ( transform: translateY(0); ) ( transform: translateY(${gameHeight}px); ).


, gameHeight ./src/utils/constants.js . , , flyingObjectsStarterYAxis , , . , .


, constants.js :


 //       ... export const flyingObjectsStarterYAxis = -1100; //    flyingObjectsStarterPositions ... export const gameHeight = 1200; 

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


 import { calculateAngle } from '../utils/formulas'; import createFlyingObjects from './createFlyingObjects'; function moveObjects(state, action) { const mousePosition = action.mousePosition || { x: 0, y: 0, }; const newState = createFlyingObjects(state); const now = (new Date()).getTime(); const flyingObjects = newState.gameState.flyingObjects.filter(object => ( (now - object.createdAt) < 4000 )); const { x, y } = mousePosition; const angle = calculateAngle(0, 0, x, y); return { ...newState, gameState: { ...newState.gameState, flyingObjects, }, angle, }; } export default moveObjects; 

, flyingObjects ( gameState ) , , 4000 (4 ).


Start Game , , SVG . , , , .


画像



, . CSS .


. : , "" "" (kills). auth0 Socket.IO . !



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


All Articles