OSDN Git Service

TeleportMachine 鋳型
[h58pcdgame/GameScriptCoreLibrary.git] / www / corelib / core.js
1 /*
2 ****PCD-2013 GameScriptCoreLibrary****
3 Tokyo Gakugei University Senior High School.
4
5 <クライアント要件>
6 Mac OSX: Safari, Chrome
7 Windows: Chrome, IE(9 or later)
8
9 以下の技術が実行可能である必要があります:
10 HTML5
11         Canvas
12         FormData
13
14 <Usage>
15 HTMLソース側に、
16 <div id="MainArea">
17 </div>
18 <div id="Resources">
19 </div>
20 および
21 mainManager = new GameManager();
22 が必要。
23 */
24
25 //
26 //定数
27 //
28
29 var loc = document.location.href;
30
31 var URL_PCD_Root = loc.slice(0, loc.lastIndexOf("/") + 1);
32
33 var URL_PCD_Auth = URL_PCD_Root + "auth.php";
34 var URL_PCD_Audio = URL_PCD_Root + "audio/";
35 var URL_PCD_Stage = URL_PCD_Root + "stage/";
36
37 // ゲームを呼び出す関数       適切なdiv要素で呼び出すとゲームを初期化できる。
38 // つまり、自作ゲームをしたい場合
39 // audio, corelib, images, stageの各フォルダをコピーしたdirにhtmlを置き、<div>.InitGameManager() と実行するスクリプトを書く
40 // stageName == nullだとステージを開始しない。指定する時は.jsおよびパスを省く
41
42 //
43 var CollideBody = 16;
44 var CollideTop = 8;
45 var CollideBottom = 4;
46 var CollideLeft = 2;
47 var CollideRight = 1;
48
49 HTMLDivElement.prototype.InitGameManager = function(stageName)
50 {
51         if(this instanceof HTMLDivElement)
52         {
53                 var man = new GameManager(this, null);
54                 if(stageName) man.loadStageFromNetwork.apply(man, [stageName]);
55                 return man;
56         }else
57         {
58                 throw new TypeError("InitGameManager はdiv要素にしか実行できません");
59         }
60 };
61
62 //
63 //ゲームマネージャー
64 //
65
66 function GameManager(parent, debugTextName){
67         
68         //引数チェック
69         if(debugTextName == undefined) debugTextName = "DebugText";
70         if(parent == undefined) parent = document.getElementById("MainArea");
71         
72         //parentの初期設定
73         if(parent.style.position != 'absolute') parent.style.position = 'relative';
74         
75         this.userID = 0;
76         //サブマネージャーの設定
77         this.networkManager = new NetworkManager(this);
78         this.UIManager = new UIManager(this);
79         this.userManager = new UserManager(this);
80         this.mainArea = parent;
81         //必要最低限のCanvasとコンテキストの設定
82         this.mainCanvas = createCanvas("MainCanvas", 640, 480, 0, 0, 1, parent);
83         this.mainCanvas.style.border = "solid 1px";
84         this.mainContext = this.mainCanvas.getContext('2d');
85         this.debugText = document.getElementById(debugTextName);        //要素が存在しないとnullになり、デバッグが無効になる
86         if(!this.debugText) this.debugText = null;
87         
88         //ブラウザチェック
89         this.isIE = false;
90         if(!this.isAvailableBrowser()){
91                 return null;
92         }
93         //描画コンテキストの初期設定
94         this.mainContext.fillStyle = "rgba(200,255,200,0.5)";
95         this.mainContext.strokeStyle = "rgba(0, 0, 0, 0.5)";
96         this.mainContext.font = "normal 20px sans-serif";
97         //実行中のGameStageオブジェクトを格納
98         this.runningStage = null;
99         this.runningStageName = null;
100         //現在存在しているWidghetのリストを格納
101         this.runningWidgets = [];
102         //タイマーカウントを初期化
103         this.tickCount = 0;
104         
105         // pauseStage()関連の配列。
106         this.stagePaused = false;
107         this.stagePausedFunctions = [];
108         this.stagePausedInitFunctions = [];
109         
110         this.backgroundMusic = null;
111
112         //**イベントリスナー設定**
113         //コールバックを行うために、イベントリスナーのmanagerプロパティにGameManagerのインスタンスを代入する。
114         //timerTick
115         timerTickEventListener.manager = this;
116         window.setInterval(timerTickEventListener, 1000/this.tickPerSecond);
117         
118         //各種コールバック(使用元のスクリプトで使う用)
119         this.stageStartedEvent = null;          //ステージが開始されたときに呼ばれる。引数: stage
120         this.stageStoppedEvent = null;          //ステージが終了されたときに呼ばれる。引数: stage
121 }
122 GameManager.prototype = {
123         //タイマーカウントの秒あたりの回数を設定
124         tickPerSecond: 60,
125         timerTick: function(){
126                 //各オブジェクトの単位時間ごとの動作と再描画を行う
127                 //単位時間ごとの動作
128                 this.tickCount++;
129                 if(!this.stagePaused){
130                         //ポーズしていなければ更新処理
131                         if(this.runningStage){
132                                 //ステージ
133                                 this.runningStage.timerTick();
134                         }
135                 }
136                 // runningStage.timerTick() 内でpauseStage()された時、ここで再度判定しないとWidghetのtickが実行されてしまう
137                 if(!this.stagePaused){
138                         //ウィジェット
139                         for(var i = 0; i < this.runningWidgets.length; i++){
140                                 var w = this.runningWidgets[i];
141                                 if(!w.tick()){
142                                         // Widghetのtick()からfalseで帰ってきたらWidghetを開放
143                                         this.removeWidget(w);
144                                         i--;
145                                 }
146                         }
147                 } else{
148                         //ポーズしているならば処理関数を実行
149                         this.stagePausedFunctions[0]();
150                 }
151                 
152                 
153                 //描画処理
154                 if(this.runningStage){
155                         //ステージ
156                         this.runningStage.draw();
157                 }
158                 //ウィジェット
159                 for(var i = 0; i < this.runningWidgets.length; i++){
160                         var w = this.runningWidgets[i];
161                         w.draw();
162                 }
163                 
164                 //各オブジェクトの走査が終わってから、死亡判定およびステージ再読み込みを行う。
165                 if(this.runningStage && this.runningStage.userControlledCharacter){
166                         if(this.runningStage.userControlledCharacter.HP == 0){
167                                 this.runningStage.userControlledCharacter.HP = this.runningStage.userControlledCharacter.max_HP;
168                                 this.addWidget(new MessageWidgetClass(this, ["死んでしまった……\n", null, function(w){
169                                         w.manager.UIManager.clearInput();
170                                         w.manager.runningStage.userControlledCharacter.HP = w.manager.runningStage.userControlledCharacter.max_HP;
171                                         if(w.manager.runningStageName){
172                                                 w.manager.loadStageFromNetwork(w.manager.runningStageName);
173                                         } else{
174                                                 //ローカルモード時は動作を停止させるだけ
175                                                 w.manager.stopStage();
176                                         }
177                                 }]));
178                                 
179                         }
180                 }
181         },
182         addWidget: function(w){
183                 w.attach();
184                 this.runningWidgets.push(w);
185         },
186         removeWidget: function(w){
187                 if(removeObjectFromArray(this.runningWidgets, w))
188                 {
189                         w.detach();
190                 }
191         },
192         runStage: function(stage){
193                 //新たなステージを開始する
194                 //実行中のステージがあれば終了処理を行わせる。
195                 if(this.runningStage){
196                         this.stopStage();
197                 }
198                 //新たに開始するステージの初期化
199                 //GameManager側の情報をGameStageに渡す。
200                 stage.manager = this;
201                 stage.mainCanvas = this.mainCanvas
202                 stage.debugCanvas = this.debugCanvas
203                 stage.mainContext = this.mainContext
204                 stage.debugContext = this.debugContext
205                 //GameStage側の初期化処理を行わせる。
206                 stage.runStage();
207                 //ネットワーク同期初期化
208                 this.networkManager.joinStage(stage);
209                 //runningStageに登録することで、イベントの通知が開始され、GameStageは実行状態に入る。
210                 this.runningStage = stage;
211                 
212                 if(this.stageStartedEvent)
213                 {
214                         this.stageStartedEvent.apply(window, [stage]);
215                 }
216                 
217         },
218         // func : 停止中にtick毎に呼ばれる関数。
219         // initFunc : pauseが可能となった時に呼ばれる関数。指定しないと、その時点でpauseが不可能だったときに例外がおこる。
220         pauseStage: function(func, initFunc){
221                 //ステージの実行を一時停止する。一時停止中、funcに指定された関数が毎tick毎に呼ばれる
222                 if(!this.stagePaused){
223                         if(!initFunc)
224                         {
225                                 initFunc = null;
226                         }else
227                         {
228                                 initFunc();
229                         }
230                         this.stagePausedFunctions.push(func);
231                         this.stagePausedInitFunctions.push(initFunc);
232                         this.stagePaused = true;
233                         return true;
234                 } else{
235                         if(!initFunc)
236                         {
237                                 throw "pauseStage calling doubled.";
238                         }else
239                         {
240                                 this.stagePausedFunctions.push(func);
241                                 this.stagePausedInitFunctions.push(initFunc);
242                                 return true;
243                         }
244                 }
245         },
246         //func は、pauseStageのinitFuncが実行されてそのpauseStage()による待機状態に入る前にresumeStage()が実行された時の安全策
247         resumeStage: function(func){
248                 //ステージの実行を再開する
249                 if(this.stagePaused) {
250                         //必ずpauseStage()の引数に指定したfunc()の中から呼ばれる・・・はず。
251                         var beforeFunc = this.stagePausedFunctions[0];
252                         if(func)
253                         {
254                                 var ind = -1;
255                                 for(var i in this.stagePausedFunctions)
256                                 {
257                                         if(this.stagePausedFunctions[i] == func)
258                                         {
259                                                 ind = i; break;
260                                         }
261                                 }
262                                 if(ind == -1)
263                                 {
264                                         return false;
265                                 }else
266                                 {
267                                         this.stagePausedFunctions.splice(ind, 1);
268                                         this.stagePausedInitFunctions.splice(ind, 1);
269                                 }
270                         }else
271                         {
272                                 this.stagePausedFunctions.splice(0, 1);
273                                 this.stagePausedInitFunctions.splice(0, 1);
274                         }
275                         if(this.stagePausedFunctions.length == 0)
276                         {
277                                 this.stagePaused = false;
278                         }else
279                         {
280                                 if(this.stagePausedFunctions[0] != beforeFunc) this.stagePausedInitFunctions[0]();
281                         }
282                         return true;
283                 } else{
284                         return false;
285                 }
286         },
287         stopStage: function(){
288                 //現在実行中のステージを終了する
289                 if(this.runningStage){
290                         //runningStageから設定解除することで、イベントの通知は行われなくなる。
291                         var aGameStage = this.runningStage;
292                         this.runningStage = null;
293                         //GameStage側の終了処理を行わせる。
294                         aGameStage.stopStage();
295                         //GameStageインスタンスからGameManagerの情報を削除する。
296                         aGameStage.manager = null;
297                         aGameStage.mainCanvas = null;
298                         aGameStage.debugCanvas = null;
299                         aGameStage.mainContext = null;
300                         aGameStage.debugContext = null;
301                         this.stagePausedFunctions = [];
302                         this.stagePausedInitFunctions = [];
303                         this.stagePaused = false;
304                         
305                         //画面上に表示されたすべてのWidgetを解放する
306                         for(;this.runningWidgets.length>0;)
307                         {
308                                 this.removeWidget(this.runningWidgets[0]);
309                         }
310                         
311                         if(this.stageStoppedEvent)
312                         {
313                                 this.stageStoppedEvent.apply(window, [aGameStage]);
314                         }
315                 }
316         },
317         loadStageFromLocal: function(code){
318                 //各種パスをローカル用に変更
319                 URL_PCD_Root = "./";
320                 URL_PCD_Auth = URL_PCD_Root + "auth.php";
321                 URL_PCD_Audio = URL_PCD_Root + "audio/";
322                 URL_PCD_Stage = URL_PCD_Root + "stage/";
323                 
324                 var stage = eval(code);
325                 mainManager.runStage(stage);
326         },
327         loadStageFromNetwork: function(name){
328                 //URL_PCD_Stage/name.jsを利用してステージを作成する。
329                 var request = this.networkManager.CreateRequestObject();
330                 //同期モード
331                 request.open('GET', URL_PCD_Stage + name + ".js", false);
332                 this.networkManager.RequestObjectDisableCache(request);
333                 request.send(null);
334                 
335                 if(request.status == 0){
336                         alert("ネットワークにアクセスできません。" + request.status + ":" + request.statusText);
337                 }else if((200 <= request.status && request.status < 300) || (request.status == 304)){
338                         if(this.userID != 0){
339                                 var rq2 = this.networkManager.CreateRequestObject();
340                                 //同期モード
341                                 rq2.open('GET', URL_PCD_Auth + "?action=chstg&name=" + name + "&id=" + this.userID);
342                                 this.networkManager.RequestObjectDisableCache(rq2);
343                                 rq2.send(null);
344                         }
345                         var stage = eval(request.responseText);
346                         this.runStage(stage);
347                         this.runningStageName = name;
348                 }else{
349                         alert("サーバーがエラーを返しました。" + request.status + ":" + request.statusText);
350                 }
351         },
352         debugOut: function(str){
353                 if(this.debugText != null)
354                 {
355                         if(this.isIE)
356                         {
357                                 //CRLF
358                                 this.debugText.value = str.replace(/\n/g,"\r\n") + this.debugText.value;
359                         } else{
360                                 //LF
361                                 this.debugText.innerHTML = str + this.debugText.value;
362                         }
363                 }
364         },
365         isAvailableBrowser: function(){
366                 //ブラウザの判別を行う。実行不可能な場合はfalseを返す。
367                 //http://d.hatena.ne.jp/Naotsugu/20110927/1317140891
368                 var userAgent = window.navigator.userAgent.toLowerCase();
369                 var appVersion = window.navigator.appVersion.toLowerCase();
370                 
371                 if (userAgent.indexOf('opera') != -1) {
372                         //opera
373                         this.debugOut("Browser:Opera\n");
374                 } else if (userAgent.indexOf('msie') != -1) {
375                         if (appVersion.indexOf("msie 9.") != -1) {
376                                 //ie9
377                                 this.debugOut("Browser:IE9\n");
378                         } else if (appVersion.indexOf("msie 8.") != -1) {
379                                 //ie8
380                                 this.debugOut("Browser:IE8\n");
381                         } else if (appVersion.indexOf("msie 7.") != -1) {
382                                 //ie7
383                                 this.debugOut("Browser:IE7\n");
384                         } else if (appVersion.indexOf("msie 6.") != -1) {
385                                 //ie6
386                                 this.debugOut("Browser:IE6\n");
387                         } else{
388                                 this.debugOut("Browser:IE?\n");
389                         }
390                         this.isIE = true;
391                 } else if (userAgent.indexOf('chrome') != -1) {
392                         //chrome
393                         this.debugOut("Browser:Chrome\n");
394                 } else if (userAgent.indexOf('safari') != -1) {
395                         //safari
396                         this.debugOut("Browser:Safari\n");
397                 } else if (userAgent.indexOf('gecko') != -1) {
398                         //gecko
399                         this.debugOut("Browser:Gecko\n");
400                 } else {
401                         //unknown
402                         this.debugOut("Browser:Unknown\n");
403                 }
404                 //描画コンテキストからHTML5対応チェック
405                         if(!this.mainCanvas || !this.mainCanvas.getContext){
406                                 //HTML5未対応の場合
407                                 alert("このゲームを遊ぶためには、HTML5に対応しているブラウザ(Google Chrome等)でアクセスしてください...。");
408                         return false;
409                 }
410                 return true;
411         },
412         setBackgroundMusic: function(name){
413                 if(this.backgroundMusic){
414                         //再生していたら止める
415                         this.backgroundMusic.pause();
416                         this.backgroundMusic = null;
417                 }
418                 if(name){
419                         //引数がnullでなければaudioオブジェクトを取得
420                         this.backgroundMusic = createAudio(name);
421                         if(this.backgroundMusic){
422                                 //ループを設定して再生開始
423                                 this.backgroundMusic.loop = true;
424                                 this.backgroundMusic.play();
425                         }
426                 }
427         },
428 };
429
430 //
431 //イベントリスナー
432 //
433 //イベントリスナーにおけるthisは、イベントリスナーを登録したオブジェクトまたはwindowになり、通常とは異なるので注意。
434
435 function timerTickEventListener(event)
436 {
437         timerTickEventListener.manager.timerTick(event);
438 }