前回から2ヶ月近く空いてしまいましたが、今回は、パックマンの画面で一気にテンションがあがるモジュールが、エサ部分です。
前回まで作ったパーツを一同に画面に乗せて、さらに、ステージにエサを設置してみたいと思います。
あと、これまでと違って、プログラミングを意識した作業を行うので、新しいフォルダを作って今回のコードをコピペしてみてください。
今回の目的
1. プログラムファイルの構成とソースコード
2. ステージデータをjsonデータで管理する
3. 敵キャラのhtmlをJavascriptで設置
これまでは、HTML + CSSでの構成で単一パーツを構築してきましたが、
今回から、本格的にゲームページを意識したファイル構成にしてみたいと思います。
とりあえず、今回の構成は以下のように作ってみました。
index.html
├ assets/
│ ├ frame.json
│ └ ghost.html
├css/
│ ├ frame.css
│ ├ ghost.css
│ ├ pacman.css
│ └ style.css
└js/
├ frame.js
├ ghost.js
└ main.js
index.html
<link rel='stylesheet' href='css/style.css' />
<script type='module' src='js/main.js'></script>
<style>body{background-color:black}</style>
<div class='pacman'></div>
<div class='ghost' data-color='1'></div>
<div class='ghost' data-color='2'></div>
<div class='ghost' data-color='3'></div>
<div class='ghost' data-color='4'></div>
<div>
<div class='frame-area'></div>
</div>
assets/frame.json
[
"D1","D2","D2","D2","D2","D2","D2","D2","D2","D2","D2","D2","D2","Da","Db","D2","D2","D2","D2","D2","D2","D2","D2","D2","D2","D2","D2","D3",
"D4","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","S4","S6","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","D6",
"D4","P1","S1","S2","S2","S3","P1","S1","S2","S2","S2","S3","P1","S4","S6","P1","S1","S2","S2","S2","S3","P1","S1","S2","S2","S3","P1","D6",
"D4","P2","S4","S5","S5","S6","P1","S4","S5","S5","S5","S6","P1","S4","S6","P1","S4","S5","S5","S5","S6","P1","S4","S5","S5","S6","P2","D6",
"D4","P1","S7","S8","S8","S9","P1","S7","S8","S8","S8","S9","P1","S7","S9","P1","S7","S8","S8","S8","S9","P1","S7","S8","S8","S9","P1","D6",
"D4","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","D6",
"D4","P1","S1","S2","S2","S3","P1","S1","S3","P1","S1","S2","S2","S2","S2","S2","S2","S3","S5","S1","S3","P1","S1","S2","S2","S3","P1","D6",
"D4","P1","S7","S8","S8","S9","P1","S4","S6","P1","S7","S8","S8","S3","S1","S8","S8","S9","S5","S4","S6","P1","S7","S8","S8","S9","P1","D6",
"D4","P1","P1","P1","P1","P1","P1","S4","S6","P1","S5","S5","S5","S4","S6","S5","S5","S5","S5","S4","S6","P1","P1","P1","P1","P1","P1","D6",
"D7","D8","D8","D8","D8","S3","P1","S4","S7","S2","S2","S3","S5","S4","S6","S5","S1","S2","S2","S9","S6","P1","S1","D8","D8","D8","D8","D9",
"S5","S5","S5","S5","S5","D4","P1","S4","S1","S8","S8","S9","S5","S7","S9","S5","S7","S8","S8","S3","S6","P1","D6","S5","S5","S5","S5","S5",
"S5","S5","S5","S5","S5","D4","P1","S4","S6","S5","S5","S5","S5","S5","S5","S5","S5","S5","S5","S4","S6","P1","D6","S5","S5","S5","S5","S5",
"S5","S5","S5","S5","S5","D4","P1","S4","S6","S5","S1","D8","D8","D8","D8","D8","D8","S3","S5","S4","S6","P1","D6","S5","S5","S5","S5","S5",
"D2","D2","D2","D2","D2","S9","P1","S7","S9","S5","D6","S5","S5","S5","S5","S5","S5","D4","S5","S7","S9","P1","S7","D2","D2","D2","D2","D2",
"S5","S5","S5","S5","S5","S5","P1","S5","S5","S5","D6","S5","S5","S5","S5","S5","S5","D4","S5","S5","S5","P1","S5","S5","S5","S5","S5","S5",
"D8","D8","D8","D8","D8","S3","P1","S1","S3","S5","D6","S5","S5","S5","S5","S5","S5","D4","S5","S1","S3","P1","S1","D8","D8","D8","D8","D8",
"S5","S5","S5","S5","S5","D4","P1","S4","S6","S5","S7","D2","D2","D2","D2","D2","D2","S9","S5","S4","S6","P1","D6","S5","S5","S5","S5","S5",
"S5","S5","S5","S5","S5","D4","P1","S4","S6","S5","S5","S5","S5","S5","S5","S5","S5","S5","S5","S4","S6","P1","D6","S5","S5","S5","S5","S5",
"S5","S5","S5","S5","S5","D4","P1","S4","S6","S5","S1","S2","S2","S2","S2","S2","S2","S3","S5","S4","S6","P1","D6","S5","S5","S5","S5","S5",
"D1","D2","D2","D2","D2","S9","P1","S7","S9","S5","S7","S8","S8","S3","S1","S8","S8","S9","S5","S7","S9","P1","S7","D2","D2","D2","D2","D3",
"D4","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","S4","S6","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","D6",
"D4","P1","S1","S2","S2","S3","P1","S1","S2","S2","S2","S3","P1","S4","S6","P1","S1","S2","S2","S2","S3","P1","S1","S2","S2","S3","P1","D6",
"D4","P1","S7","S8","S3","S6","P1","S7","S8","S8","S8","S9","P1","S7","S9","P1","S7","S8","S8","S8","S9","P1","S4","S1","S8","S9","P1","D6",
"D4","P2","P1","P1","S4","S6","P1","P1","P1","P1","P1","P1","P1","S5","S5","P1","P1","P1","P1","P1","P1","P1","S4","S6","P1","P1","P2","D6",
"Dc","S2","S3","P1","S4","S6","P1","S1","S3","P1","S1","S2","S2","S2","S2","S2","S2","S3","P1","S1","S3","P1","S4","S6","P1","S1","S2","De",
"Dd","S8","S9","P1","S7","S9","P1","S4","S6","P1","S7","S8","S8","S3","S1","S8","S8","S9","P1","S4","S6","P1","S7","S9","P1","S7","S8","Df",
"D4","P1","P1","P1","P1","P1","P1","S4","S6","P1","P1","P1","P1","S4","S6","P1","P1","P1","P1","S4","S6","P1","P1","P1","P1","P1","P1","D6",
"D4","P1","S1","S2","S2","S2","S2","S9","S7","S2","S2","S3","P1","S4","S6","P1","S1","S2","S2","S9","S7","S2","S2","S2","S2","S3","P1","D6",
"D4","P1","S7","S8","S8","S8","S8","S8","S8","S8","S8","S9","P1","S7","S9","P1","S7","S8","S8","S8","S8","S8","S8","S8","S8","S9","P1","D6",
"D4","P1","P1","P1","P1","P1","P1","P1","p1","P1","p1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","P1","D6",
"D7","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D8","D9"
]
assets/ghost.html
<div class='head'></div>
<div class='face'>
<div class='eye-left'></div>
<div class='eye-right'></div>
</div>
<div class='under'>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 30">
<path fill='red' d="M 0,0 Q 0,15 5,20 T 20,15 40,15 60,15 80,15 100,15 L 100,0" fill='red'>
<animate
attributeName='d'
dur='500ms'
repeatCount='indefinite'
calcMode="linear"
values='M 0,0 Q 0,15 5,20 T 15,15 35,15 55,15 75,15 100,15 L 100,0;
M 0,0 Q 0,15 5,20 T 25,15 45,15 65,15 85,15 100,15 L 100,0;
M 0,0 Q 0,15 5,20 T 15,15 35,15 55,15 75,15 100,15 L 100,0;' />
</path>
</svg>
</div>
css/frame.css
:root{
--color : blue;
--block : 16px;
--border : 2px;
}
.frame-area{
display:inline-grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
}
.frame-area,
.frame-area *,
.frame-area *::before,
.frame-area *::after{
font-size:0;
-webkit-box-sizing : border-box;
-moz-box-sizing : border-box;
-o-box-sizing : border-box;
-ms-box-sizing : border-box;
box-sizing : border-box;
}
.frame-area > *{
width : var(--block);
height : var(--block);
font-size : 0;
display : block;
position : relative;
/* border : 1px solid rgba(255,0,0,0.5); */
}
.frame-area .D1::after,
.frame-area .D2::after,
.frame-area .D3::after,
.frame-area .D4::after,
.frame-area .D6::after,
.frame-area .D7::after,
.frame-area .D8::after,
.frame-area .D9::after,
.frame-area .Da::after,
.frame-area .Db::after,
.frame-area .Dc::after,
.frame-area .Dd::after,
.frame-area .De::after,
.frame-area .Df::after,
.frame-area .Dg::after,
.frame-area .Dh::after{
width : 100%;
height: 100%;
top:0;
left:0;
border-style : solid;
border-color : var(--color);
}
.frame-area > *::before,
.frame-area > *::after{
content : '';
font-size : 0;
display : block;
border-style : solid;
border-color : var(--color);
border-width : 0;
position : absolute;
}
.frame-area .S1::before,
.frame-area .D1::before,
.frame-area .Db::before,
.frame-area .Dd::before{
width : calc(var(--block) / 2);
height : calc(var(--block) / 2);
bottom : 0;
right : 0;
border-top-width : var(--border);
border-left-width : var(--border);
border-top-left-radius : 100%;
}
.frame-area .S2::before,
.frame-area .D2::before{
width : 100%;
height : calc(var(--block) / 2);
bottom : 0;
left : 0;
border-top-width : var(--border);
}
.frame-area .S3::before,
.frame-area .D3::before,
.frame-area .Da::before,
.frame-area .Df::before{
width : calc(var(--block) / 2);
height : calc(var(--block) / 2);
bottom : 0;
left : 0;
border-top-width : var(--border);
border-right-width : var(--border);
border-top-right-radius : 100%;
}
.frame-area .S4::before,
.frame-area .D4::before{
width : calc(var(--block) / 2);
height : 100%;
top : 0;
right : 0;
border-left-width : var(--border);
}
.frame-area .S5::before,
.frame-area .D5::before,
.frame-area .D5::after{
border-width : 0;
}
.frame-area .S6::before,
.frame-area .D6::before{
width : calc(var(--block) / 2);
height : 100%;
top : 0;
left : 0;
border-right-width : var(--border);
}
.frame-area .S7::before,
.frame-area .D7::before,
.frame-area .Dc::before,
.frame-area .Dh::before{
width : calc(var(--block) / 2);
height : calc(var(--block) / 2);
top : 0;
right : 0;
border-bottom-width : var(--border);
border-left-width : var(--border);
border-bottom-left-radius : 100%;
}
.frame-area .S8::before,
.frame-area .D8::before{
width : 100%;
height : calc(var(--block) / 2);
top : 0;
left : 0;
border-bottom-width : var(--border);
}
.frame-area .S9::before,
.frame-area .D9::before,
.frame-area .De::before,
.frame-area .Dg::before{
width : calc(var(--block) / 2);
height : calc(var(--block) / 2);
top : 0;
left : 0;
border-bottom-width : var(--border);
border-right-width : var(--border);
border-bottom-right-radius : 100%;
}
.frame-area .D1::after{
border-width : var(--border) 0 0 var(--border);
border-radius : 100% 0 0 0;
}
.frame-area .D2::after,
.frame-area .Da::after,
.frame-area .Db::after{
border-top-width : var(--border);
}
.frame-area .D3::after{
border-width : var(--border) var(--border) 0 0;
border-radius : 0 100% 0 0;
}
.frame-area .D4::after,
.frame-area .Dc::after,
.frame-area .Dd::after{
border-left-width : var(--border);
}
.frame-area .D6::after,
.frame-area .De::after,
.frame-area .Df::after{
border-right-width : var(--border);
}
.frame-area .D7::after{
border-width : 0 0 var(--border) var(--border);
border-radius : 0 0 0 100%;
}
.frame-area .D8::after,
.frame-area .Dg::after,
.frame-area .Dh::after{
border-bottom-width : var(--border);
}
.frame-area .D9::after{
border-width : 0 var(--border) var(--border) 0;
border-radius : 0 0 100% 0;
}
.frame-area .P1,
.frame-area .P2{
position:relative;
}
.frame-area .P1:before{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
width : calc(var(--block) / 4);
height : calc(var(--block) / 4);
border-radius : 50%;
background-color:yellow;
}
.frame-area .P2:before{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
width : calc(var(--block) / 1);
height : calc(var(--block) / 1);
border-radius : 50%;
background-color:yellow;
}
css/ghost.css
.ghost{
display:inline-block;
--size-width : calc(var(--size-chara) * 1.0);
--size-eye : calc(var(--size-width) * 0.3);
--size-under : calc(var(--size-width) * 0.2);
--color-body : red;
width:var(--size-chara);
height:var(--size-chara);
}
.ghost[data-color='1']{--color-body : red;}
.ghost[data-color='2']{--color-body : orange;}
.ghost[data-color='3']{--color-body : lightblue;}
.ghost[data-color='4']{--color-body : pink;}
.ghost[data-status='weak']{--color-body : blue;}
.ghost > *{
margin : 0 auto;
width : var(--size-width);
}
.ghost > .head{
height : calc(var(--size-width) / 2);
background-color : var(--color-body);
border-radius : var(--size-chara) var(--size-chara) 0 0;
}
.ghost > .face{
height : 30%;
background-color : var(--color-body);
position:relative;
}
.ghost > .face > .eye-left,
.ghost > .face > .eye-right{
position:absolute;
top:-75%;
background-color:white;
width : var(--size-eye);
height : var(--size-eye);
background-color:white;
border-radius:50%;
}
.ghost > .face .eye-left{
left:13%;
}
.ghost > .face .eye-right{
right:13%;
}
.ghost:not([data-status='weak']) > .face > *::before{
content:'';
display:block;
background-color:black;
width : 50%;
height : 50%;
margin : 25%;
border-radius:50%;
transition-property : margin;
transition-duration : 0.3s;
}
.ghost:not([data-status='weak']) > .face[data-direction='right'] > *::before{
margin-left : 50%;
margin-right : 0;
}
.ghost:not([data-status='weak']) > .face[data-direction='left'] > *::before{
margin-left : 0;
margin-right : 50%;
}
.ghost:not([data-status='weak']) > .face[data-direction='top'] > *::before{
margin-top : 0;
margin-bottom : 50%;
}
.ghost:not([data-status='weak']) > .face[data-direction='bottom'] > *::before{
margin-top : 50%;
margin-bottom : 0;
}
.ghost[data-status='weak'] > .face > .eye-left,
.ghost[data-status='weak'] > .face > .eye-right{
width : calc(var(--size-eye) / 2);
height : calc(var(--size-eye) / 2);
background-color:white;
border-radius:50%;
}
.ghost[data-status='weak'] > .face > .eye-left{
left:25%;
}
.ghost[data-status='weak'] > .face > .eye-right{
right:25%;
}
.ghost[data-status='weak'] > .face > .mouse{
margin-top:10px;
}
.ghost > .under{
height : var(--size-under);
}
.ghost > .under path{
fill:var(--color-body);
}
css/pacman.css
.pacman{
/* position:absolute; */
display:inline-block;
top:100px;
left:100px;
width:var(--size-chara);
height:var(--size-chara);
}
.pacman::before,
.pacman::after{
content:'';
display:block;
width:100%;
height:50%;
background-color:yellow;
animation-duration:0.4s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
.pacman::before{
transform-origin:center bottom;
border-radius:var(--size-chara) var(--size-chara) 0 0;
transform:rotate(45deg);
animation-name : pacman_before;
}
.pacman::after{
transform-origin:center top;
border-radius:0 0 var(--size-chara) var(--size-chara);
transform:rotate(-45deg);
animation-name : pacman_after;
}
@keyframes pacman_before{
0%{
transform:rotate(45deg);
}
50%{
transform:rotate(0deg);
}
100%{
transform:rotate(45deg);
}
}
@keyframes pacman_after{
0%{
transform:rotate(-45deg);
}
50%{
transform:rotate(0deg);
}
100%{
transform:rotate(-45deg);
}
}
css/style.css
@import 'frame.css';
@import 'pacman.css';
@import 'ghost.css';
:root{
--size-chara : 32px;
}
js/frame.js
export class Frame{
constructor(){
Frame.stage_datas = this.stage_datas = []
this.load_asset()
}
static get root(){
return document.querySelector(`.frame-area`)
}
get block_size(){
const s5 = document.querySelector('.S5')
return s5.offsetWidth
}
load_asset(){
const xhr = new XMLHttpRequest()
xhr.open('get' , `assets/frame.json` , true)
xhr.setRequestHeader('Content-Type', 'text/html');
xhr.onreadystatechange = ((e) => {
if(xhr.readyState !== XMLHttpRequest.DONE){return}
if(xhr.status === 404){return}
if (xhr.status === 200) {
Frame.frame_datas =this.frame_datas = JSON.parse(e.target.response)
this.view()
}
}).bind(this)
xhr.send()
}
view(){
for(let i=0; i<this.frame_datas.length; i++){
const p = document.createElement('p')
p.className = this.frame_datas[i]
Frame.root.appendChild(p)
// this.stage_datas[i] = this.frame_datas[i] === 'S5' ? 1 : 0
}
}
}
js/ghost.js
export class Ghost{
constructor(){
this.load_asset()
}
static get elm_ghosts(){
return document.querySelectorAll('.ghost')
}
load_asset(){
const xhr = new XMLHttpRequest()
xhr.open('get' , `assets/ghost.html` , true)
xhr.setRequestHeader('Content-Type', 'text/html');
xhr.onreadystatechange = ((e) => {
if(xhr.readyState !== XMLHttpRequest.DONE){return}
if(xhr.status === 404){return}
if (xhr.status === 200) {
this.asset = e.target.response
this.ghost_set()
}
}).bind(this)
xhr.send()
}
ghost_set(){
const elm_ghosts = Ghost.elm_ghosts
for(const elm_ghost of elm_ghosts){
elm_ghost.innerHTML = this.asset
this.change_eye(elm_ghost)
}
}
static get directions(){
return ['right','left','top','bottom']
}
change_eye(elm_ghost){
const num = Math.floor(Math.random() * Ghost.directions.length)
elm_ghost.querySelector('.face').setAttribute('data-direction' , Ghost.directions[num])
setTimeout(this.change_eye.bind(this , elm_ghost) , 3000)
}
}
js/main.js
import { Ghost } from './ghost.js'
import { Frame } from './frame.js'
export const Main = {}
Main.frame = new Frame()
Main.ghost = new Ghost()
前回、HTMLでベタにタグで書いたステージデータを、jsonデータに変換して、
assetフォルダにある、frame.jsonに書き込んでみました。
js/frame.jsでは、frame.jsonを元に、HTMLタグに変換して設置するようにすることで、index.htmlの内容をシンプルにすることができました。
何か修正する時も、jsonフォーマットにしておいたほうが、見やすいし修正し易いと思います。
frame.jsでは、jsonの読み込みをして読み込んだデータをObject(配列)データに変換して、それをHTMLに配置する内容が1ファイルで書かれています。
ちなみに、前回の内容に、エサのドット情報を付与して、同時に表示しているので、より本物に近づいていると思います。
敵キャラは、HTML+CSS+Javascriptで構成していたので、専用のghost.jsを設置しました。
内容としては、asset/ghost.htmlを読み込んで、それぞれ4体の敵キャラ内に設置をして、タイプを振り分けます。
次に、それらのキャラの目アニメーションを起動してますが、これは本来動きに合わせて目を動かすことになるので、後に変更されるでしょう。
まとめ
これまで作ったパーツをまとめて1つのゲームに組み立てていく作業は、いつもドキドキしてしまいます。
うまく動かなかったり、想定外のコンフリクトが起きるのは当たり前なので、それらを即座に対処して、うまく出来たらそれはそれでモチベーションがアップしてしまいます。
とりあえず、今後はJavascriptのプログラミングが中心になるので、これまでは単なる関数で書いていたのを、class構成にして構築してあります。
もし、class記述がよくわからないという人は、わからない箇所を学習しながらブログを読み勧めてもらえると、プログラミング学習も進むのではないかと思います。
とりあえず、見た目は完璧と自画自賛できたので、次は実際にキャラクターをカーソルなどで動かしてみたいと思います。
お楽しみに!
知財
パックマンは、バンダイナムコ社の登録商標です。
PAC-MAN™ & ©1980 BANDAI NAMCO Entertainment Inc.
0 件のコメント:
コメントを投稿