ニコ生ゲームを作ろうと思ったときの目次

この記事はニコ生ゲーム開発について作成した一連の記事をまとめたリンク集です。
どこに何があるかわからなくなるので、リンクと各記事の目次を並べていきます。

記事外へのリンク

唐突ですが、先にこの記事とは関係ない情報のリンクを掲載しておきます。

 □ 公式の基本的な情報はこちら。
    https://akashic-games.github.io/tutorial/v3/
    https://akashic-games.github.io/shin-ichiba/
    https://akashic-games.github.io/reverse-reference/v3/

 □ ニコ生ゲーム関係のリンク集を記事にしました。
    https://nicorakku.hatenablog.com/entry/2022/06/16/064706


ここから本編。

 

ニコ生ゲームを作ろうと思ったらすぐ作ろう その1

開発準備 & サンプルゲーム起動

【開発準備】

【サンプルゲーム起動】

  • 保存場所
  • ダウンロード akashic init
  • 起動テスト akashic-sandbox


ニコ生ゲームを作ろうと思ったらすぐ作ろう その2

サンプルコードの数値を変更する

【改造の準備】

【改造内容】

  • キャラクターの動きを変える (数字を書き換える)
  • 残弾数を決める (数値を記録する)
  • 残弾ゼロで打てなくする (条件をつける)

 


ニコ生ゲームを作ろうと思ったらすぐ作ろう その3

サンプルコードに表示を追加する

【改造内容】

  • 残弾表示を作る (画像を追加する)
  • 残弾表示を更新する (常に処理する)
  • 残弾を補充する (条件の例外について)
  • タイルを作る (四角を追加する)new g.FilledRect
  • タイルを並べる (四角を並べる)

 


ニコ生ゲームを作ろうと思ったらすぐ作ろう その4

サンプルコードにゲーム要素を追加する

【改造内容】

  • 各行ごとの積み数を作る (配列を作ってみる)
  • 積み数に応じてタイルを増やす (配列を参照する)
  • 射撃に応じて積み数を操作する
  • 積み数の一致をチェックする (配列を操作する)
  • 積み数をリセットしてランダムに積む g.game.random.get()
  • 積み数を得点に反映させる
  • 積み数の超過をチェックする


ニコ生ゲームを作ろうと思ったらすぐ作ろう その5

サンプルコードの見た目を変えて別のゲームにする

【改造内容】

  • 画像を用意する
  • 画像を差し替える akashic scan asset
  • 画像を追加する
  • チュートリアルと終了の画像を表示する
  • 制限時間を設定する param.sessionParameter.totalTimeLimit


ニコ生ゲームを作ろうと思ったらすぐ作ろう その6

ニコ生ゲームとしてリリースする

【改造内容】

  • フォントを追加する new g.BitmapFont
  • 音を用意する scene.assets[].play();
  • 音を追加する

【ゲーム公開】

  • zipにする akashic export html
  • アイコンを用意する
  • アツマールにアップロードする
  • 生放送に登録する

 

ニコ生ゲームを作ろうと思ったらすぐコピペしよう その1

クリックして得点を集めるゲームを作る

  • コメント表記
  • 画像入れ替え
  • ランダム出題 g.game.random.generate()
  • レイヤー生成
  • フォント作成
  • 時間管理 scene.setTimeout(function() {})
  • 背景
  • ランダム配置
  • var から let へ
  • マルチタップ対策(同時押し排除)
  • ステージ進行
  • 画面内で反射する画像
  • 重なったときのクリック判定 touchable
  • 定期的な得点ボーナス g.game.age  g.game.fps
  • 画像重ねの表示順整理 children.sort
  • ランキング登録 window.RPGAtsumaru.experimental.scoreboards.setRecord
  • ランキングボタンとリスタートボタン g.game.replaceScene()


ニコ生ゲームを作ろうと思ったらすぐコピペしよう その2

キャラクターが移動するゲームを作る

  • タッチ操作 touch.onPointDown.add touch.onPointMove.add touch.onPointUp.add
  • フレームアニメーション g.FrameSprite
  • プレイヤーと敵キャラの状態
  • プレイヤーの見た目変化
  • ヒット判定


ニコ生ゲームを作ろうと思ったらすぐコピペしよう その3

タイミングよくクリックするゲームを作る

  • 音量の調整 scene.assets[ ].play().changeVolume()
  • FPSの変更
  • 状態の切り替わり
  • タイミングの計測
  • 背景を動かす移動表現
  • バーの表示 アンカーの指定 anchorY
  • 画像の切り抜き g.Pane
  • リスタート時のウォームアップ時間


ニコ生ゲームを作ろうと思ったらすぐコピペしよう その4

4択クイズゲームを作る

  • 共通ランダム化 param.random
  • ダイナミックフォント g.DynamicFont
  • 縁取りビットマップフォント作成


ニコ生ゲームを作ろうと思ったらすぐコピペしよう その5

間違い探しゲームを作る

  • レイヤーの切り替えで出題する g.E


ニコ生ゲームを作ろうと思ったらすぐコピペしよう その6

シンプルな動きのゲーム集

  • 改造準備のおさらい
  • 画像ファイルの変更(おさらい)
  • 音声ファイルの変更(おさらい)
  • 制限時間の変更(おさらい)
  • ゲームの公開(おさらい)


ニコ生ゲームを作ろうと思ったらすぐコピペしよう その7

音ゲー

 

 

ニコ生ゲームを作ろうと思ったらやっぱりマルチ! その1

育成ゲームまたは育成ツールを作る

  • マルチゲーム作成に必要なこと supportedModes
  • マルチゲームの動作確認 akashic serve -s nicolive
  • ローカルとグローバル
  • ローカルエンティティとローカルイベント local
  • ローカル処理の注意事項
  • 操作の全体への共有 raiseEventscene.message
  • 放送者だけ特別な操作を可能にする g.game.onJoin
  • スキップ中の処理 g.game.isSkipping
  • もっとシンプルにグローバルだけで処理する

 

ニコ生ゲームを作ろうと思ったらやっぱりマルチ! その2

全員のキャラクターがスコアを競うゲームを作る

  • 拡張機能の利用
  • 名前取得の拡張機能
  • 放送者専用のボタンを用意する
  • 監視処理を中止する
  • 参加方式のバリエーション
  • いつでも参加できるようにする
  • 参加者ごとのキャラクターの作成する
  • キャラクターにパーツを追加する
  • キャラクターに名前をつける
  • 参加者のリストを作る
  • アイテムとの接触
  • キャラクター同士の接触
  • リスタート処理
  • グローバルとローカルの確認
  • 状態遷移時のraiseEventの確認


ニコ生ゲームを作ろうと思ったらやっぱりマルチ! その3

  • ジャンプ
  • 当たり判定 g.Collision
  • レーザーの当たり判定と表示
  • 分割画像の外周映り込み
  • 効果音の重複再生防止
  • 生ゲームプレイ中ページへの掲出 multi_admission
  • 開発者ツールによるエラー箇所の特定
  • アツマールマルチ対応 envinronment atsumaru 

 

ニコ生ゲームを作ろうと思ったらやっぱりマルチ! その4

  • 前々回の訂正 resolvePlayerInfo
  • 匿名希望かどうかを参照する info.userData.accepted
  • 非表示にする hide()
  • カメラで表示する場所を変える camera
  • 観戦するプレイヤーを切り替える
  • 画面外を非表示にする
  • ゲーム展開がズレるエラーについて

 

ニコ生ゲームを作ろうと思ったら物理的に考える その1

  • 物理演算のメリット
  • akashic-box2dの情報
  • akashic-box2dのインストール
  • akashic-box2dの利用方法
  • 世界を創造する  b2.Box2D box2d.step
  • 物体を出す box2d.createBody
  • 物体の位置を指定する body.b2Body.SetPosition
  • 固定された箱を作る
  • 自由に動く物体を出す b2.BodyType.Dynamic
  • 物体の情報を取る .entity .b2Body
  • 物体を消去する box2d.removeBody
  • 世界をリセットする
  • 時を止める
  • 立体的な文字
  • 直感的な色指定方法

 

 

ニコ生ゲームを作ろうと思ったら物理的に考える その2

  • 重力の設定 gravity
  • 物体の操作 速度指定 b2Body.SetLinearVelocity
  • スリープ状態 b2Body.SetAwake(true)
  • フリック操作の速度
  • 速度の減衰 damping  linearDamping angularDamping
  • 反発係数 restitution
  • 物体生成位置の重なり
  • 物体のリストアップ box2d.bodies
  • 親エンティティの乗り換え

 

 

ニコ生ゲームを作ろうと思ったら物理的に考える その3

  • 円を作る box2d.createCircleShape()
  • 多角形を作る box2d.createPolygonShape()
  • 画像を物体に貼る
  • 正多角形を作る
  • 静止を待つ b2Body.IsAwake()
  • 物理演算の予測
  • 接触イベント b2.Box2DWeb.Dynamics.b2ContactListener box2d.isContact
  • 複数の物体との接触イベント
  • 接触
  • 絵文字

 

 

ニコ生ゲームを作ろうと思ったら物理的に考える その4

  • 物体を運動させる b2.BodyType.Kinematic
  • 衝突の有無を場合分けする filter
  • 衝突の場合分けを変更する  body.b2Body.CreateFixture()
  • 衝突処理の重さ

 

 

ニコ生ゲームを作ろうと思ったら物理的に考える その5

  • 物体に力を加える
  • 物体に衝撃を加える
  • 接触イベントで物体の情報を利用する .GetFixtureA().GetBody()
  • b2BodyからEbodyとentityを特定する .getEBodyFromb2Body()
  • 接触した物体を操作する
  • 物体をつかんで操作する
  • ジョイントの種類
  • マウスジョイントを作る
  • ローカル座標のグローバル座標を取得する localToGlobal
  • マウスジョイントの目的地を変える
  • マウスジョイントを解除する
  • 物体の角度を固定する body.b2Body.SetFixedRotation(true)
  • 物体をすり抜けさせない bullet

 

ニコ生ゲームを作ろうと思ったら物理的に考える その6 

  • 回転ジョイントの基本 .Joints.b2RevoluteJointDef()
  • 背景にピン止めする
  • ジョイントを回転させる
  • 回転の可動範囲を設定する
  • 特定の角度に制御する
  • 複数の関節を組み合わせる
  • SetFixedRotationとの組み合わせ
  • ピストンジョイントの基本 .Joints.b2PrismaticJointDef()
  • ピストンを動かす
  • ピストンの可動範囲を設定する
  • ピストンの現在位置を読み取る
  • バネの動きをさせる
  • ウェルドジョイントの基本 .Joints.b2WeldJointDef()

 

 

 


 

記事アイコン用画像




ニコ生ゲームを作ろうと思ったらすぐ作ろう その6

第6回はフォントと音を用意して公開までいきます。

■目次

【改造内容】

  • フォントを追加する
  • 音を用意する
  • 音を追加する

【ゲーム公開】

  • zipにする
  • アイコンを用意する
  • アツマールにアップロードする
  • 生放送に登録する

 



【改造内容】フォントを追加する

スコア表示と時間表示の文字が弱々しいので変えていきます。

[2021/2/19加筆]
以下の記事は、ビットマップフォントを使う前提で記載しています。
ダイナミックフォントでもくっきりと力強く表示できることがわかりましたので、
検討している方はこちらダイナミックフォントの項目を確認してください。


フォントファイル等は、好きなフォントから作ることができるのですが、作り方がややこしいので今回は省略します。


公式が用意しているこちらの一番下にあるもの使います。


展開すると font16_1.png と glyph_area_16.json というのがあります。

それぞれkaizouの imageフォルダ と textフォルダ に格納します。

画像を追加したときと同じように、akashic scan asset で追加します。

さらに同じように、プログラム内でも登録します。

assetIds: [
  "player",
  "shot",
  "tile",
  "tutorial",
  "finish",
  "font16_1",
  "glyph_area_16",
  "se"
]

そして、現時点のフォント関連の記載がある、 var font var scoreLabel var timeLabel にvar glyphを加えつつ以下のように変えます。


// フォントの生成
var glyph = JSON.parse(scene.assets["glyph_area_16"].data);
var font = new g.BitmapFont({
  src: scene.assets["font16_1"],
  map: glyph,
  defaultGlyphWidth: 16,
  defaultGlyphHeight: 16,
});
// スコア表示用のラベル
var scoreLabel = new g.Label({
  scene: scene,
  text: "SCORE: 0",
  font: font,
  fontSize: font.size*2.5,
});
scene.append(scoreLabel);
// 残り時間表示用ラベル
var timeLabel = new g.Label({
  scene: scene,
  text: "TIME: 0",
  font: font,
  fontSize: font.size*2.5,
  x: 0.75 * g.game.width
});
scene.append(timeLabel);
 


  


上のようにフォントが表示されたでしょうか。


【改造内容】音を用意する

音を用意していきましょう。

今回ほしい音は6種類あります。

  • 発射音 (すでにある音に置き換え)
  • 弾がタイルに変化した音
  • 積み数が一致した音
  • 積み数が超過してミスになった音
  • ゲームが終了した音
  • BGM

ゲーム開始音はタイミングを合わせるのが面倒なので省略します。


音源は自分で作ってもいいですが、無償で公開されていらっしゃる方々がいらっしゃいます。

状況によって使用可不可や、表記の要不要が異なります。私の方でコメントすることではありませんが、よく確認して使わせていただくのが良いのではないでしょうか。

開始音と終了音は「泥棒バスター」の素材があるのでそちらをお借りするのも良いでしょう。
一応ライセンス表示は必要みたいです。


音源を用意して、ゲームにのせる前に加工をします。
ogg形式のファイルとaac形式のファイルの両方が必要です。

ffmpegというのをインストールすると楽なのですが、若干難しいかもしれません。簡単にインストールできそうならおすすめしますが、そうでなければ他の方法を模索してください。
また、こちらのツールも使えるはずです。

ffmpegを使うと音量の調節も同時にできたりします。
素材サイトから頂いた状態だと、ちょっと音が大きめのことが多いように思います。


2種類の形式のファイルが用意できたら、kaizouのaudioフォルダに格納します。
そしてakashic scan assetを行い、ゲーム内に登録します。

assetIds: [
  "player",
  "shot",
  "tile",
  "tutorial",
  "finish",
  "font16_1",
  "glyph_area_16",
  "se_finish",
  "se_match",
  "se_miss",
  "se_shot",
  "se_tile",
  "bgm",
  "se"
]

"se"は消してもいいんですが、他の場所も消す必要がでてくるので残しています。

そして、ちょっと書き方が変わりますが音源の再生を以下のようにに記載します。


// 画面をタッチしたとき、SEを鳴らします
scene.onPointDownCapture.add(function () {
  if (bullet > 0 && time < 53) {
    bullet = bullet - 1;
    scene.assets["se_shot"].play();
    // プレイヤーが発射する弾を生成します
    var shot = new g.Sprite({
      scene: scene,
      src: scene.assets["shot"],
      width: scene.assets["shot"].width,
      height: scene.assets["shot"].height
    });

そしてこの、scene.assets["se_shot"].play();の中身を書き換えて各所に配置していきます。


終了時のse_finishのみタイミングらしいタイミングがないので、次のように配置します。

scene.onUpdate.add(function () {
  if (time < 0) {
    if (finish.opacity == 0) scene.assets["se_finish"].play();
    finish.opacity = 1;
  } else {
    finish.opacity = 0;
  }
  finish.modified();
});

これで、1度だけ処理されるはずです。ただ単にscene.onUpdate.add(function () {の中に入れていると、何重にも重なって再生されて非常にうるさくなります。
想定外の処理で無限ループにならないよう注意が必要です。

それ以外の配置場所は完成したコードをみて確認してください。


音の処理は1行だけでできますが、一つだけ追加の設定があります。
ゲームの長さにもよりますがBGMは、ループして再生されることが望ましいですね。

BGMをループにする設定はgame.jsonの方にあります。akashic scan assetを実行したあとであれば、各素材の情報がgame.jsonに記載されています。

BGMのファイルのみ以下のように変更します。

"bgm": {
  "type": "audio",
  "path": "audio/bgm",
  "systemId": "music",
  "duration": 66264
},
"se_finish": {
  "type": "audio",
  "path": "audio/se_finish",
  "systemId": "sound",
  "duration": 1344
},

systemIdsound となっているところを music にします。これでループになります。



 

【ゲーム公開】zipにする

ひとまずこれで完成しました。アップロードするためにzipにします。

コマンドプロンプトから、次の呪文を行います。

  akashic export html -f --output jelly.zip --atsumaru

kaizouフォルダにjelly.zipが生成されたかと思います。

【ゲーム公開】アイコンを用意する

サイズは160x160 以上 320x320 以下にしましょう。生放送向けの制限のようです。
外周に隙間があってもいいですがあまり中央に寄りすぎると寂しくなります。
下の絵ぐらいが下限だと思います。

  

gifアニメのアイコンはやめたほうがいいですね。
アツマールだとアニメーションアイコンは珍しくないのですが、生放送向けのものは運営に差し替えられてしまいます。目障りだからでしょうか。

→ [追記]  最近はアニメーションのアイコンでも残してもらえてます。

→ [再追記] いつの間にか「ニコ生ゲームではgifアニメーション画像は1フレーム目のみ表示されます」という文言が追加されていました

運営にアニメーションだと気づかれないやつは残るのかな?

 

 

[2022/12/20加筆]
以下の記事は、アツマールがサービス提供中の時点の内容です。
アツマールがサービス終了したあとの手順はこの一連の記事では説明していません。
別途、公式チュートリアルや解説記事を探してください。

 

【ゲーム公開】アツマールにアップロードする

アツマールにアクセスすると右上にアカウントのプルダウンメニューがあるはずです。


  


ここでゲーム投稿を選びます。




ゲームファイルにさきほど作ったzipファイルをドロップします。

アイコンも同じようにし、ゲームサイズなどを入れていきます。
「背景色」は透過が多いゲームだと役に立ちそうですが、実際は役に立たず黒になります。

最後の「公開設定」のところは、最初は非公開にしてテストするのが良いですね。
ただし、最終的にニコ生ゲームに登録するには、ここを公開にする必要があります。

  


アツマールで動作や表示を確認します。

アツマールは背景が真っ黒なので、テスト環境の真っ白と比べて大きく印象が変わってしまうことがあります。生放送でも真っ黒にしている方は珍しくないですね。

それ以外でも、スコアボードがうまく機能するかなど、プレイしてみましょう。
ゲームを公開状態にして、アツマールでいろいろな方にプレイいただいて、十分にバグ出しをしてから生放送に登録するのもありです。


【ゲーム公開】生放送に登録する

問題がなければアツマールでゲームを公開状態にし、あとは登録申請するだけです。

まず投稿ゲーム管理のページに行きます。




そしてゲームのパネルの右下のその他から「ニコ生ゲームに登録申請」を押します。






その後に最終確認の注意事項が出てくるのでよく確認してください。

ニコ生ゲームは一度登録してしまうと、自分で非公開にすることはできません。
お問合せフォームから運営に削除を依頼する必要があるので、よく確認しましょう。

一応ファイルをアップデートすれば、旧版をプレイできないようにはできます。




終了

これでニコ生ゲームリリースまでの流れは終了です。
簡単に説明するつもりが恐ろしく長い記事になってしまいました。

最終コードは、こちら
game.jsonは名前を変えていますが、こちら
画像ファイルは前回と同じですが、こちらです。
フォントファイルは上にリンクがあります。
音声ファイルは再配布しないので、テストで動かしたい場合は、適当なファイルに名前をつけて置いておくか、main.jsの記載を消してscan assetをやってください。

アツマールはこちら
このあと、アツマール用のリスタート機能を追加しましたが、ブロマガには反映させてません。
リスタート機能は次の記事で紹介しています。

今回作ったゲームが新しく作るゲームのテンプレになるかというと、ちょっと難しいですね。
サンプルゲームを無理やり活かしているため、これ以上ゲーム性を広げたり、素材を変えて新しいゲームとして見せるのには向かないと思います。

次回からはゲームの大枠の進行やデザインを決め、作り込みの少ないシンプルなゲームを作ろうと思います。

一方で、共通して使える機能は最初から搭載しておき、転用しやすくなることを目指します。


以上です。お疲れさまでした。

ニコ生ゲームを作ろうと思ったらすぐ作ろう その5

第5回は画像を用意していきます。

見た目が変われば結構印象も変わるので、サンプルゲームの改造だとばれないといいですね。

■目次

改造内容】

  • 画像を用意する
  • 画像を差し替える
  • 画像を追加する
  • チュートリアルと終了の画像を表示する
  • 制限時間を設定する

 


 

【改造内容】画像を用意する

それではplayershotを差し替えていきます。

画像を用意しますが、背景が透過されたPNG画像がいいですね。
サイズはタイルと同じ90x90ピクセルでいいでしょう。

画像編集は慣れてる人も多いと思うので、特に詳しくもない私からわざわざ説明することもないのですが、photoshopgimpを使ったことがない人にgoogle図形描画を紹介しておきます。普通に絵を描ける人はどうぞ好きなように描いてください。


Googleのアカウントが必要ですが、ドライブの新規作成のところからGoogle図形描画を選択することができます。

  



自動でgoogleドライブに保存してくれて、透過PNGも出力しやすいです。

新規作成したら、まずページ設定を変えましょう。


  

カスタムピクセルを選んで数字を入力します。

  


適当に図形を配置します。ホイップクリームを出すやつのゼリー版のイメージで・・・

  


描いたらダウンロードからPNGで保存します。
ダウンロード?と思わなくもないですが、クラウド保存ではなくてローカルにダウンロードする、ということなんでしょう。

  



飛んでいくゼリーも曲線を使って書きます。線を太くすると曲線の汚さをごまかせます。
ちなみに光沢部分は極限まで薄くしたアーチの線を太くしたものです。

  


実際に書いている現場がこちらです。
画像ファイルを実際に使う場合は こちら。後で使う画像もあります。

【改造内容】画像を差し替える
それではダウンロードしたファイルの名前を「player」「shot」にします。
そして「kaizou」の「image」フォルダの中に入れて、差し替えます。

それではakashic-sandboxのページを更新しましょう。


  

はい。うまく行っていませんね。
playerの画像が切れてしまっていて、shotは映りもしません。

これは、画像のサイズが変わったことが反映されていないためです。
画像のサイズを更新するためには、コマンドプロンプトで以下の呪文を唱えます。
一度 Ctrl+C でakashic-sandboxを終了してから入力しましょう。

  akashic scan asset

  


再度akashic-sandboxを起動して更新しましょう。


  


ちゃんと表示されました。
ついでに、左上の残弾表示がかぶるのでちょっと左に寄せておきます。

画像のサイズが変わったときは、akashic scan assetが必要ですが、逆にサイズが変わらない微修正であればやる必要はありません



【改造内容】画像を追加する

タイルの方も画像に変えていこうと思います。

タイルは86x86にしてしまったので、図形描画でサイズ変更してから保存します。

  


画像に「tile」の名前をつけて、imageフォルダに入れ、akashic scan assetを実行します。


var shotのところの真似をして下のようにタイルの書き方を変えます。

var tiless = ;
for (var j = 0; j < 12; j++) {
  var tiles =
;
  for (var i = 0; i < 7; i++) {
    var tile = new g.Sprite({
      scene: scene, src: scene.assets["tile"], parent: scene,
      x: g.game.width - 100 - 90 * j, y: 90 * i + 47,
      width: 86, height: 86, opacity: 1, tag: {row: i, column: j,},
    });
    tiles[i] = tile;
  }
  tiless[j] = tiles;
}

これで起動してみると、
  
このようなエラーが出るはずです。

これは新しく追加した画像がプログラム上で登録されていないためです。
登録する場所はゲームの準備のところにあります。
scene.onLoaded.add(function () {の更に上です。


var scene = new g.Scene({
  game: g.game,
  // このシーンで利用するアセットのIDを列挙し、シーンに通知します
  assetIds: ["player", "shot", "se"]
});
var time = 60; // 制限時間
if (param.sessionParameter.totalTimeLimit) {
  time = param.sessionParameter.totalTimeLimit; // セッションパラメータで制限時間が指定されたらその値を使用します
}
// 市場コンテンツのランキングモードでは、g.game.vars.gameState.score の値をスコアとして扱います
g.game.vars.gameState = { score: 0 };

scene.onLoaded.add(function () {


あとから追加しやすいように改行を入れて、"tile"を入れます。

assetIds: [
  "player",
  "shot",
  "tile",
  "se"
]


  

このように表示されるはずです。


【改造内容】チュートリアルと終了の画像を追加する
チュートリアル画像はサイズを1280x720にして、図形描画上で位置を調整してしまいます。

  

この画像を開始7秒間だけ表示して、その間は操作も制限します。
残り時間が53秒より大きければ表示して、それ以外は透明にします。

var tutorial = new g.Sprite({
  scene: scene,
  src: scene.assets["tutorial"], parent: scene,
});
scene.onUpdate.add(function () {
  if (time > 53) {
    tutorial.opacity = 1;
  } else {
    tutorial.opacity = 0;
  }
  tutorial.modified();
});

このvar tutorialは追加する場所を気をつけないと、チュートリアル画像の上にタイル画像が重なってしまいます。

プログラムは記載してある順に表示するので、チュートリアルはできる限り下の方の、scene.onPointDownCapture.add(function () {の前に追加しましょう。

ゲームへの画像の登録と、akashic scan asset ももちろんやります。


操作の制限はすでに bullet > 0 という条件が追加されているところに条件を追加します。

// 画面をタッチしたとき、SEを鳴らします
scene.onPointDownCapture.add(function () {

if (bullet > 0 && time < 53) {
bullet = bullet - 1;


&&は条件の追加で、「bullet > 0」かつ「time < 53」という意味になります。


終了も同じように追加します。場所も同じでいいでしょう。
ついでにブロマガの宣伝を載せます。
終了と同時にスコアも大きく表示したいところですが、今回は省略します。

ここで黒い文字を表示させようとしていますが、背景が真っ黒の場合黒い文字は全く見えなくなってしまいます。周りを白で縁取りするなどして、見やすくするのが良いでしょう。  

残念ながらGoogle図形描画では縁取りは難しいのでGIMPや他の方法を探してください。
[22/1/27追記]Googleスライドのワードアートで縁取りできることがわかりました。
たまに変更しても見た目が更新しないときがありますが、背景を透明にして複数のワードアートを重ねれば多重縁取りもできます。
  

var finish = new g.Sprite({
  scene: scene,
  src: scene.assets["finish"], parent: scene,
});
scene.update.add(function () {
  if (time < 0) {
    finish.opacity = 1;
  } else {
    finish.opacity = 0;
  }
  finish.modified();
});



【改造内容】制限時間を設定する

さて、このへんでおかしなことが起きているかもしれません。
ゲームの準備のところでvar time = 60; // 制限時間としているのに、
akashic-sandboxは85秒から始まっているかもしれません。

これはakashix-sandboxの設定によって変わり、sandboxの右上のギアボタンを押して、「Niconico」のタブ「セッションパラメータを送る」にチェックが入っていて、「game.jsonの~」のチェックも入っていると起きます。逆にそうでないならば、60秒から始まっているはずです。

  



唐突に複雑な設定について話し始めましたが、重要なことなので説明をしていきます。
そもそも、ランキング形式のニコ生ゲームの制限時間には2種類あります。

  • ゲームをプレイできなくなるまでの時間 (60秒)
  • ゲームが終了してランキング表示が始まるまでの時間 (70秒)

終了待ちは自分のゲームの終了が表示され、ランキングが始まるのを待っている状態です。
この終了待ちの状態が用意されていないゲームは、有名ゲームでも割とよくあるのですが若干問題があります。

生放送の場合、プレイヤーによってロードにかかる時間が異なり、開始するタイミングが数秒ずれます。

 

この場合、たとえゲームの時間が残っていたとしても強制的にランキング表示に移行します。

時間が残っているのにゲームが終わってしまった経験はないでしょうか。これは競技性の面でも少し不公平になります。

また、ゲーム終了時に得点を計算するプログラムにしている場合、得点計算をせずに終了して0点になってしまうこともあります。得点は常に更新するようにしたほうがいいですね。

これまでの経験から10秒あれば一部の人を除いてほぼ、ロード時間の違いを吸収できます。
ランキング表示が始まるまでの時間を70秒に設定していきます。


main.jsのなかで制限時間はどのように設定されているかというと、60に設定したあとに、param.sessionParameter.totalTimeLimitに設定し直されています。

var time = 60; // 制限時間
if (param.sessionParameter.totalTimeLimit) {
  time = param.sessionParameter.totalTimeLimit; // セッションパラメータで制限時間が指定されたらその値を使用します
}

このparam.sessionParameter.totalTimeLimitというのが、ランキング表示が始まるまでの時間なのですが、このままだと60秒と70秒というように分けることができません。var time = 60; // 制限時間のみ残してあとは消します。


そして、param.sessionParameter.totalTimeLimitはどこで設定するのかというと、これまで編集してきたmain.jsではなく、kaizouフォルダに有るgame.jsonです。

どこかに85という数字が書いてるかというと・・・
どこにもありません。85はデフォルトの数字であって、書き換えるというやり方ではだめなので、game.jsonの一番下に次のように記載を追加してください。

"moduleMainScripts": {},

"environment": {

"sandbox-runtime": "3",
"niconico": {

"supportedModes": [

"ranking"

],

"preferredSessionParameters": {

"totalTimeLimit": 70

}

}

}

}


途中の カンマ ,  も忘れないようにしてください。
あと、このgame.jsonの方は全角のスペースはダメみたいなので気をつけてください。
game.jsonを保存してsandboxを更新してみましょう。

  

「game.jsonのpreferredSessionParameters~」の右端が70秒になっていればOKです。
その下の「時間経過後にゲームを停止」のチェックも入れておくと、実際にどのタイミングでランキング表示に移行するかわかりやすくなります。


また、制限時間がどれくらいがいいかというと、60~70秒が一般的です。短期決戦のゲームだと40~50秒もありです。

ゆったりしたゲームだと90秒ぐらいでもありですが、単調なゲームで90秒あると結構不評が出てきます。

もちろん、じっくりと攻略する要素がある場合は2分以上でも大丈夫です。ランキングモードの場合、確か180秒が最大だったと思います。





終了

はい5回はここまで。

見た目を変えると言いながら、制限時間の話に脱線しました。

最終コードはこちら
game.jsonはこちら
画像ファイルは こちら

次はフォントと音の追加をやります。

ニコ生ゲームを作ろうと思ったらすぐ作ろう その4

第4回は前回起動したサンプルゲームのゲーム性を高めていきます。

■目次

【改造の方針】

【改造内容】

  • 各行ごとの積み数を作る (配列を作ってみる)
  • 積み数に応じてタイルを増やす (配列を参照する)
  • 射撃に応じて積み数を操作する
  • 積み数の一致をチェックする (配列を操作する)
  • 積み数をリセットしてランダムに積む
  • 積み数を得点に反映させる
  • 積み数の超過をチェックする

 


 

【改造の方針】

残弾数程度ではまだまだゲームとは言えません。
具体的に物足りないところを挙げてみます。

  • 標的がない
  • タイルの意味がない
  • 爽快感がない
  • ゲームの展開が単調、というか無い

 

作品として最低限の面白さが生まれるように以下の変更をやってみます。

  • ショットによってタイルが積もるようにする
  • タイルの数が一致すると消えるようにする
  • タイルをたくさん貯めると高得点にする
  • 最後まで貯まるとミスになるようにする

 

単に敵を倒すシューティングゲームにすると、泥棒バスターと似てしまいそうなので、
横向きのテトリスクォースのようにしてみます。

 

【改造内容】 各行ごとの積み数を作る (配列を作る)

タイルは7行作りました。
それぞれの行の数字の積み数を保存し、その数だけ右からタイルを表示します。

 

bulletの数字を作ったときのように var stack = 0; をloadの中に置きます。
7列あるので7つの値が必要です。

var stack1 = 0;
 ︙
var stack7 = 0;

上のように7個名前をつくってもいいのですが、今回は配列を使います。

var stack = [4, 3, 2, 1, 2, 3, 4];

このようにカンマで区切られた複数の値を一つにまとめることができます。

利用するときは stack[0] のように番号を指定すると、
カッコの中の一番最初の数字4が呼び出されます。番号は0から始まり6までです。



【改造内容】積み数に応じてタイルを増やす (配列を参照する)

stackの値に合わせてタイルが消えたりついたりするようにします。

また新しい書き方を導入しますが・・・ちょっと説明が重いので、とりあえず下のものをコピペしてください。タイルを繰り返し配置したところに置き換えます。

var tiless = ;
for (var j = 0; j < 12; j++) {
  var tiles =
;
  for (var i = 0; i < 7; i++) {
    var tile = new g.FilledRect({
      scene: scene, cssColor: "royalblue", parent: scene,
      x: g.game.width - 100 - 90 * j, y: 90 * i + 47,
      width: 86, height: 86, opacity: 1, tag: {row: i, column: j,},
    });
    tiles[i] = tile;
  }
  tiless[j] = tiles;
}

scene.onUpdate.add(function () {
  for (var j = 0; j < 12; j++) {
    for (var i = 0; i < 7; i++) {
      if (stack[tiless[j][i].tag.row] <= tiless[j][i].tag.column) {
        tiless[j][i].opacity = 0;
      } else {
        tiless[j][i].opacity = 1;
      }
      tiless[j][i].modified();
    }
  }
});


細かい話をすると以下の方法を導入しています。

  • 常に更新する処理 scene.onUpdate.add
  • tile に tag というパラメータを追加
  • tag に row , column という2つのパラメータを収納 とその利用方法 .tag.row
  • tile を複数収納する tiles という配列
  • tiles を複数収納する tiless という配列 とその利用方法 tiless[j][i]

このままコピペで使い回せるといいのですが、その時やりたいことによると思います。

もし暗号解読が趣味の人はにらめっこして理解して見てください。多次元配列、連想配列などで検索すると役立つかも。

 

【改造内容】射撃に応じて積み数を操作する

射撃に応じて積み数を操作するには、弾がどの行にあるか判別する必要があります。
行0~行6のどこにいるかの判定式は下のようになります。

Math.floor ( (shot.y + shot.height/2 - 45) / 90 )

この数値が0~6のどれかの整数になります。
Math.floorは小数点以下を切り捨てて整数にする関数ですね。

 

行の番号がわかればその積み数は stack[] のカッコの中に番号を入れたものなので、

stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )]

これが操作すべき積み数になります。

飛んでいく弾が消えるタイミングでこの値を増やしましょう。

// 弾の初期座標を、プレイヤーの少し右に設定します
shot.x = player.x + player.width;
shot.y = player.y + player.height/2 - shot.height/2;
shot.onUpdate.add(function () {
  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x > g.game.width) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});

ここで数値を1だけ増加させる表現 ++ を使用しています。
ついでにshotが発生する高さshot.yを微調整し、
さらについでにshotのスピードも遅いので、shot.x += 40;に変更します。


  



これでなんとなく射撃でタイルが積み上がってきていますが、射撃が画面端まで行かないと反応しないのが感触が悪いですね。
弾が消える位置も積み数に応じて変更していきましょう。

shot.onUpdate.add(function () {
  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});

射撃したものが固まってタイルになっている雰囲気にしたいですが、とりあえずここまでで。

  

  

【改造内容】積み数の一致をチェックする

積み数が揃って、きれいにならされたら得点が入ることにしましょう。
打つ場所をミスると状況が悪化するので、若干緊張感が生まれます。

得点を増やす処理は、scene.onPointDownCapture.add(function () {のすぐ下にあります。
長い名前ですが、g.game.vars.gameState.scoreがスコアになります。

// 制限時間以内であればタッチ1回ごとにSCOREに+1します
if (time > 0) {
  g.game.vars.gameState.score++;
  scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
  scoreLabel.invalidate();
}


タイミングは積み数が変わるタイミングにしましょう。
積み数が揃ったかどうかの判定を追加し、上記の得点処理を移動します。


shot.onUpdate.add(function () {
  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
    var match = 0;
    for (var i = 0; i < 7; i++) {
      match += Math.abs( stack[i] - stack[0] );
    }
    if ( match == 0 ){
      if (time > 0) {
        g.game.vars.gameState.score++;
        scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
        scoreLabel.invalidate();
      }
    }
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});


Math.absはカッコ内を絶対値にする関数です。
+=は前にある数値に後ろの値を加えて、前にある数値に代入します。
match == 0のイコールを2つ重ねているのは、等しいかどうかを判定するものです。
match = 0 だとmatchの値を0にするという入力の意味になってしまいます。


【改造内容】積み数をリセットしてランダムに積む

タイルが揃ったら消さないとだめですね。

タイル数を全部0にすれば消したことになりますが、消したあとのタイル数が
毎回同じだと面白みがないので、積み数を2~4ぐらいでランダムで置き直します。
かつ、最初から揃っているという状態を防ぐため、1箇所だけ積み数を1にします。

shot.onUpdate.add(function () {  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
    var match = 0;
    for (var i = 0; i < 7; i++) {
      match += Math.abs( stack[i] - stack[0] );
    }
    if ( match == 0 ){
      if (time > 0) {
        g.game.vars.gameState.score++;
        scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
        scoreLabel.invalidate();
        for (var i = 0; i < 7; i++) {
          stack[i] = g.game.random.get(2, 4);
        }        
        stack[g.game.random.get(0, 6)] = 1;
      }
    }
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});


g.game.random.get(2, 4)がランダムの処理です。2~4の整数のどれかになります。
2も4も可能性があるので、この場合は1/3の確率になります。



【改造内容】積み数を得点に反映させる

得点が1点ずつだと寂しいので、積み数を反映させます。

似たようなスコアが出ないように細かく分かれるのがいいのですが、このゲームだと積み数は9種類しかなく、残り時間と絡めるのも難しそうです。

積み数に比例して得点があがる
ようにして、バリエーションが出るようにします。
消す数が多ければ得点がどんどんインフレしていくゲームもよくありますが、
ちょっとやってみると加速しすぎたので比例にとどめておきます。

shot.onUpdate.add(function () {  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
    var match = 0;
    for (var i = 0; i < 7; i++) {
      match += Math.abs( stack[i] - stack[0] );
    }
    if ( match == 0 ){
      if (time > 0) {
        g.game.vars.gameState.score+= stack[0]*100;
        scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
        scoreLabel.invalidate();
        for (var i = 0; i < 7; i++) {
          stack[i] = g.game.random.get(2, 4);
        }        
        stack[g.game.random.get(0, 6)] = 1;
      }
    }
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});


【改造内容】 積み数の超過をチェックする

最大まで積むと高得点ですが、1個でも超過すると無に帰すリスク要素を追加します。
stackが12を超えたら、得点無しでリセットをかけます。

shot.onUpdate.add(function () {  // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
  if (shot.x + shot.width/2 > g.game.width - 100 - stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] * 90) {
    shot.destroy();
    bullet = bullet + 1;
    stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] ++;
    var match = 0;
    for (var i = 0; i < 7; i++) {
      match += Math.abs( stack[i] - stack[0] );
    }
    if ( match == 0 ){
      if (time > 0) {
        g.game.vars.gameState.score+= stack[0]**2*10;
        scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
        scoreLabel.invalidate();
        for (var i = 0; i < 7; i++) {
          stack[i] = g.game.random.get(2, 4);
        }        
        stack[g.game.random.get(0, 6)] = 1;
      }
    }
    if (stack[Math.floor ( (shot.y + shot.height/2 - 45) / 90 )] > 12 ){
      if (time > 0) {
        for (var i = 0; i < 7; i++) {
          stack[i] = g.game.random.get(2, 4);
        }        
        stack[g.game.random.get(0, 6)] = 1;
      }
    }
  }
  // 弾を右に動かし、弾の動きを表現します
  shot.x += 40;
  // 変更をゲームに通知します
  shot.modified();
});

 



【終了】

これでとりあえずゲームっぽくはなったと思います。

すごく面白いかと言われると、最初の感触では虚無ゲーかなと思われてしまいそうなレベルです。
が、一応は動きと弾数が制限されている中で、素早く積み上げるための最適解は多少複雑になっているはずです。

最大まで積み上げるときは、適当に積み上げるシーンと、気をつけながらならすシーンの両方があることになります。

最終コードは こちらです。

では次は見た目と音を変えて、サンプルゲームの印象を払拭しましょう。

ニコ生ゲームを作ろうと思ったらすぐ作ろう その3

第3回は表示を追加していきます。残弾表示標的となるタイルを追加します。

■目次

【改造内容】

  • 残弾表示を作る (画像を追加する)
  • 残弾表示を更新する (常に処理する)
  • 残弾を補充する (条件の例外について)
  • タイルを作る (四角を追加する)
  • タイルを並べる (四角を並べる)

 


 

【改造内容】 残弾表示を作る (画像を追加する)

 

前回は残弾の数値を追加しましたが、現状だと今何発残っているのかわかりません。
表示を追加しましょう。

数字でもいいんですが画像でやってみます。

 

弾を発射したときに画像を表示する呪文がサンプルゲームにすでにあります。
こちらをコピーしていきましょう。

  // プレイヤーが発射する弾を生成します

var shot = new g.Sprite({
  scene: scene,
  src: shotImageAsset,
  width: shotImageAsset.width,
  height: shotImageAsset.height
});
// 弾の初期座標を、プレイヤーの少し右に設定します
shot.x = player.x + player.width;
shot.y = player.y;


この弾は var shot という呪文で、飛んでいく画像に「shot」の名前をつけています。
残弾表示の弾の名前が、飛んでいく弾と同じ名前だと具合が悪いので、残弾表示の方は「shot1」にしましょう。

ついでに初期座標も左上にします。

var shot1 = new g.Sprite({
  scene: scene,
  src: shotImageAsset,
  width: shotImageAsset.width,
  height: shotImageAsset.height
});

shot1.x = 40;
shot1.y = 80;


これで良さそうですが、残念ながら2つの落とし穴があります。以下の2つを追加します。

shot1.modified();
scene.append(shot1);

 

1行目の shot1.modified(); はshot.1に加えた変更を反映させるものです。
shot1.xとshot1.yの値を40と80に指定したのでそれを反映させるために必要です。

2行目の scene.append(shot1); はsceneにshot1を加えるものです。
これがないとそもそも表示されません。sceneはゲームのことだと思っててください。

 

これらをどこに追加するのか。loadの中である必要があります。

しかし、 scene.onPointDownCapture.add(function () { の中はタッチしたときしか処理されないので、その外になるように直前に入れてみましょう

 

さて、1つだけでは3つの残弾は表現できません。

さきほどの呪文の「shot1」を「shot2」「shot3」としたものも追加しましょう。

同じ場所に重ねると見えないので、縦のy座標のところは数字を 160、240 と変えます。

var shot1 = new g.Sprite({
  scene: scene,
  src: shotImageAsset,
  width: shotImageAsset.width,
  height: shotImageAsset.height
});
shot1.x = 40;
shot1.y = 80;
shot1.modified();
scene.append(shot1);

var shot2 = new g.Sprite({
  scene: scene,
  src: shotImageAsset,
  width: shotImageAsset.width,
  height: shotImageAsset.height
});
shot2.x = 40;
shot2.y = 160;
shot2.modified();
scene.append(shot2);

var shot3 = new g.Sprite({
  scene: scene,
  src: shotImageAsset,
  width: shotImageAsset.width,
  height: shotImageAsset.height
});
shot3.x = 40;
shot3.y = 240;
shot3.modified();
scene.append(shot3);

 

ここで起動テストをしてみましょう。すでに起動していた場合は、ブラウザを更新するだけで良いです。

左上に弾が3つ並んでいるでしょうか。


  

【改造内容】 残弾表示を更新する (常に処理する)

では実際の残弾数 bullet と表示の状態を一致させます。

左端のshot1は残弾が1より小さければ表示を消します。
表示を消すのには不透明度 opacityを0に変えます。

if (bullet < 1) {
  shot1.opacity = 0;
}
shot1.modified();

これも shot2 と shot3 の分をコピペして、条件をそれぞれ bullet < 2 bullet < 3 とします。opacity と modified のところもshot2 shot3に変えましょう。

 

そしてこれらを追加するところは、先ほどと同じ loadの中であるだけではだめです。loadの直下はゲームの開始時に1度実行されるだけなので、ゲーム中の変化は反映されません。

 

前々回のセクション分けで「(9)常に更新する処理」 と書いてある場所に追加します。ちょうどtimeLabelという残り時間表示の更新が書かれているところがあるので、その後に追加しましょう。

var updateHandler = function () {
  if (time <= 0) {
    // RPGアツマール環境であればランキングを表示します
    if (param.isAtsumaru) {
      var boardId_1 = 1;
      window.RPGAtsumaru.experimental.scoreboards.setRecord(boardId_1, g.game.vars.gameState.score).then(function () {
        window.RPGAtsumaru.experimental.scoreboards.display(boardId_1);
      });
    }
    scene.onUpdate.remove(updateHandler); // カウントダウンを止めるためにこのイベントハンドラを削除します
  }
  // カウントダウン処理
  time -= 1 / g.game.fps;
  timeLabel.text = "TIME: " + Math.ceil(time);
  timeLabel.invalidate();
  if (bullet < 1) {
    shot1.opacity = 0;
  }
  shot1.modified();
  if (bullet < 2) {
    shot2.opacity = 0;
  }
  shot2.modified();
  if (bullet < 3) {
    shot3.opacity = 0;
  }
  shot3.modified();
};
scene.onUpdate.add(updateHandler);


sandboxを更新してみましょう。射撃ごとに残弾表示が消えていったでしょうか。



【改造内容】 残弾を補充する (条件の例外処理について)

弾が3つでなくなってしまうので物足りないですね。

画面内から弾がなくなったら一つ弾を補充するようにしましょう。

画面をタッチしたときの処理 scene.onPointDownCapture.add(function () {の中に、画面内から弾がなくなるときの処理があります。

  if (shot.x > g.game.width)
  shot.destroy();

shotが画面の右端よりも右に行った場合shotを破壊する、という処理になっています。


ここのifには { } のカッコがありません。
ifはカッコがない場合、後ろの処理を一つだけ処理します。今回は残弾を復活させる処理を追加するので、下のようにカッコも追加しましょう。

if (shot.x > g.game.width) {
  shot.destroy();
  bullet = bullet + 1;
}

これでテスト起動してみると弾は補充されるはずですが、残弾表示は減ったままです。
残弾表示は弾が減ったときに透明にしましたが、増えたときのことは決めていませんでした。


残弾表示のshot1はbulletが1より小さいときは透明ですが、それ以外は不透明であるべきです。この「それ以外は」という処理を追加する場合は次のようにします。

if (bullet < 1) {
  shot1.opacity = 0;
} else {
  shot1.opacity = 1;
}
shot1.modified();

ifのカッコに続けてelseと入力し、またカッコで処理を記載します。
shot2とshot3も同じようにします。

テスト起動してみましょう。弾が画面外に行くと同時に残弾表示が増えるはずです。




【改造内容】 タイルを配置する (四角の配置)

現状は射撃するだけなので、標的を用意します。
標的用の画像を用意してもいいのですが、単純な四角形を配置してみます。これは標準で用意されているので、うまく使えばかなり楽ができます。

var tile = new g.FilledRect({
  scene: scene, cssColor: "royalblue", parent: scene,
  x: g.game.width-100, y: g.game.height/2,
  width: 80, height: 80, opacity: 1,
});

は、cssColor: "royalblue" というように色の名前で指定しています。下のサイトのような色見本があるサイトを見て名前を指定するのが楽です。

"rgb(0,100,200)" や "#0066bb" のような数字で指定することもできます。

 

また、これまで使っていないパラメーター parent: scene, を追加しました。

これはこの四角をどこに配置するか指定するものです。

「どこ」の候補は色々あるのですが、今はゲーム本体であるsceneに置きます。

これだと scene.append(tile); を後で記載する必要がなくなります。

 

tileの呪文もloadの直下に配置してください。右側に青い四角が配置されたでしょうか。


  


【改造内容】 タイルを並べる (四角を並べる)

これを縦に7個並べたいと思います。残弾表示のように呪文を複数コピペしてもいいのですが、数が多くなってくると記載がどんどん長くなって見づらくなってきます。

同じような処理は以下のような繰り返し処理を使います。

for (var i = 0; i < 処理回数; i++) {
  処理内容
}

処理回数に7、処理内容にさきほどの四角配置処理を入れるのですが、縦に並べて配置するには少しづつ位置を変える必要があります。

ここで i という値を使います。これは最初の繰り返しでは0で、処理ごとに数値が1増えるようになっています。これを使って縦の位置が90ずつずれるようにします。

for (var i = 0; i < 7; i++) {
  var tile = new g.FilledRect({
    scene: scene, cssColor: "royalblue", parent: scene,
    x: g.game.width-100, y: 90 * i,
    width: 90, height: 90, opacity: 1,
  });
}

更新してみると四角がくっついてしまって、中心もずれていますね。




四角を少し小さくしつつ全体的に下に動かします。

for (var i = 0; i < 7; i++) {
  var tile = new g.FilledRect({
    scene: scene, cssColor: "royalblue", parent: scene,
    x: g.game.width-100, y: 90 * i + 47,
    width: 86, height: 86, opacity: 1,
  });
}

これできれいに縦に並んだはずです。

  


しかしまだスペースがあるので、手前にも配置してしまいましょう。
横の位置をずらした列を用意するのに、また繰り返し処理を使います。

for (var j = 0; j < 12; j++) {
  for (var i = 0; i < 7; i++) {
    var tile = new g.FilledRect({
      scene: scene, cssColor: "royalblue", parent: scene,
      x: g.game.width-100 - 90 * j, y: 90 * i + 47,
      width: 86, height: 86, opacity: 1,
    });
  }
}

今度は j をつかって i とかぶらないようにし、xの値を90ずつずらしています。

タイルが敷き詰められたでしょうか。

  

 



終了

第3回は以上で終了です。お疲れさまです。

最終コードはこちらです。

次はゲームの要素を増やすため、様々な判定を追加していきます。

 

 

ニコ生ゲームを作ろうと思ったらすぐ作ろう その2

第2回は前回起動したサンプルゲームの改造の仕方を説明していきます。

さすがにサンプルゲームはシンプルゲームなので、そのままの投稿は気が引けます。

 

簡単にゲームを変えるのにどういう事ができるか。基礎的な変更から紹介します

 

■目次

【改造の準備】

【改造内容】

  • キャラクターの動きを変える (数字を書き換える)
  • 残弾数を決める (数値を記録する)
  • 残弾ゼロで打てなくする (条件をつける)

 


 

【改造の準備】 ダウンロードしたファイルについて

改めて前回ダウンロードしたファイルを見てみましょう。

こういうフォルダ構造になっているはずです。

  namage

   └ sample
      ├ audio
      ├ image
      ├ script
         ├ main.js
         └ その他のファイル
      ├ text
      ├ game.json
      └ その他のファイル

 

これからメインに編集するのは「main.js」です。

まれに「game.json」も編集しますので、軽く覚えておいてください。

音素材を追加する場合はaudioフォルダに入れます。画像素材はimageフォルダ。

フォントに関するファイルをtextフォルダに入れることもあります。

 

今回はダウンロードしたファイルはそのまま置いておいて、名前を変えたコピーを作りましょう。

sampleフォルダごとコピーアンドペーストしましょう。フォルダの名前は何でもいいですがkaizouあたりにします。

これだけでゲームのコピーができます。特に設定はありません。



【改造の準備】 テキストエディタ

さてここからmain.jsを編集していきますが、Windowsのメモ帳では流石に難しいです。

 

一番オススメなのは、プログラマ用のエディタです。文字を自動で色分けしてくれたりするのが非常に便利です。

Visual Studio Codeというのが評判が良いのですが、日本語化が必要です。日本語化のやり方を調べて導入できそうな方はぜひ導入してください。ちなみに私は存在を知らなかったのでAtomというのを導入しました。こちらも英語ですが悪くはないと思います。

 

あ、そういうのはちょっと…という方はシンプルなテキストエディタでも可能です。

フリーソフトTeraPadや様々なテキストエディタが公開されていますので、好きなのを使ってください。

 

【改造の準備】 main.jsの中身

つぎにmain.jsの中身を見てみると、よくわからない呪文が116行も並んでますね。

例のごとく内容はほとんど説明しませんが、全体の流れ「カッコ」がどのように配置されているかが最低限わからないと、どこに編集を加えていいのかわからないはずです。

 

今回のプログラムに改行を入れて区切りを入れ、11個のセクションに分けてみます。


 


セクション名だけで記載するとこういう構造になっています。

  (1)  プログラム はじまり
  (2)    ゲームの準備
  (3)    ゲーム部分 はじまり load
  (4)      プレイヤー 配置
  (5)      スコアと時間表示 配置
  (6)      画面タッチ はじまり
  (7)        画面タッチ処理
  (8)      画面タッチ おわり
  (9)      常に更新する処理
  (10)   ゲーム部分 おわり
  (11) プログラム おわり


カッコ {   } や(  )で括られた階層構造になっていて、はじまりーおわりで対になっています。

改造で編集する箇所は、基本的に(4)~(9)のゲーム部分(loadの中)です。


【改造内容】 プレイヤーの動きを変える (数字を書き換える)

まずプレイヤーの位置を左にずらしましょう。

プレイヤーの動きが書いてあるのは(4)のこの辺ですね。

  // プレイヤーの初期座標を、画面の中心に設定します
  player.x = (g.game.width - player.width) / 2;
  player.y = (g.game.height - player.height) / 2
  player.onUpdate.add(function () {
    // 毎フレームでY座標を再計算し、プレイヤーの飛んでいる動きを表現します
    // ここではMath.sinを利用して、時間経過によって増加するg.game.ageと組み合わせて
    player.y = (g.game.height - player.height) / 2 + Math.sin(g.game.age % (g.game.fps * 10) / 4) * 10;
    // プレイヤーの座標に変更があった場合、 modified() を実行して変更をゲームに通知します
    player.modified();
  });


横の位置が書いてある2行目をこのように変えます。

  変更前 player.x = (g.game.width - player.width) / 2;
  変更後 player.x = (g.game.width - player.width) / 10;

g.game.widthはゲーム全体の幅です。今回は1280になります。
それを / 2 の割り算で2分の1にすることで画面の真ん中に配置していました。
そして、もっと左に寄せるために10分の1にしてみます。



次に縦の動きです。7行目の2つの数字を変えます。

あと「 % (g.game.fps * 10)」というのがちょっと不自然な動きを生むので消します。

  変更前 player.y = (g.game.height - player.height) / 2 + Math.sin(g.game.age % (g.game.fps * 10) / 4) * 10;
  変更後 player.y = (g.game.height - player.height) / 2 + Math.sin(g.game.age / 15) * 270;

これは三角関数 Math.sin の式ですが、せっかくなので数学の話も省略します。



値を変えてmain.jsを保存したら、前回やったテスト起動をやりましょう。
コマンドプロンプトを開いて、今回のフォルダ kaizouに移動します。

起動の呪文akashic-sandboxを入力します。
もしくは、コマンドプロンプトは上キーを押すと過去の呪文を呼び出せます。


キャラクターが左寄りで大きく動いていればOK。





【改造内容】 残弾数を決める (数値を記録する)

ここまではすでにある数字を編集するだけでしたが、新しい数字を導入していきましょう。


ゲーム部分のはじまりのloadのすぐ下に以下の呪文を挿入します。

  var bullet = 3;


前後を合わせると以下のようになります。

  scene.loaded.add(function () {
    // ここからゲーム内容を記述します

    var bullet = 3;
    // 各アセットオブジェクトを取得します

これでbulletという数値(変数)が追加されました。

 

次に、射撃したら残弾数が減るようにします。

loadの中のタッチ処理のところ(6)に以下の文字列があります。

  scene.onPointDownCapture.add(function () {


ここから先が scene(シーン)が pointDown(押された)時に処理する所です。

このすぐ下にbulletの値の操作を入れましょう。

  scene.onPointDownCapture.add(function () {

    bullet = bullet - 1;

これでタッチするごとに残弾数が一つ減っていきます。

 

【改造内容】 残弾ゼロで打てなくする (条件をつける)

残弾数なので弾がなくなったら打てなくならないとだめですね。

bulletが0より大きい時だけタッチ処理を実行するという条件をつけましょう。

条件は以下のようなカッコを使います。

  if (条件式) {
    処理内容
  }

条件式に bullet > 0 を入れて、さきほどのscene.pointDownCapture.の中身をすべて処理内容として上のカッコに入れます。

  // 画面をタッチしたとき、SEを鳴らします
  scene.pointDownCapture.add(function () {

    if (bullet > 0) {
      bullet = bullet -1;
      // 制限時間以内であればタッチ1回ごとにSCOREに+1します
      if (time > 0) {
        g.game.vars.gameState.score++;
        scoreLabel.text = "SCORE: " + g.game.vars.gameState.score;
        scoreLabel.invalidate();
      }
      seAudioAsset.play();
      // プレイヤーが発射する弾を生成します
      var shot = new g.Sprite({
        scene: scene,
        src: shotImageAsset,
        width: shotImageAsset.width,
        height: shotImageAsset.height
      });
      // 弾の初期座標を、プレイヤーの少し右に設定します
      shot.x = player.x + player.width;
      shot.y = player.y;
      shot.onUpdate.add(function () {
        // 毎フレームで座標を確認し、画面外に出ていたら弾をシーンから取り除きます
        if (shot.x > g.game.width)
          shot.destroy();
        // 弾を右に動かし、弾の動きを表現します
        shot.x += 10;
        // 変更をゲームに通知します
        shot.modified();
      });
      scene.append(shot);
    }
  });


カッコが離れていると、数が合わなくなったりしますのでよく確認してください。
カッコの中をtabで一段ずらすのも見やすくする常套手段です。

 

保存してテストページを更新しましょう。3回射撃して打ち止めになればOK。

 



【終了】

今回はここまでです。最終段階のソースはこちらです。

次は残弾表示などを追加していきます

 

ニコ生ゲームを作ろうと思ったらすぐ作ろう その1

この記事ではニコ生ゲーム(ニコニコ新市場対応コンテンツ、ニコ生ネタ、生ゲーム、生ゲ、ニコゲー)の作り方を解説していきます。


ニコ生ゲームはニコ生で放送者と視聴者が一緒に遊ぶことができるゲームです。他のゲーム公開プラットフォームと比べて以下のような非常に魅力的な点があります。

  • すぐにプレイしてくれる人がいる
  • イデアレベルのシンプルなゲームでも受け入れてもらえる
  • プレイの反応がダイレクトにわかる(音声付きの場合も多い)
  • マルチプレイゲームも"比較的"簡単に作れる


筆者はいくつかニコ生ゲームをリリースしていますが、プログラミングを本業でやったことはありません。

プログラミングに関する詳細は、専門家の方に任せて基本省略します。素人目線でニコ生ゲームリリースにたどり着くことを意識して解説をやっていきます。



一連の解説の全体像はこのようになる予定です。

まず開発が用意しているサンプルゲームを改造して、リリースするところまで行きます。
手順を真似して習得してもらってもいいですし、パラパラ眺めるだけでもいいです。

  • 開発準備 & サンプルゲーム起動  ← この記事です
  • サンプルコードの数値を変更する
  • サンプルコードに表示を追加する
  • サンプルコードにゲーム要素を追加する
  • サンプルコードの見た目を変えて別のゲームにする
  • ニコ生ゲームとしてリリースする

実際にはサンプルゲームの改造だけでは、新しく作るゲームに対応できないですね。
その後は色んなタイプのシンプルなゲームを作って、その中身を紹介していきます。

  • クリックして得点を集めるゲームを作る
  • キャラクターが移動するゲームを作る
  • タイミングよくクリックするゲームを作る

 etc..

これらのゲームを改造して、また新しいゲームを作れればと思います。
しかし、体系的でない流れになるので、別途まとめとして以下の記事も予定しています。

  • 記事の目次 こちら
  • やりたいことー記載例 対応表

 [2021/6/2追記]
 公式の逆引きリファレンス(やりたいことー記載例 対応表)が公開されました!
 https://akashic-games.github.io/reverse-reference/v3/


また、ここの記事ではなく公式の入門記事で一通り機能に触れる方法もあります。
こちらのリンクへどうぞ。

  https://akashic-games.github.io/tutorial/v3/
  https://akashic-games.github.io/shin-ichiba/

 


 

前置きが長くなりましたが、この記事では以下の2章をやります。

開発の準備を整え、最終的にサンプルゲームを起動する所まで行きます。

■目次

【開発準備】

【サンプルゲーム起動】

  • 保存場所
  • ダウンロード
  • 起動テスト

 


 

【開発準備】Node.jsインストール

はい! Node.jsという文字を見て「なんか難しそう」と思ったあなた!
ちょっと待ってください!

これが何なのかは知らなくていいし説明しません。何より私が知りません

下のサイトの「LTS 推奨版」をダウンロード・インストールしてください。


  https://nodejs.org/ja/


間違って「最新版」をインストールしても多分問題ありません。

わけのわからんものをインストールできるか!
という方は大変ごもっともなので公式の記事を見てやってください。

 

逆に「Node.jsが難しそうとか何いってんだこいつ」と思った方には、
私の説明は冗長になると思います。
どんどん読み飛ばすか。公式の記事を読んでもらえば十分理解できるはずです。

 


【開発準備】コマンドプロンプトを使う

インストールが上手く行ったかどうかを知るには「コマンドプロンプト」(Windowsの場合)を使います。コマンドプロンプトはこのあとも頻繁に使うのでやってみましょう。

 

コマンドプロンプトの使い方詳細は一般の解説サイトに任せますが、初回は以下の起動方法でいいでしょう。

  1. Windows」キー を押しながら 「R」キー を押す
  2. cmd」と入力して 「Enter」キー を押す


  

コマンドプロンプトは下のような見た目のやつですね。毎回のように使うのでどこかにピン留めやショートカットしておきましょう。

  


では、Node.jsとやらがインストールされているか確認しましょう。
以下の呪文を唱えます。(コマンドプロンプトに入力してEnterをおします)

  node -v

  


下のような雰囲気の数字が出たらインストール成功です。数字は時価です。
いまならv14とかになってるはず。


  


【開発準備】Akashic Engineインストール

Akashic Engineドワンゴが作ったものっぽいので、一応形だけでも使わせていただけることに感謝しておきましょう。

私のような素人が説明して万が一間違いがあると良くないので、これが何なのかの説明は省略します。

 

さっきのコマンドプロンプトを使って、以下の長い呪文を入力してインストールします。

  npm install -g @akashic/akashic-cli


  


コマンドプロンプトは右クリックから貼り付けをすることができます。

Ctrl+Vはできません。

 

ちょっと時間がかかりますがこんな感じの呪文がかえってきますね。

何が書いてあるんでしょうか。もちろん説明しません。警告メッセージが出ている? ふーんそうなんだ。

 


もう一つ呪文を入力します。これはなんか別のやつです。(なんで別々なんだよ・・・)

  npm install -g @akashic/akashic-sandbox

 

以下の2つの呪文で同じようにインストールの確認をすることもできます。

めんどくさかったら無視してしまって、後でおかしなことがあったらやってください。

  akashic -V

  akashic-sandbox -V



【サンプルゲーム起動】保存場所

これからサンプルゲームをダウンロードするので、保存場所を決めましょう

 

Cドライブの直下などにニコ生ゲーム用フォルダ「namage」をつくり「sample」というフォルダにサンプルゲームを入れることにしましょう。

  C:\namage\sample

 

フォルダ名は好きにしてもいいですが文字数少なめで打ちやすい名前がいいですね。
漢字と仮名をいれるのはやめておきましょう。



【サンプルゲーム起動】ダウンロード

サンプルゲームのダウンロードコマンドプロンプトでやります。

まず、コマンドプロンプトでさきほど作った保存場所に移動します。

移動には「cd (半角スペース)」と入力してから、sampleフォルダコマンドプロンプトドラッグアンドドロップして、Enterします。

  cd

  
  


移動ができるとコマンドプロンプトにこのように表示されます。


  


そして、ダウンロードを始めるために以下の呪文を入力します。


  akashic init -t javascript-shin-ichiba-ranking

そうするとゲームの「幅」「高さ」「fpsの3つを聞かれます。ダウンロードをしつつ初期設定もやってくれるわけです。そのままEnterを押すか、自分で入力します。


  


ゲームの 幅x高さ は640x360などの16:9が無難です。今回は1280x720にしてみます。これはあとから変えられますが、最大は1280x720です。

私は最初に768x432をよく使っていましたが、公式が提供しているキーボード素材が1280x720だったので最近はそちらに合わせています。


fpsは30
でいいでしょう。60も一般的にはよく使われますが、重いゲームでは処理落ちしやすくなります。


さきほどのsampleフォルダにファイルがダウンロードされているでしょうか。

 

【サンプルゲーム起動】起動テスト

いったい何をダウンロードしたのか気になりますが、
そんなことよりゲームを起動しましょう。

起動にはコマンドプロンプトに以下の呪文を入力します。

  akashic-sandbox

こんな文字が出ていれば起動に成功しています。
  

そして、ブラウザ(ChromeFirefoxなど)で以下のURLにアクセスします。
せっかくなのでブックマークしておきましょう。

  http://localhost:3000/game/

 

下のようなキャラクターが表示されたでしょうか。

クリックで射撃もできます。射撃ごとに得点が上がり、時間制限もあります。






上の呪文がうまく行かない人もいるようです。その場合は下のプランBでやってみましょう。

  akashic serve -s nicolive

  http://localhost:3300/public/

これは本来マルチゲーム用ですが、普通に使えるみたいです。

 

テスト起動をやめる時はコマンドプロンプトCtrl+Cを押すとやめるかどうか聞かれます。
y をおして Enter をおすとやめられます。

 


 

終了

ゲームの起動に成功しました。一旦この記事は終了です。

次回からこのサンプルゲームの改造として数値を変更していきます。