OSDN Git Service

ネットワーク関連修正。ステージの変更も含まれるので、ステージ作成者の皆さんは変更箇所を上書きしないよう注意してください。
[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         //タイマーカウントを初期化
104         this.tickCount = 0;
105         //タイマーカウントの秒あたりの回数を設定
106         this.tickPerSecond = 60;
107         // pauseStage()が呼ばれたときにnullじゃなくなる
108         this.stagePausedFunction = null;
109         
110         this.backgroundMusic = null;
111         //**イベントリスナー設定**
112         //コールバックを行うために、イベントリスナーのmanagerプロパティにGameManagerのインスタンスを代入する。
113         //timerTick
114         timerTickEventListener.manager = this;
115         window.setInterval(timerTickEventListener, 1000/this.tickPerSecond);
116         
117         //各種コールバック(使用元のスクリプトで使う用)
118         this.stageStartedEvent = null;          //ステージが開始されたときに呼ばれる。引数: stage
119         this.stageStoppedEvent = null;          //ステージが終了されたときに呼ばれる。引数: stage
120 }
121 GameManager.prototype = {
122         timerTick: function(){
123                 //各オブジェクトの単位時間ごとの動作と再描画を行う
124                 //単位時間ごとの動作
125                 this.tickCount++;
126                 if(this.stagePausedFunction == null){
127                         //ポーズしていなければ更新処理
128                         if(this.runningStage){
129                                 //ステージ
130                                 this.runningStage.timerTick();
131                         }
132                 }
133                 // runningStage.timerTick() 内でpauseStage()された時、ここで再度判定しないとWidghetのtickが実行されてしまう
134                 if(this.stagePausedFunction == null){
135                         //ウィジェット
136                         for(var i = 0; i < this.runningWidgets.length; i++){
137                                 var w = this.runningWidgets[i];
138                                 if(!w.tick()){
139                                         // Widghetのtick()からfalseで帰ってきたらWidghetを開放
140                                         this.removeWidget(w);
141                                         i--;
142                                 }
143                         }
144                 } else{
145                         //ポーズしているならば処理関数を実行
146                         this.stagePausedFunction();
147                 }
148                 
149                 
150                 //描画処理
151                 if(this.runningStage){
152                         //ステージ
153                         this.runningStage.draw();
154                 }
155                 //ウィジェット
156                 for(var i = 0; i < this.runningWidgets.length; i++){
157                         var w = this.runningWidgets[i];
158                         w.draw();
159                 }
160                 
161                 //各オブジェクトの走査が終わってから、死亡判定およびステージ再読み込みを行う。
162                 if(this.runningStage && this.runningStage.userControlledCharacter){
163                         if(this.runningStage.userControlledCharacter.HP == 0){
164                                 this.runningStage.userControlledCharacter.HP = this.runningStage.userControlledCharacter.max_HP;
165                                 this.addWidget(new MessageWidgetClass(this, ["死んでしまった……\n", null, function(w){
166                                         w.manager.UIManager.clearInput();
167                                         w.manager.runningStage.userControlledCharacter.HP = w.manager.runningStage.userControlledCharacter.max_HP;
168                                         if(w.manager.runningStageName){
169                                                 w.manager.loadStageFromNetwork(w.manager.runningStageName);
170                                         } else{
171                                                 //ローカルモード時は動作を停止させるだけ
172                                                 w.manager.stopStage();
173                                         }
174                                 }]));
175                                 
176                         }
177                 }
178         },
179         addWidget: function(w){
180                 w.attach();
181                 this.runningWidgets.push(w);
182         },
183         removeWidget: function(w){
184                 if(removeObjectFromArray(this.runningWidgets, w))
185                 {
186                         w.detach();
187                 }
188         },
189         runStage: function(stage){
190                 //新たなステージを開始する
191                 //実行中のステージがあれば終了処理を行わせる。
192                 if(this.runningStage){
193                         this.stopStage();
194                 }
195                 //新たに開始するステージの初期化
196                 //GameManager側の情報をGameStageに渡す。
197                 stage.manager = this;
198                 stage.mainCanvas = this.mainCanvas
199                 stage.debugCanvas = this.debugCanvas
200                 stage.mainContext = this.mainContext
201                 stage.debugContext = this.debugContext
202                 //GameStage側の初期化処理を行わせる。
203                 stage.runStage();
204                 //ネットワーク同期初期化
205                 this.networkManager.joinStage(stage);
206                 //runningStageに登録することで、イベントの通知が開始され、GameStageは実行状態に入る。
207                 this.runningStage = stage;
208                 
209                 if(this.stageStartedEvent)
210                 {
211                         this.stageStartedEvent.apply(window, [stage]);
212                 }
213                 
214         },
215         pauseStage: function(func){
216                 //ステージの実行を一時停止する。一時停止中、funcに指定された関数が毎tick毎に呼ばれる
217                 if(this.stagePausedFunction == null){
218                         this.stagePausedFunction = func;
219                         return true;
220                 } else{
221                         //ステージが一時停止中のfunc()の中から二重にpauseStage()を呼んではいけない
222                         return false;
223                 }
224         },
225         resumeStage: function(){
226                 //ステージの実行を再開する
227                 if(this.stagePausedFunction != null) {
228                         //必ずpauseStage()の引数に指定したfunc()の中から呼ばれる・・・はず。
229                         this.stagePausedFunction = null;
230                         return true;
231                 } else{
232                         return false;
233                 }
234         },
235         stopStage: function(){
236                 //現在実行中のステージを終了する
237                 if(this.runningStage){
238                         //runningStageから設定解除することで、イベントの通知は行われなくなる。
239                         var aGameStage = this.runningStage;
240                         this.runningStage = null;
241                         //GameStage側の終了処理を行わせる。
242                         aGameStage.stopStage();
243                         //GameStageインスタンスからGameManagerの情報を削除する。
244                         aGameStage.manager = null;
245                         aGameStage.mainCanvas = null;
246                         aGameStage.debugCanvas = null;
247                         aGameStage.mainContext = null;
248                         aGameStage.debugContext = null;
249                         
250                         //画面上に表示されたすべてのWidgetを解放する
251                         for(;this.runningWidgets.length>0;)
252                         {
253                                 this.removeWidget(this.runningWidgets[0]);
254                         }
255                         
256                         if(this.stageStoppedEvent)
257                         {
258                                 this.stageStoppedEvent.apply(window, [aGameStage]);
259                         }
260                 }
261         },
262         loadStageFromLocal: function(code){
263                 //各種パスをローカル用に変更
264                 URL_PCD_Root = "./";
265                 URL_PCD_Auth = URL_PCD_Root + "auth.php";
266                 URL_PCD_Audio = URL_PCD_Root + "audio/";
267                 URL_PCD_Stage = URL_PCD_Root + "stage/";
268                 
269                 var stage = eval(code);
270                 mainManager.runStage(stage);
271         },
272         loadStageFromNetwork: function(name){
273                 //URL_PCD_Stage/name.jsを利用してステージを作成する。
274                 request = this.networkManager.CreateRequestObject();
275                 //同期モード
276                 request.open('GET', URL_PCD_Stage + name + ".js", false);
277                 this.networkManager.RequestObjectDisableCache(request);
278                 request.send(null);
279                 
280                 if(request.status == 0){
281                         alert("ネットワークにアクセスできません。" + request.status + ":" + request.statusText);
282                 }else if((200 <= request.status && request.status < 300) || (request.status == 304)){
283                         var stage = eval(request.responseText);
284                         this.runStage(stage);
285                         this.runningStageName = name;
286                 }else{
287                         alert("サーバーがエラーを返しました。" + request.status + ":" + request.statusText);
288                 }
289         },
290         debugOut: function(str){
291                 if(this.debugText != null)
292                 {
293                         if(this.isIE)
294                         {
295                                 //CRLF
296                                 this.debugText.value = str.replace(/\n/g,"\r\n") + this.debugText.value;
297                         } else{
298                                 //LF
299                                 this.debugText.innerHTML = str + this.debugText.value;
300                         }
301                 }
302         },
303         isAvailableBrowser: function(){
304                 //ブラウザの判別を行う。実行不可能な場合はfalseを返す。
305                 //http://d.hatena.ne.jp/Naotsugu/20110927/1317140891
306                 var userAgent = window.navigator.userAgent.toLowerCase();
307                 var appVersion = window.navigator.appVersion.toLowerCase();
308                 
309                 if (userAgent.indexOf('opera') != -1) {
310                         //opera
311                         this.debugOut("Browser:Opera\n");
312                 } else if (userAgent.indexOf('msie') != -1) {
313                         if (appVersion.indexOf("msie 9.") != -1) {
314                                 //ie9
315                                 this.debugOut("Browser:IE9\n");
316                         } else if (appVersion.indexOf("msie 8.") != -1) {
317                                 //ie8
318                                 this.debugOut("Browser:IE8\n");
319                         } else if (appVersion.indexOf("msie 7.") != -1) {
320                                 //ie7
321                                 this.debugOut("Browser:IE7\n");
322                         } else if (appVersion.indexOf("msie 6.") != -1) {
323                                 //ie6
324                                 this.debugOut("Browser:IE6\n");
325                         } else{
326                                 this.debugOut("Browser:IE?\n");
327                         }
328                         this.isIE = true;
329                 } else if (userAgent.indexOf('chrome') != -1) {
330                         //chrome
331                         this.debugOut("Browser:Chrome\n");
332                 } else if (userAgent.indexOf('safari') != -1) {
333                         //safari
334                         this.debugOut("Browser:Safari\n");
335                 } else if (userAgent.indexOf('gecko') != -1) {
336                         //gecko
337                         this.debugOut("Browser:Gecko\n");
338                 } else {
339                         //unknown
340                         this.debugOut("Browser:Unknown\n");
341                 }
342                 //描画コンテキストからHTML5対応チェック
343                         if(!this.mainCanvas || !this.mainCanvas.getContext){
344                                 //HTML5未対応の場合
345                                 alert("このゲームを遊ぶためには、HTML5に対応しているブラウザ(Google Chrome等)でアクセスしてください...。");
346                         return false;
347                 }
348                 return true;
349         },
350         setBackgroundMusic: function(name){
351                 if(this.backgroundMusic){
352                         //再生していたら止める
353                         this.backgroundMusic.pause();
354                         this.backgroundMusic = null;
355                 }
356                 if(name){
357                         //引数がnullでなければaudioオブジェクトを取得
358                         this.backgroundMusic = createAudio(name);
359                         if(this.backgroundMusic){
360                                 //ループを設定して再生開始
361                                 this.backgroundMusic.loop = true;
362                                 this.backgroundMusic.play();
363                         }
364                 }
365         },
366 };
367
368 //
369 //イベントリスナー
370 //
371 //イベントリスナーにおけるthisは、イベントリスナーを登録したオブジェクトまたはwindowになり、通常とは異なるので注意。
372
373 function timerTickEventListener(event)
374 {
375         timerTickEventListener.manager.timerTick(event);
376 }