Reactで物理エンジンを使ってみよう!(React+matter+SVG)
はじめに
最近仕事から帰ってきてすぐダラダラしてしまうの本当によくないので、趣味でチマチマ技術ブログ的なことでもやろうかなと思います。
どなたかの役に立てば幸いです。
Reactを始めよう
Reactで何かを試作するときって普通は今まで組んできたやつを適当にコピペしてやってきたんですが、今回は気が向いたので create-react-app
も使わずにwebpack-dev-server
一本でいこうと思います。
Reactの初歩の初歩の構築はこのサイトが非常にまとまっていてよいです。
仕様
物理エンジンといってもReact用に開発されたものは少なく(ちょっと見当たらない)、結構ジェネラルにJavascript用~~~って感じであるのがほとんどです。
個人的にはこの matter.js
がデモサイトも凝っていて気になったので試用してみます。
何はともあれはじめる
class App extends Component { constructor(props) { super(props); this.state = { width: 640, height: 480 }; } componentDidMount() {} render() { return ( <div> <h3>react-physics</h3> <svg width={this.state.width} height={this.state.height} style={{ backgroundColor: '#243d52' }} /> </div> ); } }
はじめのはじめ。こんなかんじでいいかな。最初は。
matter.js 導入
おもむろに npm install 。
npm install --save matter-js
初期化部分は matter-js
のサンプルコードを見ればよいでしょう。
const engine = Engine.create(); const runner = Runner.create(); class App extends Component { constructor(props) { super(props); this.state = { width: 640, height: 480, bodies: [] }; this.handleUpdate = this.handleUpdate.bind(this); } componentDidMount() { Events.on(engine, 'afterUpdate', this.handleUpdate); Runner.run(runner, engine); } componentWillUnmount() { Events.off(engine, 'afterUpdate', this.handleUpdate); Runner.stop(runner); } handleUpdate() { const bodies = Matter.Composite.allBodies(engine.world); this.setState({ bodies }); } handleButton() { const circle = Bodies.circle(0, -1.0, 10); Matter.Body.applyForce(circle, { x: 0, y: 0 }, { x: 0, y: -0.01 }); World.add(engine.world, circle); } render() { const bodyItems = this.state.bodies.map(body => { if (!body.parts) return null; return body.parts.map(part => { return ( <circle cx={part.position.x} cy={part.position.y} r={part.circleRadius} fill="lightgreen" /> ); }); }); return ( <div> <h3>react-physics</h3> <svg width={this.state.width} height={this.state.height} style={{ backgroundColor: '#243d52' }} > <g transform={`scale(1,1) translate(${this.state.width / 2},${this .state.height / 2})`} > {bodyItems} </g> </svg> <button onClick={() => this.handleButton()}>PUSH</button> </div> ); } }
ちょこっと解説を。
matter-js
はengineをスタートさせると afterUpdate
イベントをトリガーするようになります。ですので、そこで Events.on
(matterのヘルパです)で更新内容を取得し、stateに設定すれば render()が自動的に走り、更新内容を描画できるってわけ。
なんで matter-js
にはレンダラが用意されてるのにわざわざ SVG 使ってるのかって??それは・・・
です!
楽しい!
終わりに
今後も物理エンジン使って何か技術ブログ的なことをやりたいと考えてはいるんですが、どうも時間がかかりすぎる・・・
「コード書く→動画撮る・変換する→記事書く」
このフローだけで1時間半はかかってる気がする・・・もうちょっと効率化したいです。
以上、コメント何かあればお願いいたします!
追記20201109:DIV版
Atsushiさんより、DIVでやってみる方法のご質問をいただきましたので、
僭越ながら修正してみました。
このようにすればDIVでも物理エンジンを使って動かすことができますね。
コメントいただいたAtsushiさんに感謝!
// MatterTest.js import React from 'react'; import Matter, { Engine, Events, Runner, World, Bodies } from 'matter-js'; const engine = Engine.create(); const runner = Runner.create(); class MatterTest extends React.Component { constructor(props) { super(props); this.state = { width: 640, height: 480, bodies: [] }; this.handleUpdate = this.handleUpdate.bind(this); } componentDidMount() { //Wall World.add(engine.world, [ Bodies.rectangle(0, 240, 800, 6, { isStatic: true, render: { fillStyle: '#2E2E2E', //fillStyle: "#000", }, }), ]); Events.on(engine, 'afterUpdate', this.handleUpdate); Runner.run(runner, engine); } componentWillUnmount() { Events.off(engine, 'afterUpdate', this.handleUpdate); Runner.stop(runner); } handleUpdate() { const bodies = Matter.Composite.allBodies(engine.world); this.setState({ bodies }); } handleButton() { const circle = Bodies.circle(0, -1.0, 40); Matter.Body.applyForce(circle, { x: 0, y: 0 }, { x: 0, y: -0.2 }); World.add(engine.world, circle); } render() { const bodyItems = this.state.bodies.map(body => { if (!body.parts) return null; return body.parts.map((part, idx) => { const radius = part.circleRadius; const left = part.position.x - radius + this.state.width / 2; const top = part.position.y - radius + this.state.height / 2; const divStyle = { position: 'absolute', left, top, width: radius * 2, height: radius * 2, backgroundColor: 'lightgreen', }; return <div className="object" style={divStyle} key={`obj_${idx}`} />; }); }); return ( <React.Fragment> <div className="object_container" style={{ width: this.state.width, height: this.state.height, backgroundColor: '#243d52', }} > {bodyItems} </div> <button onClick={() => this.handleButton()}>PUSH</button> </React.Fragment> ); } } export default MatterTest;
EOS M100を壊してしまいました…
ぐおお・・・
過去の記事でEOS Mが生えてきたことを報告しましたが、、、短命であった・・・
M100破損
発端
私はその日リュックで出かけていたんですが(もちろん中にはKissMとM100)、
トイレに行こうと思い個室の荷物をかける部分にそのリュックをかけたんです。
すると ゴッ という鈍い音とともにリュックが床に落下…
ヒェ~~~~な状況になってました。
症状
完全にシャッターボタンがオシャカになりました。どうも落下の衝撃がシャッターボタン周囲のダイアル(?)に集中したようで、 ダイアルが変形してしまいました…
おかげでシャッターを押そうにも十分に押し込めなくなってしまいました。
修理に出す
というわけで、これから修理に出そうと思います。いくらかかることやら、、、とほほ
終わりに
注意しましょう!(つらい)。KissMが無事だったのが不幸中の幸い…!!!