| 1 |
///<reference path="all.ts"/> |
| 2 |
module jg { |
| 3 |
/** |
| 4 |
* メインループなどを管理するゲームエンジン。本クラスを基点にjgame.jsのゲームは実行される。 |
| 5 |
* オーバーライドすることも想定しており、https://github.com/tsugehara/jgengine にいくつかサンプルがある。 |
| 6 |
*/ |
| 7 |
export class Game { |
| 8 |
/** 終了フラグ。_がついているが直接操作してもいい */ |
| 9 |
_exit: boolean; |
| 10 |
/** 前回の実行時間 */ |
| 11 |
tick: number; |
| 12 |
/** 前回の描画時間 */ |
| 13 |
renderTick: number; |
| 14 |
/** KeytypeとkeyCodeの関連付けを行うマップ。JGUtilへの移行を検討中 */ |
| 15 |
keymap: any; |
| 16 |
/** ドラッグ中情報。マルチタッチサポート後にインターフェース変更の可能性がある */ |
| 17 |
dragParam: InputPointEvent; |
| 18 |
|
| 19 |
/** 描画オブジェクト */ |
| 20 |
renderer: GameRenderer; |
| 21 |
/** 管理しているシーン */ |
| 22 |
scenes: Scene[]; |
| 23 |
/** 現在のシーン */ |
| 24 |
scene: Scene; |
| 25 |
/** 管理しているリソース。リソースは単一インスタンスであるため、Resource.getInstanceと等価であることが保証されている */ |
| 26 |
resource: Resource; |
| 27 |
/** ゲームの横幅 */ |
| 28 |
width:number; |
| 29 |
/** ゲームの縦幅 */ |
| 30 |
height:number; |
| 31 |
/** 表示上の拡大比率 */ |
| 32 |
scale: number; |
| 33 |
/** 読み込み中シーンのコンストラクタ */ |
| 34 |
loadingSceneClass: any; //これの型指定方法わかんない |
| 35 |
/** 読み込み中シーン。読み込み中の場合のみ値が設定される */ |
| 36 |
loadingScene: LoadingScene; |
| 37 |
/** このゲームの内部ID。単一ページに複数のゲームを表示するような場合以外、特に利用する機会は無い */ |
| 38 |
id: number; |
| 39 |
|
| 40 |
/** 画面描画間隔。指定するとこのFPS以下に画面描画が抑制される */ |
| 41 |
targetFps: number; |
| 42 |
|
| 43 |
/** FPS表示用のDOM要素。将来的に変更される可能性がある */ |
| 44 |
fps: HTMLElement; |
| 45 |
|
| 46 |
/** preload処理の完了時に呼び出されるイベント */ |
| 47 |
loaded: Trigger; |
| 48 |
/** |
| 49 |
* ゲーム内時間の更新時に呼び出されるイベント。 |
| 50 |
* 基本的なゲーム内更新処理はすべてこのイベントのハンドラで実行するが、精度が不要でかつ定期的に実行するアニメーションのような処理は、addTimerメソッドでの実行でもよい。 |
| 51 |
* 引数として経過時間がミリ秒で渡されるため、その経過時間に則った処理を行う必要がある。 |
| 52 |
* また、解放漏れに注意。 |
| 53 |
*/ |
| 54 |
update: Trigger; |
| 55 |
/** |
| 56 |
* ゲーム内タイマー |
| 57 |
*/ |
| 58 |
timers: GameTimer[]; |
| 59 |
/** |
| 60 |
* 描画時に呼び出されるイベント。利用は想定されていないので、通常時はundefined。利用する際は自前でnewする必要がある。 |
| 61 |
* updateとは異なり、経過時間は取得出来ない。 |
| 62 |
*/ |
| 63 |
render: Trigger; |
| 64 |
/** |
| 65 |
* キーが押された時に呼び出されるイベント。InputKeyboardEventをパラメータとして持つ。 |
| 66 |
* 将来的にインターフェース変更の可能性あり。 |
| 67 |
*/ |
| 68 |
keyDown: Trigger; |
| 69 |
/** |
| 70 |
* キーが離された時に呼び出されるイベント。InputKeyboardEventをパラメータとして持つ。 |
| 71 |
* 将来的にインターフェース変更の可能性あり。 |
| 72 |
*/ |
| 73 |
keyUp: Trigger; |
| 74 |
/** |
| 75 |
* ポインティングデバイスが押された時に呼び出されるイベント。InputPointEventをパラメータとして持つ。 |
| 76 |
* 将来的にインターフェース変更の可能性あり。 |
| 77 |
*/ |
| 78 |
pointDown: Trigger; |
| 79 |
/** |
| 80 |
* ポインティングデバイスが離された時に呼び出されるイベント。InputPointEventをパラメータとして持つ。 |
| 81 |
* 将来的にインターフェース変更の可能性あり。 |
| 82 |
*/ |
| 83 |
pointUp: Trigger; |
| 84 |
/** |
| 85 |
* ポインティングデバイスが移動された時に呼び出されるイベント。ただし移動だけでは発生せず、必ずpointDownが事前に発生している必要がある。InputPointEventをパラメータとして持つ。 |
| 86 |
* 将来的にインターフェース変更の可能性あり。 |
| 87 |
*/ |
| 88 |
pointMove: Trigger; |
| 89 |
/** |
| 90 |
* 現在ゲームがフォーカスを持っているかどうか |
| 91 |
*/ |
| 92 |
focus: boolean; |
| 93 |
|
| 94 |
/** |
| 95 |
* 発生済みのユーザ入力イベント群。 |
| 96 |
* jgame.jsではメインループ内で入力処理を発火させるため、keydownなどのDOMイベントでいったんここにプールしてから、メインループでイベント発火という手順を踏む。 |
| 97 |
*/ |
| 98 |
eventQueue: InputEvent[]; |
| 99 |
/** Enumと各種イベント名のマップ */ |
| 100 |
inputEventMap: any; |
| 101 |
/** pointDown発生済みかどうかのフラグ */ |
| 102 |
isPointDown: boolean; |
| 103 |
|
| 104 |
/** このゲームの乱数シード */ |
| 105 |
seed: number; |
| 106 |
/** このゲームの乱数エンジン */ |
| 107 |
mt: MT; |
| 108 |
/** 独自イベント */ |
| 109 |
userEvent: jg.Trigger; |
| 110 |
|
| 111 |
/** |
| 112 |
* 新しいゲームを生成する。 |
| 113 |
* 現在引数をwidth, heightを廃止しargsのみにする変更が検討されている。 |
| 114 |
* @param width ゲームの横幅 |
| 115 |
* @param height ゲームの縦幅 |
| 116 |
* @param args RenderTransferModeを指定すると、このゲームのRenderTransferModeの変更が可能。HTMLElementを指定すると、DOMコンテナを指定可能。文字列を指定すると、window[文字列]のコンストラクタをRendererに指定する |
| 117 |
*/ |
| 118 |
constructor(width:number, height:number, ...args:any[]) { |
| 119 |
this._exit = false; |
| 120 |
this.id = JGUtil.generateId(); |
| 121 |
this.width = width; |
| 122 |
this.height = height; |
| 123 |
this.targetFps = 0; |
| 124 |
this.loadingSceneClass = LoadingScene; |
| 125 |
this.keymap = { |
| 126 |
13: Keytype.Enter, |
| 127 |
27: Keytype.Esc, |
| 128 |
37: Keytype.Left, |
| 129 |
38: Keytype.Up, |
| 130 |
39: Keytype.Right, |
| 131 |
40: Keytype.Down |
| 132 |
} |
| 133 |
|
| 134 |
this.loaded = new Trigger(); |
| 135 |
this.update = new Trigger(); |
| 136 |
this.pointDown = new Trigger(); |
| 137 |
this.pointUp = new Trigger(); |
| 138 |
this.pointMove = new Trigger(); |
| 139 |
this.keyDown = new Trigger(); |
| 140 |
this.keyUp = new Trigger(); |
| 141 |
this.userEvent = new jg.Trigger(); |
| 142 |
this.timers = []; |
| 143 |
|
| 144 |
this.scene = new Scene(this); |
| 145 |
this.scenes = []; |
| 146 |
this.scenes.push(this.scene); |
| 147 |
|
| 148 |
this.resource = Resource.getInstance(); |
| 149 |
|
| 150 |
var container:HTMLElement, transferMode:RenderTransferMode; |
| 151 |
for (var i=2; i<arguments.length; i++) { |
| 152 |
if (arguments[i] instanceof HTMLElement) |
| 153 |
container = <HTMLElement>arguments[i]; |
| 154 |
else if (typeof arguments[i] == "string") { |
| 155 |
this.renderer = new (<any>window[arguments[i]])(this, container, transferMode); |
| 156 |
this.renderer.changeScene(this.scene); |
| 157 |
} else |
| 158 |
transferMode = <RenderTransferMode>arguments[i] |
| 159 |
} |
| 160 |
if (! this.renderer) { |
| 161 |
this.renderer = new GameRenderer(this, container, transferMode); |
| 162 |
this.renderer.changeScene(this.scene); |
| 163 |
} |
| 164 |
|
| 165 |
this.eventQueue = []; |
| 166 |
this.inputEventMap = {} |
| 167 |
this.inputEventMap[InputEventType.Keyboard] = {}; |
| 168 |
this.inputEventMap[InputEventType.Keyboard][InputEventAction.Down] = "keyDown"; |
| 169 |
this.inputEventMap[InputEventType.Keyboard][InputEventAction.Up] = "keyUp"; |
| 170 |
this.inputEventMap[InputEventType.Point] = {}; |
| 171 |
this.inputEventMap[InputEventType.Point][InputEventAction.Down] = "pointDown"; |
| 172 |
this.inputEventMap[InputEventType.Point][InputEventAction.Move] = "pointMove"; |
| 173 |
this.inputEventMap[InputEventType.Point][InputEventAction.Up] = "pointUp"; |
| 174 |
//this.enableKeyboardHandler(); |
| 175 |
//this.enablePointHandler(); |
| 176 |
|
| 177 |
if (document.getElementById("fps_show")) |
| 178 |
this.fps = document.getElementById("fps_show"); |
| 179 |
|
| 180 |
this.renderer.handler.addEventListener("focus", () => { |
| 181 |
this.focus = true; |
| 182 |
}, false) |
| 183 |
this.renderer.handler.addEventListener("blur", () => { |
| 184 |
this.focus = false; |
| 185 |
}, false) |
| 186 |
this.focus = true; |
| 187 |
|
| 188 |
this.setSeed(); |
| 189 |
|
| 190 |
this.main() |
| 191 |
} |
| 192 |
|
| 193 |
/** |
| 194 |
* 乱数シードを指定する。 |
| 195 |
* 再現性のあるゲーム以外で明示的に呼び出す必要はなく、通常は初期化時に自動的に設定される。 |
| 196 |
* @param seed 乱数シード。省略時は自動設定 |
| 197 |
*/ |
| 198 |
setSeed(seed?:number) { |
| 199 |
this.seed = seed === undefined ? new Date().getTime() : seed; |
| 200 |
this.mt = new MT(seed); |
| 201 |
} |
| 202 |
|
| 203 |
/** |
| 204 |
* 乱数を取得する。再現性のあるゲームを作る場合、Math.randomではなくこちらのrandomを利用する必要がある |
| 205 |
* @param min 最小値 |
| 206 |
* @param max 最大値 |
| 207 |
*/ |
| 208 |
random(min:number, max:number) { |
| 209 |
return this.mt.nextInt(min, max-min+1); |
| 210 |
} |
| 211 |
|
| 212 |
/** |
| 213 |
* windowのサイズを取得する |
| 214 |
*/ |
| 215 |
getWindowSize() { |
| 216 |
return { |
| 217 |
width: document.documentElement.clientWidth, |
| 218 |
height: document.documentElement.clientHeight |
| 219 |
}; |
| 220 |
} |
| 221 |
|
| 222 |
/** |
| 223 |
* 現在の画面の大きさに合わせて拡大する。 |
| 224 |
* @param no_center trueに設定すると中央寄せにしない |
| 225 |
*/ |
| 226 |
fitToWindow(no_center?:boolean) { |
| 227 |
var elem = this.renderer.container.parentElement; |
| 228 |
elem.style.margin = "0"; |
| 229 |
elem.style.padding = "0"; |
| 230 |
elem.style.overflow = "hidden"; |
| 231 |
this.renderer.container.style.margin = "0"; |
| 232 |
this.renderer.container.style.padding = "0"; |
| 233 |
|
| 234 |
var size = this.getWindowSize(); |
| 235 |
this.renderer.container.style.width = size.width+"px"; |
| 236 |
this.renderer.container.style.height = size.height+"px"; |
| 237 |
|
| 238 |
this.scale = Math.min( |
| 239 |
size.width / this.width, |
| 240 |
size.height / this.height |
| 241 |
); |
| 242 |
var size2 = { |
| 243 |
width: Math.floor(this.width * this.scale), |
| 244 |
height: Math.floor(this.height * this.scale) |
| 245 |
} |
| 246 |
this.renderer.changeFrontCanvasSize(size2, no_center ? undefined : { |
| 247 |
x:Math.floor((size.width-size2.width) / 2), |
| 248 |
y:Math.floor((size.height-size2.height) / 2) |
| 249 |
}); |
| 250 |
} |
| 251 |
|
| 252 |
/** |
| 253 |
* 背景色を設定する。 |
| 254 |
* このメソッドは廃止が検討されている。 |
| 255 |
* @param r 0~255の範囲で赤色値を指定 |
| 256 |
* @param g 0~255の範囲で緑色値を指定 |
| 257 |
* @param b 0~255の範囲で青色値を指定 |
| 258 |
* @param a 0~255の範囲で透明度を指定 |
| 259 |
*/ |
| 260 |
setBgColor(r:number, g:number, b:number, a:number) { |
| 261 |
for (var i=0; i<this.renderer.bg.data.length; i+=4) { |
| 262 |
this.renderer.bg.data[i] = r; |
| 263 |
this.renderer.bg.data[i+1] = g; |
| 264 |
this.renderer.bg.data[i+2] = b; |
| 265 |
this.renderer.bg.data[i+3] = a; |
| 266 |
} |
| 267 |
} |
| 268 |
|
| 269 |
/** |
| 270 |
* ゲーム内のすべてのオブジェクトをリフレッシュする。 |
| 271 |
* スタンバイからの復帰時などでcanvasが壊れていても本メソッドでの復旧が可能だが、BufferedRendererなど、破壊されるオブジェクトもある点に注意。 |
| 272 |
*/ |
| 273 |
refresh() { |
| 274 |
this.renderer.refresh(); |
| 275 |
for (var i=0; i<this.scenes.length; i++) |
| 276 |
this.scenes[i].refresh(); |
| 277 |
} |
| 278 |
|
| 279 |
/** |
| 280 |
* 現在実行されている環境でタッチイベントが有効化を判定する。 |
| 281 |
* copied by enchant.js (enchant.ENV.TOUCH_ENABLED) |
| 282 |
*/ |
| 283 |
isTouchEnable() { |
| 284 |
var div:any = document.createElement('div'); |
| 285 |
div.setAttribute('ontouchstart', 'return'); |
| 286 |
return typeof div.ontouchstart === 'function'; |
| 287 |
} |
| 288 |
|
| 289 |
/** |
| 290 |
* マウスなどのDOMイベントからjgame.jsが利用可能なoffset値を取得する |
| 291 |
*/ |
| 292 |
getOffsetByEvent(e:any):CommonOffset { |
| 293 |
e.offset = { |
| 294 |
x: e.pageX - this.renderer._pageX, |
| 295 |
y: e.pageY - this.renderer._pageY |
| 296 |
} |
| 297 |
return { |
| 298 |
x: this.scale ? e.offset.x / this.scale : e.offset.x, |
| 299 |
y: this.scale ? e.offset.y / this.scale : e.offset.y |
| 300 |
} |
| 301 |
} |
| 302 |
|
| 303 |
/** |
| 304 |
* DOMのmousedownに対するイベントハンドラ |
| 305 |
* @param e DOMのMouseEvent |
| 306 |
*/ |
| 307 |
onmousedown(e:MouseEvent) { |
| 308 |
this.isPointDown = true; |
| 309 |
this.eventQueue.push(new InputPointEvent( |
| 310 |
InputEventAction.Down, e, this.getOffsetByEvent(e) |
| 311 |
)); |
| 312 |
if (!this.focus) |
| 313 |
this.renderer.handler.focus(); |
| 314 |
e.preventDefault(); |
| 315 |
} |
| 316 |
/** |
| 317 |
* DOMのtouchstartに対するイベントハンドラ。 |
| 318 |
* 現状lib.d.tsに型情報が定義されていないようなので、anyになっている。 |
| 319 |
* @param e DOMのTouchEvent |
| 320 |
*/ |
| 321 |
ontouchstart(e:any) { |
| 322 |
var touches = e.changedTouches; |
| 323 |
this.isPointDown = true; |
| 324 |
for (var i = 0, l = touches.length; i < l; i++) |
| 325 |
this.eventQueue.push(new InputPointEvent( |
| 326 |
InputEventAction.Down, touches[i], this.getOffsetByEvent(touches[i]) |
| 327 |
)); |
| 328 |
|
| 329 |
if (!this.focus) |
| 330 |
this.renderer.handler.focus(); |
| 331 |
|
| 332 |
e.preventDefault(); |
| 333 |
} |
| 334 |
|
| 335 |
/** |
| 336 |
* DOMのmousemoveに対するイベントハンドラ |
| 337 |
* @param e DOMのMouseEvent |
| 338 |
*/ |
| 339 |
onmousemove(e:MouseEvent) { |
| 340 |
if (! this.isPointDown) |
| 341 |
return; |
| 342 |
|
| 343 |
this.eventQueue.push(new InputPointEvent( |
| 344 |
InputEventAction.Move, e, this.getOffsetByEvent(e) |
| 345 |
)); |
| 346 |
|
| 347 |
e.preventDefault(); |
| 348 |
} |
| 349 |
/** |
| 350 |
* DOMのtouchmoveに対するイベントハンドラ。 |
| 351 |
* 現状lib.d.tsに型情報が定義されていないようなので、anyになっている。 |
| 352 |
* @param e DOMのTouchEvent |
| 353 |
*/ |
| 354 |
ontouchmove(e:any) { |
| 355 |
if (! this.isPointDown) |
| 356 |
return; |
| 357 |
|
| 358 |
var touches = e.changedTouches; |
| 359 |
for (var i = 0, l = touches.length; i < l; i++) |
| 360 |
this.eventQueue.push(new InputPointEvent( |
| 361 |
InputEventAction.Move, touches[i], this.getOffsetByEvent(touches[i]) |
| 362 |
)); |
| 363 |
|
| 364 |
e.preventDefault(); |
| 365 |
} |
| 366 |
|
| 367 |
/** |
| 368 |
* DOMのmouseupに対するイベントハンドラ |
| 369 |
* @param e DOMのMouseEvent |
| 370 |
*/ |
| 371 |
onmouseup(e:MouseEvent) { |
| 372 |
if (! this.isPointDown) |
| 373 |
return; |
| 374 |
|
| 375 |
this.eventQueue.push(new InputPointEvent( |
| 376 |
InputEventAction.Up, e, this.getOffsetByEvent(e) |
| 377 |
)); |
| 378 |
|
| 379 |
this.isPointDown = false; |
| 380 |
|
| 381 |
e.preventDefault(); |
| 382 |
} |
| 383 |
/** |
| 384 |
* DOMのtouchendに対するイベントハンドラ。 |
| 385 |
* 現状lib.d.tsに型情報が定義されていないようなので、anyになっている。 |
| 386 |
* @param e DOMのTouchEvent |
| 387 |
*/ |
| 388 |
ontouchend(e:any) { |
| 389 |
if (! this.isPointDown) |
| 390 |
return; |
| 391 |
|
| 392 |
var touches = e.changedTouches; |
| 393 |
for (var i = 0, l = touches.length; i < l; i++) |
| 394 |
this.eventQueue.push(new InputPointEvent( |
| 395 |
InputEventAction.Up, touches[i], this.getOffsetByEvent(touches[i]) |
| 396 |
)); |
| 397 |
|
| 398 |
this.isPointDown = false; |
| 399 |
|
| 400 |
e.preventDefault(); |
| 401 |
} |
| 402 |
|
| 403 |
|
| 404 |
/** |
| 405 |
* ポインティングイベントを有効にする。 |
| 406 |
* 無効化処理も実行するため、何度も切り替えるアプリケーションの場合pointDown, pointMove, pointUpのイベントハンドラを独自に復旧する必要がある点に注意。 |
| 407 |
*/ |
| 408 |
enablePointHandler() { |
| 409 |
this.disablePointHandler(); |
| 410 |
|
| 411 |
try { |
| 412 |
if (this.isTouchEnable()) { |
| 413 |
this.renderer.handler.addEventListener("touchstart", JGUtil.createIdProxy(this.id, this.ontouchstart, this), false); |
| 414 |
this.renderer.handler.addEventListener("touchmove" , JGUtil.createIdProxy(this.id, this.ontouchmove, this) , false); |
| 415 |
this.renderer.handler.addEventListener("touchend" , JGUtil.createIdProxy(this.id, this.ontouchend, this) , false); |
| 416 |
} else { |
| 417 |
this.renderer.handler.addEventListener("mousedown" , JGUtil.createIdProxy(this.id, this.onmousedown, this) , false); |
| 418 |
this.renderer.handler.addEventListener("mousemove" , JGUtil.createIdProxy(this.id, this.onmousemove, this) , false); |
| 419 |
this.renderer.handler.addEventListener("mouseup" , JGUtil.createIdProxy(this.id, this.onmouseup, this) , false); |
| 420 |
} |
| 421 |
} catch (ex) { |
| 422 |
// ignore error of addEventListener |
| 423 |
} |
| 424 |
} |
| 425 |
/** |
| 426 |
* ポインティングイベントを無効化する。 |
| 427 |
*/ |
| 428 |
disablePointHandler() { |
| 429 |
this.dragParam = null; |
| 430 |
try { |
| 431 |
if (this.isTouchEnable()) { |
| 432 |
if (JGUtil.getIdProxy(this.id, this.ontouchstart, this)) { |
| 433 |
this.renderer.handler.removeEventListener("touchstart", JGUtil.getIdProxy(this.id, this.ontouchstart, this), false); |
| 434 |
this.renderer.handler.removeEventListener("touchmove" , JGUtil.getIdProxy(this.id, this.ontouchmove, this) , false); |
| 435 |
this.renderer.handler.removeEventListener("touchend" , JGUtil.getIdProxy(this.id, this.ontouchend, this) , false); |
| 436 |
JGUtil.deleteIdProxy(this.id, this.ontouchstart, this); |
| 437 |
JGUtil.deleteIdProxy(this.id, this.ontouchmove, this); |
| 438 |
JGUtil.deleteIdProxy(this.id, this.ontouchend, this); |
| 439 |
} |
| 440 |
} else { |
| 441 |
if (JGUtil.getIdProxy(this.id, this.onmousedown, this)) { |
| 442 |
this.renderer.handler.removeEventListener("mousedown" , JGUtil.getIdProxy(this.id, this.onmousedown, this) , false); |
| 443 |
this.renderer.handler.removeEventListener("mousemove" , JGUtil.getIdProxy(this.id, this.onmousemove, this) , false); |
| 444 |
this.renderer.handler.removeEventListener("mouseup" , JGUtil.getIdProxy(this.id, this.onmouseup, this) , false); |
| 445 |
JGUtil.deleteIdProxy(this.id, this.onmousedown, this); |
| 446 |
JGUtil.deleteIdProxy(this.id, this.onmousemove, this); |
| 447 |
JGUtil.deleteIdProxy(this.id, this.onmouseup, this); |
| 448 |
} |
| 449 |
} |
| 450 |
} catch (ex) { |
| 451 |
// ignore error of removeEventListener |
| 452 |
} |
| 453 |
} |
| 454 |
|
| 455 |
/** |
| 456 |
* DOMのkeydownイベントに対するイベントハンドラ。 |
| 457 |
* lib.d.tsにおいて型情報が不明なためanyになっている。KeyboardEventExtensionsでいいのだろうか。 |
| 458 |
* @param e DOMのキーボードイベントパラメータ。内部的にはkeyCodeしか利用していない |
| 459 |
*/ |
| 460 |
onkeydown(e:any) { |
| 461 |
var keyParam = new InputKeyboardEvent(InputEventAction.Down,this.keymap[e.keyCode], e); |
| 462 |
this.eventQueue.push(keyParam); |
| 463 |
if (this.keymap[e.keyCode] != undefined) |
| 464 |
e.preventDefault(); |
| 465 |
} |
| 466 |
|
| 467 |
/** |
| 468 |
* DOMのkeyupイベントに対するイベントハンドラ。 |
| 469 |
* lib.d.tsにおいて型情報が不明なためanyになっている。KeyboardEventExtensionsでいいのだろうか。 |
| 470 |
* @param e DOMのキーボードイベントパラメータ。内部的にはkeyCodeしか利用していない |
| 471 |
*/ |
| 472 |
onkeyup(e:any) { |
| 473 |
var keyParam = new InputKeyboardEvent(InputEventAction.Up,this.keymap[e.keyCode], e); |
| 474 |
this.eventQueue.push(keyParam); |
| 475 |
if (this.keymap[e.keyCode] != undefined) |
| 476 |
e.preventDefault(); |
| 477 |
} |
| 478 |
|
| 479 |
/** |
| 480 |
* キーボードイベントを有効化する。 |
| 481 |
* このイベントを有効にしてしまうと、keymapに登録されているキーコードにpreventDefaultが動くため、textareaなどが存在するページで実行する場合は注意が必要。 |
| 482 |
* また無効化処理も実行するため、何度も切り替えるアプリケーションの場合keyDown, keyUpのイベントハンドラを独自に復旧する必要がある点に注意。 |
| 483 |
*/ |
| 484 |
enableKeyboardHandler() { |
| 485 |
this.disableKeyboardHandler(); |
| 486 |
try { |
| 487 |
this.renderer.handler.addEventListener("keydown", JGUtil.createIdProxy(this.id, this.onkeydown, this), false); |
| 488 |
this.renderer.handler.addEventListener("keyup" , JGUtil.createIdProxy(this.id, this.onkeyup, this) , false); |
| 489 |
} catch(ex) { |
| 490 |
//ignore error of addEventListener |
| 491 |
} |
| 492 |
} |
| 493 |
/** |
| 494 |
* キーボードイベントを無効化する。 |
| 495 |
*/ |
| 496 |
disableKeyboardHandler() { |
| 497 |
if (JGUtil.getIdProxy(this.id, this.onkeydown, this)) { |
| 498 |
this.renderer.handler.removeEventListener("keydown", JGUtil.getIdProxy(this.id, this.onkeydown, this), false); |
| 499 |
this.renderer.handler.removeEventListener("keyup" , JGUtil.getIdProxy(this.id, this.onkeyup, this) , false); |
| 500 |
JGUtil.deleteIdProxy(this.id, this.onkeydown, this); |
| 501 |
JGUtil.deleteIdProxy(this.id, this.onkeyup, this); |
| 502 |
} |
| 503 |
} |
| 504 |
|
| 505 |
/** |
| 506 |
* 指定した時間間隔で実行するタイマーを追加する。 |
| 507 |
* このタイマーは大体のタイマーであるため、アニメーションなど正確性の不要な作業のみの利用に限定するべきである。 |
| 508 |
* @param wait 実行時間間隔をミリ秒で指定する |
| 509 |
* @param owner タイマーのコールバックに対するthis |
| 510 |
* @param handler コールバック |
| 511 |
*/ |
| 512 |
addTimer(wait:number, owner:any, handler:Function) { |
| 513 |
var timer:GameTimer = null; |
| 514 |
for (var i=0; i<this.timers.length; i++) { |
| 515 |
if (this.timers[i].wait == wait) { |
| 516 |
timer = this.timers[i]; |
| 517 |
break; |
| 518 |
} |
| 519 |
} |
| 520 |
if (timer == null) { |
| 521 |
timer = new GameTimer(wait); |
| 522 |
this.timers.push(timer); |
| 523 |
} |
| 524 |
timer.trigger.handle(owner, handler); |
| 525 |
} |
| 526 |
|
| 527 |
/** |
| 528 |
* 指定した時間間隔で実行するタイマーのコールバックを削除する。 |
| 529 |
* 一つもコールバックが存在しないタイマー自体の削除処理も行っている。 |
| 530 |
* @param wait 実行時間間隔をミリ秒で指定する |
| 531 |
* @param owner タイマーのコールバックに対するthis |
| 532 |
* @param handler コールバック |
| 533 |
*/ |
| 534 |
removeTimer(wait:number, owner:any, handler:Function) { |
| 535 |
var timer:GameTimer = null; |
| 536 |
for (var i=0; i<this.timers.length; i++) { |
| 537 |
if (this.timers[i].wait == wait) { |
| 538 |
timer = this.timers[i]; |
| 539 |
break; |
| 540 |
} |
| 541 |
} |
| 542 |
if (timer == null) |
| 543 |
throw "error removeTimer: dont have "+wait+" timer"; |
| 544 |
|
| 545 |
timer.trigger.remove(owner, handler); |
| 546 |
} |
| 547 |
|
| 548 |
/** |
| 549 |
* 指定したオーナーのタイマーに対するコールバックをすべて削除する |
| 550 |
* @param owner タイマーのコールバックに対するthis |
| 551 |
*/ |
| 552 |
removeTimerAll(owner:any) { |
| 553 |
for (var i=0; i<this.timers.length; i++) { |
| 554 |
this.timers[i].trigger.removeAll(owner); |
| 555 |
} |
| 556 |
} |
| 557 |
|
| 558 |
/** |
| 559 |
* シーンを変更する |
| 560 |
* @param scene 変更後のシーン |
| 561 |
* @param effect 変更時にかけるエフェクト。省略時はエフェクト無しになる。通常、EffectTypeの値を指定する |
| 562 |
* @param endOldScene trueを指定すると、切り替え前に前のシーンを削除する。 |
| 563 |
*/ |
| 564 |
changeScene(scene:Scene, effect?:any, endOldScene?:boolean) { |
| 565 |
if (effect) { |
| 566 |
var currentScene = this.scene; |
| 567 |
Effect.sceneEffect(this, currentScene, scene, effect, () => { |
| 568 |
this.endScene(); |
| 569 |
this.changeScene(scene); |
| 570 |
}, endOldScene); |
| 571 |
return; |
| 572 |
} |
| 573 |
this.scenes.push(scene); |
| 574 |
scene.game = this; |
| 575 |
this.scene.hid.fire(); |
| 576 |
this.scene = scene; |
| 577 |
this.renderer.changeScene(this.scene); |
| 578 |
this.scene.started.fire(); |
| 579 |
} |
| 580 |
|
| 581 |
/** |
| 582 |
* シーンを終了する |
| 583 |
* @param effect 変更時にかけるエフェクト。省略時はエフェクト無しになる。通常、EffectTypeの値を指定する |
| 584 |
*/ |
| 585 |
endScene(effect?:any) { |
| 586 |
if (this.scenes.length == 1) { |
| 587 |
this.end(); |
| 588 |
return; |
| 589 |
} |
| 590 |
if (effect) { |
| 591 |
Effect.sceneEffect(this, this.scene, this.scenes[this.scenes.length-2], effect, () => { |
| 592 |
this.endScene(); |
| 593 |
}, true); |
| 594 |
return; |
| 595 |
} |
| 596 |
this.scene.destroy(); |
| 597 |
this.scenes.pop(); |
| 598 |
this.scene.ended.fire(); |
| 599 |
this.scene = this.scenes[this.scenes.length-1]; |
| 600 |
this.renderer.changeScene(this.scene); |
| 601 |
this.scene.showed.fire(); |
| 602 |
} |
| 603 |
|
| 604 |
/** |
| 605 |
* 指定した名前のリソースを取得する。サウンドはsメソッドでの取得である点に注意 |
| 606 |
* @param name リソース名 |
| 607 |
*/ |
| 608 |
r(name:string) { |
| 609 |
return this.resource.get(name); |
| 610 |
} |
| 611 |
|
| 612 |
/** |
| 613 |
* 指定した名前のサウンドリソースを取得する。画像などはrメソッドの取得である点に注意 |
| 614 |
* @param name サウンドリソース名 |
| 615 |
*/ |
| 616 |
s(name:string) { |
| 617 |
return this.resource.sound(name); |
| 618 |
} |
| 619 |
|
| 620 |
/** |
| 621 |
* 事前の読み込み処理を行う。 |
| 622 |
* 配列、オブジェクト、文字列のいずれかが指定可能で、複数回の呼び出しも可能。 |
| 623 |
* 配列の場合、リソース名はURLと同じ扱いになる。 |
| 624 |
* game.preload(["a.png", "b.png", "c.png"]) |
| 625 |
* オブジェクトの場合、リソース名はキーで値がURLとなる。 |
| 626 |
* game.preload({a: "a.png", b: "b.png", c: "c.png"}) |
| 627 |
* 文字列の場合、第二、第三引数などを配列と同じように処理する。 |
| 628 |
* game.preload("a.png", "b.png", "c.png") |
| 629 |
* @param ary 配列、オブジェクト、文字列のいずれかが指定可能。 |
| 630 |
*/ |
| 631 |
preload(ary: any) { |
| 632 |
if (ary instanceof Array) { |
| 633 |
for (var i=0; i<ary.length; i++) |
| 634 |
this.resource.load(ary[i], ary[i]); |
| 635 |
} else if (typeof ary == "string") { |
| 636 |
for (var i=0; i<arguments.length; i++) |
| 637 |
this.resource.load(arguments[i], arguments[i]); |
| 638 |
} else { |
| 639 |
for (var j in ary) |
| 640 |
this.resource.load(j, ary[j]); |
| 641 |
} |
| 642 |
|
| 643 |
if (this.loadingSceneClass && !this.loadingScene) |
| 644 |
this.setLoadingScene(this.loadingSceneClass); |
| 645 |
} |
| 646 |
|
| 647 |
/** |
| 648 |
* 他のライブラリで読み込み中のリソースをjgame.jsに登録する。 |
| 649 |
* @param identity リソースの識別名 |
| 650 |
*/ |
| 651 |
preloadOther(identity:string) { |
| 652 |
this.resource.loadManual(identity); |
| 653 |
} |
| 654 |
|
| 655 |
/** |
| 656 |
* 他のライブラリで読み込み中のリソースが読み込み完了となった事をjgame.jsに通知する |
| 657 |
* @param identity リソースの識別名 |
| 658 |
*/ |
| 659 |
preloadCompleteOther(identity:string) { |
| 660 |
this.resource.completeManual(identity); |
| 661 |
} |
| 662 |
|
| 663 |
/** |
| 664 |
* 読み込み中シーンを設定し、現在のシーンを切り替える。 |
| 665 |
* 現在既に読み込み中である場合、本メソッドは処理を行わない。 |
| 666 |
*/ |
| 667 |
setLoadingScene(scene:any) { |
| 668 |
if (! this.loadingScene) { |
| 669 |
if (scene instanceof LoadingScene) |
| 670 |
this.loadingScene = scene; |
| 671 |
else |
| 672 |
this.loadingScene = new scene(this, this.resource); |
| 673 |
|
| 674 |
this.loadingScene.finished.handle(this, this.preloadComplete); |
| 675 |
this.changeScene(this.loadingScene); |
| 676 |
} |
| 677 |
} |
| 678 |
|
| 679 |
/** |
| 680 |
* preloadの完了処理として、loadingSceneフィールドの削除、loadedイベントの発火を行う |
| 681 |
*/ |
| 682 |
preloadComplete() { |
| 683 |
if (this.loadingScene) |
| 684 |
delete this.loadingScene; |
| 685 |
this.loaded.fire(); |
| 686 |
} |
| 687 |
|
| 688 |
/** |
| 689 |
* ゲームを終了する。 |
| 690 |
* 実態はメインループの終了のみであり、本処理実行後でも_exitフラグの削除とmainメソッドの再実行によりゲームは再開可能 |
| 691 |
*/ |
| 692 |
end() { |
| 693 |
if (this._exit) |
| 694 |
return false; |
| 695 |
this.renderer.render(); |
| 696 |
this._exit = true; |
| 697 |
return true; |
| 698 |
} |
| 699 |
|
| 700 |
/** |
| 701 |
* ゲームを再開する。 |
| 702 |
* endによって行われたメインループの再起動。 |
| 703 |
*/ |
| 704 |
resume() { |
| 705 |
if (!this._exit) |
| 706 |
return false; |
| 707 |
this._exit = false; |
| 708 |
this.main(); |
| 709 |
return true; |
| 710 |
} |
| 711 |
|
| 712 |
/** |
| 713 |
* ポインティングされたEntityを設定する |
| 714 |
* @param param 対象のポインティングイベント |
| 715 |
*/ |
| 716 |
setPointingEntity(param:InputPointEvent) { |
| 717 |
var layers = this.scene.getLayerArray(); |
| 718 |
var layer; |
| 719 |
var offset = param.point; |
| 720 |
while (layer = layers.pop()) { //上のレイヤーから先に処理 |
| 721 |
if (! layer.pointCapture) |
| 722 |
continue; |
| 723 |
|
| 724 |
var dragObj = layer.getEntityByPoint(offset); |
| 725 |
if (! dragObj) |
| 726 |
dragObj = layer; |
| 727 |
|
| 728 |
param.set(dragObj); |
| 729 |
this.dragParam = param; |
| 730 |
|
| 731 |
break; |
| 732 |
} |
| 733 |
} |
| 734 |
|
| 735 |
/** |
| 736 |
* 入力イベントを実行する |
| 737 |
*/ |
| 738 |
raiseInputEvent() { |
| 739 |
var e:InputEvent; |
| 740 |
while (e = this.eventQueue.shift()) { |
| 741 |
if (! this.inputEventMap[e.type]) { |
| 742 |
this.userEvent.fire(e); |
| 743 |
continue; |
| 744 |
} |
| 745 |
|
| 746 |
var n = this.inputEventMap[e.type][e.action]; |
| 747 |
if (e.type == InputEventType.Keyboard) { |
| 748 |
if (this.scene[n]) |
| 749 |
this.scene[n].fire(e); |
| 750 |
this[n].fire(e); |
| 751 |
} else { |
| 752 |
if (e.action == InputEventAction.Down) |
| 753 |
this.setPointingEntity(<InputPointEvent>e); |
| 754 |
else if (!this.dragParam) |
| 755 |
continue; |
| 756 |
else |
| 757 |
(<InputPointEvent>e).set(this.dragParam.entity); |
| 758 |
|
| 759 |
if ((<InputPointEvent>e).entity && (<InputPointEvent>e).entity[n]) |
| 760 |
(<InputPointEvent>e).entity[n].fire(e); |
| 761 |
if (this.scene[n]) |
| 762 |
this.scene[n].fire(e); |
| 763 |
|
| 764 |
this[n].fire(e); |
| 765 |
} |
| 766 |
} |
| 767 |
} |
| 768 |
|
| 769 |
/** |
| 770 |
* メインループ |
| 771 |
*/ |
| 772 |
main() { |
| 773 |
var fps_stack:number[] = []; |
| 774 |
var _main = (t:number) => { |
| 775 |
if (this._exit) |
| 776 |
return; |
| 777 |
|
| 778 |
if (t === undefined) |
| 779 |
t = Date.now ? Date.now() : new Date().getTime(); |
| 780 |
if ((this.tick+500) < t || this.tick > t) { |
| 781 |
//this.tick > t自体はタブ切り替え程度でも結構頻発する |
| 782 |
if ((this.tick+10000) < t || (this.tick > t+500)) |
| 783 |
this.refresh(); |
| 784 |
this.tick = t - 1000 / 60; |
| 785 |
this.renderTick = t; |
| 786 |
} |
| 787 |
|
| 788 |
var time = t - this.tick; |
| 789 |
this.raiseInputEvent(); |
| 790 |
this.update.fire(time); |
| 791 |
this.tick = t; |
| 792 |
|
| 793 |
for (var i=0; i<this.timers.length; i++) |
| 794 |
this.timers[i].tryFire(time); |
| 795 |
|
| 796 |
if (this.renderTick <= t) { |
| 797 |
if (this.render) |
| 798 |
this.render.fire(); |
| 799 |
|
| 800 |
this.renderer.render(); |
| 801 |
if (this.targetFps) |
| 802 |
this.renderTick = t+this.targetFps; |
| 803 |
if (this.fps) { |
| 804 |
if (fps_stack.length == 19) { |
| 805 |
this.fps.innerHTML = Math.round(20000 / (t-fps_stack[0])).toString(); |
| 806 |
fps_stack = []; |
| 807 |
} else { |
| 808 |
fps_stack.push(t); |
| 809 |
} |
| 810 |
} |
| 811 |
} |
| 812 |
|
| 813 |
window.requestAnimationFrame(_main); |
| 814 |
} |
| 815 |
|
| 816 |
this.tick = 0; |
| 817 |
this.renderTick = 0; |
| 818 |
window.requestAnimationFrame(_main); |
| 819 |
} |
| 820 |
|
| 821 |
/** |
| 822 |
* フルスクリーン化を行う |
| 823 |
*/ |
| 824 |
fullscreen() { |
| 825 |
var t = this.renderer.container; |
| 826 |
if (t["requestFullScreen"]) |
| 827 |
t["requestFullScreen"](); |
| 828 |
else if (t["webkitRequestFullScreen"]) |
| 829 |
t["webkitRequestFullScreen"](); |
| 830 |
else if (t["mozRequestFullScreen"]) |
| 831 |
t["mozRequestFullScreen"](); |
| 832 |
else |
| 833 |
return false; |
| 834 |
return true; |
| 835 |
} |
| 836 |
|
| 837 |
/** |
| 838 |
* フルスクリーンを終了する |
| 839 |
*/ |
| 840 |
exitFullscreen() { |
| 841 |
var t = this.renderer.container; |
| 842 |
if (t["exitFullscreen"]) |
| 843 |
t["exitFullscreen"](); |
| 844 |
else if (t["webkitCancelFullScreen"]) |
| 845 |
t["webkitCancelFullScreen"](); |
| 846 |
else if (t["mozCancelFullScreen"]) |
| 847 |
t["mozCancelFullScreen"](); |
| 848 |
else |
| 849 |
return false; |
| 850 |
return true; |
| 851 |
} |
| 852 |
} |
| 853 |
} |