Posted at

Box2dWeb + Pixi.js をTypescriptでやってみた。

More than 1 year has passed since last update.

軽い気持ちでデモ的に使ってみたのでメモ。

一応試してみたけど地味に時間とられたわりにたぶん使わないので記事にして供養。


とりあえず、

npm install box2dweb-commonjs

ちなみに"box2dweb"が本家?で"box2dweb-commonjs"が派生?

importして使いたかったので(たしかbox2dwebをimportで使うとうまくいかなったので)

"box2dweb-commonjs"の方つかってみました。


typescriptで使うには

typescript環境だったのでそこで使うのに地味に試行錯誤することになった。

多分これで動く。

npm install @types/box2d

tsconfig.jsonに

"types" : [~, "box2d"],

を書く。

"noImplicitAny"はfalseにすること。

import * as Box2d from 'box2dweb-commonjs'

noImplictAnyがtrueだと怒られたので。

Error TS7016: Could not find a declaration file for module 'box2dweb-commonjs'. '~/node_modules/box2dweb-commonjs/Box2dWeb-2.1.a.3.js' implicitly has an 'any' type.

正直なところ型定義ファイルの正しい書き方とか、moduleのimportまわりが

よくわかっていないので原因はわからない。。

自分の環境下ではとりあえずこうしておけば使えた。


pixi.jsとbox2d合わせて使う

https://liginc.co.jp/web/js/other-js/88419

から引用させていただくと、


Box2DWebはあくまで計算してくれるだけなので、画面には何も表示されません。


ということなので、どうやってpixijsで書いた図形とかと連動させるかということを

いろいろサンプルみながら調べた。

https://codepen.io/OddlyTimbot/pen/lycip

が非常に参考になったので、上記2つの参考リンクをあわせればできると思います。

jsのコードを上記記事から拝借して、その先をちょっと書き足すと

bodyDef.type = b2Body.b2_dynamicBody; // 今回は動く物体

fixDef.shape = new b2CircleShape(30 / WORLDSCALE); // 適当な半径をもつ丸にする
bodyDef.position.x = 300 / WORLDSCALE; // 横300の位置に置く
bodyDef.position.y = 0 / WORLDSCALE; // 高さは0の場所から
var body = world.CreateBody(bodyDef);
body.CreateFixture(fixDef); // 世界に突っ込む

world.CreateBody(bodyDef)を変数として確保しておきます。

このbodyに、

GetAngle() と GetWorldCenter().x, GetWorldCenter().y という

メンバ関数があるのでその値をアップデート関数(setIntervalとか)で

pixiでつくったオブジェクトのposition, rotationの値に入れてあげればよい。

(ちゃんとした説明が面倒になってしまったので、最後に自分が書いたコードを長いけどそのままコピペしておきます。)


できたやつ


コード

pixi.jsでうまいことクラス分けして作るやり方が試行錯誤すぎる。。


import * as $ from 'jquery';
import * as Box2d from 'box2dweb-commonjs';
import * as PIXI from 'pixi.js';

$(document).ready(() => {

const box2dTest = new Box2dTest();

});

/*本記事には関係ないクラス*/
class PixiBase{
protected app: PIXI.Application;
protected loader: PIXI.loaders.Loader;
protected window: {w:number, h: number};

constructor(w: number, h: number, opt:PIXI.IApplicationOptions, parentId:string, replace:boolean = false){
this.app = new PIXI.Application(w, h, opt);

const elem:HTMLElement = document.getElementById(parentId);
if(replace == false){
elem.appendChild(this.app.view);
}else{
elem.parentNode.replaceChild(this.app.view, elem);
this.app.view.id = parentId;
}

this.window = {w: w, h: h};

this.loader = new PIXI.loaders.Loader();

this.app.ticker.add(()=> {
this.animate();
});
}

protected setSpirte(sprite:PIXI.Sprite, x:number, y:number): void{
sprite.anchor.set(0.5);
sprite.x = x;
sprite.y = y;
this.app.stage.addChild(sprite);
}

protected animate():void{

}
}

class Box2dTest extends PixiBase{
private $trgId: JQuery;

private container: PIXI.Container;

private world: Box2d.b2World;
private worldScale:number;

private bodyDef: Box2d.b2BodyDef;
private fixDef: Box2d.b2FixtureDef;
private b:Box2dPixiCircle[];
constructor(){

const ww = window.innerWidth;
const wh = window.innerHeight;
super(ww, wh, {transparent : true, antialias: true}, "canvas", true);

this.$trgId = $('#overray-one');
this.$trgId.css({'background-color': 'transparent'})

this.container = new PIXI.Container();
this.app.stage.addChild(this.container);

// box2d=======================================
this.worldScale = 100.0;
this.world = new Box2d.b2World(
new Box2d.b2Vec2(0,9.8),
true);

this.bodyDef = new Box2d.b2BodyDef;
this.bodyDef.type = Box2d.b2Body.b2_staticBody;

// // オブジェクトの設定
this.fixDef = new Box2d.b2FixtureDef;
this.fixDef.density = 1.0; // 密度
this.fixDef.friction = 0.5; // 摩擦係数
this.fixDef.restitution = 0.4; // 反発係数

// 壁を作る
this.bodyDef.type = Box2d.b2Body.b2_staticBody;
this.fixDef.shape = new Box2d.b2PolygonShape;

// 1番下にラインを引く 縦1pxのラインを引く
this.fixDef.shape.SetAsBox(this.window.w / this.worldScale , 1 / this.worldScale);
this.bodyDef.position.Set(0 , this.window.h / this.worldScale);
this.world.CreateBody(this.bodyDef).CreateFixture(this.fixDef);
this.fixDef.shape.SetAsBox(1/this.worldScale , this.window.h/ 2 / this.worldScale);
this.bodyDef.position.Set(0 , (this.window.h/2) / this.worldScale);
this.world.CreateBody(this.bodyDef).CreateFixture(this.fixDef);
this.fixDef.shape.SetAsBox(1/this.worldScale , this.window.h/ 2 / this.worldScale);
this.bodyDef.position.Set(this.window.w/this.worldScale , (this.window.h/2) / this.worldScale);
this.world.CreateBody(this.bodyDef).CreateFixture(this.fixDef);

this.b = [];

//---debug
var debugDraw = new Box2d.b2DebugDraw(); // debug用オブジェクト
var canvas = <HTMLCanvasElement> document.getElementById('canvas');
debugDraw.SetSprite(canvas.getContext("2d")); // 描画するcanvasを設定
debugDraw.SetDrawScale(this.worldScale); // この世界のスケールを設定
debugDraw.SetFillAlpha(0.5); // 要素の透過度を設定
debugDraw.SetFlags(Box2d.b2DebugDraw.e_shapeBit | Box2d.b2DebugDraw.e_jointBit); // 表示する内容を定数で指定
this.world.SetDebugDraw(debugDraw); // 世界にdebug要素を突っ込む

this.world.ClearForces();

var c = 0;

//たくさん円がふってくるように
setInterval(()=>{
if(c > 300) return;
let b = new Box2dPixiCircle();
this.container.addChild(b.setPixiCircle(this.window.w/2+(Math.random()-0.5)*30, -30, 15, 0xffffff));
b.setPhysics(this.world, this.fixDef, this.bodyDef, this.worldScale);
this.b.push(b);
c++;
}, 10);

}

protected animate():void{
this.world.Step(1 / 60, 10, 10);
this.world.ClearForces();
this.world.DrawDebugData();

for(var i=0; i<this.b.length; i++){
this.b[i].updatePhysics();

}
}
}

class Box2dPixiCircle{
private c:PIXI.Graphics;
public body: Box2d.b2Body;
private scale: number;
private radius: number;
public count: number;
public merged: boolean;

constructor(){
this.count = 0;
}

public setPixiCircle(posX:number, posY:number, rad:number, col:number):PIXI.Graphics{
this.c = new PIXI.Graphics();
this.radius = rad;
this.c.lineStyle(0);
this.c.beginFill(col, 1);
this.c.drawCircle(0, 0, rad);
this.c.x = posX;
this.c.y = posY;
this.c.endFill();
return this.c;
}

public setPhysics(world: Box2d.b2World, fixDef:Box2d.b2FixtureDef, bodyDef:Box2d.b2BodyDef, scale:number){
this.scale = scale;
bodyDef.type = Box2d.b2Body.b2_dynamicBody;
fixDef.shape = new Box2d.b2CircleShape(this.radius / scale);
bodyDef.position.x = this.c.x / scale;
bodyDef.position.y = this.c.y / scale;
let body:Box2d.b2Body = world.CreateBody(bodyDef);
body.CreateFixture(fixDef);

this.body = body;
}

public updatePhysics(){
this.count++;
this.c.rotation = this.body.GetAngle() * (180 / Math.PI);
this.c.x = this.body.GetWorldCenter().x * this.scale;
this.c.y = this.body.GetWorldCenter().y * this.scale;
}
}