前回 ASP.NET Core版 戦車対戦ゲームをつくる(4) の続きです。
VIDEO
サーバーサイドからデータを受信したときの処理
ゲームスタートのボタンが押されたら接続を開始します。
function GameStart ( ) {
if ( connectionID == '' )
ConnectAndGameStart ( ) ;
}
function ConnectAndGameStart ( ) {
document . getElementById ( "conect-result" ) . innerHTML = '接続しようとしています' ;
connection . start ( ) . catch ( function ( err ) {
document . getElementById ( "conect-result" ) . innerHTML = '接続失敗' ;
} ) ;
}
接続に成功時の処理
接続に成功したらIDを表示させ、切断されたらその旨を表示させます。
let connectionID = '' ;
connection . on ( "ReceiveConnected" , function ( result , id ) {
connectionID = id ;
document . getElementById ( "conect-result" ) . innerHTML = ` conect -result $ { result } :$ { id } ` ;
} ) ;
connection . onclose ( async ( ) => {
connectionID = '' ;
document . getElementById ( "conect-result" ) . innerHTML = '切断' ;
} ) ;
ゲームが開始されたら現在存在する破壊できる壁と破壊できない壁の座標がカンマ区切りの文字列でおくられてくるので、これを配列に変換して前述のAddWalls1関数とAddWalls2関数でsceneに追加します。
connection . on ( "ReceiveExistingWalls" , function ( xs1 , zs1 ) {
AddWalls1 ( scene , xs1 . split ( ',' ) , zs1 . split ( ',' ) ) ;
} ) ;
connection . on ( "ReceiveIndestructibleWalls" , function ( xs1 , zs1 ) {
AddWalls2 ( scene , xs1 . split ( ',' ) , zs1 . split ( ',' ) ) ;
} ) ;
キー操作時の処理
キーが押されたり離されたりしたらサーバーサイドにデータを送信します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
document . onkeydown = function ( e ) {
// ゲーム開始以降はデフォルトの動作を抑制する
if ( connectionID ! = '' && (e.key == "ArrowUp" || e.key == "ArrowDown" || e.key == "ArrowLeft" || e.key == "ArrowRight" || e.key == " "))
e.preventDefault();
// キーが押しっぱなしになっているときの二重送信を防ぐ
if ( e . key == "ArrowUp" && isUpKeyDown)
return;
if ( e . key == "ArrowDown" && isDownKeyDown)
return;
if ( e . key == "ArrowLeft" && isLeftKeyDown)
return;
if ( e . key == "ArrowRight" && isRightKeyDown)
return;
if ( e . key == "ArrowUp" )
isUpKeyDown = true ;
if ( e . key == "ArrowDown" )
isDownKeyDown = true ;
if ( e . key == "ArrowLeft" )
isLeftKeyDown = true ;
if ( e . key == "ArrowRight" )
isRightKeyDown = true ;
// ゲーム中にプレイヤー名を変更可能なのでキーが押されたらそのつどプレイヤー名も送信する
// 接続されていない場合はなにもしない
if ( connectionID ! = '' ) {
let playerName = document . getElementById ( 'player-name' ) . value ;
if ( playerName == '' )
playerName = '名無しさん' ;
connection . invoke ( "DownKey" , e . key , playerName ) . catch ( function ( err ) {
return console . error ( err . toString ( ) ) ;
} ) ;
}
}
document . onkeyup = function ( e ) {
if ( e . key == "ArrowUp" )
isUpKeyDown = false ;
if ( e . key == "ArrowDown" )
isDownKeyDown = false ;
if ( e . key == "ArrowLeft" )
isLeftKeyDown = false ;
if ( e . key == "ArrowRight" )
isRightKeyDown = false ;
// 接続されていない場合はなにもしない
if ( connectionID ! = '' ) {
connection . invoke ( "UpKey" , e . key ) . catch ( function ( err ) {
return console . error ( err . toString ( ) ) ;
} ) ;
}
}
更新用のデータを受信したときの処理
サーバーサイドから更新処理のためのデータが送られるまえにReceiveStartUpdateが送られてくるので、これを受信したら更新用のデータを格納する配列を初期化しておきます。
connection . on ( "ReceiveStartUpdate" , function ( ) {
xs = [ ] ;
zs = [ ] ;
rys = [ ] ;
isDeads = [ ] ;
bxs = [ ] ;
bzs = [ ] ;
hideXs = [ ] ;
hideYs = [ ] ;
} ) ;
サーバーサイドから戦車の座標や砲弾の座標、非表示にする壁の座標などが送信された場合は、これをグローバル変数と配列に格納しておきます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
connection . on ( "ReceiveUpdateTank" , function ( _id , _x , _z , _ry , _name , _isDead ) {
if ( connectionID == _id ) {
id = _id ;
x = _x ;
z = _z ;
ry = _ry ;
name = _name ;
isDead = _isDead ;
}
else {
xs . push ( _x ) ;
zs . push ( _z ) ;
rys . push ( _ry ) ;
isDeads . push ( _isDead ) ;
}
} ) ;
connection . on ( "ReceiveUpdateBullet" , function ( _x , _z ) {
bxs . push ( _x ) ;
bzs . push ( _z ) ;
} ) ;
connection . on ( "ReceiveHideWalls" , function ( _id , _xs , _zs ) {
if ( connectionID == _id ) {
hideXs = _xs . split ( ',' ) ;
hideZs = _zs . split ( ',' ) ;
}
} ) ;
connection . on ( "ReceiveScore" , function ( id , _score , _rest ) {
if ( id == connectionID ) {
score = _score ;
rest = _rest ;
}
} ) ;
ReceiveEndUpdateを受信したら前述のUpdate関数を呼び出します。するとこれまでに受信し保存されているデータを利用して更新処理がおこなわれます。
connection . on ( "ReceiveEndUpdate" , function ( ) {
Update ( ) ;
} ) ;
壁が破壊されたときの処理
ReceiveBrokenWallを受信した場合は、壁が破壊されたということなのでWall.Break関数を呼び出して壁をsceneから取り除き、爆発を発生させます。
connection . on ( "ReceiveBrokenWall" , function ( wallX , wallZ ) {
for ( let i = 0 ; i < walls . length ; i ++) {
if ( walls [ i ] . X == wallX && walls[i].Z == wallZ) {
walls[i].Break();
explosions . push ( new Explosion ( walls [ i ] . X , walls [ i ] . Z ) ) ;
break ;
}
}
} ) ;
戦車が撃破されたときの処理
ReceiveDefeatedを受信した場合は戦車が撃破されたときなので、どのプレイヤーがどのプレイヤーの戦車を撃破したのかを調べます。そして自機が敵を撃破した場合と敵によって自機が撃破された場合はその結果を3秒間表示させます。そして撃破された戦車を地下に移動させて非表示にして爆発を発生させます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
connection . on ( "ReceiveDefeated" , function ( defeatID , defeatName , defeatedID , defeatedName , x , z ) {
console . log ( "ReceiveDefeated" ) ;
console . log ( "dead!! x, z = " + x + ", " + z ) ;
const tf1 = document . getElementById ( "info" ) ;
tf1 . style . transform = "translate(30px, 50px)" ;
tf1 . style . backgroundColor = "#00008B" ;
tf1 . style . color = "white" ;
tf1 . style . fontSize = "18px" ;
tf1 . style . fontFamily = 'MS ゴシック' ;
if ( connectionID == defeatedID ) {
myTank . position . y = -100 ;
explosions . push ( new Explosion ( myTank . position . x , myTank . position . z ) ) ;
if ( document . getElementById ( 'SoundCheckbox' ) . checked ) {
soundDead . currentTime = 0 ;
soundDead . play ( ) ;
}
tf1 . innerHTML = "あなたは " + defeatName + " に撃破されました!!" ;
setTimeout ( ( ) => {
tf1 . innerHTML = "" ;
} , 3000 ) ;
}
if ( connectionID == defeatID ) {
explosions . push ( new Explosion ( x , z ) ) ;
tf1 . innerHTML = "あなたは " + defeatedName + " を撃破しました!!" ;
setTimeout ( ( ) => {
tf1 . innerHTML = "" ;
} , 3000 ) ;
}
if ( connectionID ! = defeatID && connectionID != defeatedID) {
explosions.push(new Explosion(x, z));
}
} ) ;
砲弾の発射と命中時の処理
砲弾を発射したりその砲弾が命中したときはReceiveShotやReceiveBombを受信することになります。この場合はチェックボックスの状態を調べて必要であれば効果音を鳴らします。
connection . on ( "ReceiveShot" , function ( id ) {
if ( id == connectionID && document.getElementById('SoundCheckbox').checked) {
soundShot.currentTime = 0;
soundShot . play ( ) ;
}
} ) ;
connection . on ( "ReceiveBomb" , function ( id ) {
if ( id == connectionID && document.getElementById('SoundCheckbox').checked) {
soundBomb.currentTime = 0;
soundBomb . play ( ) ;
}
} ) ;
ゲームオーバー時の処理
ゲームオーバーのときはゲームオーバーであることを表示させるためにUpdate関数を呼び出します。その2秒後に別のページ(スコアランキングのページ)にリダイレクトします。
connection . on ( "ReceiveGameOver" , function ( id ) {
if ( id == connectionID ) {
Update ( ) ;
setTimeout ( ( ) => {
window . location . href = './hi-score' ;
} , 2000 ) ;
}
} ) ;
ゲームに参加できないときの処理
すでにユーザーの上限に達していてゲームに参加できない場合は接続を拒否された旨を表示するページにリダイレクトさせます。
connection . on ( "ReceiveDenyNewPlayer" , function ( ) {
window . location . href = './deny-new-player' ;
} ) ;
スコアランキングと接続拒否時にリダイレクトされるページの表示
スコアランキングのページを表示させる処理を示します。
スコアランキングは同じディレクトリのhi-scoreで表示させます。
hi-score.cshtmlの内容は以下のとおりです。
Pages\Tank\hi-score.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@ page
@ {
Layout = "" ;
}
< ! DOCTYPE html >
< html lang ="ja" >
< head >
< meta charset ="UTF-8" >
< title > 戦車対戦ゲーム 上位30 位< /title >
< meta http -equiv ="X-UA-Compatible" content ="IE=edge" >
< meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
< link rel ="stylesheet" href ="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" >
< link rel ="stylesheet" href ="../css/hiscore.css" >
< /head >
< body >
@ {
string path = "../hiscore-tank.txt" ;
List < Tank . Hiscore > hiscores = new List < Tank . Hiscore > ( ) ;
if ( System . IO . File . Exists ( path ) )
{
System . IO . StreamReader sr = new StreamReader ( path ) ;
string text = sr . ReadToEnd ( ) ;
string [ ] vs1 = text . Split ( '\n' ) ;
foreach ( string str in vs1 )
{
try
{
string [ ] vs2 = str . Split ( ',' ) ;
Tank . Hiscore hiscore = new Tank . Hiscore ( vs2 [ 0 ] , long . Parse ( vs2 [ 1 ] ) , vs2 [ 2 ] ) ;
hiscores . Add ( hiscore ) ;
}
catch
{
}
}
sr . Close ( ) ;
}
}
< div id = "container" >
< div id = "h1" > 鳩でもわかる戦車対戦ゲーム 上位30 位< /div >
< div id = "left" >
< p > < a href ="./game" > ⇒ 戦車対戦ゲームのページへ戻る< /a > < /p >
< div id = "result" >
< table class ="table" border ="1" id ="table" >
@ { int num = 0 ; }
@ foreach ( Tank . Hiscore hiscore in hiscores )
{
num ++;
< tr >
< td > @ num 位< /td >
< td > @ hiscore . Name < /td >
< td > @ hiscore . Score < /td >
< td > @ hiscore . Time < /td >
< /tr >
}
@ if ( num < 30 )
{
@ for ( num ++; num < = 30 ; num ++)
{
< tr >
< td > @ num 位< /td >
< td > < /td >
< td > < /td >
< td > < /td >
< /tr >
}
}
< /table >
< /div >
< /div >
< div id = "right" >
< div id = "h2" > 鳩がつくったその他のゲーム< /div >
< ! -- 見てもらいたいページへのリンク -->
< p class = "game" > < a href ="https://lets-csharp.com/samples/2204/aspnetcore-app-zero/CrashRoller" target ="_blank" rel ="noopener" > クラッシュローラー< /a > < /p >
< ! -- ほかにも自作ゲームのページへのリンクを設置する -->
< /div >
< /div >
< /body >
< /html >
/css/hiscore.cssの内容は クライアントサイドの処理 ASP.NET Coreで3Dっぽいカーレースをつくる(4) の最後のほうを参照してください。
ゲームの参加を拒否されたときにリダイレクトされるページのcshtmlを示します。
Pages\Tank\deny-new-player.cshtml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ page
@ {
Layout = "" ;
}
< ! DOCTYPE html >
< html lang ="ja" >
< head >
< meta charset ="UTF-8" >
< title > プレイ不可< /title >
< meta http -equiv ="X-UA-Compatible" content ="IE=edge" >
< meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
< /head >
< body >
< h1 > 現在、混雑しています。< /h1 >
< p > 同時にプレイできるのは7 人までです。空きができるまでしばらくお待ちください。< /p >
< /body >
< /html >
VIDEO