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