TL; DRïŒãããã®ãšããœãŒãã§ã¯ãReactãšReduxã§SVGèŠçŽ ãå¶åŸ¡ããŠã²ãŒã ãäœæããæ¹æ³ãåŠã³ãŸãã ãã®ã·ãªãŒãºã§åŸãããç¥èã«ãããã²ãŒã ã ãã§ãªãã¢ãã¡ãŒã·ã§ã³ãäœæã§ããŸãã ãã®ããŒãã§éçºããããœãŒã¹ã³ãŒãã®æçµããŒãžã§ã³ã¯ã GitHubã«ãããŸãã
ïŒ 3çªç®ã®éšåã¯æçµã§ããã²ãŒã èªäœã®éçºãå®äºããããšã«å ããŠãAuth0ãšåçŽãªãªã¢ã«ã¿ã€ã ãµãŒããŒã䜿çšããæ¿èªã«ã€ããŠèª¬æããŸã-翻蚳è
ã®ã³ã¡ã³ã ïŒ

ãã®ã·ãªãŒãºã§éçºããã²ãŒã ã¯ããšã€ãªã¢ã³ãã²ããã¢ãŠã§ã€ããŒã ïŒ ã²ãŒã ã®ã¢ã€ãã¢ã¯ã·ã³ãã«ã§ããå°çã«äŸµå
¥ããããšããŠããããã©ã€ã³ã°ãã£ã¹ã¯ããæã¡èœãšãéããããŸãã ãããã®UFOãç Žå£ããã«ã¯ãããŠã¹ã«ã«ãŒãœã«ãåãããŠã¯ãªãã¯ããŠå€§ç ²ãçºå°ããå¿
èŠããããŸãã
èå³ãããå Žåã¯ã ããã§ã²ãŒã ã®æçµããŒãžã§ã³ãèŠã€ããŠå®è¡ã§ããŸãïŒ ãªã³ã¯ãåžžã«æ©èœãããšã¯éããŸãã-翻蚳è
ã®ã¡ã¢ ïŒã ããããã²ãŒã ã«åå ããªãã§ãã ãããããªãã¯ä»äºãããŠããŸãïŒ
åã®ã·ãªãŒãº
æåã®ã·ãªãŒãºã§ã¯ã create-react-app
ã䜿çšcreate-react-app
ãŠReactã¢ããªã±ãŒã·ã§ã³ããã°ããèµ·åããã²ãŒã ã®ç¶æ
ãå¶åŸ¡ããããã«Reduxãã€ã³ã¹ããŒã«ããã³æ§æããŸããã æ¬¡ã«ãReactã³ã³ããŒãã³ãã§SVGã®äœ¿çšããã¹ã¿ãŒãã Sky
ã Ground
ã CannonBase
ãããã³CannonPipe
ã²ãŒã èŠçŽ ãäœæããŸããã æåŸã«ãã€ãã³ããã³ãã©ãŒãšééã䜿çšããŠã¬ã³ã®ã¹ã³ãŒããããŠã³ãããReduxã¢ã¯ã·ã§ã³ãããªã¬ãŒããŸããããã«ããã CannonPipe
è§åºŠã倿ŽãããŸãã
ãããã®æŒç¿ã§ã¯ãReactãReduxãããã³SVGã䜿çšããŠãã²ãŒã ãäœæããã¹ãã«ïŒã ãã§ãªãïŒãããã³ããããŸããã
2çªç®ã®ã·ãªãŒãºã§ã¯ãã²ãŒã ã«å¿
èŠãªä»ã®èŠçŽ ïŒ Heart
ã FlyingObject
ããã³CannonBall
ïŒãäœæãããã¬ã€ã€ãŒã«ã²ãŒã ãéå§ããæ©äŒãäžãããšã€ãªã¢ã³ãé£ã°ããŸããïŒæçµçã«ã¯äœãããŸãããïŒïŒã
ãããã®ãæ©èœãã¯ãã¹ãŠéåžžã«ã¯ãŒã«ã§ãããšããäºå®ã«ãããããããã²ãŒã ã®éçºã¯ãŸã å®äºããŠããŸããã§ããã å€§ç ²ã¯ãŸã ã³ã¢ã«ãããããŸããããŸããã³ã¢ãã¿ãŒã²ããã«ããããããšå€æããã¢ã«ãŽãªãºã ã¯ãããŸããã ããã«ããã¬ã€ã€ãŒãå¥ã®ãšã€ãªã¢ã³ãåããã³ã«CurrentScore
ã³ã³ããŒãã³ãã®å€ãå¢å ããã¯ãã§ãã
ãã¡ããããšã€ãªã¢ã³ã殺ããŠãããªãã®ãã€ã³ããã©ã®ããã«èç©ããããèŠãã®ã¯ã¯ãŒã«ã§ãããã²ãŒã ãããã«é¢çœãããããšãã§ããŸãã ãããè¡ãã«ã¯ã ãªãŒããŒããŒãæ©èœ-ãªãŒããŒè©äŸ¡ã远å ããå¿
èŠããããŸãã ãã¬ã€ã€ãŒã¯ããå€ãã®æéãè²»ãããŠãè©äŸ¡ããªãŒããŒã·ããã«é«ããŸãã
ãããã®æ¡ä»¶ããã¹ãŠæºããããšã§ãéçºãå®äºãããšå®å
šã«èšãããšãã§ããŸãã ãã®å Žåãæéãç¡é§ã«ããã«éå§ããŸãã
泚ïŒäœããã®çç±ã§åã®ã»ã¯ã·ã§ã³ã§èšè¿°ããã³ãŒãããªãå Žåã¯ãGitHubãªããžããªããåçŽã«ã³ããŒã§ããŸã ã ã³ããŒåŸã以äžã®æé ã«åŸã£ãŠãã ããã
LeaderBoard颿°ã®å®è£
ïŒè©äŸ¡ïŒ
ã²ãŒã ãæ¬åœã«ã²ãŒã ã«ããããã«æåã«è¡ãå¿
èŠãããã®ã¯ãè©äŸ¡æ©èœãå®è£
ããããšã§ãã ããã«ããããã¬ã€ã€ãŒã¯ã·ã¹ãã ã«åå ã§ããã²ãŒã ã¯æå€§ãã€ã³ããèªã¿åããã©ã³ã¯ã衚瀺ããŸãã
ReactãšAuth0ãçµ±åãã
Auth0ã§ãã¬ãŒã€ãŒãèå¥ããã«ã¯ããŸãAuth0ã®ã¢ã«ãŠã³ããå¿
èŠã§ãã ãŸã ãæã¡ã§ãªãå Žåã¯ã ãã¡ãããç¡æã®ã¢ã«ãŠã³ããäœæã§ããŸã ã
ã¢ã«ãŠã³ããéããããã²ãŒã ã衚ãAuth0ã¯ã©ã€ã¢ã³ããäœæããã ãã§ãã ãããè¡ãã«ã¯ãAuth0ã³ã³ãããŒã«ããã«ã®[ã¯ã©ã€ã¢ã³ã]ããŒãžã«ç§»åãã[ã¯ã©ã€ã¢ã³ãã®äœæ]ãã¿ã³ãã¯ãªãã¯ããŸãã æ
å ±ããã«ã«ã¯ãã¯ã©ã€ã¢ã³ãã®ååãšã¿ã€ããæå®ããå¿
èŠããããã©ãŒã ããããŸãã Aliens, Go Home!
é Œãã§Aliens, Go Home!
ååãšããŠã Single Page Web Application
ã¿ã€ããéžæãSingle Page Web Application
ïŒã²ãŒã ã¯Reactã®SPAã§ãïŒã 次ã«ããäœæããã¯ãªãã¯ããŸãã

ãã®åŸãã¯ã©ã€ã¢ã³ãã®[ã¯ã€ãã¯ã¹ã¿ãŒã]ã¿ãã«ãªãã€ã¬ã¯ããããŸãã ãã®èšäºã§ã¯ReactãšAuth0ãçµ±åããæ¹æ³ãåŠç¿ããããããã®ã¿ãã¯ç¡èŠã§ããŸãã 代ããã«ããèšå®ãã¿ããå¿
èŠãªã®ã§ããããéããŸãã
[èšå®]ããŒãžã§è¡ãå¿
èŠããã3ã€ã®ããšããããŸãã æåïŒå€http://localhost:3000
ãAllowed Callback URLsãšãããã£ãŒã«ãã«è¿œå ããŸã ã ããã·ã¥ããŒãïŒ ããã·ã¥ããŒã ïŒã§èª¬æãããŠããããã«ãAuth0ã§ã®èªèšŒåŸããã¬ãŒã€ãŒã¯ãã®ãã£ãŒã«ãã§æå®ãããURLã«ãªãã€ã¬ã¯ããããŸãã ãããã£ãŠãã€ã³ã¿ãŒãããã§ã²ãŒã ãå
¬éããå Žåã¯ãå¿
ãå
¬éURLïŒ http://aliens-go-home.digituz.com.br
ïŒã远å ããŠhttp://aliens-go-home.digituz.com.br
ã
ãã®ãã£ãŒã«ãã«ãã¹ãŠã®URLãå
¥åããåŸããä¿åããã¿ã³ãã¯ãªãã¯ãããã ctrl + s
æŒããŸãïŒMacBookãããå Žåã¯ã command+s
æŒããŸãïŒã 2ã€ã®ããšã¯æ®ããŸããããã¡ã€ã³ããã£ãŒã«ããšãã¯ã©ã€ã¢ã³ãIDããã£ãŒã«ãããå€ãã³ããŒããŸãã ãããããããã䜿çšããåã«ãå°ãããã°ã©ãã³ã°ããå¿
èŠããããŸãã
ãŸããã²ãŒã ã®ã«ãŒãã§æ¬¡ã®ã³ãã³ããå
¥åããŠã auth0-web
ããã±ãŒãžãã€ã³ã¹ããŒã«ããå¿
èŠããããŸã
npm i auth0-web
ã芧ã®ãšããããã®ããã±ãŒãžã¯Auth0ãšSPAã®çµ±åã容æã«ããŸãã
次ã®ã¹ãããã¯ããŠãŒã¶ãŒãAuth0ãä»ããŠèªèšŒã§ããããã«ãã²ãŒã ã«ãã°ã€ã³ãã¿ã³ã远å ããããšã§ãã ãããè¡ãã«ã¯ã次ã®ã³ãŒãã䜿çšããŠ./src/components
ãã£ã¬ã¯ããªå
ã«æ°ããLogin.jsx
ãã¡ã€ã«ãäœæããŸãã
import React from 'react'; import PropTypes from 'prop-types'; const Login = (props) => { const button = { x: -300, y: -600, width: 600, height: 300, style: { fill: 'transparent', cursor: 'pointer', }, onClick: props.authenticate, }; const text = { textAnchor: 'middle',
äœæãããã³ã³ããŒãã³ãã¯ãã¯ãªãã¯ããããšãã«äœãããããšããç¹ã§ã¯äžå¯ç¥è«çã§ãã ãã®ã¢ã¯ã·ã§ã³ãå®çŸ©ããã«ã¯ã Canvas
ã³ã³ããŒãã³ãã«è¿œå ããŸãã ãããã£ãŠã Canvas.jsx
ãéããŠæŽæ°ããŸãã
ã芧ã®ãšãããæ°ããããŒãžã§ã³ã§ã¯ã auth0-web
ããã±ãŒãžããLogin
ã³ã³ããŒãã³ããšsignIn
颿°ãã€ã³ããŒãããŸããã ã³ãŒãã«ã¯å¥ã®ã³ã³ããŒãã³ãã衚瀺ããããŠãŒã¶ãŒãã²ãŒã ãéå§ãããŸã§è¡šç€ºãããŸãã æ¿èªãã¿ã³ãã¯ãªãã¯ãããšã signIn
颿°ã®éå§ãç»é²ããŸããã
ããããã¹ãŠè¡ã£ãåŸãAuth0ã¯ã©ã€ã¢ã³ãããããã£ã§auth0-web
ãæ§æããŸãã ãããè¡ãã«ã¯ã App.js
ãã¡ã€ã«ãéããŸãã
æ³šïŒ YOUR_AUTH0_DOMAIN
ããã³YOUR_AUTH0_CLIENT_ID
ã¯ã©ã€ã¢ã³ãã® [ ãã¡ã€ã³]ããã³[ ã¯ã©ã€ã¢ã³ãID]ãã£ãŒã«ãããã³ããŒYOUR_AUTH0_CLIENT_ID
å€ã«çœ®ãæããå¿
èŠããããŸãã ããã«å ããŠãã²ãŒã ãå
¬éãããšãã«ã redirectUri
ã®å€ã眮ãæããå¿
èŠããããŸãã
ãã®ã³ãŒãã®æ¹åã¯éåžžã«ç°¡åã§ãã ãªã¹ãã¯æ¬¡ã®ãšããã§ãã
configure
ïŒãã®é¢æ°ã䜿çšããŠãAuth0 Clientããããã£ã§auth0-web
ããã±ãŒãžãæ§æãauth0-web
ãhandleAuthCallback
ïŒ "" componentDidMount
ã§ãã®é¢æ°ãåŒã³åºããŠãèªèšŒåŸã«ãã¬ãŒã€ãŒãAuth0ãè¿ããã©ããã倿ããŸãã ãã®é¢æ°ã¯ãURLããããŒã¯ã³ãæœåºããããšããã ãã§ãæåããå Žåããã¬ãŒã€ãŒã®ãããã¡ã€ã«ãéžæãããã¹ãŠãlocalstorage
ä¿åããŸããsubscribe
ïŒãã®é¢æ°ã¯ããã¬ãŒã€ãŒãèªèšŒãããŠãããã©ããã倿ããããã«äœ¿çšãããŸãïŒtrue-ã¢ã¯ã»ã¹ã®å Žåãfalse-èªèšŒãããŠããªãå ŽåïŒã
ããã§ãã²ãŒã ã§ã¯Auth0ãID管çãµãŒãã¹ãšããŠäœ¿çšãããŸã ã ã¢ããªã±ãŒã·ã§ã³ãå®è¡ãïŒ npm start
ïŒããã©ãŠã¶ãŒã§éããšïŒ http://localhost:3000
ïŒããã°ã€ã³ãã¿ã³ã衚瀺ãããŸãã ã¯ãªãã¯ãããšã Auth0ãã°ã€ã³ããŒãžã«ãªãã€ã¬ã¯ãããã ãã°ã€ã³ã§ããŸãã
èªèšŒåŸãAuth0ã¯åã³ã²ãŒã ã«ãªãã€ã¬ã¯ããã handleAuthCallback
颿°handleAuthCallback
ããŒã¯ã³handleAuthCallback
åŒãåºããŸãã ãã®åŸãã¢ããªã±ãŒã·ã§ã³ã«console.log
ãå®è¡ããããã«æç€ºãããšããã©ãŠã¶ã³ã³ãœãŒã«ã§å€true
ã確èªã§ããŸãã

LeaderBoardïŒè©äŸ¡ïŒãäœæãã
Auth0ãID管çã·ã¹ãã ãšããŠæ§æããã®ã§ããã¬ãŒã€ãŒã®ã¬ãŒãã£ã³ã°ãšæå€§ãã€ã³ãã衚瀺ããã³ã³ããŒãã³ããäœæããå¿
èŠããããŸãã ãããã¯ã leaderboard
ãšrank
ããã«åŒã°ããŸãã ãã¬ãŒã€ãŒã®ããŒã¿ãçŸãã衚瀺ããã®ã¯ããã»ã©ç°¡åã§ã¯ãªãããïŒããšãã°ãåŸç¹ãååãäœçœ®ãã¢ãã¿ãŒãªã©ïŒã2ã€ã®ã³ã³ããŒãã³ããå¿
èŠã«ãªããŸãã ããã¯é£ãããããŸãããããã®ããã«ã¯ããã€ãã®è¯ãã³ãŒããæžãå¿
èŠããããŸãã äžè¬ã«ããããã1ã€ã®ã³ã³ããŒãã³ãã圫å»ããããšã¯ãæãå·§åŠãªææ³ã§ã¯ãããŸããã
ãã¬ãŒã€ãŒãããªããããæåã«è¡ãå¿
èŠãããã®ã¯ããªãŒããŒããŒãã«èšå
¥ããããã® ãã¬ã€ã¢ãŠãããŒã¿ãïŒ ãããããéã-翻蚳è
ã³ã¡ã³ã ïŒãå®çŸ©ããããšã§ãã ããã¯Canvas
ã³ã³ããŒãã³ãã§è¡ãã®ãæé©ã§ãã ãŸãããã£ã³ãã¹ãæŽæ°ããããã Login
ã³ã³ããŒãã³ããLeaderboard
ã³ã³ããŒãã³ãã«çœ®ãæããããšãã§ããŸãïŒåæã«Login
ãLeaderboard
远å ããŸãïŒã
æ°ããããŒãžã§ã³ã§ã¯ãæ¶ç©ºã®ãã¬ãŒã€ãŒã®é
åãå«ãleaderboard
宿°ã«ã€ããŠèª¬æããŸããã ãããã®ãã¬ãŒã€ãŒã«ã¯ã id
ã maxScore
ã name
ããã³picture
ããããã£ããããŸãã æ¬¡ã«ã svg
èŠçŽ å
ã«ã次ã®ãã©ã¡ãŒã¿ãŒã䜿çšããŠleaderboard
ã³ã³ããŒãã³ãã远å ããŸããã
currentPlayer
ïŒçŸåšèª°ããã¬ã€ããŠããããæ±ºå®ããŸãã æ¶ç©ºã®ãã¬ã€ã€ãŒãããã®ã§ããã¹ãŠãã©ã®ããã«æ©èœããããèŠãŠã¿ãŸãããã ãã®ãã©ã¡ãŒã¿ãŒãæž¡ãç®çã¯ããã¬ãŒã€ãŒãããŒãã«ã§åŒ·èª¿è¡šç€ºããããšã§ããauthenticate
ïŒä»¥åã«Login
ã³ã³ããŒãã³ãã«è¿œå ãããã©ã¡ãŒã¿ãŒãšåããã©ã¡ãŒã¿ãŒãleaderboard
ïŒåœã®ãã¬ãŒã€ãŒã®é
åã çŸåšã®è©äŸ¡ã衚瀺ããããã«äœ¿çšãããŸãã
次ã«ã Leaderboard
ã³ã³ããŒãã³ãã«ã€ããŠèª¬æããå¿
èŠããããŸãã ãããè¡ãã«ã¯ãã ./src/components
ãã£ã¬ã¯ããªã«æ°ãã./src/components
ãã¡ã€ã«ãäœæããæ¬¡ã远å ããŸãã
import React from 'react'; import PropTypes from 'prop-types'; import Login from './Login'; import Rank from "./Rank"; const Leaderboard = (props) => { const style = { fill: 'transparent', stroke: 'black', strokeDasharray: '15', }; const leaderboardTitle = { fontFamily: '"Joti One", cursive', fontSize: 50, fill: '#88da85', cursor: 'default', }; let leaderboard = props.leaderboard || []; leaderboard = leaderboard.sort((prev, next) => { if (prev.maxScore === next.maxScore) { return prev.name <= next.name ? 1 : -1; } return prev.maxScore < next.maxScore ? 1 : -1; }).map((member, index) => ({ ...member, rank: index + 1, currentPlayer: member.id === props.currentPlayer.id, })).filter((member, index) => { if (index < 3 || member.id === props.currentPlayer.id) return member; return null; }); return ( <g> <text filter="url(#shadow)" style={leaderboardTitle} x="-150" y="-630">Leaderboard</text> <rect style={style} x="-350" y="-600" width="700" height="330" /> { props.currentPlayer && leaderboard.map((player, idx) => { const position = { x: -100, y: -530 + (70 * idx) }; return <Rank key={player.id} player={player} position={position}/> }) } { ! props.currentPlayer && <Login authenticate={props.authenticate} /> } </g> ); }; Leaderboard.propTypes = { currentPlayer: PropTypes.shape({ id: PropTypes.string.isRequired, maxScore: PropTypes.number.isRequired, name: PropTypes.string.isRequired, picture: PropTypes.string.isRequired, }), authenticate: PropTypes.func.isRequired, leaderboard: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, maxScore: PropTypes.number.isRequired, name: PropTypes.string.isRequired, picture: PropTypes.string.isRequired, ranking: PropTypes.number, })), }; Leaderboard.defaultProps = { currentPlayer: null, leaderboard: null, }; export default Leaderboard;
å¿é
ããªãã§ãã ããïŒ å®éãã³ãŒãã¯éåžžã«åçŽã§ãã
- è©äŸ¡è¡šã®ã¿ã€ãã«ã®å€èгãèšå®ããã«ã¯ã
leaderboardTitle
宿°ãå®çŸ©ããŸãã dashedRectangle
宿°ãå®çŸ©ããŠãããŒãã«ã®ãã³ã³ããããšããŠæ©èœããdashedRectangle
èŠçŽ ãäœæããŸããprops.leaderboard
倿°ã®sort
颿°ãåŒã³åºããŠãã©ã³ã¯ã調æŽããŸãã ãã®åŸãããŒãã«ã®äžçªäžã®è¡ã¯æãå€ãã®ãã€ã³ããæã€ãã¬ã€ã€ãŒã«ãã£ãŠå æãããäžçªäžã®ãã€ã³ãã¯æãå°ãããã¬ã€ã€ãŒã«ãã£ãŠå æãããŸãã ãã¬ãŒã€ãŒã®ãã€ã³ããçããå Žåãååé ã«äžŠã¹ãããŸãã- åã®ã¢ã¯ã·ã§ã³ã®çµæã«å¿ããŠã
map
颿°ãåŒã³åºãããåãã¬ãŒã€ãŒã«ã©ã³ã¯ã远å ãã currentPlayer
ãã©ã°ã远å ããŸãã ãã®ãã©ã°ã¯ãçŸåšã®ãã¬ãŒã€ãŒãé
眮ãããŠããè¡ã匷調衚瀺ããŸãã - åã®æé ïŒ
map
æ©èœïŒã®çµæãšããŠã filter
æ©èœã䜿çšããŠãTOP-3ã«ããªããã¬ãŒã€ãŒãfilter
ããŸãã å®éãçŸåšã®ãã¬ãŒã€ãŒãããã3ã«å«ãŸããŠããªããŠããæçµçãªé
åã«æ®ãããšãèš±å¯ããŸãã - æåŸã«ããã¬ãŒã€ãŒããã°ã€ã³ããŠããå ŽåïŒ
props.currentPlayer
&& props.currentPlayer
ïŒããŸãã¯Login
ãã¿ã³ã衚瀺ãããŠããªãå Žåããã£ã«ã¿ãŒãããé
åãå埩åŠçããŠRank
èŠçŽ ã衚瀺ããŸãã
æçµæ®µéã«Rank
ãŸã- Rank
ã³ã³ããŒãã³ããäœæããŸãã ãããè¡ãã«ã¯ã Rank.jsx
ãã¡ã€ã«ã®é£ã«ã次ã®ã³ãŒããå«ãæ°ããRank.jsx
ãã¡ã€ã«ãäœæããŸãã
import React from 'react'; import PropTypes from 'prop-types'; const Rank = (props) => { const { x, y } = props.position; const rectId = 'rect' + props.player.rank; const clipId = 'clip' + props.player.rank; const pictureStyle = { height: 60, width: 60, }; const textStyle = { fontFamily: '"Joti One", cursive', fontSize: 35, fill: '#e3e3e3', cursor: 'default', }; if (props.player.currentPlayer) textStyle.fill = '#e9ea64'; const pictureProperties = { style: pictureStyle, x: x - 140, y: y - 40, href: props.player.picture, clipPath: `url(#${clipId})`, }; const frameProperties = { width: 55, height: 55, rx: 30, x: pictureProperties.x, y: pictureProperties.y, }; return ( <g> <defs> <rect id={rectId} {...frameProperties} /> <clipPath id={clipId}> <use xlinkHref={'#' + rectId} /> </clipPath> </defs> <use xlinkHref={'#' + rectId} strokeWidth="2" stroke="black" /> <text filter="url(#shadow)" style={textStyle} x={x - 200} y={y}>{props.player.rank}º</text> <image {...pictureProperties} /> <text filter="url(#shadow)" style={textStyle} x={x - 60} y={y}>{props.player.name}</text> <text filter="url(#shadow)" style={textStyle} x={x + 350} y={y}>{props.player.maxScore}</text> </g> ); }; Rank.propTypes = { player: PropTypes.shape({ id: PropTypes.string.isRequired, maxScore: PropTypes.number.isRequired, name: PropTypes.string.isRequired, picture: PropTypes.string.isRequired, rank: PropTypes.number.isRequired, currentPlayer: PropTypes.bool.isRequired, }).isRequired, position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }).isRequired, }; export default Rank;
ãã®ã³ãŒããæããªãã§ãã ããã çããããšã¯1ã€ã ãã§ãclipPath
èŠçŽ ãšrect
èŠçŽ ãdefs
èŠçŽ å
ã®ãã®ã³ã³ããŒãã³ãã«è¿œå ããŠãäžžã¿ã垯ã³ãããŒãã¬ãŒããäœæããŸãã
ãã®ãã¹ãŠã®åŸãã¢ããªã±ãŒã·ã§ã³ïŒ http://localhost:3000/
ïŒã«ç§»åããŠãæ°ããè©äŸ¡ããŒãã«ã衚瀺ã§ããŸãã

Socket.IOã䜿çšããŠãä¿æã®ãªã¢ã«ã¿ã€ã ããŒãã«ãäœæãã
ããŠãAuth0ãID管çãµãŒãã¹ãšããŠäœ¿çšããè©äŸ¡ããŒãã«ã衚瀺ããããã®ãã¹ãŠã®ã³ã³ããŒãã³ããçšæããŸããã æ¬¡ã¯ïŒ ããã§ãããªã¢ã«ã¿ã€ã ã§ã€ãã³ããéä¿¡ããŠè©äŸ¡ããŒãã«ãæŽæ°ã§ããããã¯ãšã³ããå¿
èŠã§ãã
ããããããã®ãããªãµãŒããŒïŒ ããã¯ãšã³ã ïŒãäœæããã®ã¯é£ãããšæã£ãã®ã§ããããïŒ ãããããŸã£ãããããŸããã Socket.IOã䜿çšãããšããã®æ©èœãç°¡åã«éçºã§ããŸãã ãšã«ããããã®ãµãŒãã¹ãä¿è·ãããã§ããïŒ ãããè¡ãã«ã¯ããµãŒãã¹ã衚ãAuth0 APIãäœæããŸãã
ããã¯ããã»ã©é£ãããããŸããã Auth0ã³ã³ãããŒã«ããã«ã®APIããŒãžã«ç§»åãã[APIã®äœæ]ãã¿ã³ãã¯ãªãã¯ããã ãã§ãã ãã®åŸã3ã€ã®ãã£ãŒã«ããå«ããã©ãŒã ã«å
¥åããå¿
èŠããããŸãã
- API å ïŒ name ïŒïŒãã®APIãäœã衚ãããèŠããŠããã«ã¯ãããããããååãèšå®ããå¿
èŠããããŸãã ããšã€ãªã¢ã³ãåž°ã£ãŠïŒããšåŒã³ãŸãããã
2. API èå¥å ïŒ identifier ïŒïŒã²ãŒã ã®æçµURLãæå®ããããšããå§ãããŸãããå®éã«ã¯äœã§ãæ¿å
¥ã§ããŸãã ãã ãã https://aliens-go-home.digituz.com.br
ãšå
¥åãhttps://aliens-go-home.digituz.com.br
ã - 眲åã¢ã«ãŽãªãºã ã«ã¯ãRS256ãšHS256ã®2ã€ã®ãªãã·ã§ã³ããããŸãã ãã®ãã£ãŒã«ãã¯ç©ºçœã®ãŸãŸã«ããŠãããšããã§ãããïŒããã©ã«ãã§ã¯RS256ïŒã éããäœãã«èå³ãããå Žåã¯ã ãã¡ãããã§ãã¯ããŠãã ãã ã

ãã¹ãŠã®ãã£ãŒã«ãã«å
¥åãããããäœæããã¯ãªãã¯ããŸãã æ°ããAPIå
ã®[ ã¯ã€ãã¯ã¹ã¿ãŒã ]ã¿ãã«ãªãã€ã¬ã¯ããããŸãã ããããã ãã¹ã³ãŒããã¿ããã¯ãªãã¯ãã ã manage:points
ãšåŒã°ããæ°ããé åã远å ããŸãããæå€§ãã€ã³ãã®èªã¿åããšæžã蟌ã¿ããšãã説æããããŸãã ããã¯ãAuth0 APIã¢ããªã±ãŒã·ã§ã³ã§é åãå®çŸ©ããã®ã«é©ããæ¹æ³ã§ãã
ãšãªã¢ã远å ããããå°ãããã°ã©ãã³ã°ããå¿
èŠããããŸãã ãªã¢ã«ã¿ã€ã ã®è©äŸ¡è¡šãå®è£
ããã«ã¯ãæ¬¡ã®æé ãå®è¡ããŸãã
# mkdir server # ( ) cd server # NPM npm init -y # npm i express jsonwebtoken jwks-rsa socket.io socketio-jwt # touch index.js
æ°ãããã¡ã€ã«ã§ãã³ãŒãã远å ããŸãã
const app = require('express')(); const http = require('http').Server(app); const io = require('socket.io')(http); const jwt = require('jsonwebtoken'); const jwksClient = require('jwks-rsa'); const client = jwksClient({ jwksUri: 'https://YOUR_AUTH0_DOMAIN/.well-known/jwks.json'
ãã®ã³ãŒãã®æ©èœãçè§£ããåã«ã YOUR_AUTH0_DOMAIN
Auth0ãã¡ã€ã³ïŒ App.js
ãã¡ã€ã«ã«è¿œå ãããã¡ã€ã³ïŒã«çœ®ãæãYOUR_AUTH0_DOMAIN
ãã ããã ãã®å€ã¯jwksUri
ããããã£ã«ãããŸãã
次ã«ããããã©ã®ããã«æ©èœããããçè§£ããããã«ã次ã®ãªã¹ãã確èªããŠãã ããã
express
ãšsocket.io
ïŒããã¯ã socket.io
æ¡åŒµããããšã¯ã¹ãã¬ã¹ãµãŒããŒã§ããããªã¢ã«ã¿ã€ã ã§ã®äœæ¥æ¹æ³ãæããŠããŸãã 以åã«Socket.IOã䜿çšããããšããªãå Žåã¯ã Get Startedãã¥ãŒããªã¢ã«ãã芧ãã ããã ãšãŠãç°¡åã§ããjwt
ããã³jwksClient
ïŒ jwt
ãä»ããŠèªèšŒããå Žåããã¬ãŒã€ãŒã¯ïŒç¹ã«ïŒJWTïŒJSON Web TokenïŒã®åœ¢åŒã§access_tokenãåãåããŸãã RS256ã¢ã«ãŽãªãºã ã䜿çšããŠããããã jwksClient
ããã±ãŒãžã䜿çšããŠãJWTæ€èšŒçšã®æ£ããå
¬éããŒãååŸããå¿
èŠããããŸããjwt.verify
ïŒæ£ããããŒãååŸããæ¹æ³ããã®é¢æ°ã䜿çšããŠJWTããã³ãŒãããã³è©äŸ¡ããŸãã ãã¹ãŠãæ£åžžã§ããå ŽåãèŠæ±ã«å¿ããŠãã¬ãŒã€ãŒã®ãªã¹ããéä¿¡ããã ãã§ãã ããã§ãªãå Žåã¯ãïŒ socket
ïŒã¯ã©ã€ã¢ã³ããdisconnect
ãŸããon('new-max-score', ...)
ïŒæåŸã«ã newMaxScoreHandler
颿°ãnew-max-score
ã€ãã³ãã«ã¢ã¿ããããŸãã ãããã£ãŠããŠãŒã¶ãŒã®æå€§ãã€ã³ããæŽæ°ããå¿
èŠãããå Žåã¯åžžã«ãReactãããã®ã€ãã³ããããªã¬ãŒããŸãã
æ®ãã®ã³ãŒãã¯çŽæçã§ãã ãã®ãµãŒãã¹ãã²ãŒã ã«çµ±åããããšã«éäžã§ããŸãã
Socket.IOãšReact
ããªã¢ã«ã¿ã€ã ããã¯ãšã³ããµãŒãã¹ããäœæãããããããReactã«çµ±åããŸãã ReactãšSocket.IOã䜿çšããæè¯ã®æ¹æ³ã¯ socket.io-client
ãã€ã³ã¹ããŒã«ãã socket.io-client
ã ãããè¡ãã«ã¯ãReactã¢ããªã±ãŒã·ã§ã³ã®ã«ãŒãã«æ¬¡ã®ã³ãŒããå
¥åããŸãã
npm i socket.io-client
次ã«ããã¬ãŒã€ãŒãèªèšŒãããã³ã«ã²ãŒã ããµãŒãã¹ã«æ¥ç¶ããŸãïŒããŒãã«ã«ã¯èš±å¯ãããŠããªããŠãŒã¶ãŒã¯ããŸããïŒã Reduxã䜿çšããŠã²ãŒã ã®ç¶æ
ãä¿åããŠãããããã¹ãã¬ãŒãžãæŽæ°ããã«ã¯2ã€ã®æé ãå¿
èŠã§ãã ./src/actions/index.js
ãã¡ã€ã«ãéããŠæŽæ°ããŸãã
export const LEADERBOARD_LOADED = 'LEADERBOARD_LOADED'; export const LOGGED_IN = 'LOGGED_IN';
æ°ããããŒãžã§ã³ã§ã¯ã2ã€ã®ã¹ãããã§èµ·åããã¢ã¯ã·ã§ã³ãå®çŸ©ããŠããŸãã
LOGGED_IN
ïŒãã®ã¢ã¯ã·ã§ã³ã«ããããã¬ã€ã€ãŒããã°ã€ã³ãããšãã«ã²ãŒã ãããã¯ãšã³ãã«æ¥ç¶ããŸããLEADERBOARD_LOADED
ïŒãã®ã¢ã¯ã·ã§ã³ã䜿çšãããšãããã¯ãšã³ãããã¬ãŒã€ãŒã®ãªã¹ããéä¿¡ãããšãã«ããã¬ãŒã€ãŒãã§Reduxã¹ãã¢ãæŽæ°ããŸãã
Reduxããããã®ã¢ã¯ã·ã§ã³ã«å¿çããã«ã¯ãã ./src/reducers/index.js
ãã¡ã€ã«ãéããŠæŽæ°ããŸãã
import { LEADERBOARD_LOADED, LOGGED_IN, MOVE_OBJECTS, START_GAME } from '../actions';
LEADERBOARD_LOADED
ãã²ãŒã ã§åŒã³åºãããã®ã§ãæ°ãããã¬ãŒã€ãŒã®é
åã§ReduxãæŽæ°ããŸãã , , , currentPlayer
.
, , ./src/containers/Game.js
:
, realtime- ( ), . ./src/App.js
:
, :
audience
Auth0
.- (
Auth0.getProfile()
) currentPlayer
(Redux store) ( this.props.loggedIn(...)
). - (
io('http://localhost:3001', ...)
) access_token
( Auth0.getAccessToken()
). players
, , Redux store ( this.props.leaderboardLoaded(...)
).
, , (events) new-max-score
( ). -, maxScore
120
, 5 . , 5 ( (setTimeout(..., 5000)
), c maxScore
, 222
, .
Canvas
: currentPlayer
players
. , ./src/components/Canvas.jsx
:
:
leaderboard
. .<Leaderboard />
. : props.currentPlayer
props.players
.propTypes
, , Canvas
currentPlayer
players
.
ã§ããïŒ Socket.IO. ( â â ., ) :
# cd server # node index.js & # (cd .. = ) cd .. # npm start
: ( http://localhost:3000
). , , :

. , , . :
- : , "" .
- "": , , .
- "" : , , ( score ) . , "".
- : .
.
, onClick
Canvas
. Redux-, ( ). moveObjects
.
"" . ./src/actions/index.js
:
( ./src/reducers/index.js
):
import { LEADERBOARD_LOADED, LOGGED_IN, MOVE_OBJECTS, SHOOT, START_GAME } from '../actions';
, shoot
, SHOOT
. . shoot.js
:
import { calculateAngle } from '../utils/formulas'; function shoot(state, action) { if (!state.gameState.started) return state; const { cannonBalls } = state.gameState; if (cannonBalls.length === 2) return state; const { x, y } = action.mousePosition; const angle = calculateAngle(0, 0, x, y); const id = (new Date()).getTime(); const cannonBall = { position: { x: 0, y: 0 }, angle, id, }; return { ...state, gameState: { ...state.gameState, cannonBalls: [...cannonBalls, cannonBall], } }; } export default shoot;
, . , . , . , . , calculateAngle
. , , (Redux store) .
, , Game
, App
. , ./src/containers/Game.js
:
./src/App.js
:
, App
shoot
props ( , "" shoot â . ) canvasMousePosition
. Canvas
. "" , onClick
svg
, "".
: cannonBalls.map
CannonPipe
, "" .
, ( x: 0, y: 0
) , ( angle
) . , "" ( ).
, ./src/utils/formulas.js
:
: , , .
moveCannonBalls.js
calculateNextPosition
. ./src/reducers/
:
import { calculateNextPosition } from '../utils/formulas'; const moveBalls = cannonBalls => ( cannonBalls .filter(cannonBall => ( cannonBall.position.y > -800 && cannonBall.position.x > -500 && cannonBall.position.x < 500 )) .map((cannonBall) => { const { x, y } = cannonBall.position; const { angle } = cannonBall; return { ...cannonBall, position: calculateNextPosition(x, y, angle, 5), }; }) ); export default moveBalls;
. -, filter
, cannonBalls
(), . , -800 Y , ( -500) ( 500).
, ./src/reducers/moveObjects.js
:
moveObjects
, moveBalls
. cannonBalls
gameState
.
, , . -:

, , , , , . "" , . "": .
: , . , , , . , , .
, ./src/utils/formulas.js
:
, "" . checkCollisions.js
./src/reducers
:
import { checkCollision } from '../utils/formulas'; import { gameHeight } from '../utils/constants'; const checkCollisions = (cannonBalls, flyingDiscs) => { const objectsDestroyed = []; flyingDiscs.forEach((flyingDisc) => { const currentLifeTime = (new Date()).getTime() - flyingDisc.createdAt; const calculatedPosition = { x: flyingDisc.position.x, y: flyingDisc.position.y + ((currentLifeTime / 4000) * gameHeight), }; const rectA = { x1: calculatedPosition.x - 40, y1: calculatedPosition.y - 10, x2: calculatedPosition.x + 40, y2: calculatedPosition.y + 10, }; cannonBalls.forEach((cannonBall) => { const rectB = { x1: cannonBall.position.x - 8, y1: cannonBall.position.y - 8, x2: cannonBall.position.x + 8, y2: cannonBall.position.y + 8, }; if (checkCollision(rectA, rectB)) { objectsDestroyed.push({ cannonBallId: cannonBall.id, flyingDiscId: flyingDisc.id, }); } }); }); return objectsDestroyed; }; export default checkCollisions;
, :
objectsDestroyed
.flyingDiscs
( forEach
) . , CSS, Y currentLifeTime
.cannonBalls
( forEach
) .checkCollision
( ), , (). , objectsDestroyed
, .
moveObjects.js
, :
checkCollisions
, cannonBalls
flyingObjects
.
, "" , moveObjects
gameState
. -.
""
, - , "". "" , . "" . â ./src/reducers/moveObject.js
. :
import { calculateAngle } from '../utils/formulas'; import createFlyingObjects from './createFlyingObjects'; import moveBalls from './moveCannonBalls'; import checkCollisions from './checkCollisions'; function moveObjects(state, action) {
flyingObjects
, , "" . , , 4 ( (now - object.createdAt) < 4000
), , .
, "", Canvas
. ./src/components/Canvas.jsx
:
. ; , . , , , .
. ./src/reducers/moveObjects.js
:
./src/components.Canvas.jsx
CurrentScore
( 15) :
<CurrentScore score={props.gameState.kills} />
! , , React, Redux, SVG CSS . , .
./server/index.js
players
. "" ( ) "" . . , :
const players = [];
App
. ./src/App.js
:
, :
- (
socket
currentPlayer
), . - ,
new-max-score
. players
( ), maxScore
. , , maxScore
.componentWillReceiveProps
, ( maxScore
). new-max-score
.
! -. , Socket.IO React :
# node ./server/index & # React- npm start
, . , .

ãããã«
ãããã®ãšããœãŒãã§ã¯ãã·ã³ãã«ã§æ¥œããã²ãŒã ãäœæããããã«å€ãã®çŽ æŽãããæè¡ãé©çšããŸãããReactã䜿çšããŠã²ãŒã èŠçŽ ãå®çŸ©ããã³å¶åŸ¡ããSVGïŒHTMLã§ã¯ãªãïŒã䜿çšããŠãããã®èŠçŽ ãã¬ã³ããªã³ã°ããReduxã䜿çšããŠã²ãŒã ã®ç¶æ
ãå¶åŸ¡ããæåŸã«CSSã¢ãã¡ãŒã·ã§ã³ã䜿çšããŠãšã€ãªã¢ã³ãç»é¢äžã§ç§»åããŸãããããã«ãSocket.IOã¯ããªã¢ã«ã¿ã€ã ã®è©äŸ¡ããŒãã«ãããã³ID管çã·ã¹ãã ãšããŠã®Auth0ãäœæããã®ã«åœ¹ç«ã¡ãŸããã
ããïŒããªãã¯é·ãéã®ããæ©ã¿ãå€ããåŠã³ãŸãããå°ããªã©ãã¯ã¹ããŠæ®åœ±ããæéã§ãïŒ