itch.ioにHTML5で作成したゲームを投稿してみるテスト
タイトルにあるように、itch.ioにHTML5で作成したゲームを投稿してみました。
実際にitchi.ioに投稿した最終完成品は phaserSampleGame になります。 操作にキーボードを使用するのでPC用です。
ゲーム自体は前にこのnoteに載せていた、Phaser3というゲーム用のライブラリのサンプルゲームを少し改良したものです。
改良といってもメインとなるゲーム部分は元々のサンプルとほぼ変わらない内容でしたが、「HTML5で作成したゲームをitch.ioに投稿してブラウザでププレイできるようにする」という目的をまず達成するつもりで投稿してみました。
以下登録時の手順をいくつかメモしおきます。
まずitch.ioに登録後、ログインします
itch.ioトップ画面より、
アカウントを作成します。
アカウントを作成してログインするといきなり「Create new project」と出てきたのでそのままプロジェクトを作成。 プロジェクト下に公開するゲームを設定していくようです。
プロジェクトに必要な情報をいろいろと入力。
今回のタイトルは「phaserSampleGame」というテキトーなものにしました。
HTML5を使用したブラウザゲームの公開を目的としているので、Kind of projectを「HTML」に設定しました。
Pricingは「No Payments」に。 このあたりは後で変えられる?、または新しいプロジェクトを作成し直し&設定し直しでいくらでもやりなおせる?
Uplaodsで作成したゲームのzipファイルをアップロードします。 zipの構成等はこのnoteの後のほうにのせています。
Embed options、よくわからないのでそのままにしてますw。
Descriptionにはゲームの説明が書けるようです。
Genreは「Action」としました。(←「Platformaer」とどう分類するのかよくわからず。 2Dのときのみ「Platformer」?)
SteamやGoogle Play、Apple App Storeといった他のアプリストアへのリンクも貼れるよう。
Community機能はとりあえず「Comments」を選択。
最初のアップロード時は、動作確認用としてのDraftしか選べない? 自分はとりあえずDraftにしてテストしてから公開に(Publicに)しました。
ちょっと画面の表示順忘れましたが、プロジェクトの登録が終わるとこのDashboard画面が出てきました。
Draftの状態で投稿したゲームを試してみました。
↑上記画面の「Run game」ボタン押下で以下のような感じで表示されました。 これをDraftとして確認後、ゲームを公開しました。
このnoteの最初のほうにものせましたが、公開後のリンクはこちらになります。
以下はzipとしてまとめてアップロードしたフォルダ、ファイルについてです。
phaserSampleGameフォルダ内に以下ようにファイルを設置後、zipファイルとしてまとめてitchi.ioに投稿しました。
phaserSampleGame フォルダ構成
phaserSampleGame ―┬― js
├― css
└― assets ― images
以下ファイル名は、それを含むphaserSampleGame内のフォルダ名を付けてフルパスで表示してます
■ phaserSampleGame フォルダ直下のファイル(下記のJavaScriptファイルとCSSファイルをインクルードしているhtmlファイル)
index.html
■ phaserSampleGame/js 下のファイル(phaserライブラリとゲーム本体となるJavaScriptファイル)
js/phaser.min.js
js/game.js
■ phaserSampleGame/css 下のファイル
css/game.css
■ phaserSampleGame/assets/images 下のファイル(画像ファイル)
/assets/images/bomb.png
/assets/images/dude.png
/assets/images/platform.png
/assets/images/sky.png
/assets/images/star.png
これらのファイルは、このnoteに出てきたCodePenのコードをitch.io投稿用に切り分けて少し整理したものです。
ファイルをセーブ時は文字コードをUTF8 BOM無しとしてセーブしました。
おまけにindex.html、game.css、game.jsのソースコードを載せておきます。
index.html
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="css/game.css">
<script src="js/phaser.min.js"></script>
<script src="js/game.js"></script>
</head>
<body>
</body>
</html>
game.css
body {
margin: 0;
}
game.js
//Phaser3サンプルゲームを少し修正してitch.ioへアップロード
////////////////////////////////////////////////////////////////////////////////////////
// LoadingSceneシーン → TitleSceneシーン → MainSceneシーン と遷移
//
// MainSceneシーンでのゲームオーバー時に画面クリックでTitleSceneシーンに再遷移
// 再遷移したTitleSceneシーンからゲーム再開できる(MainSceneシーンへ再遷移)
//
////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////
// LoadingSceneシーン
// 画像等のリソースファイルを読み込み用
// 読み込み完了後自動でタイトル画面(TitleScene)に遷移
////////////////////////////////////////////////////////////////////////////////////////
class LoadingScene extends Phaser.Scene {
constructor() {
// Sceneを拡張してクラスを作る際にコンストラクタでSceneの設定を渡します
super({ key: 'LoadingScene', active: true });
}
preload() {
}
create() {
// 描画領域のサイズを取得
const { width, height } = this.game.canvas;
// テキストをロゴの下に表示
const currentLoadingText = this.add.text(width/2, height/2, 'Loading... 0%').setFontSize(32).setOrigin(0.5);
// アセットをロード(一度ロードしたアセットは他のシーンでも使用可)
this.load.image('sky', 'assets/images/sky.png');
this.load.image('ground', 'assets/images/platform.png');
this.load.image('star', 'assets/images/star.png');
this.load.image('bomb', 'assets/images/bomb.png');
this.load.spritesheet('dude', 'assets/images/dude.png', { frameWidth: 32, frameHeight: 48 });
// アセットのロードに進捗があるたびに発生するイベント
this.load.on('progress', (progress) => {
//テキストの内容を書き換える
currentLoadingText.text = 'Loading...' + Math.round(progress * 100) + "%";
});
// アセットのロードが完了したらTitleSceneに遷移
this.load.on('complete', () => {
this.scene.start('TitleScene');
});
// アセットのロードを開始(preload外でロードを行う場合はこのメソッドを呼ぶ必要がある)
this.load.start();
}
update() {
}
}
//ハイスコア保持用(グローバル変数的なのでよい?)
let highScore = 0;
////////////////////////////////////////////////////////////////////////////////////////
// TitleSceneシーン
// タイトル画面を表示
// 画面クリックでゲームのメイン画面(MainScene)に遷移
////////////////////////////////////////////////////////////////////////////////////////
class TitleScene extends Phaser.Scene {
constructor() {
// Sceneを拡張してクラスを作る際にコンストラクタでSceneの設定を渡します
// 自動実行をオフに
super({ key: 'TitleScene', active: false });
}
preload() {
}
create() {
const { width, height } = this.game.canvas;
this.add.image(400, 300, 'sky');
this.add.text(16, 16, 'HIGH SCORE: ' + highScore ).setFontSize(32).setColor('#000');
this.add.text(80, 100, 'Hey Dude', { fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif' }).setFontSize(128).setColor('#00f');
this.add.text(100, 300, 'Click to Start Game').setFontSize(32).setColor('#00f');
// 画面を埋めるようなZoneを作成
const zone = this.add.zone(width/2, height/2, width, height);
// Zoneをクリックできるように設定
zone.setInteractive({
useHandCursor: true // マウスオーバーでカーソルが指マークになる
});
// ZoneをクリックしたらMainSceneに遷移
zone.on('pointerdown', () => {
this.scene.start("MainScene")
});
}
update() {
}
}
////////////////////////////////////////////////////////////////////////////////////////
// MainSceneシーン
// ゲームのためのメイン画面を表示
////////////////////////////////////////////////////////////////////////////////////////
class MainScene extends Phaser.Scene {
//this.bgImg
//this.platforms
//this.player
//this.cursors
//this.stars
//this.scoreText
//this.bombs
constructor() {
// Sceneを拡張してクラスを作る際にコンストラクタでSceneの設定を渡します
// 自動実行をオフに
super({ key: 'MainScene', active: false });
this.score = 0;
this.gameOver = false;
}
preload() {
}
create() {
this.bgImg = this.add.image(400, 300, 'sky');
this.platforms = this.physics.add.staticGroup();
this.platforms.create(400, 568, 'ground').setScale(2).refreshBody();
this.platforms.create(600, 400, 'ground');
this.platforms.create(50, 250, 'ground');
this.platforms.create(750, 220, 'ground');
this.player = this.physics.add.sprite(100, 450, 'dude');
this.player.setBounce(0.2);
this.player.setCollideWorldBounds(true);
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'dude', frame: 4 } ],
frameRate: 20
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
});
this.cursors = this.input.keyboard.createCursorKeys();
this.stars = this.physics.add.group({
key: 'star',
repeat: 11,
setXY: { x: 12, y: 0, stepX: 70 }
});
this.stars.children.iterate(function (child) {
child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
});
this.bombs = this.physics.add.group();
this.scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
this.physics.add.collider(this.player, this.platforms);
this.physics.add.collider(this.stars, this.platforms);
this.physics.add.collider(this.bombs, this.platforms);
this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this);
this.physics.add.collider(this.player, this.bombs, this.hitBomb, null, this);
}
update() {
if (this.gameOver) {
const { width, height } = this.game.canvas;
let gameOverText = this.add.text(80, 100, 'Game Over', { fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif' }).setFontSize(128).setColor('#f00');
let clickPrmptText = this.add.text(100, 300, 'Click to Return to Title Screen ').setFontSize(32).setColor('#000');
// 画面を埋めるようなZoneを作成
let zone = this.add.zone(width/2, height/2, width, height);
// Zoneをクリックできるように設定
zone.setInteractive({
useHandCursor: true // マウスオーバーでカーソルが指マークになる
});
// Zoneをクリックしたら必要なデータを初期化しTitleSceneに遷移
zone.on('pointerdown', () => {
// ハイスコア(最高得点)が更新された場合は登録
if(highScore < this.score) highScore = this.score;
// 参考
// Best way to destroy a object
// https://phaser.discourse.group/t/best-way-to-destroy-a-object/3209
//object.setActive(false).setVisible(false);
//object.destroy();
// 以下 destroyで消すことのできたオブジェクト
//this.bgImg.destroy();
//this.player.destroy();
//this.scoreText.destroy();
//gameOverText.destroy();
//zone.destroy();
// 消さなくてもこのMainSceneを再び呼び出したときに問題なさそうだったので現在はコメントアウトにしている
// Sceneを切り替えるごとにPhaserが自動で、上記のサイトの説明にある
// .setActive(false).setVisible(false) のようなことを行ってくれている?
// MainSceneを再び呼び出し時の状態を見るに
// gameOverText、zone では初期化的なことも自動で行ってくれている?
// 以下 destroyで消すことのできなかったオブジェクト
//this.cursors.destroy();
//this.platforms.destroy();
//this.stars.destroy();
//this.bombs.destroy();
// this.cursors キーボード入力のようなPCの基本的な機能を表すようなオブジェクトは消せない?
// this.platforms、this.stars、this.bombsのような内部にさらに複数のオブジェクトを保持しているような
// オブジェクトは直接消せない? または消す前に何らかの処理が必要?
//
// MainSceneを再び呼び出し時の状態を見るにdestroyによる削除がなくても今のところ問題なさそう
// こちらの消せないオブジェクトも.setActive(false).setVisible(false) のようなことを自動で行ってくれている?
// this.stars、this.bombsはMainScene再呼び出し時には初期かもちゃんとされていそう
// 数値、文字列、真偽値といった基本的なデータはdestroyで削除できないが
// そのまま残しておくと、このMainSceneを再び呼び出したときに最後に保持したデータ内容がそのまま残ってしまう
// なのでthis.score、this.gameOverは初期化を行う
// 参考
// → this.scoreは初期化を行わないと、得点が累積の得点となっていまう
// → this.gameOverは初期化を行わないと一度ゲームオーバー状態設定後にずっとゲームオーバー状態が続いてしまう
//this.score.destroy();
//this.gameOver.destroy();
this.score = 0;
this.gameOver = false;
// 必要な初期化後に、タイトル画面(TitleScene)へ遷移
this.scene.start("TitleScene")
});
return;
}
if (this.cursors.left.isDown) {
this.player.setVelocityX(-160);
this.player.anims.play('left', true);
} else if (this.cursors.right.isDown) {
this.player.setVelocityX(160);
this.player.anims.play('right', true);
} else {
this.player.setVelocityX(0);
this.player.anims.play('turn');
}
if (this.cursors.up.isDown && this.player.body.touching.down) {
this.player.setVelocityY(-330);
}
}
////////////////////////////////////////////////////////////////////////////////////////
// collectStarメソッド
// Playerがスターを取得すると得点を加算して表示
// 画面上のスターが取得されて全てなくなるごとにスターを再生&爆弾を1つ追加していく
////////////////////////////////////////////////////////////////////////////////////////
collectStar (player, star) {
star.disableBody(true, true);
this.score += 10;
this.scoreText.setText('Score: ' + this.score);
if (this.stars.countActive(true) === 0) {
// A new batch of stars to collect
this.stars.children.iterate(function (child) {
child.enableBody(true, child.x, 0, true, true);
});
let x = (this.player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400);
let bomb = this.bombs.create(x, 16, 'bomb');
bomb.setBounce(1);
bomb.setCollideWorldBounds(true);
bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);
bomb.allowGravity = false;
}
}
////////////////////////////////////////////////////////////////////////////////////////
// hitBombメソッド
// Playerが爆弾に触れたらゲームオーバー
// 爆弾に触れたらPlayerを正面を向かせて赤くして、ゲームオーバー状態に設定
////////////////////////////////////////////////////////////////////////////////////////
hitBomb (player, bomb) {
this.physics.pause();
player.setTint(0xff0000);
player.anims.play('turn');
this.gameOver = true;
}
}
var config = {
type: Phaser.AUTO,
width: 800,
height: 600,
scene: [LoadingScene, TitleScene, MainScene],
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
},
}
let game = new Phaser.Game(config);
関連note
作成したゲーム関連noteまとめ