MagicaVoxelの使い方(2) - ブラシの基本編1

ボクセルを置く(Attach)

空間にボクセルを置く操作。

ショートカットキーはTが割り当てられている。

エディットモードが「V」の時、カーソルのある位置に1つのボクセルを生成する。

エディットモードが「F」の時、カーソルのある平面全体にボクセルを生成する。

エディットモードが「B」の時、カーソルをドラッグして形成されるボックス内にボクセルを生成する。

ボクセルを消す(Erase)

ボクセルを消す操作。

ショートカットキーはR

エディットモードが「V」の時、カーソルのある位置に1つのボクセルを削除する。

エディットモードが「F」の時、カーソルのある平面のボクセルを削除する。

エディットモードが「B」の時、カーソルをドラッグして形成されるボックス内のボクセルを削除する。

ボクセルを塗る(Paint)

パレットなどで選択した色でボクセルを塗る操作。

着色はボクセル単位で行われる。ボクセルのある一面だけを塗るといったことは不可能

エディットモードが「V」の時、カーソルが指すボクセルを着色する。

エディットモードが「F」の時、カーソルのある平面のボクセルを着色する。

エディットモードが「B」の時、カーソルをドラッグして形成されるボックス内のボクセルを着色する。

編集中のモデルを動かす

編集中のモデルをXYZのいずれかの軸に沿って平行移動する。

移動先がフレーム外の場合、逆側から現れる(空間がループしている)。

ボクセルから色を選択(スポイト)

スポイト機能。

Alt+左クリックでもOK。

同じ色のボクセルをすべて削除

カーソルが指すボクセルと同じ色のボクセルをすべて削除する操作。

同じ色のボクセルをすべて変更

カーソルが指すボクセルと同じ色のボクセルをすべて選択中の色で着色する。

MagicaVoxelの使い方(1) - ファイルの操作編

ウワサのボクセルアート作成ツール「MagicaVoxel」 ってなんだ!?

最近ボクセルがキてる。ような気がする。たぶん。

というわけで無料で使えるお手軽サクサクボクセルアート作成ツール「MagicaVoxel」について紹介する。

以前の記事でも簡単に触れたが、@ephtracyさんという方が開発されている。

配布サイトはコチラ

簡単になにが出来るのかまとめると、

  • 小さなキューブ(立方体)を組み合わせて3Dモデルを作ることが出来る
  • 編集可能なボクセルデータは最大で126x126x126の大きさ
  • 256色のパレットを使用可能
  • 作ったボクセルアートを美麗にレンダリングすることが出来る
  • レンダリング後はPNG画像として出力可能(背景の透過も可)
  • 独自形式(voxファイル)で保存
  • objなどの一般的なフォーマットで3Dモデルをエクスポート出来る
  • 動作が軽い
  • 無料
  • WindowsでもMacでも使える

こんな感じ。

絵心のない俺のような人間でもそれなりのものを作ることができるので、ゲーム素材等を作るのに重宝している。

ただし、けっこう独特のUIを持っているため、最初はとっつきにくいかもしれない。

そんなわけで、これから何回かに分けてMagicaVoxelの使い方を説明したい。

マウス操作編

カメラ移動

右ドラッグ

カメラ注視点移動

スペースを押しながら右ドラッグ

ファイルの操作編

読み込み

MagicaVoxelをインストールすると、はじめからいくつかのサンプルデータが入ってくる。

右側のModelタブ内に表示されているのが、サンプルやユーザーが作ったデータだ。

データを読み込む方法は簡単で、Modelタブ内のデータ名をクリックするだけでいい。

この時、たとえ作成中のデータがあったとしても保存の確認などは一切されないので気をつけよう!

複製

読み込んだデータを複製して編集を始めたい時は、左上のcopyボタンを押す。

中央上にあるファイル名のボックスが空欄になるので、そこに新しいファイル名を入力する。

新規作成

データを新規に作りたい時はnewボタンを押す。デフォルトの状態である立方体が画面に現れる。

この時も、保存の確認などは一切されない! 気をつけよう。

ファイル名を入力することで新たなファイルが作られる。

保存

編集中のデータを保存(上書き保存)したい時はsaveボタンを押す。

削除

不要なファイルを削除する時は左下のdeleteボタンを押す。

そろそろわかってきたことかと思うが、削除の確認なども当然されない! 十分気をつけてほしい。

データはどこに保存されているの?

最後に、MagicaVoxelのファイルはどこに保存されているかについて。

MagicaVoxelのデータファイルはすべてインストールフォルダの中のvoxフォルダに保存される。

他所から持ってきたファイルを読み込みたい場合などは、ここに置くことでMagicaVoxelで開くことができるようになる。




phina.jsでスマホSTGのタッチ操作を実装しよう

タッチ操作でSTG!!

シューターたる者、いつでもどこでもSTGをプレイしたいものだ。

読者諸君もいつも持ち歩いている携帯電話に、「怒首領蜂大復活」や「ゴシックは魔法乙女」、「エスプガルーダII ~覚聖せよ。生まれし第三の輝石~ ARCADE VERSION」などをインストールしていることだろう。

さて、スマートフォンにおけるSTGの操作方法にはいくつかのパターンがあるようだ。

  1. 端末を傾けた方向に自機が移動する
  2. タッチした場所と自機の位置が完全に同期する
  3. バーチャルキーパッドで自機を操作する
  4. タッチした場所に向かって自機が移動する
  5. スワイプした方向に自機が移動する

個人的に「これはねーだろ」と思った順に並べてみた。

「1.端末を傾けた方向に自機が移動する」「3.バーチャルキーパッドで自機を操作する」なんかはiPhoneが流行り始めた時期にはよく見かけた気がする。当然だがすごく操作しづらい。

「2.タッチした場所と自機の位置が完全に同期する」は初心者が作ったSTGでよく採用されている気がする。指で完全に自機が隠れてしまい、弾避けどころではない。

「4.タッチした場所に向かって自機が移動する」はタブレット端末の場合は意外と良い場合もあったりするが、片手で操作したい場合はやはりつらい。

というわけで「5.スワイプした方向に自機が移動する」である。

画面のどこでも良いから指をすべらせると、指の移動方向に合わせて自機が移動する方法だ。これが究極だと思う。

phina.jsやtmlib.jsで実装しよう!!

さて、こいつをphina.js(またはtmlib.js)で実装してみる。これは驚くほどカンタンだ。

まずは適当なオブジェクトを表示しよう。

1
2
3
4
5
6
7
phina.main(function() {
var app = phina.display.CanvasApp({ query: "#app" });
app.run();
// 自機
var fighter = phina.display.TriangleShape().setPosition(320, 480).addChildTo(app.currentScene);
});

tmlib.jsの場合はこう。

1
2
3
4
5
6
7
tm.main(function() {
var app = tm.display.CanvasApp("#app").resize(640, 960).fitWindow();
app.run();
// 自機
var fighter = tm.display.TriangleShape().setPosition(320, 480).addChildTo(app.currentScene);
});

で、自機にenterframeイベントのリスナーを登録する。

リスナーの内容はこんな感じ。

phina.jsの場合

1
2
3
4
5
6
7
8
9
10
11
12
13
// 感度
var sensitivity = 2.0;
fighter.onenterframe = function(e) {
// ポインター(マウスやタッチ)
var pointer = e.app.pointer;
// 移動量×感度を自機の位置に足す
if (pointer.getPointing())
this.position.add(pointer.deltaPosition.mul(sensitivity));
};

tmlib.jsの場合

1
2
3
4
5
6
7
8
9
10
var sensitivity = 2.0;
fighter.onenterframe = function(e) {
var pointing = e.app.pointing;
if (pointing.getPointing())
this.position.add(pointing.deltaPosition.mul(sensitivity));
};

phina.jsではオブジェクトの位置を2次元ベクトルとして保持している。

くわえて、そのフレームでのポインターの移動量を同じく2次元ベクトルとして取得することが出来る。

なので処理としては単純に加算するだけでOKなのだ。

また、例では感度(sensitivity)として2.0という値を設定している。

指の移動量をそのまま自機の移動量としてしまってもいいのだが、それだとたとえば自機を画面の左端から右端へ移動させたい場合に不都合が起こる。(高い確率で指が端末の端まで到達してしまって、一度離してタッチしなおさなければならない!!)

また、指というのは案外精密に動く。指の動作に対し自機が多少大袈裟に動いたとしても問題ない場合が多いのだ。

そんなわけで

みんなもスマホブラウザ向けSTGを作ろうぜ!




phina.jsでゲームパッドを使う

ゲームパッドを使おう!

phina.jsにはGamepad APIに対応した機能が備わっているので、作ったブラウザゲームをゲームパッドで遊ぶことが出来る。

使い方はわりと簡単。

  1. phina.input.GamepadManagerオブジェクトを毎フレームでupdate
  2. phina.input.GamepadManagerオブジェクトからGamepadオブジェクトを取得
  3. 入力情報を取得してゲームに反映させる

0.準備

とりあえず四角形を表示する。今回のサンプルではこの四角をゲームパッドで動かしてみよう。

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
</head>
<body>
<canvas id="app"></canvas>
<script src="main.js"></script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
phina.main(function() {
var app = phina.display.CanvasApp({
query: "#app"
});
var scene = app.currentScene;
// こいつを動かすぜ!
var rectangle = phina.display.RectangleShape();
rectangle.addChildTo(scene);
app.run();
});

1.phina.input.GamepadManagerを毎フレームでupdate

phina.input.GamepadManagerオブジェクトを作成し、scene.update()など、毎フレームで実行される場所で update() を呼び出す。

1
2
3
4
5
6
7
// GamepadManager作成
app.gamepadManager = phina.input.GamepadManager();
// gamepadManagerを更新
scene.update = function(app) {
app.gamepadManager.update();
};

2. gamepadManagerからGamepadオブジェクトを取得

gamepadManager.get() メソッドがGamepadオブジェクトを返す。

ゲームパッドはコンピュータに複数接続されている可能性があるので、インデックスを指定したい場合は get(1) のように指定する。

1
2
3
4
5
rectangle.update = function(app) {
var gamepad = app.gamepadManager.get();
};

3.入力情報を取得してゲームに反映させる

取得したGamepadオブジェクトからは、入力情報がphina.input.Keyboardと同じように取り出すことが出来る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var gamepad = app.gamepadManager.get();
// Aボタンが押されているか
var aPressed = gamepad.getKey("a");
// このフレームでBボタンが押された
var bDown = gamepad.getKeyDown("b");
// このフレームでXボタンが離された
var xUp = gamepad.getKeyDown("x");
// 十字キーの入力状態をベクトルで
var crossButtonVector = gamepad.getKeyDirection();
// アナログスティックの入力状態をベクトルで
var stickVector = gamepad.getStickDirection();

今回はゲームオブジェクトをアナログスティックで操作することにしよう。

アナログスティックはかなりデリケートなので、「これ以上の入力があれば有効とする」という閾値を決めておく必要がある。

これはphina.geom.Vector2のlengthメソッドを使えば簡単に実現することが出来る。

1
2
3
4
5
6
7
8
9
// アナログスティックの入力状態をベクトルで
var stickVector = gamepad.getStickDirection();
// アナログスティックの倒し具合が0.5以上の場合のみ入力を有効とする
if (stickVector.length() > 0.5) {
// 四角を移動させる
rectangle.position.add(stickVector.mul(10));
}

まとめ

まあこんな感じで、phina.jsではゲームパッドが簡単に使えるわけだ。

PCで使えるGamepad APIに対応したゲームパッドとしては、Xbox360コントローラーがオススメである。

単品でも購入できるが、Xbox360を買えばもれなく付いてくるので持ってない人は是非買いましょう。

CAVEの面白いシューティングゲームがたくさん遊べるぞ!




phina.jsでSpriteの輪郭を強調

Gyazo

自機や敵やアイテムが視認しづらいめう!

PhinaShooterでは全体をドット調で表現していこうと思っている。
で、そのためにCanvasを480x320という小さなサイズで描いた上で拡大するという方法をとっている。

ただ、当然画面が粗くなって各スプライトの視認性が悪くなってしまった。

そこで上の動画のようにスプライトの輪郭を点滅させて強調表示することにした。

輪郭を強調するめう!

方法としては原始的で、

  1. 画像アセットのロード後に輪郭のみ抽出した画像を作成する
  2. AssetManagerに作成した画像を登録する
  3. スプライトに輪郭のみの画像を重ねて表示する

こんな感じでやってみた。

まず、輪郭抽出の方法だが、元画像からピクセルデータを取り出し、

  • アルファ値が0より大きく
  • 上下左右にアルファ値が0のピクセルがある

…という条件を満たすピクセルだけを白く塗りつぶした新しい画像を作成する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var texture = phina.asset.AssetManager.get("image", textureName);
var w = texture.domElement.width;
var h = texture.domElement.height;
// 元画像
var src = phina.graphics.Canvas().setSize(w, h);
src.context.drawImage(texture.domElement, 0, 0);
// 元画像からピクセルデータを取り出す
var srcData = src.context.getImageData(0, 0, w, h);
// 輪郭のみの画像
var dst = phina.graphics.Canvas().setSize(w, h);
dst.fillStyle = "white";
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var cIndex = ((y + 0) * w + (x + 0)) * 4 + 3;
var a = srcData.data[cIndex]; // 対象ピクセルのアルファ値
var lIndex = ((y + 0) * w + (x - 1)) * 4 + 3;
var rIndex = ((y + 0) * w + (x + 1)) * 4 + 3;
var tIndex = ((y - 1) * w + (x + 0)) * 4 + 3;
var bIndex = ((y + 1) * w + (x + 0)) * 4 + 3;
var l = (0 <= lIndex && lIndex < srcData.data.length) ? srcData.data[lIndex] : 255; // 左隣のピクセルのアルファ値
var r = (0 <= rIndex && rIndex < srcData.data.length) ? srcData.data[rIndex] : 255; // 右
var t = (0 <= tIndex && tIndex < srcData.data.length) ? srcData.data[tIndex] : 255; // 上
var b = (0 <= bIndex && bIndex < srcData.data.length) ? srcData.data[bIndex] : 255; // 下
// 対象ピクセルが不透明で、上下左右に透明なピクセルがある場合
if (a > 0 && (l == 0 || r == 0 || t == 0 || b == 0)) {
dst.fillRect(x, y, 1, 1); // 1x1の四角を描画
}
}
}

こうして作った画像をAssetManagerに登録する。

1
phina.asset.AssetManager.set("image", textureName + "Outline", dst);

アセット名はもとの名前に後ろに”Outline”をつけてみた。

で、こいつを利用するスプライトだが、通常のSpriteクラスを拡張して作る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
phina.define("ps.OutlinedSprite", {
superClass: "phina.display.Sprite",
init: function(texture, width, height) {
this.superInit(texture, width, height);
var self = this;
// 輪郭スプライト
this.outline = phina.display.Sprite(texture + "Outline", width, height).addChildTo(this);
this.outline.update = function(app) {
// frameIndexは親と同期させる
this.frameIndex = self.frameIndex;
this.alpha = ps.OutlinedSprite.staticAlpha;
};
},
_static: {
staticAlpha: 1.0
}
});

輪郭スプライトを元スプライトの子要素として追加しておく。

さらにupdateメソッド内でframeIndexが親スプライトと同期するようにする。

また、輪郭スプライトのアルファ値は外部から設定できるようにしておき、MainSceneなどのupdateで更新する。

オブジェクトの種類に応じて色を変えるめう!

これで視認性が良くなったと思うので、さらに自機は青アイテムは緑敵は赤といった具合にオブジェクトの種類に応じて輪郭色を変えてみた。

これで粗い画面でも最低限の識別ができるようになったと思う。




PhinaShooter近況

とりあえず現状できてること

  • bulletml.jsによる敵弾発射
  • 自機のコントロール(キーボード、タッチ、ゲームパッド)
  • ゲーム画面の各種HUD表示
  • 背景
  • 全体的なシーンフロー

背景はgl-matrix.jsを使って3D表示してみた。WebGLは使わず普通のCanvasAPIで描いてる。

今回は全体的にドット調で行こうと思っているので、フォントもレトロゲームっぽいものを選択した。




Phina Shooter製作開始

rougeを作る手がどうにも止まってしまって進まないので、某所からの圧力に負けて違うのを作り始めた。

タイトルはPhina Shooterになる予定。

https://github.com/daishihmr/phina-shooter

今回はphina.jsをまっとうに使って普通に実装し、読みやすいソースにしていきたい。ちゃんとphina.js利用者が参考にできるようにね。

とりあえずゲームのフローや得点システムあたりを考え中。




MagicaVoxelで作ったデータをThree.jsで表示する

時代はボクセル!

ゲーム「Crossy Road」とか映画「ピクセル」とかあるしね。

MagicaVoxelってなんだ

MagicaVoxel

@ephtracy さんが開発された、ボクセルアート作成ツールです。

シンプルな操作系とサクサクした動作の軽さが魅力!

vox.js

というわけで、MagicaVoxelで作ったデータをJSで読み込むライブラリを作りました。

vox.js

使い方は簡単。

1
<script src="https://cdn.rawgit.com/daishihmr/vox.js/1.0.1/build/vox.min.js"></script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// パーサーを作ります
var parser = new vox.Parser();
// *.voxファイルを読み込みます
parser.parse("hoge.vox").then(function(voxelData) { // ←ボクセルデータが取れます
// データ全体の大きさ
voxelData.size; // => { x: number, y: number, z: number }
// ボクセルの配列
voxelData.voxels; // => [Voxel, Voxel, Voxel, ...]
// ボクセル一個のデータ
voxelData.voxels[0]; // => { x: number, y: number, z: number, colorIndex: number }
// カラーパレット
voxelData.palette; // => [Color, Color, Color, ...]
voxelData.palette[0]; // => { r: number, g: number, b: number, a: number }
});

Three.jsで表示

読み込んだボクセルデータをTHREE.Meshに変換する機能も持っています。

1
2
3
4
5
6
7
// ビルダーを作ります。引数にボクセルデータをわたします
var builder = new vox.MeshBuilder(voxelData);
// THREE.Meshを作ります
var mesh = builder.createMesh();
// THREE.Sceneに追加するなどして使ってください
scene.add(mesh);

できたー!

まとめ

MagicaVoxelは本当に簡単に使えるツールです。
絵心というものとは無縁の人生を送ってきた僕でもこれくらいの物はチャチャッと作ることが出来ました。
みなさんも是非やってみてください。




新作STG 現在の進捗

今作ってるゲームについて。

コードネームは rouge 。自作STGとしては7作目となる。

やること(やりたいこと)。

  • 内容は普通の弾幕STG
  • WebGLを使って弾をたくさん出す
  • 2Dスプライトではなく3Dモデルでゲーム画面を作る
  • three.js等のWebGLライブラリは使わない
  • phina.jsを使う。同時にphina.jsに機能追加の貢献をする
  • 弾銃フィーバロン方式。小型機編隊はすべて早回しを起こす
  • ランクを維持する限り敵弾の速度が上昇し続ける

目下の課題。

  • タイトルが未定
  • ビジュアルイメージが決まらない。キャラが地上を走る? 戦闘機が空を飛ぶ? ボクセル調? 普通にモデリング?
  • 配布形式が未定。当初はElectronでデスクトップアプリ化するつもりだった。が、ElectronがGamepad APIに対応してないように見えるため迷走中。

現状出来てること。

  • オリジナルのエンジンでメッシュの表示
  • 3Dオブジェクトの親子関係
  • 弾幕を1回のドローコールで描画
  • 弾と自機の当たり判定をピクセルデータで行う
  • パーティクルシステム
  • MagicaVoxelで作ったobjデータを読み込む
  • ゲームパッドでの操作
  • 3Dモデルの奥や手前に2Dスプライト表示
  • 自機移動平面上にない砲口から敵弾が発射される場合でも自機移動平面上を移動していく。(かつ、きちんと砲口から出ているように見える)
  • 任意の回転軸を持つ砲塔が自機を追う




DOGAを使うことにするかも

DOGA-L3で作った多関節物体をポーズデータ込みでgl.enchant.jsのSprite3Dに変換するツールを以前作ったのだが、そいつを今作ってるゲームに使おうと思い始めた。
ツールが吐き出すJSONはそのままに、jsでメッシュ化するだけで出来るはず。