【EgretEngine】p2.js 物理エンジンを使用して1000個の玉を描画する方法
はじめに
この記事では、p2.js 物理エンジンを使用して、1000個の玉を描画してみようと思います。
フィア: いっぱい玉が出てきたら楽しいよね!
カレン: 前回の記事も参照してみてね!
前回の記事
p2.js 物理エンジンを使用して1000個の玉を描画する方法
Main.ts
class Main extends eui.UILayer {
public constructor() {
super();
this.once(egret.Event.ADDED_TO_STAGE, this.addToStage, this);
}
private addToStage(event:egret.Event) {
const world = new p2.World();
world.sleepMode = p2.World.BODY_SLEEPING;
world.gravity = [0, 9.8 * 50];
//見えない壁や地面の生成
for(let i = 0; i < 3; i++){
const planeBody: p2.Body[] = [];
planeBody[i] = new p2.Body({fixedRotation:true ,type:p2.Body.STATIC});
const planeShape: p2.Plane[] = [];
planeShape[i] = new p2.Plane();
switch(i){
//地面
case 0:
planeBody[i].position= [0, 700];
planeBody[i].angle = Math.PI;//rad表記
break;
//右の壁
case 1:
planeBody[i].position= [1260, 700];
planeBody[i].angle = Math.PI/2;//rad表記
break;
//左の壁
case 2:
planeBody[i].position= [20, 700];
planeBody[i].angle = 3* Math.PI/2;//rad表記
break;
}
planeBody[i].addShape(planeShape[i]);
world.addBody(planeBody[i]);
}
const shape:egret.Shape[] = [];
const body :p2.Body[] = [];
const bodyShape : p2.Circle[] = [];
for (let i = 0; i < 1000; i++){
shape[i] = new egret.Shape();
this.addChild(shape[i]);
body[i] = new p2.Body({mass:1, position:[30 + i , -100-i*10], fixedRotation:true});
bodyShape[i] = new p2.Circle({ radius: 5 });
body[i].addShape(bodyShape[i]);
world.addBody(body[i]);
}
//繰り返しメソッド
egret.Ticker.getInstance().register(loopMethod,this);
function loopMethod(dt:number) {
//1/60秒に一度実行
world.step(1/60, dt/1000, 10);
for (let i = 0; i < 1000; i++){
drawBall(shape[i], body[i].position[0], body[i].position[1]);
}
}
function drawBall(shape:egret.Shape, x:number, y:number){
//一旦ステージに描画した図形を消す。これがないと図形が重なって線になる。
shape.graphics.clear();
//円の描画
shape.graphics.beginFill(0xff0000);
shape.graphics.drawCircle(x, y, 5);
shape.graphics.endFill();
}
}
}p2.js 物理世界を構築
物理エンジンを使用するために、まずp2.worldを構築します。
const world = new p2.World();
world.sleepMode = p2.World.BODY_SLEEPING;
world.gravity = [0, 9.8 * 50];見えない壁を構築
//見えない壁や地面の生成
for(let i = 0; i < 3; i++){
const planeBody: p2.Body[] = [];
planeBody[i] = new p2.Body({fixedRotation:true ,type:p2.Body.STATIC});
const planeShape: p2.Plane[] = [];
planeShape[i] = new p2.Plane();
switch(i){
//地面
case 0:
planeBody[i].position= [0, 700];
planeBody[i].angle = Math.PI;//rad表記
break;
//右の壁
case 1:
planeBody[i].position= [1260, 700];
planeBody[i].angle = Math.PI/2;//rad表記
break;
//左の壁
case 2:
planeBody[i].position= [20, 700];
planeBody[i].angle = 3* Math.PI/2;//rad表記
break;
}
planeBody[i].addShape(planeShape[i]);
world.addBody(planeBody[i]);
}物理エンジンを組み込んだオブジェクトを生成します。
基本は「p2.Body」を生成して、「p2.Plane」で形成します。Planeは厚みの無い平面です。
Body系の位置を変更するには、
Body.position = [0, 700];//[x, y 座標]
Body.position[0] = 10;//x座標
Body.position[1] = 10;//y座標を使用します。
Body系を回転させるには、
Body.angle = Math.PI;//rad表記を使用します。回転はラジアンで制御します。180°回転 = π、90°回転 = π/2 です。
BodyにShapeを適用し、worldに追加します。
Body.addShape(planeShape);
world.addBody(Body);1000個の玉のコライダーを生成
const shape:egret.Shape[] = [];
const body :p2.Body[] = [];
const bodyShape : p2.Circle[] = [];
for (let i = 0; i < 1000; i++){
shape[i] = new egret.Shape();
this.addChild(shape[i]);
body[i] = new p2.Body({mass:1, position:[30 + i , -100-i*10], fixedRotation:true});
bodyShape[i] = new p2.Circle({ radius: 5 });
body[i].addShape(bodyShape[i]);
world.addBody(body[i]);
}まず、1000分の玉のコライダーを生成します。少しでCPU負荷を下げるため、fixedRotation:trueで玉が回転しないようにします。
1000個の玉を描画する
function drawBall(shape:egret.Shape, x:number, y:number){
//一旦ステージに描画した図形を消す。これがないと図形が重なって線になる。
shape.graphics.clear();
//円の描画
shape.graphics.beginFill(0xff0000);
shape.graphics.drawCircle(x, y, 5);
shape.graphics.endFill();
}Bodyだけでは図形は表示されないので、Shapeを使用して描画します。
描画をアップデートする
//繰り返しメソッド
egret.Ticker.getInstance().register(loopMethod,this);
function loopMethod(dt:number) {
//1/60秒に一度実行
world.step(1/60, dt/1000, 10);
for (let i = 0; i < 1000; i++){
drawBall(shape[i], body[i].position[0], body[i].position[1]);
}
}Shapeは一度描画するだけなので、「egret.Ticker.getInstance().register(loopMethod,this);」で描画をアップデートし、動いているように表現します。
(2019/01/12 追記)
Shapeを移動させて描画する方法
Main.ts
class Main extends eui.UILayer {
public constructor() {
super();
this.once(egret.Event.ADDED_TO_STAGE, this.addToStage, this);
}
private addToStage(event:egret.Event) {
const world = new p2.World();
world.sleepMode = p2.World.BODY_SLEEPING;
world.gravity = [0, 9.8 * 50];
//見えない壁や地面の生成
for(let i = 0; i < 3; i++){
const planeBody: p2.Body[] = [];
planeBody[i] = new p2.Body({fixedRotation:true ,type:p2.Body.STATIC});
const planeShape: p2.Plane[] = [];
planeShape[i] = new p2.Plane();
switch(i){
//地面
case 0:
planeBody[i].position= [0, 700];
planeBody[i].angle = Math.PI;//rad表記
break;
//右の壁
case 1:
planeBody[i].position= [1260, 700];
planeBody[i].angle = Math.PI/2;//rad表記
break;
//左の壁
case 2:
planeBody[i].position= [20, 700];
planeBody[i].angle = 3* Math.PI/2;//rad表記
break;
}
planeBody[i].addShape(planeShape[i]);
world.addBody(planeBody[i]);
}
const shape:egret.Shape[] = [];
const body :p2.Body[] = [];
const bodyShape : p2.Circle[] = [];
for (let i = 0; i < 1000; i++){
shape[i] = new egret.Shape();
this.addChild(shape[i]);
body[i] = new p2.Body({mass:1, position:[30 + i , -100-i*10], fixedRotation:true});
bodyShape[i] = new p2.Circle({ radius: 5 });
body[i].addShape(bodyShape[i]);
world.addBody(body[i]);
}
//繰り返しメソッド
egret.Ticker.getInstance().register(loopMethod,this);
function loopMethod(dt:number) {
//1/60秒に一度実行
world.step(1/60, dt/1000, 10);
for (let i = 0; i < 1000; i++){
drawBall(shape[i], body[i].position[0], body[i].position[1]);
}
}
function drawBall(shape:egret.Shape, x:number, y:number){
//一旦ステージに描画した図形を消す。これがないと図形が重なって線になる。
shape.graphics.clear();
//shapeの位置をbodyに合わせて移動する
shape.x = x;
shape.y = y;
//円の描画
shape.graphics.beginFill(0xff0000);
shape.graphics.drawCircle(0, 0, 5);
shape.graphics.endFill();
}
}
}冒頭のMain.tsとの違いは、drawBallメソッド内です。
function drawBall(shape:egret.Shape, x:number, y:number){
//一旦ステージに描画した図形を消す。これがないと図形が重なって線になる。
shape.graphics.clear();
//shapeの位置をbodyに合わせて移動する
shape.x = x;
shape.y = y;
//円の描画
shape.graphics.beginFill(0xff0000);
shape.graphics.drawCircle(0, 0, 5);
shape.graphics.endFill();
}冒頭の方では、drawCircle(x, y, 5); として、bodyの動きに合わせて描画を移動させていきました。
しかし、この方法では衝突判定を行いたいときなどで、shapeの位置が常に(x, y) = (0, 0)となり都合が悪いときがあります。
なので、実際にshapeを移動させて、drawCircle(0, 0, 5);で描画する方がいいと思います。
また、こちらの方法ではshape.graphics.clear(); をする必要はありません。上記のコードをからclear()を削除して実行してみてください。以下のような結果が得られます。
ただし、clear()しないからといって、FPSが向上するということはなく、むしろ重くなった気もします…。clear()に関してはお好みでどうぞ(ちょっと図形がにじんでる気もします)。