見た目はもうパックマンそのものなので、前回まででもテンションが上った人も多いと思います。(自分もそうです)
今回は、自機をパソコンの上下左右カーソルキーで動かしてみます。
単に動かすだけですが、前回までのHTML構造ではうまく出来ないことがわかったので、大幅に変更をしています。
今回の目的
・パソコンのキーボードのカーソルキーで上下左右を押した時に、自キャラを移動させる
修正ファイル一覧
- index.html
- css/frame.css
- css/ghost.css
- css/pacman.css
- css/style.css
- js/control.js
- js/frame.js
- js/ghost.js
- js/main.js
- js/pacman.js
ソースコード(前回からの差分)
[全部更新]index.html
キャラクターの配置を、.frame-areaの中に入れました。
これにより、理由としては、ステージ内の座標を相対的に取得したかったので、左上をx:0,y:0とする必要があったので、移動させました。
<!DOCTYPE html>
<html lang='ja'>
<head>
<link rel='stylesheet' href='css/style.css' />
<script type='module' src='js/main.js'></script>
<style>body{background-color:black}</style>
</head>
<body>
<div class='frame-area'>
<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>
</body>
</html>
[全部更新] css/frame.css
index.htmlの修正に伴って、cssも変更する必要があったので、内容をガラッと変更。
すべて書き換えて下さい。
"position:relative"を追加して、それに伴って、内部にアクセスするセレクタを変更しています。
:root{
--color : blue;
--border : 2px;
}
.frame-area{
position:relative;
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 p,
.frame-area p::before,
.frame-area p::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 > p{
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 > p::before,
.frame-area > p::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部分のみ、下記のコードに入れ替えて下さい。
.ghost{
position:absolute;
z-index:9;
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);
transform:translate(calc(var(--block) / -2) , calc(var(--block) / -2));
}
[一部更新+追加] css/pacman.css
セレクターが.pacmanの箇所(一番上です)を下記のコードに入れ替えてください。
これにより、pacmanが座標でコントロールできるようになります。
.pacman{
position:absolute;
display:inline-block;
width:var(--size-chara);
height:var(--size-chara);
}
同じファイルの最後に次のコードを追加
.pacman,
.pacman[data-direction="left"]{
transform:translate(calc(var(--block) / -2) , calc(var(--block) / -2));
}
.pacman[data-direction="right"]{
transform:translate(calc(var(--block) / -2) , calc(var(--block) / -2)) scaleX(-1);
}
.pacman[data-direction="up"]{
transform:translate(calc(var(--block) / -2) , calc(var(--block) / -2)) rotate(90deg);
}
.pacman[data-direction="down"]{
transform:translate(calc(var(--block) / -2) , calc(var(--block) / -2)) rotate(-90deg);
}
[一部更新] css/style.css
グローバルプロパティ変数に、ブロックのサイズを追加しました。
下記の赤い箇所1行のみを追加してください。
:root{
--block : 16px;
--size-chara : 32px;
}
[新規追加] js/control.js
キー操作の為のイベント管理をする命令を追加しました。
パソコンのキーボードだけでの操作にしています。(スマホは動作しません、スミマセン・・・)
import { Pacman } from './pacman.js'
export class Control{
constructor(){
window.addEventListener('keydown' , this.keydown.bind(this))
window.addEventListener('keyup' , this.keyup.bind(this))
}
static key2name(key){
switch(key){
case 37 : return 'left'
case 38 : return 'up'
case 39 : return 'right'
case 40 : return 'down'
}
}
keydown(e){
if(e.repeat === true){return}
const key = e.keyCode
const name = Control.key2name(key)
if(!name){return}
Control.key_data = {
key_code : key,
name : name,
}
Pacman.move(Control.key_data)
}
keyup(e){
if(!Control.key_data){return}
const name = Control.key2name(e.keyCode)
if(Control.key_data.name !== name){return}
delete Control.key_data
}
}
[全部更新] js/frame.js
キャラクターを配置する座標計算の関数と、その座標にキャラクタを配置する処理を追加しました。
変更点が多かったので、全部書き換えて下さい。
export class Frame{
constructor(){
return new Promise(resolve => {
this.resolve = resolve
Frame.stage_datas = this.stage_datas = []
this.load_asset()
})
}
static get root(){
return document.querySelector(`.frame-area`)
}
get block_size(){
return Frame.block_size
}
static 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.finish()
}
finish(){
if(this.resolve){
this.resolve(this)
}
}
static put(elm , coodinates){
if(!elm){return}
const pos = this.calc_coodinates2position(coodinates)
this.pos(elm , pos)
elm.setAttribute('data-x' , coodinates.x)
elm.setAttribute('data-y' , coodinates.y)
}
static calc_coodinates2position(coodinates){
const size = Frame.block_size
return {
x : (coodinates.x) * size,
y : (coodinates.y) * size,
}
}
static pos(elm , pos){
elm.style.setProperty('left' , `${pos.x}px` , '')
elm.style.setProperty('top' , `${pos.y}px` , '')
}
}
[全部更新] js/ghost.js
キャラクタ配置を変更したため、このモジュールも大きく書き換えました。
import { Frame } from './frame.js'
export class Ghost{
constructor(){
this.load_asset()
}
static get elm_ghosts(){
return document.querySelectorAll('.ghost')
}
static start_coodinates = [
{ x : 12, y : 11 },
{ x : 15, y : 11 },
{ x : 12, y : 14 },
{ x : 15, y : 14 },
]
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){
Frame.put(elm_ghost, Ghost.start_coodinates.shift())
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
frame.jsを起動時に実行する必要があったので、それに伴って起動処理をするmain.jsを書き換えました。
import { Ghost } from './ghost.js'
import { Frame } from './frame.js'
import { Control } from './control.js'
import { Pacman } from './pacman.js'
export const Main = {}
function init(){
new Frame().then(()=>{
new Ghost()
new Pacman()
new Control()
})
}
switch(document.readyState){
case 'complete':
case 'interactive':
init()
break
default:
window.addEventListener('DOMContentLoaded' , (()=>init()))
}
[新規追加] js/pacman.js
自キャラの動きを制御する処理を作りました。
単純にキー操作で動くだけで、まだ壁判定やワープ処理などはやっていません。
import { Frame } from './frame.js'
import { Control } from './control.js'
export class Pacman{
// 初期表示座標処理
constructor(){
Pacman.anim_speed = 400
Pacman.coodinates = this.start_coodinates
Frame.put(this.elm, Pacman.coodinates)
this.elm.style.setProperty('--anim-speed' , `${Pacman.anim_speed}ms` , '')
}
get start_coodinates(){
return {
x : 14,
y : 23,
}
}
get elm(){
return Pacman.elm
}
static get elm(){
return document.querySelector('.pacman')
}
static move(key_data){
if(Pacman.key_data ){
if(Pacman.key_data.name !== key_data.name){
Pacman.key_data = key_data
}
return
}
Pacman.key_data = key_data
this.elm.setAttribute('data-anim' , "true")
this.moving()
}
static moving(){
const next_pos = {
x : Pacman.coodinates.x,
y : Pacman.coodinates.y,
}
switch(Pacman.key_data.name){
case 'left':
next_pos.x -= 1
break
case 'right':
next_pos.x += 1
break
case 'up':
next_pos.y -= 1
break
case 'down':
next_pos.y += 1
break
default: return
}
this.elm.setAttribute('data-direction' , Pacman.key_data.name)
this.elm.animate(
[
{
left : `${Pacman.coodinates.x * Frame.block_size}px`,
top : `${Pacman.coodinates.y * Frame.block_size}px`,
},
{
left : `${next_pos.x * Frame.block_size}px`,
top : `${next_pos.y * Frame.block_size}px`,
}
],
{
duration: Pacman.anim_speed
}
)
Promise.all(this.elm.getAnimations().map(e => e.finished)).then(()=>{
Pacman.moved(next_pos)
})
}
static moved(next_pos){
if(!Pacman.key_data){return}
Pacman.coodinates = next_pos
Frame.put(this.elm, Pacman.coodinates)
if(Control.key_data){
Pacman.moving()
}
else{
this.elm.setAttribute('data-anim' , "")
delete Pacman.key_data
}
}
}
画面キャプチャ
あとがき
キーボードのカーソルキーを押している間に、その方向の座標の値をインクリメントすればいいか・・・と甘く考えていましたが、
実際は、壁やエサパーツのグリッドに沿ってそのサイズ単位で移動しないといけないという事がわかり、少し特殊な方法で座標移動をさせるようにしました。
pacman.jsの下の方に書いてある、move -> moving -> movedの箇所がソレで、animate()機能を使って、移動処理をしています。
0.3秒ほどの操作誤差が発生していますが、それほど違和感がないので、この方式での安定感を感じました。
次回は、コリジョン判定(壁の衝突判定)を行います。
知財
パックマンは、バンダイナムコ社の登録商標です。
PAC-MAN™ & ©1980 BANDAI NAMCO Entertainment Inc.
0 件のコメント:
コメントを投稿