画面をタップして魚を釣りましょう。改造するとオリジナルのゲームが作れます。
ダウンロードした zip ファイルを解凍して以下のコマンドを実行してください。
npm install
npm run build
akashic-sandbox
最初に実行されるコードです。
import { Timeline } from "@akashic-extension/akashic-timeline";
import {
BACKGROUND_ALPHA,
BACKGROUND_COLOR,
BEAR_COLOR,
BEAR_POS,
BEAR_SIZE,
FONT_FAMILY,
FONT_SIZE,
GRASS_COLOR,
GRASS_POS,
GRASS_SIZE,
ISLAND_COLOR,
ISLAND_POS,
ISLAND_SIZE,
STUCK_DURATION,
TIMELIMIT,
WATERSURFACE_COLOR,
WATERSURFACE_POS
} from "./constants";
import { FishingRod } from "./entity/FishingRod";
import { HUDManager } from "./HUDManager";
import { Sea } from "./entity/Sea";
import { GameMainParameterObject, RPGAtsumaruWindow } from "./parameterObject";
import { getResources, setResources } from "./Resources";
declare const window: RPGAtsumaruWindow;
class TsurikkumaStyleGame {
private scene: g.Scene;
private root: g.E;
private sea: Sea;
private fishingRod: FishingRod;
private hudManager: HUDManager;
private isPlaying: boolean = false;
constructor(scene: g.Scene) {
this.scene = scene;
this.root = new g.E({ scene: scene });
this.scene.append(this.root);
createStage(this.root);
createBear(this.root);
this.sea = createSea(this.root);
this.fishingRod = createFishingRod(this.root);
this.hudManager = createHUDManager(this.root);
}
/**
* ゲームを開始する
*/
start(): void {
this.hudManager.startCountdown(() => this._startGame());
}
/**
* ゲームを1フレーム進める
*/
step(): void {
if (!this.isPlaying) return;
this.sea.checkFishOnHook(this.fishingRod);
this.hudManager.updateTime();
if (this.hudManager.getNowTime() <= 0) {
// ゲーム終了
this.isPlaying = false;
this._finishGame();
}
}
/**
* タップしたときの処理
*/
onPointDown(): void {
if (!this.isPlaying) return;
this.fishingRod.catchUp(() => {
const pattern = this.fishingRod.getFishingPattern(this.sea.capturedFishList);
this.hudManager.addScore(this.hudManager.calcScore(this.sea.capturedFishList));
this.fishingRod.fishing(pattern);
this.sea.destroyCapturedFish();
});
}
/**
* ゲーム本編開始
*/
private _startGame(): void {
this.isPlaying = true;
this.sea.startFishTimer();
}
/**
* ゲーム終了時の処理
*/
private _finishGame(): void {
this.scene.pointUpCapture.removeAll();
this.sea.clearFishTimer();
this.hudManager.showTimeUp();
if (getResources().param.isAtsumaru) {
const boardId = 1;
window.RPGAtsumaru.experimental.scoreboards.setRecord(boardId, g.game.vars.gameState.score).then(function() {
window.RPGAtsumaru.experimental.scoreboards.display(boardId);
});
}
}
}
export function main(param: GameMainParameterObject): void {
const scene = new g.Scene({ game: g.game });
let timeLimit = TIMELIMIT;
if (param.sessionParameter.totalTimeLimit) {
/**
* セッションパラメータで制限時間が指定されたらその値を使用します
* 制限時間の 10 秒ほど前にはゲーム上の演出が完了するようにします
*/
timeLimit = Math.max(param.sessionParameter.totalTimeLimit - 10, 1);
}
setResources({
timeline: new Timeline(scene),
font: createFont(),
timeLimit: timeLimit,
param: param
});
const tsurikkumaStyleGame = new TsurikkumaStyleGame(scene);
scene.loaded.add(() => {
tsurikkumaStyleGame.start();
});
scene.update.add(() => {
tsurikkumaStyleGame.step();
});
scene.pointDownCapture.add(() => {
tsurikkumaStyleGame.onPointDown();
});
g.game.pushScene(scene);
}
/**
* フォントを作成
*/
function createFont(): g.DynamicFont {
return new g.DynamicFont({
game: g.game,
fontFamily: FONT_FAMILY,
size: FONT_SIZE
});
}
/**
* 背景を作成
*/
function createStage(parent: g.E): void {
/**
* 背景 (空と海)
*/
new g.FilledRect({
scene: parent.scene,
cssColor: BACKGROUND_COLOR,
width: g.game.width,
height: g.game.height,
opacity: BACKGROUND_ALPHA,
parent: parent
});
/**
* 島
*/
new g.FilledRect({
scene: parent.scene,
cssColor: ISLAND_COLOR,
...ISLAND_SIZE,
...ISLAND_POS,
parent: parent
});
/**
* 草
*/
new g.FilledRect({
scene: parent.scene,
cssColor: GRASS_COLOR,
...GRASS_SIZE,
...GRASS_POS,
parent: parent
});
/**
* 水面
*/
new g.FilledRect({
scene: parent.scene,
cssColor: WATERSURFACE_COLOR,
width: g.game.width,
height: 3,
...WATERSURFACE_POS,
parent: parent
});
}
/**
* くまを作成
*/
function createBear(parent: g.E): void {
new g.FilledRect({
scene: parent.scene,
cssColor: BEAR_COLOR,
...BEAR_SIZE,
...BEAR_POS,
parent: parent
});
}
/**
* 海を作成
*/
function createSea(parent: g.E): Sea {
return new Sea({ parent });
}
/**
* 釣竿を作成
*/
function createFishingRod(parent: g.E): FishingRod {
const fishingRod = new FishingRod({ parent: parent });
fishingRod.onStuck.add(() => {
createMissLabel(parent);
});
return fishingRod;
}
/**
* HUDマネージャーを作成
*/
function createHUDManager(parent: g.E): HUDManager {
const hudManager = new HUDManager({
scoreLabel: createScoreLabel(parent),
timeLabel: createTimeLabel(parent),
systemLabel: createSystemLabel(parent)
});
hudManager.setScore(0);
hudManager.setTimeLimit(getResources().timeLimit);
return hudManager;
}
/**
* スコアラベルを作成
*/
function createScoreLabel(parent: g.E): g.Label {
return new g.Label({
scene: parent.scene,
text: "",
font: getResources().font,
fontSize: FONT_SIZE,
width: g.game.width - 10,
y: 5,
textAlign: g.TextAlign.Right,
widthAutoAdjust: false,
parent: parent
});
}
/**
* 制限時間ラベルを作成
*/
function createTimeLabel(parent: g.E): g.Label {
return new g.Label({
scene: parent.scene,
text: "",
font: getResources().font,
fontSize: FONT_SIZE,
width: g.game.width - 220,
y: 5,
textAlign: g.TextAlign.Right,
widthAutoAdjust: false,
parent: parent
});
}
/**
* システムラベルを作成
*/
function createSystemLabel(parent: g.E): g.Label {
return new g.Label({
scene: parent.scene,
text: "3",
font: getResources().font,
fontSize: FONT_SIZE * 2,
x: g.game.width / 2,
y: g.game.height / 2,
anchorX: 0.5,
anchorY: 0.5,
parent: parent
});
}
/**
* 釣りミス時のラベルを作成
*/
function createMissLabel(parent: g.E): void {
const missLabel = new g.Label({
scene: parent.scene,
text: "miss!",
textColor: "red",
font: getResources().font,
fontSize: Math.floor(FONT_SIZE / 2),
x: BEAR_POS.x + BEAR_SIZE.width * 2,
y: BEAR_POS.y,
parent: parent
});
getResources()
.timeline.create(missLabel)
.wait(STUCK_DURATION)
.call(() => missLabel.destroy());
}
Sea.ts
には
が実装されています。
import { FISH_FONT_SIZE, FISH_INTERVAL, SWIMMING_TIME_RANGE, WATERSURFACE_POS } from "../constants";
import { Fish } from "./Fish";
import { FishingRod } from "./FishingRod";
/**
* 魚情報インターフェース
*/
interface FishInfo {
/**
* 魚の名前
*/
readonly name: string;
/**
* 獲得できるスコア
*/
readonly score: number;
}
/**
* 出現する魚の種類
*/
const fishInfoList: FishInfo[] = [
{name: "さかな", score: 1},
{name: "くらげ", score: 0}
];
/**
* 海クラス生成時のパラメータ
*/
export interface SeaParameterObject {
/**
* 親エンティティ
*/
readonly parent: g.E;
}
/**
* 海クラス
*/
export class Sea {
/**
* 釣られた魚リスト
*/
capturedFishList: Fish[];
private _parent: g.E;
/**
* 作成した魚リスト
*/
private _fishList: Fish[];
/**
* 魚作成タイマー
*/
private _fishTimerIdentifier: g.TimerIdentifier;
constructor(param: SeaParameterObject){
this.capturedFishList = [];
this._parent = param.parent;
this._fishList = [];
}
/**
* 定期的に魚を作成する
*/
startFishTimer(): void {
this._fishTimerIdentifier = this._parent.scene.setInterval(() => {
const fish = this._createRandomFish(this._parent);
fish.swim();
this._fishList.push(fish);
}, FISH_INTERVAL);
}
/**
* タイマーをクリアする
*/
clearFishTimer(): void {
if (!this._fishTimerIdentifier) return;
this._parent.scene.clearInterval(this._fishTimerIdentifier);
this._fishTimerIdentifier = null;
}
/**
* 釣り針と魚の当たり判定をチェックする
*/
checkFishOnHook(fishingRod: FishingRod): void {
if (!this._fishList.length) return;
if (!fishingRod.isCatching) return;
this._fishList.forEach(fish => {
// 釣り針と魚が当たっていた場合は釣り上げる
if (g.Collision.intersectAreas(fishingRod.hookArea, fish.area)) {
if (fish.isCaptured) return;
fish.stop();
fish.followHook(fishingRod);
this._fishList = this._fishList.filter(item => item !== fish);
this.capturedFishList.push(fish);
}
});
};
/**
* 捕まえた魚たちを destroy する
*/
destroyCapturedFish(): void {
this.capturedFishList.forEach(capturedFish => capturedFish.destroy());
this.capturedFishList = [];
}
/**
* ランダムな魚を作成
*/
private _createRandomFish(parent: g.E): Fish {
// 作成する魚の種類
const fishIdx = g.game.random.get(0, fishInfoList.length - 1);
// 魚の泳ぎ方のパターン
const pattern = (g.game.random.get(0, 1)) ? "right_to_left" : "left_to_right";
// 魚が泳ぐ水深
const depth = WATERSURFACE_POS.y + FISH_FONT_SIZE * g.game.random.get(0, 4);
// 魚が泳ぐ時間
const swimTime = g.game.random.get(SWIMMING_TIME_RANGE.min, SWIMMING_TIME_RANGE.max);
return new Fish({
parent: parent,
name: fishInfoList[fishIdx].name,
score: fishInfoList[fishIdx].score,
swimmingStyle: {
pattern: pattern,
depth: depth,
swimTime: swimTime
}
});
}
}
Fish.ts
には
が実装されています。
import { Tween } from "@akashic-extension/akashic-timeline";
import { FISH_FONT_SIZE } from "../constants";
import { getResources } from "../Resources";
import { FishingRod } from "./FishingRod";
/**
* 魚クラス生成時のパラメータ
*/
export interface FishParameterObject {
/**
* 親エンティティ
*/
readonly parent: g.E;
/**
* 魚の名前(文字列)
*/
readonly name: string;
/**
* 魚を釣ったときのスコア
*/
readonly score: number;
/**
* 泳ぎ方
*/
readonly swimmingStyle: SwimmingStyle;
}
/**
* 泳ぎ方インターフェース
*/
export interface SwimmingStyle {
/**
* 魚が動く方向
*/
readonly pattern: "left_to_right" | "right_to_left";
/**
* 魚が泳いでいる深さ (y座標)
*/
readonly depth: number;
/**
* 魚の移動時間 (ミリ秒)
*/
readonly swimTime: number;
}
/**
* 魚クラス
*/
export class Fish {
private _parent: g.E;
private _label: g.Label;
private _score: number;
private _swimmingStyle: SwimmingStyle;
/**
* 泳ぐアニメーション用の Tween
*/
private _swimTween: Tween | null = null;
/**
* 既に釣り上げられたかどうか
*/
private _isCaptured: boolean;
constructor(param: FishParameterObject) {
this._parent = param.parent;
this._label = this._createLabel(param);
this._parent.append(this._label);
this._isCaptured = false;
this._score = param.score;
this._swimmingStyle = param.swimmingStyle;
}
get isCaptured(): boolean {
return this._isCaptured;
}
get name(): string {
return this._label.text;
}
get score(): number {
return this._score;
}
/**
* 魚の当たり判定を返す
*/
get area(): g.CommonArea {
return {
width: this._label.width,
height: this._label.height,
x: this._label.x,
y: this._label.y
};
}
destroy(): void {
this._label.destroy();
}
/**
* 釣られる
*/
followHook(fishingRod: FishingRod): void {
this._label.update.add(() => {
this._label.y = Math.min(fishingRod.hookArea.y, this._label.y);
this._label.modified();
});
}
/**
* 泳ぐ
*/
swim(): void {
const timeline = getResources().timeline;
const toX = this._label.x < g.game.width / 2 ? g.game.width : -this._label.width;
if (this._swimTween) {
timeline.remove(this._swimTween);
}
this._swimTween = timeline
.create(this._label)
.moveTo(toX, this._label.y, this._swimmingStyle.swimTime)
.call(() => this._label.destroy());
}
/**
* 泳ぎをやめる
*/
stop(): void {
this._isCaptured = true;
if (this._swimTween) {
getResources().timeline.remove(this._swimTween);
this._swimTween = null;
}
}
/**
* 魚ラベル作成
*/
private _createLabel(param: FishParameterObject): g.Label {
return new g.Label({
scene: param.parent.scene,
text: param.name,
font: getResources().font,
fontSize: FISH_FONT_SIZE,
...this._initialPos(param)
});
}
/**
* 初期位置生成
*/
private _initialPos(param: FishParameterObject): g.CommonOffset {
switch (param.swimmingStyle.pattern) {
case "left_to_right":
return { x: -FISH_FONT_SIZE, y: param.swimmingStyle.depth };
case "right_to_left":
return { x: g.game.width, y: param.swimmingStyle.depth };
}
}
}
FishingRod.ts
には
が実装されています。
import {
FISHING_DURATION,
FISHING_WAIT_DURATION,
HOOK_COLOR,
HOOK_POS,
HOOK_POS_WHEN_UP,
HOOK_SIZE,
ROD_ANGLE,
ROD_COLOR,
ROD_POS,
ROD_SIZE,
ROD_STRING_COLOR,
ROD_STRING_HEIGHT_WHEN_UP,
ROD_STRING_POS,
ROD_STRING_SIZE,
STUCK_DURATION
} from "../constants";
import { getResources } from "../Resources";
import { Fish } from "./Fish";
/**
* 釣りのパターン
*/
type FishingPattern = "Default" | "Stuck";
/**
* 釣り竿クラス生成時のパラメータ
*/
export interface FishingRodParameterObject {
/**
* 親エンティティ
*/
readonly parent: g.E;
}
/**
* 釣り竿クラス
*/
export class FishingRod {
/**
* スタック時のトリガー
*/
readonly onStuck: g.Trigger<void> = new g.Trigger();
private _parent: g.E;
private _rodString: g.FilledRect;
private _hook: g.E;
/**
* 釣り上げ中(魚との当たり判定がある状態)かどうか
*/
private _isCatching: boolean;
/**
* 釣り中かどうか
*/
private _isFishing: boolean;
constructor(param: FishingRodParameterObject) {
this._parent = param.parent;
this._isCatching = false;
this._isFishing = false;
this._createRod();
this._createRodString();
this._createHook();
}
get isCatching(): boolean {
return this._isCatching;
}
/**
* 釣り針の当たり判定を返す
*/
get hookArea(): g.CommonArea {
return {
width: this._hook.width,
height: this._hook.height,
x: this._hook.x,
y: this._hook.y
};
}
/**
* 釣り上げる
*/
catchUp(finished: () => void): void {
if (this._isFishing || this._isCatching) return;
this._isCatching = true;
this._isFishing = true;
const timeline = getResources().timeline;
timeline.create(this._rodString).to({height:ROD_STRING_HEIGHT_WHEN_UP}, FISHING_DURATION).wait(FISHING_WAIT_DURATION);
timeline.create(this._hook).moveTo(this._hook.x, HOOK_POS_WHEN_UP.y, FISHING_DURATION).wait(FISHING_WAIT_DURATION)
.call(() => {
this._isCatching = false;
finished();
});
}
/**
* 釣った魚からパターンを判定
*/
getFishingPattern(capturedFishList: Fish[]): FishingPattern {
let pattern: FishingPattern = "Default";
capturedFishList.forEach(fish => {
if (pattern !== "Default") return;
switch (fish.name){
case "くらげ":
pattern = "Stuck";
break;
}
});
return pattern;
}
/**
* パターンに従って釣りをする
*/
fishing(pattern: FishingPattern): void {
switch (pattern){
case "Default":
this._swingDown();
break;
case "Stuck":
this._stuck();
break;
}
}
/**
* 振り下ろす
*/
private _swingDown(): void {
const timeline = getResources().timeline;
timeline.create(this._rodString).to({height: ROD_STRING_SIZE.height}, FISHING_DURATION);
timeline.create(this._hook).moveTo(this._hook.x, HOOK_POS.y, FISHING_DURATION).call(() => {
this._isFishing = false;
});
}
/**
* スタックさせる
*/
private _stuck(): void {
this.onStuck.fire();
// ${STUCK_DURATION} ミリ秒後に、スタックを解除し、釣竿を振り下ろす
const timeline = getResources().timeline;
timeline.create(this._rodString).wait(STUCK_DURATION);
timeline.create(this._hook).wait(STUCK_DURATION)
.call(() => {
this._swingDown();
});
}
/**
* 釣竿を作成する
*/
private _createRod(): void {
new g.FilledRect({
scene: this._parent.scene,
cssColor: ROD_COLOR,
...ROD_SIZE,
...ROD_POS,
angle: ROD_ANGLE,
parent: this._parent
});
}
/**
* 釣り糸を作成する
*/
private _createRodString(): void {
this._rodString = new g.FilledRect({
scene: this._parent.scene,
cssColor: ROD_STRING_COLOR,
...ROD_STRING_SIZE,
...ROD_STRING_POS,
parent: this._parent
});
}
/**
* 釣り針を作成する
*/
private _createHook(): void {
const scene = this._parent.scene;
this._hook = new g.E({
scene: scene,
...HOOK_SIZE,
...HOOK_POS,
parent: this._parent
});
new g.FilledRect({
scene: scene,
cssColor: HOOK_COLOR,
width: 10,
height: this._hook.height,
x: this._hook.width - 10,
parent: this._hook
});
new g.FilledRect({
scene: scene,
cssColor: HOOK_COLOR,
width : this._hook.width,
height: 10,
y: this._hook.height - 10,
parent: this._hook
});
new g.FilledRect({
scene: scene,
cssColor: HOOK_COLOR,
width: 10,
height: 20,
y: this._hook.height - 20,
parent: this._hook
});
}
}
HUDManager.ts
には
が実装されています。
constants.ts
にはゲームで利用する定数がまとめられています。
/**
* ゲーム定数
*/
/**
* 制限時間(セッションパラメータで制限時間が指定されたらその値を使用します)
*/
export const TIMELIMIT = 30;
/**
* フォントサイズ
*/
export const FONT_SIZE = 36;
/**
* フォントファミリー
*/
export const FONT_FAMILY = g.FontFamily.SansSerif;
/**
* 背景の色
*/
export const BACKGROUND_COLOR = "#3fa7ff";
/**
* 背景の透過度
*/
export const BACKGROUND_ALPHA = 0.8;
/**
* 島の色
*/
export const ISLAND_COLOR = "#ffeca8";
/**
* 島の大きさ
*/
export const ISLAND_SIZE = {width: 200, height: 80};
/**
* 島の座標
*/
export const ISLAND_POS = {x: 0, y: g.game.height * 0.5 - ISLAND_SIZE.height};
/**
* 草の色
*/
export const GRASS_COLOR = "#549637";
/**
* 草の大きさ
*/
export const GRASS_SIZE = {width: ISLAND_SIZE.width - 40, height: ISLAND_SIZE.height / 2};
/**
* 草の座標
*/
export const GRASS_POS = {x: 0, y: ISLAND_POS.y - 20};
/**
* 水面の高さ
*/
export const WATERSURFACE_POS = {x: 0, y: g.game.height * 0.4};
/**
* 水面の色
*/
export const WATERSURFACE_COLOR = "#252525";
/**
* くまの色
*/
export const BEAR_COLOR = "white";
/**
* くまの大きさ
*/
export const BEAR_SIZE = {width: 50, height: 65};
/**
* くまの座標
*/
export const BEAR_POS = {x: 100, y: GRASS_POS.y - BEAR_SIZE.height / 2};
/**
* 魚のサイズ(横幅は魚の名前の長さに依存
*/
export const FISH_FONT_SIZE = 36;
/**
* 魚の生成間隔[ミリ秒]
*/
export const FISH_INTERVAL = 2000;
/**
* 魚が泳ぐ時間範囲[ミリ秒]
*/
export const SWIMMING_TIME_RANGE = {min: 5000, max: 10000};
/**
* 釣り竿の色
*/
export const ROD_COLOR = "#835031";
/**
* 釣り竿の大きさ
*/
export const ROD_SIZE = {width: 3, height: 100};
/**
* 釣り竿の座標
*/
export const ROD_POS = {x: 155, y: 50};
/**
* 釣り竿の角度
*/
export const ROD_ANGLE = 30;
/**
* 釣り糸の色
*/
export const ROD_STRING_COLOR = "#252525";
/**
* 釣り糸の大きさ
*/
export const ROD_STRING_SIZE = {width: 3, height: 280};
/**
* 釣り糸の座標
*/
export const ROD_STRING_POS = {x: 180, y: ROD_POS.y + 8};
/**
* 釣り上げ時の釣り糸の長さ
*/
export const ROD_STRING_HEIGHT_WHEN_UP = ROD_STRING_SIZE.height / 5;
/**
* 釣り針の色
*/
export const HOOK_COLOR = "#525252";
/**
* 釣り針の大きさ
*/
export const HOOK_SIZE = {width: FISH_FONT_SIZE, height: FISH_FONT_SIZE};
/**
* 釣り針の座標
*/
export const HOOK_POS = {x: ROD_STRING_POS.x - 30, y: ROD_POS.y + ROD_STRING_SIZE.height};
/**
* 釣り上げ時の釣り針の高さ
*/
export const HOOK_POS_WHEN_UP = {x: HOOK_POS.x, y: HOOK_POS.y / 4};
/**
* スコアラベルフォーマット
*/
export const SCORE_LABEL_FORMAT = "SCORE:";
/**
* 制限時間ラベルのフォーマット
*/
export const TIME_LABEL_FORMAT = "TIME:";
/**
* 釣りに要する時間[ミリ秒]
*/
export const FISHING_DURATION = 1000;
/**
* 釣り待機時間[ミリ秒]
*/
export const FISHING_WAIT_DURATION = 300;
/**
* スタック時間[ミリ秒]
*/
export const STUCK_DURATION = 2000;
Resources.ts
はゲーム全体で利用するリソースを g.game.vars
に保存する補助関数です。
import { Timeline } from "@akashic-extension/akashic-timeline";
import { GameMainParameterObject } from "./parameterObject";
/**
* ゲーム全体で使うリソース
*/
interface Resources {
readonly font: g.DynamicFont;
readonly timeline: Timeline;
readonly timeLimit: number;
readonly param: GameMainParameterObject;
}
export function getResources(): Resources {
return g.game.vars.resouces;
}
export function setResources(resouces: Resources): void {
g.game.vars.resouces = resouces;
}
© DWANGO Co., Ltd.