以下の枠線内のタッチされた座標から銃弾が発射されます。 画面左側をタッチしていると右側に赤い銃弾が、右側をタッチしていると左側に緑色の銃弾が発射されます。 それぞれの銃弾は、銃弾同士と同じ色の壁には衝突せず、反対の色の壁に衝突したときのみダメージに見立てた数字が表示されます。
ダウンロードした zip ファイルを解凍して以下のコマンドを実行してください。
npm install
akashic-sandbox .
"use strict";
var box2d = require("@akashic-extension/akashic-box2d");
/** 2次元ベクトル */
var b2Vec2 = box2d.Box2DWeb.Common.Math.b2Vec2;
/** 2 × 2 行列 */
var b2Mat22 = box2d.Box2DWeb.Common.Math.b2Mat22;
/** Aグループ */
var GROUP_A = -1;
/** Bグループ */
var GROUP_B = -2;
/** 壁カテゴリ */
var CATEGORY_BULLET = 0x0001;
/** 壁カテゴリ */
var CATEGORY_WALL = 0x0002;
/** ダメージ描画に使用するフォント */
var DAMAGE_FONT = new g.DynamicFont({
game: g.game,
fontFamily: g.FontFamily.Monospace,
size: 30
});
/** 物理世界のプロパティ */
var worldProperty = {
gravity: [0.0, 0.0], // 重力の方向(m/s^2)
scale: 50, // スケール(pixel/m)
sleep: true // 停止した物体を演算対象としないかどうか
};
/** 物理エンジンの世界 */
var physics = new box2d.Box2D(worldProperty);
/** グループAの銃弾生成パラメータ */
var bulletParameterA = {
/** 見た目情報 */
appear: {
assetId: "circleA",
width: 0.1 * worldProperty.scale,
height: 0.1 * worldProperty.scale
},
/** 物理定義 */
physics: {
/** 物理挙動 */
body: physics.createBodyDef({
type: box2d.BodyType.Dynamic // 自由に動ける物体
}),
/** 物理性質 */
fixture: physics.createFixtureDef({
density: 1.0, // 密度
friction: 0.3, // 摩擦係数
restitution: 0.7, // 反発係数
shape: physics.createCircleShape(0.1 * worldProperty.scale), // 衝突判定の形(直径 0.1m の円形)
filter: {
// 衝突判定のフィルタリング設定
// ※ 負のグループに属するオブジェクト同士は衝突しない
// ※ 物体のcategoryBitsとmaskBitsの論理積が真のときだけ衝突判定が行われる
groupIndex: GROUP_A,
categoryBits: CATEGORY_BULLET,
maskBits: CATEGORY_WALL
}
})
}
};
/** グループBの銃弾生成パラメータ */
var bulletParameterB = {
appear: {
assetId: "circleB",
width: 0.1 * worldProperty.scale,
height: 0.1 * worldProperty.scale
},
physics: {
body: physics.createBodyDef({
type: box2d.BodyType.Dynamic
}),
fixture: physics.createFixtureDef({
density: 1.0,
friction: 0.3,
restitution: 0.7,
shape: physics.createCircleShape(0.1 * worldProperty.scale),
filter: {
groupIndex: GROUP_B,
categoryBits: CATEGORY_BULLET,
maskBits: CATEGORY_WALL
}
})
}
};
/** グループAの壁生成パラメータ */
var wallParameterA = {
appear: {
width: 0.3 * worldProperty.scale,
height: g.game.height / 3,
cssColor: "crimson"
},
physics: {
body: physics.createBodyDef({
type: box2d.BodyType.Static // 固定されて動かない物体
}),
fixture: physics.createFixtureDef({
density: 1.0,
friction: 0.3,
restitution: 0.7,
shape: physics.createRectShape(0.3 * worldProperty.scale, g.game.height / 3),
filter: {
groupIndex: GROUP_A,
categoryBits: CATEGORY_WALL
}
})
}
};
/** グループBの壁生成パラメータ */
var wallParameterB = {
appear: {
width: 0.3 * worldProperty.scale,
height: g.game.height / 3,
cssColor: "teal"
},
physics: {
body: physics.createBodyDef({
type: box2d.BodyType.Static
}),
fixture: physics.createFixtureDef({
density: 1.0,
friction: 0.3,
restitution: 0.7,
shape: physics.createRectShape(0.3 * worldProperty.scale, g.game.height / 3),
filter: {
groupIndex: GROUP_B,
categoryBits: CATEGORY_WALL
}
})
}
};
/** 衝突判定を持つ銃弾のリスト */
var bulletList = [];
/** 衝突した物体のUserDataリスト */
var contactDataList = [];
/** 衝突イベントのリスナ */
var contactListener = new box2d.Box2DWeb.Dynamics.b2ContactListener();
// 衝突開始時のイベントリスナを設定
contactListener.BeginContact = function(contact) {
// userDataの組を保存しておく
var a = contact.GetFixtureA().GetBody();
var b = contact.GetFixtureB().GetBody();
contactDataList.push({ a: a.GetUserData(), b: b.GetUserData() });
};
// イベントリスナを設定
physics.world.SetContactListener(contactListener);
function main() {
var scene = new g.Scene({ game: g.game, assetIds: ["circleA", "circleB"] });
scene.loaded.add(function() {
var gameCenter = calcCenter(g.game);
var position = gameCenter.Copy();
// 壁Aを生成
var wallA = createRect(scene, wallParameterA);
position.x -= 1.0;
wallA.b2body.SetPosition(position);
// 壁Bを生成
var wallB = createRect(scene, wallParameterB);
position.x += 2.0;
wallB.b2body.SetPosition(position);
/** 画面をタッチしているか */
var touch = false;
/** タッチしている座標 */
var touchPosition;
// 画面をタッチしている間、タッチ座標を追う
scene.pointDownCapture.add(function(event) {
touch = true;
touchPosition = physics.vec2(event.point.x, event.point.y);
});
scene.pointMoveCapture.add(function(event) {
var delta = physics.vec2(event.prevDelta.x, event.prevDelta.y);
touchPosition.Add(delta);
});
scene.pointUpCapture.add(function() {
touch = false;
});
/** フレームカウント(銃弾の発射間隔に使用) */
var frameCount = 0;
scene.update.add(function() {
// 画面をタッチしている間、銃弾を発射
if (touch) {
// 画面左側をタッチしている場合はグループAの銃弾、右側はグループBの銃弾
if (touchPosition.x < gameCenter.x) {
// 1 / 3 Fで銃弾発射
if (2 <= frameCount++) {
frameCount = 0;
shootA(scene, touchPosition);
}
} else {
// 1 / 6 Fで銃弾発射
if (5 <= frameCount++) {
frameCount = 0;
shootB(scene, touchPosition);
}
}
}
// 衝突した銃弾を処理する
while (0 < contactDataList.length) {
var data = contactDataList.pop();
for (var i = 0; i < bulletList.length; ++i) {
var bullet = bulletList[i];
var bulletData = bullet.b2body.GetUserData();
// UserDataから衝突した銃弾を特定する
if (data.a.id === bullet.entity.id || data.b.id === bullet.entity.id) {
var position = bullet.b2body.GetPosition().Copy();
position.Multiply(worldProperty.scale);
// グループによってダメージ表示の色を変更
var filter = bullet.b2body.GetFixtureList().GetFilterData();
var color = filter.groupIndex === GROUP_A ? "crimson" : "teal";
scene.append(createDamage(scene, position, bulletData.damage, color));
removeBullet(bullet);
--i; // 消した分インデックスを詰める
}
}
}
// 物理エンジンの世界をすすめる
// ※ step関数の引数は秒数なので、1フレーム分の時間(1.0 / g.game.fps)を指定する
physics.step(1.0 / g.game.fps);
});
});
g.game.pushScene(scene);
}
/**
* グループAの銃弾を発射します
* @param {g.Scene} scene 描画を行うシーン
* @param {b2Vec2} position 発射座標
*/
function shootA(scene, position) {
var bullet = createBullet(scene, bulletParameterA);
bullet.b2body.SetPosition(position);
/** 発射制度 */
var accuracy = 10;
/** 発射角度(右から -10° ~ 10°) */
var angle = g.game.random.generate() * accuracy * 2 - accuracy;
/** 発射の瞬間の力 */
var impulse = new b2Vec2(0.1, 0);
impulse.MulM(b2Mat22.FromAngle((angle / 180) * Math.PI));
// 発射
bullet.b2body.ApplyImpulse(impulse, bullet.b2body.GetPosition());
}
/**
* グループBの銃弾を発射します
* @param {g.Scene} scene 描画を行うシーン
* @param {b2Vec2} position 発射座標
*/
function shootB(scene, position) {
var bullet = createBullet(scene, bulletParameterB);
bullet.b2body.SetPosition(position);
/** 発射制度 */
var accuracy = 3;
/** 発射角度(右から -3° ~ 3°) */
var angle = g.game.random.generate() * accuracy * 2 - accuracy;
/** 発射の瞬間の力 */
var impulse = new b2Vec2(-0.2, 0);
impulse.MulM(b2Mat22.FromAngle((angle / 180) * Math.PI));
// 発射
bullet.b2body.ApplyImpulse(impulse, bullet.b2body.GetPosition());
}
/**
* 衝突判定を持つ矩形を生成する
* @param {g.Scene} scene 描画を行うシーン
* @param {Object} parameter 矩形の生成パラメータ
*/
function createRect(scene, parameter) {
// 表示用の矩形(1m × 1m)を生成
var rect = new g.FilledRect({
scene: scene,
width: parameter.appear.width,
height: parameter.appear.height,
cssColor: parameter.appear.cssColor
});
scene.append(rect);
// 表示用の矩形と衝突判定を結び付けて返す
return physics.createBody(rect, parameter.physics.body, parameter.physics.fixture);
}
/**
* 衝突判定を持った銃弾を生成する
* @param {g.Scene} scene 描画を行うシーン
* @param {Object} parameter 銃弾の生成パラメータ
*/
function createBullet(scene, parameter) {
// 表示用の画像を生成
// ※ AkashicEngineでは円を描画することができないので、画像で表現する
var entity = new g.Sprite({
scene: scene,
src: scene.assets[parameter.appear.assetId],
srcWidth: 100,
srcHeight: 100,
width: parameter.appear.width,
height: parameter.appear.height
});
scene.append(entity);
// 表示用の円形と衝突判定を結び付けて生成
var bullet = physics.createBody(entity, parameter.physics.body, parameter.physics.fixture);
/** ダメージの揺らぎ(-5.0 ~ 5.0) */
var rand = g.game.random.generate() * 10.0 - 5.0;
// ユーザーデータにダメージを付与する
bullet.b2body.SetUserData({
id: entity.id,
damage: 20.0 + rand
});
// 3 秒後には何があろうと削除
scene.setTimeout(removeBullet.bind(this, bullet), 3000);
bulletList.push(bullet);
return bullet;
}
/**
* 銃弾を削除する
* @param {EBody} bullet 削除する銃弾
*/
function removeBullet(bullet) {
if (bullet.entity.destroyed()) {
return; // 二重で削除しない
}
bulletList.splice(bulletList.indexOf(bullet), 1);
physics.removeBody(bullet);
bullet.entity.destroy();
}
/**
* ダメージ表示を生成する
* @param {g.Scene} scene 描画を行うシーン
* @param {b2Vec2} position 表示座標
* @param {number} damage ダメージ量
* @param {string} color 描画色
*/
function createDamage(scene, position, damage, color) {
var label = new g.Label({
scene: scene,
font: DAMAGE_FONT,
text: damage.toFixed(1).toString(),
fontSize: damage,
textColor: color,
x: position.x,
y: position.y
});
label.update.add(function() {
// 徐々に透過、完全に透明になったら削除
label.opacity -= 0.05;
if (label.opacity <= 0.0) {
label.destroy();
}
});
return label;
}
/**
* オブジェクトの中心座標を計算する
* @param {Object} obj 中心座標を計算するオブジェクト
*/
function calcCenter(obj) {
return physics.vec2(obj.width / 2, obj.height / 2);
}
module.exports = main;
© DWANGO Co., Ltd.