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
ãæ¬¡ã®ããã«æŽæ°ããŠããã®åäœã確èªããŸãã

ããŒãã³ã³ããŒãã³ããäœæãã
次ã®ã³ã³ããŒãã³ãã¯ããã¬ã€ã€ãŒã®æ®ãã®ãã©ã€ãããç»é¢ã«è¡šç€ºããå¿
èŠããããŸãã ããŒã- 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,
ç»é¢äžã«åæã«è€æ°ã®ãã¿ã³ã¯å¿
èŠãªãããããã®äœçœ®ãéçã«ïŒåº§æšx: 0
ããã³y: -150
ïŒèšè¿°ããŸããã ããã«å ããŠããã®ã³ã³ããŒãã³ããšåã«èª¬æããã³ã³ããŒãã³ããšã®éã«ã¯2ã€ã®éãããããŸãã
- æåã«ãã³ã³ããŒãã³ãã¯
onClick
颿°ãæåŸ
ããŸãã ãã¿ã³ã«è§Šãããšããã®é¢æ°ã¯Reduxã¢ã¯ã·ã§ã³ãåŒã³åºããã¢ããªã±ãŒã·ã§ã³ã«æ°ããã²ãŒã ãéå§ããããæç€ºããŸãã - 次ã«ãã³ã³ããŒãã³ãã¯ãŸã å®çŸ©ãããŠããªã宿°
gameWidth
ãŸãã ãã®å®æ°ã¯ã䜿çšå¯èœãªé åãèšè¿°ããŸãã ä»ã®é åã¯ãã¢ããªã±ãŒã·ã§ã³ãå
šç»é¢ã«æ¡å€§ããããã«ã®ã¿å¿
èŠã§ãã
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
ãã¡ã€ã«ãéããããã«æ¬¡ã®ã³ãŒãã远å ããŸãïŒåã®ã³ãŒãã«ã¯è§Šããªãã§ãã ããïŒïŒã
ãã®åŸãã ./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
å
ã«åãªããžã§ã¯ãã衚瀺ãããŸãã
started
ïŒã²ãŒã ãå®è¡ãããŠãããã©ããã瀺ããã©ã°ãkills
æ°ïŒããŠã³ããé£è¡ç©äœã®æ°ãlives
ïŒæ®ãã®ãã©ã€ããã®æ°ã
ããã«ã 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
ããã«å€æããŸãã
次ã«ãã ./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
:
, (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;
. . :
- (..
! state.gameState.started
), . - ,
createInterval
maxFlyingObjects
, , . createNewObject
. createNewObject
true
, Math.floor
0 3 ( Math.random() * maxFlyingObjects
), , .- ,
newFlyingObject
. - ( 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
:
- -,
mousePosition
, action
. , , mousePosition
. - -,
newState
createFlyingObjects
; . - ,
newState
, .
App
Canvas
, , ./src/reducers/index.js
initialState
:
, , â flyingObjects
PropTypes
App
:
Canvas
, . FlyingObject
:
! , .
: 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
:
, 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
. !