SNSでパックマンを公開してみたところ、ほとんどの人がスマホで見ていたらしく、「操作できない」コメントをたくさんいただいたので、重い腰を上げてスマホ対応をしてみたいと思います。
スマホゲームはこれまで何度も作ってきたのでポイントは押さえているのですが、比較的直感的に操作できる方法で構築してみました。
今回の作業内容
- 前回修正した自機と敵キャラの変更に伴うモジュール修正
- スマホ用のタッチイベント操作の導入
修正ファイル一覧
- index.html
- assets/ghost.html
- css/footer.css
- css/ghost.css
- css/mobile.css
- css/pacman.css
- css/style.css
- js/control.js
- js/frame.js
- js/ghost.js
- js/main.js
- js/mobile.js
- js/pacman.js
ソースコード
[全部更新] index.html
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='utf-8' />
<title>Pacman for HTML</title>
<meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0" name="viewport">
<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='pacman-root'>
<div class='frame-area' data-power="0"></div>
<div class='frame-footer'>
<div class='life-count'></div>
<div class='score'></div>
<div class='fruit'></div>
</div>
</div>
</body>
</html>
[全部更新] assets/ghost.html
<div class='eye-l'></div>
<div class='eye-r'></div>
<div class='mouse'></div>
[全部更新] css/footer.css
.frame-footer .life-count{
--size-life : 32px;
}
.frame-footer .life-count .pacman{
position:static;
width:var(--size-life);
height:var(--size-life);
margin:10px 5px;
display:inline-block;
}
.frame-footer .memo{
color:white;
/* display:none; */
}
[全部更新] css/ghost.css
.ghost{
position : absolute;
z-index : 10;
display : inline-block;
width : 32px;
height : 32px;
transform:translate(calc(var(--block) / -2) , calc(var(--block) / -2));
}
.ghost::before{
content:'';
position:absolute;
display : block;
width : 100%;
height : 100%;
clip-path : path('M 0,24 Q 0,0 16,0 Q 32,0 32,24 L 32,30 Q 30,34 26,30 Q 23,26 19,30 Q 16,34 12,30 Q 10,26 6,30 Q 2,34 0,30 T 0,30 z');
}
.ghost[data-color='1']::before{
background-color : red;
}
.ghost[data-color='2']::before{
background-color : orange;
}
.ghost[data-color='3']::before{
background-color : lightblue;
}
.ghost[data-color='4']::before{
background-color : pink;
}
.ghost[data-status='weak']::before{
background-color : blue;
}
.ghost .eye-l,
.ghost .eye-r{
position:absolute;
top:8px;
width:8px;
background-color:white;
width:9px;
height:9px;
border-radius:50%;
}
.ghost .eye-l{
left:5px;
}
.ghost .eye-r{
right:5px;
}
.ghost .eye-l::before,
.ghost .eye-r::before{
content:'';
position:absolute;
left:50%;
top:50%;
width:50%;
height:50%;
background-color:black;
transform:translate(-50%,-50%);
border-radius:50%;
}
.ghost[data-direction='left'] .eye-l{
left:0px;
}
.ghost[data-direction='left'] .eye-r{
right:10px;
}
.ghost[data-direction='right'] .eye-l{
left:10px;
}
.ghost[data-direction='right'] .eye-r{
right:0px;
}
.ghost[data-direction='up'] .eye-l,
.ghost[data-direction='up'] .eye-r{
top:2px;
}
.ghost[data-direction='down'] .eye-l,
.ghost[data-direction='down'] .eye-r{
top:16px;
}
.ghost[data-direction='left'] .eye-l::before,
.ghost[data-direction='left'] .eye-r::before{
left:0;
right:auto;
transform:translate(0,-50%);
}
.ghost[data-direction='right'] .eye-l::before,
.ghost[data-direction='right'] .eye-r::before{
left:auto;
right:0;
transform:translate(0,-50%);
}
.ghost[data-direction='up'] .eye-l::before,
.ghost[data-direction='up'] .eye-r::before{
top:0;
bottom:auto;
transform:translate(-50%,0);
}
.ghost[data-direction='down'] .eye-l::before,
.ghost[data-direction='down'] .eye-r::before{
top:auto;
bottom:0;
transform:translate(-50%,0);
}
.ghost[data-status='weak'] .eye-l{
top:8px;
left:8px;
}
.ghost[data-status='weak'] .eye-r{
top:8px;
right:8px;
}
.ghost[data-status='weak'] .eye-l,
.ghost[data-status='weak'] .eye-r{
width:6px;
height:6px
}
.ghost[data-status='weak'] .eye-l::before,
.ghost[data-status='weak'] .eye-r::before{
opacity:0;
}
.ghost[data-status='weak'] .mouse{
position:absolute;
top:16px;
left:0;
width:100%;
height:8px;
background-color:#FFF;
clip-path : path('M 2,6 Q 4,2 6,4 Q 8,8 10,4 Q 12,2 14,4 Q 16,8 18,4 Q 20,2 22,4 Q 24,8 26,4 Q 28,2 30,6 L 30,7 Q 28,3 26,5 Q 24,9 22,5 Q 20,3 18,5 Q 16,9 14,5 Q 12,3 10,5 Q 8,9 6,5 Q 4,3 2,7');
}
.ghost[data-status='dead']::before{
background-color : transparent;
}
[新規追加] css/mobile.css
html{
width:100%;
padding:0;
margin:0;
}
body{
width:100%;
overflow:hidden;
padding:0;
margin:0;
}
.pacman-root{
transform-origin:top left;
}
[全部更新] css/pacman.css
.pacman{
position : absolute;
z-index : 10;
display : inline-block;
width : var(--size-chara);
height : var(--size-chara);
}
.pacman::before{
content:'';
background-color : yellow;
display : block;
width : 100%;
height : 100%;
border-radius : 50%;
}
/* パクパク処理 */
.pacman[data-status="anim"]::before{
animation-name : pacman_anim;
animation-duration:var(--anim-speed);
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
@keyframes pacman_anim{
0%{
clip-path: polygon(
50% 60%,
50% 0%,
100% 0%,
100% 100%,
0% 100%,
0% 0%,
50% 0%,
50% 60%
);
}
50%{
clip-path: polygon(
50% 60%,
100% 0%,
100% 0%,
100% 100%,
0% 100%,
0% 0%,
0% 0%,
50% 60%
);
}
100%{
clip-path: polygon(
50% 60%,
50% 0%,
100% 0%,
100% 100%,
0% 100%,
0% 0%,
50% 0%,
50% 60%
);
}
}
/* ステージ座標とのズレ解消用処理 */
.pacman{
transform : translate(calc(var(--block) / -2) , calc(var(--block) / -2));
}
.pacman::before,
.pacman[data-direction="left"]::before{
transform: rotate(-90deg);
}
.pacman[data-direction="right"]::before{
transform:rotate(90deg);
}
.pacman[data-direction="up"]::before{
transform: rotate(0deg);
}
.pacman[data-direction="down"]::before{
transform: scaleY(-1);
}
/* 動き停止 */
.frame-area[data-status='clear'] .pacman,
.pacman[data-status='crashed']{
animation-play-state: paused;
}
/* Dead処理 */
.pacman[data-status="dead"]::before{
animation-name : pacman-dead;
transform:rotate(0deg);
animation-delay : 0.0s;
animation-direction : normal;
animation-duration : 2.0s;
animation-fill-mode: forwards;
animation-iteration-count: 1;
animation-timing-function: linear;
}
@keyframes pacman-dead{
0%{
clip-path: polygon(
50% 50%,
50% 0%,
100% 0%,
100% 100%,
0% 100%,
0% 0%,
50% 0%,
50% 50%
);
}
25%{
clip-path: polygon(
50% 50%,
100% 0%,
100% 0%,
100% 100%,
0% 100%,
0% 0%,
0% 0%,
50% 50%
);
}
75%{
clip-path: polygon(
50% 50%,
100% 100%,
100% 100%,
100% 100%,
0% 100%,
0% 100%,
0% 100%,
50% 50%
);
}
100%{
clip-path: polygon(
50% 50%,
50% 100%,
50% 100%,
50% 100%,
50% 100%,
50% 100%,
50% 100%,
50% 50%
);
}
}
[一部更新] css/style.css
@import 'frame.css';
@import 'pacman.css';
@import 'ghost.css';
@import 'footer.css';
@import 'mobile.css';
:root{
--block : 16px;
--size-chara : calc(var(--block) * 2);
}
[全部更新] js/control.js
import { Pacman } from './pacman.js'
import { Frame } from './frame.js'
export class Control{
constructor(){
window.addEventListener('keydown' , Control.keydown.bind(this))
window.addEventListener('keyup' , Control.keyup.bind(this))
}
static key2name(key){
switch(key){
case 37 : return 'left'
case 38 : return 'up'
case 39 : return 'right'
case 40 : return 'down'
}
}
static keydown(e){
if(e.repeat === true){return}
if(Frame.is_clear){return}
const key = e.keyCode
const direction = Control.key2name(key)
if(!direction){return}
Control.direction = direction
Pacman.move(Control.direction)
}
static keyup(e){
if(!Control.direction){return}
// Control.clear()
}
static clear(){
if(!Control.direction){return}
delete Control.direction
}
}
[全部更新] js/frame.js
import { Main } from './main.js'
import { Pacman } from './pacman.js'
import { Ghost } from './ghost.js'
import { Feed } from './feed.js'
import { Control } from './control.js'
import { Footer } from './footer.js'
import { Mobile } from './mobile.js'
export class Frame{
constructor(){
Frame.create()
Frame.start()
}
static get root(){
return document.querySelector(`.frame-area`)
}
static get block_size(){
// const s5 = document.querySelector('.S5')
// return s5.offsetWidth
return Mobile.block_size
}
static get cols_count(){
return ~~(Frame.root.offsetWidth / Frame.block_size)
}
static get is_ready(){
return Frame.root.getAttribute('data-status') === 'ready' ? true : false
}
static get_elm(num){
return Frame.root.querySelector(`[data-num='${num}']`)
}
static get is_weak(){
const power = Frame.root.getAttribute('data-power')
switch(power){
case '1':
case '2':
return true
default:
return false
}
}
static get is_clear(){
return Frame.root.getAttribute('data-status') === 'clear' ? true : false
}
static get is_game_over(){
return Footer.life_count <= 0 ? true : false
}
static start(){
if(!Frame.is_game_over){
Frame.root.setAttribute('data-status' , 'ready')
Ghost.init()
Pacman.init()
Frame.message_on(`READY!`)
setTimeout((()=>{
Frame.root.setAttribute('data-status' , '')
Frame.message_off()
Ghost.start()
Pacman.start()
}),2000)
}
else{
Frame.game_over()
}
}
static game_over(){
Frame.message_on(`GAME OVER`)
}
static get elm_message(){
return Frame.root.querySelector('.message')
}
static message_on(message){
const div = document.createElement('div')
div.className = 'message'
div.innerHTML = message
Frame.root.appendChild(div)
}
static message_off(){
Frame.root.removeChild(this.elm_message)
}
static create(){
Frame.frame_datas = JSON.parse(Frame.asset_json)
Frame.view()
Frame.set_collision()
Frame.set_ghost_start_area()
}
static crear(){
Frame.root.innerHTML = ''
}
static view(){
for(let i=0; i<Frame.frame_datas.length; i++){
const p = document.createElement('p')
p.className = Frame.frame_datas[i]
Frame.root.appendChild(p)
p.setAttribute('data-num' , i)
}
}
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` , '')
}
// 壁座標に1を設置
static set_collision(type){
const cols_count = Frame.cols_count
const maps = []
let row_count = 0
for(const frame_data of Frame.frame_datas){
maps[row_count] = maps[row_count] || []
// 移動できる
if(frame_data.match(/^P/i)
|| frame_data.toUpperCase() === 'S5'
|| frame_data.match(/^W/i)
|| frame_data.match(/^T/i)){
maps[row_count].push(0)
}
// 壁
else{
maps[row_count].push(1)
}
if(maps[row_count].length === cols_count){
row_count++
}
}
Frame.map = this.map = maps
}
static is_collision(map){
if(!map || !Frame.map || !Frame.map[map.y]){return}
return Frame.map[map.y][map.x]
}
// type @ [pacman , ghost]
static is_through(map , direction, status){
const through_item = Frame.frame_datas[Frame.get_pos2num(map)]
if(status === 'dead'){
// return true
if(through_item === 'TU' && direction === 'up'
|| through_item === 'TD' && direction === 'down'
|| through_item === 'TL' && direction === 'left'
|| through_item === 'TR' && direction === 'right'){
return false
}
else{
return true
}
}
else{
if(through_item === 'TU' && direction !== 'up'
|| through_item === 'TD' && direction !== 'down'
|| through_item === 'TL' && direction !== 'left'
|| through_item === 'TR' && direction !== 'right'){
return false
}
else{
return true
}
}
}
static get_pos2num(pos){
return pos.y * Frame.map[0].length + pos.x
}
static get_num2pos(num){
return {
x : num % Frame.map[0].length,
y : ~~(num / Frame.map[0].length),
}
}
static is_warp(map){
const num = Frame.get_pos2num(map)
return Frame.frame_datas[num] === 'W1' ? true : false
}
static get_another_warp_pos(map){
const warp_index_arr = Frame.filterIndex(Frame.frame_datas , 'W1')
const current_index = Frame.get_pos2num(map)
const another_num = warp_index_arr.find(e => e !== current_index)
return Frame.get_num2pos(another_num)
}
static filterIndex(datas,target){
const res_arr = []
for(let i=0; i<datas.length; i++){
if(datas[i] === target){
res_arr.push(i)
}
}
return res_arr
}
static next_pos(direction , pos){
const temp_pos = {
x : pos.x,
y : pos.y,
}
switch(direction){
case 'left':
temp_pos.x -= 1
break
case 'right':
temp_pos.x += 1
break
case 'up':
temp_pos.y -= 1
break
case 'down':
temp_pos.y += 1
break
default: return
}
return temp_pos
}
static set_ghost_start_area(){
const ghost_start_area = []
for(let i=0; i<Frame.frame_datas.length; i++){
if(Frame.frame_datas[i] !== 'TU'
&& Frame.frame_datas[i] !== 'TD'
&& Frame.frame_datas[i] !== 'TL'
&& Frame.frame_datas[i] !== 'TR'){continue}
const pos = Frame.get_num2pos(i)
ghost_start_area.push({
num : i,
pos : pos,
})
}
const pos = {
x : ghost_start_area.map(e => e.pos.x).reduce((sum , e)=>{ return sum + e}) / ghost_start_area.length,
y : ghost_start_area.map(e => e.pos.y).reduce((sum , e)=>{ return sum + e}) / ghost_start_area.length,
}
Frame.ghost_start_area = pos
}
static stage_clear(){
Main.is_clear = true
Control.clear()
Pacman.move_stop()
Pacman.close_mouse()
Ghost.move_stops()
Ghost.hidden_all()
setTimeout((()=>{
Frame.root.setAttribute('data-status' , 'clear')
}),500)
setTimeout((()=>{
Control.direction = null
Main.is_clear = false
Frame.crear()
Frame.root.setAttribute('data-status' , '')
Frame.create()
Feed.reset_data()
Frame.start()
}),4000)
}
static crashed(){
Control.clear()
Ghost.move_stops()
Ghost.remove_all()
setTimeout((()=>{
Control.direction = null
Main.is_dead = false
Frame.root.setAttribute('data-status' , '')
Frame.set_ghost_start_area()
Pacman.remove()
Frame.start()
}),3000)
}
}
[全部更新] js/ghost.js
import { Main } from './main.js'
import { Frame } from './frame.js'
import { Pacman } from './pacman.js'
export class Ghost{
static get elm_ghosts(){
return document.querySelectorAll('.ghost')
}
static get_data(elm){
const color_num = elm.getAttribute('data-color')
return Ghost.datas.find(e => e.id === Number(color_num))
}
static get_id(elm){
const data = Ghost.get_data(elm)
return data.id
}
static get_coodinate(elm){
const data = Ghost.get_data(elm)
return data.coodinate
}
static init(){
Ghost.create_element()
Ghost.set_ghost_asset()
}
static start(){
Ghost.set_move()
}
static create_element(){
Ghost.datas = JSON.parse(Ghost.data_json)
for(const data of Ghost.datas){
const elm = document.createElement('div')
elm.className = 'ghost'
elm.setAttribute('data-color' , data.id)
Frame.root.appendChild(elm)
}
}
static set_ghost_asset(){
const elm_ghosts = Ghost.elm_ghosts
for(const elm_ghost of elm_ghosts){
const coodinate = Ghost.get_coodinate(elm_ghost)
Frame.put(elm_ghost, coodinate)
elm_ghost.innerHTML = Ghost.asset
}
}
static set_move(){
const elm_ghosts = Ghost.elm_ghosts
for(const elm_ghost of elm_ghosts){
Ghost.move(elm_ghost)
}
}
static move(elm_ghost){
if(!elm_ghost){return}
const data = Ghost.get_data(elm_ghost)
const coodinate = Ghost.get_coodinate(elm_ghost)
const status = Ghost.get_status(elm_ghost)
const directions = Ghost.get_enable_directions(coodinate , data.direction , status) || Ghost.get_enable_directions(coodinate)
const direction = Ghost.get_direction(elm_ghost, directions)
const next_pos = Frame.next_pos(direction , coodinate)
Ghost.set_direction(elm_ghost , direction)
Ghost.moving(elm_ghost , next_pos)
}
static moving(elm_ghost , next_pos){
if(!elm_ghost || !next_pos){return}
const data = Ghost.get_data(elm_ghost)
if(!data){return}
if(Main.is_dead){
return
}
//warp
next_pos = Ghost.warp(data , next_pos)
if(!next_pos){return}
const before_pos = Ghost.get_pos(data.coodinate)
const after_pos = Ghost.get_pos(next_pos)
if(Pacman.is_collision(next_pos)){
Ghost.hit(elm_ghost)
}
data.next_pos = next_pos
const id = 'ghost_anim'
elm_ghost.animate(
[
{
left : `${before_pos.x}px`,
top : `${before_pos.y}px`,
},
{
left : `${after_pos.x}px`,
top : `${after_pos.y}px`,
}
],
{
id : id,
duration: Ghost.get_speed(elm_ghost)
}
)
Promise.all([elm_ghost.getAnimations().find(e => e.id === id) && elm_ghost.getAnimations().find(e => e.id === id).finished])
.then(Ghost.moved.bind(this , elm_ghost))
}
static moved(elm_ghost , e){
if(!elm_ghost){return}
const data = Ghost.get_data(elm_ghost)
Ghost.set_pos(elm_ghost , Ghost.get_pos(data.next_pos))
data.coodinate = data.next_pos
if(Main.is_dead){return}
if(Pacman.is_collision(data.coodinate)){
Ghost.hit(elm_ghost)
}
// dead -> alive
if(Ghost.get_status(elm_ghost) === 'dead'){
const current_stage_item = Frame.frame_datas[Frame.get_pos2num(data.coodinate)]
if(current_stage_item.match(/^T/i)){
Ghost.alive(elm_ghost)
}
}
if(elm_ghost.hasAttribute('data-reverse')){
elm_ghost.removeAttribute('data-reverse')
Ghost.reverse_move(elm_ghost , data)
}
else{
Ghost.next_move(elm_ghost , data)
}
}
static warp(data, next_pos){
if(!Frame.is_warp(next_pos)){return next_pos}
data.coodinate = Frame.get_another_warp_pos(next_pos)
return Frame.next_pos(data.direction , data.coodinate)
}
static hit(elm_ghost){
switch(elm_ghost.getAttribute('data-status')){
case 'weak':
Ghost.dead(elm_ghost)
break
case 'dead':
break
default:
Pacman.crashed(elm_ghost)
Ghost.crashed(elm_ghost)
return
}
}
static get_pos(pos){
return {
x : pos.x * Frame.block_size,
y : pos.y * Frame.block_size,
}
}
static set_pos(elm_ghost , pos){
elm_ghost.style.setProperty('left' , `${pos.x}px` , '')
elm_ghost.style.setProperty('top' , `${pos.y}px` , '')
}
static reverse_move(elm_ghost , data){
const direction = Ghost.reverse_direction(data.direction)
Ghost.set_direction(elm_ghost , direction)
data.direction = direction
const next_pos = Frame.next_pos(data.direction , data.coodinate)
Ghost.moving(elm_ghost , next_pos)
}
static next_move(elm_ghost , data){
const directions = Ghost.get_enable_directions(data.coodinate , data.direction , Ghost.get_status(elm_ghost))
const direction = Ghost.get_direction(elm_ghost, directions)
Ghost.set_direction(elm_ghost , direction)
const next_pos = Frame.next_pos(data.direction , data.coodinate)
if(Frame.is_collision(next_pos)){
Ghost.move(elm_ghost)
}
else{
Ghost.moving(elm_ghost , next_pos)
}
}
static get_direction(elm_ghost, directions){
if(!directions || !directions.length){return null}
switch(Ghost.get_status(elm_ghost)){
// dead : go to the start-area
case 'dead':
if(directions.length === 1){
return directions[0]
}
const ghost_data = Ghost.get_data(elm_ghost)
const start_datas = Frame.ghost_start_area
if(directions.indexOf('right') !== -1
&& ghost_data.coodinate.x > start_datas.x){
const index = directions.findIndex(e => e === 'right')
directions.splice(index,1)
}
if(directions.indexOf('left') !== -1
&& ghost_data.coodinate.x < start_datas.x){
const index = directions.findIndex(e => e === 'left')
directions.splice(index,1)
}
if(directions.indexOf('bottom') !== -1
&& ghost_data.coodinate.y > start_datas.y){
const index = directions.findIndex(e => e === 'bottom')
directions.splice(index,1)
}
if(directions.indexOf('top') !== -1
&& ghost_data.coodinate.y < start_datas.y){
const index = directions.findIndex(e => e === 'top')
directions.splice(index,1)
}
const num = Math.floor(Math.random() * directions.length)
return directions[num] || null
// normal
default:
const direction_num = Math.floor(Math.random() * directions.length)
return directions[direction_num] || null
}
}
// 移動可能な方向の一覧を取得する
static get_enable_directions(pos , direction , status){
const directions = []
// Through(通り抜け)
const frame_data = Frame.frame_datas[Frame.get_pos2num(pos)]
if(frame_data.match(/^T/i)){
return [direction]
}
// 右 : right
if(pos.x + 1 < Frame.map[pos.y].length
&& !Frame.is_collision({x: pos.x + 1, y: pos.y})
&& Frame.is_through({x: pos.x + 1, y: pos.y} , 'right' , status)
&& direction !== 'left'){
directions.push('right')
}
// 左 : left
if(pos.x - 1 >= 0
&& !Frame.is_collision({x: pos.x - 1, y: pos.y})
&& Frame.is_through({x: pos.x - 1, y: pos.y} , 'left' , status)
&& direction !== 'right'){
directions.push('left')
}
// 上 : up
if(pos.y - 1 >= 0
&& !Frame.is_collision({x: pos.x, y: pos.y - 1})
&& Frame.is_through({x: pos.x, y: pos.y - 1} , 'up' , status)
&& direction !== 'down' ){
directions.push('up')
}
// 下 : down
if(pos.y + 1 < Frame.map.length
&& !Frame.is_collision({x: pos.x, y: pos.y + 1})
&& Frame.is_through({x: pos.x, y: pos.y + 1} , 'down' , status)
&& direction !== 'up'){
directions.push('down')
}
if(directions.length){
return directions
}
else{
return [Ghost.reverse_direction(direction)]
}
}
static reverse_direction(direction){
switch(direction){
case 'right' : return 'left'
case 'left' : return 'right'
case 'up' : return 'down'
case 'down' : return 'up'
}
}
static set_direction(elm_ghost , direction){
const data = Ghost.get_data(elm_ghost)
data.direction = direction
// const head = elm_ghost.querySelector('.head')
// if(!head){return}
// head.setAttribute('data-direction' , direction)
elm_ghost.setAttribute('data-direction' , direction)
}
static power_on(){
for(const elm of Ghost.elm_ghosts){
if(Ghost.get_status(elm) === 'dead'){continue}
elm.setAttribute('data-reverse' , '1')
elm.setAttribute('data-status' , 'weak')
}
}
static power_off(){
for(const elm of Ghost.elm_ghosts){
if(elm.getAttribute('data-status') === 'weak'){
elm.setAttribute('data-status' , '')
}
}
}
static crashed(elm_ghost){
Main.is_crash = true
Main.is_dead = true
Ghost.move_stops()
Frame.crashed()
}
static move_stops(){
for(const elm_ghost of Ghost.elm_ghosts){
Ghost.move_stop(elm_ghost)
}
}
static move_stop(elm_ghost){
// const svg = elm_ghost.querySelector('.under svg')
// svg.pauseAnimations()
const anim = elm_ghost.getAnimations()
if(anim && anim.length){
anim[0].pause()
}
}
static hidden_all(){
for(const elm of Ghost.elm_ghosts){
elm.style.setProperty('display' , 'none' , '');
}
}
static remove_all(){
for(const elm of Ghost.elm_ghosts){
elm.parentNode.removeChild(elm)
}
}
static dead(elm_ghost){
elm_ghost.setAttribute('data-status' , 'dead')
}
static alive(elm_ghost){
elm_ghost.setAttribute('data-status' , '')
}
static get_status(elm_ghost){
return elm_ghost.getAttribute('data-status')
}
static get_speed(elm_ghost){
switch(Ghost.get_status(elm_ghost)){
case 'weak':
return Main.ghost_weak_speed
case 'dead':
return Main.ghost_dead_speed
default:
return Main.ghost_normal_speed
}
}
}
[全部更新] js/main.js
import { Asset } from './asset.js'
import { Frame } from './frame.js'
import { Control } from './control.js'
import { Feed } from './feed.js'
import { Footer } from './footer.js'
import { Mobile } from './mobile.js'
export const Main = {
anim_speed : 200,
ghost_normal_speed : 200,
ghost_weak_speed : 400,
ghost_dead_speed : 50,
is_crash : false,
is_dead : false,
is_clear : false,
life_count : 3,
}
function init(){
new Asset({
files:[
{
file : 'assets/frame.json',
target : 'frame',
name : 'asset_json',
type : 'data',
},
{
file : 'assets/ghost.json',
target : 'ghost',
name : 'data_json',
type : 'data',
},
{
file : 'assets/ghost.html',
target : 'ghost',
name : 'asset',
type : 'html',
}
]
}).then(()=>{
new Footer()
new Frame()
new Control()
new Feed()
new Mobile()
})
}
switch(document.readyState){
case 'complete':
case 'interactive':
init()
break
default:
window.addEventListener('DOMContentLoaded' , (()=>init()))
}
[新規追加] js/mobile.js
import { Frame } from './frame.js'
import { Css } from './css.js'
export class Mobile{
constructor(){
this.set_window_size()
}
get window_size(){
return window.innerWidth
}
get frame_size(){
return Frame.root.offsetWidth
}
get elm_pacman_root(){
return document.querySelector('.pacman-root')
}
static get block_size(){
return Mobile.data_block_size || document.querySelector('.S5').offsetWidth
}
set_window_size(){
if(this.window_size >= this.frame_size){return}
const rate = this.window_size / this.frame_size
// const value = Css.get_css(':root' , '--block')
// const block_size = Number(value.replace('px',''))
// const new_size = block_size * rate
// Css.set_css(':root' , '--block' , `${new_size}px`)
// Mobile.data_block_size = new_size
// console.log(value,block_size,new_size)
this.elm_pacman_root.style.setProperty('transform',`scale(${rate})`,'')
// console.log(Css.get_css(':root' , '--block'))
}
set_event(){
if(typeof window.ontouchstart !== 'undefined'){
window.addEventListener('touchstart' , Control.touchstart.bind(this))
window.addEventListener('touchmove' , Control.touchmove.bind(this))
window.addEventListener('touchend' , Control.touchend.bind(this))
}
}
static touchstart(e){
console.log(e)
}
static touchmove(e){
console.log(e)
}
static touchend(e){
console.log(e)
}
}
[全部更新] js/mobile.js
import { Frame } from './frame.js'
import { Pacman } from './pacman.js'
import { Control } from './control.js'
export class Mobile{
constructor(){
this.set_window_size()
this.set_event()
}
get window_size(){
return window.innerWidth
}
get frame_size(){
return Frame.root.offsetWidth
}
get elm_pacman_root(){
return document.querySelector('.pacman-root')
}
static get block_size(){
return Mobile.data_block_size || document.querySelector('.S5').offsetWidth
}
static get elm_memo(){
return document.querySelector('.frame-footer .memo')
}
set_window_size(){
if(this.window_size >= this.frame_size){return}
const rate = this.window_size / this.frame_size
this.elm_pacman_root.style.setProperty('transform',`scale(${rate})`,'')
}
set_event(){
if(typeof window.ontouchstart !== 'undefined'){
window.addEventListener('touchstart' , Mobile.touchstart.bind(this))
window.addEventListener('touchmove' , Mobile.touchmove.bind(this))
window.addEventListener('touchend' , Mobile.touchend.bind(this))
}
}
static touchstart(e){
Mobile.touch_datas = {
pos : {
x : e.touches[0].pageX,
y : e.touches[0].pageY,
},
direction : null,
}
Mobile.view_memo()
}
static touchmove(e){
if(!Mobile.touch_datas){return}
Mobile.touch_datas.direction = Control.direction || null
const pos = {
x : e.touches[0].pageX,
y : e.touches[0].pageY,
}
Control.direction = Mobile.get_direction(pos)
if(Control.direction === Mobile.touch_datas.direction){return}
Mobile.view_memo()
Mobile.pacman_move(Control.direction)
}
static touchend(e){
if(Mobile.touch_datas){
delete Mobile.touch_datas
}
if(Control.direction){
delete Control.direction
}
}
static get_direction(pos){
if(!Mobile.touch_datas){return}
const min = 10
const diff_pos = {
x : pos.x - Mobile.touch_datas.pos.x,
y : pos.y - Mobile.touch_datas.pos.y,
}
const volume = {
x : Math.abs(diff_pos.x),
y : Math.abs(diff_pos.y),
}
diff_pos.x = volume.x >= min ? diff_pos.x : 0
diff_pos.y = volume.y >= min ? diff_pos.y : 0
// vertical
if(diff_pos.x && volume.x > volume.y){
if(diff_pos.x < 0){
return 'left'
}
else{
return 'right'
}
}
// horizontal
else if(diff_pos.y && volume.x < volume.y){
if(diff_pos.y < 0){
return 'up'
}
else{
return 'down'
}
}
// other
return null
}
static view_memo(str){
// console.log(Mobile.elm_memo,Mobile.touch_datas.direction)
Mobile.elm_memo.textContent = str || Control.direction || ''
}
static pacman_move(direction){
if(!direction){return}
// Control.direction = direction
Pacman.move(direction)
}
}
[全部更新] js/pacman.js
import { Main } from './main.js'
import { Frame } from './frame.js'
import { Control } from './control.js'
import { Feed } from './feed.js'
import { Ghost } from './ghost.js'
import { Footer } from './footer.js'
export class Pacman{
static init(){
Footer.delete_life()
Pacman.direction = null
Pacman.next_pos = null
Pacman.create()
Pacman.coodinates = Pacman.start_coodinates
Frame.put(Pacman.elm, Pacman.coodinates)
Pacman.elm.style.setProperty('--anim-speed' , `${Main.anim_speed}ms` , '')
}
static start(){
if(!Control.direction){return}
Pacman.move(Control.direction)
}
static create(){
if(Pacman.elm){return}
const div = document.createElement('div')
div.className = 'pacman'
Frame.root.appendChild(div)
}
static get start_coodinates(){
return {
x : 14,
y : 23,
}
}
static get elm(){
return document.querySelector('.frame-area .pacman')
}
static move(direction){
if(Frame.is_ready){return}
if(Pacman.direction){
return
}
Pacman.direction = direction
Pacman.elm.setAttribute('data-status' , "anim")
this.moving()
}
static moving(){
if(Main.is_dead || Main.is_clear){return}
Pacman.next_pos = Frame.next_pos(Pacman.direction , Pacman.coodinates)
//warp
if(Frame.is_warp(Pacman.next_pos)){
Pacman.coodinates = Frame.get_another_warp_pos(Pacman.next_pos)
Pacman.next_pos = Frame.next_pos(Pacman.direction , Pacman.coodinates)
}
if(Frame.is_collision(Pacman.next_pos)
&& !Pacman.is_wall(Pacman.next_pos)){
Pacman.elm.setAttribute('data-status' , "")
delete Pacman.direction
return
}
Pacman.elm.setAttribute('data-direction' , Pacman.direction)
Pacman.elm.animate(
[
{
left : `${Pacman.coodinates.x * Frame.block_size}px`,
top : `${Pacman.coodinates.y * Frame.block_size}px`,
},
{
left : `${Pacman.next_pos.x * Frame.block_size}px`,
top : `${Pacman.next_pos.y * Frame.block_size}px`,
}
],
{
duration: Main.anim_speed
}
)
Promise.all(Pacman.elm.getAnimations().map(e => e.finished)).then(()=>{
Pacman.moved()
})
}
static moved(){
Pacman.coodinates = Pacman.next_pos
Frame.put(Pacman.elm, Pacman.coodinates)
Feed.move_map()
if(Control.direction && Control.direction !== Pacman.direction){
const temp_pos = Frame.next_pos(Control.direction , Pacman.coodinates)
if(!Frame.is_collision(temp_pos)
&& !Pacman.is_wall(temp_pos)){
Pacman.direction = Control.direction
}
}
Pacman.moving()
}
static is_wall(map){
const through_item = Frame.frame_datas[Frame.get_pos2num(map)]
if(through_item === 'TU'
|| through_item === 'TD'
|| through_item === 'TL'
|| through_item === 'TR'){
return true
}
else{
false
}
}
static is_collision(pos){
if(!pos){return}
if(Pacman.coodinates && pos.x === Pacman.coodinates.x && pos.y === Pacman.coodinates.y){
return true
}
else if(Pacman.next_pos && pos.x === Pacman.next_pos.x && pos.y === Pacman.next_pos.y){
return true
}
else{
return false
}
}
static crashed(elm_ghost){
// Pacman.elm.setAttribute('data-anim' , '')
setTimeout(Pacman.dead , 1000)
}
static dead(){
Ghost.hidden_all()
Pacman.elm.setAttribute('data-direction' , 'up')
Pacman.elm.setAttribute('data-status' , 'dead')
}
static move_stop(){
const anim = Pacman.elm.getAnimations()
if(anim && anim.length){
// console.log(anim.length)
anim[0].pause()
}
else{
}
}
static hidden(){
Pacman.elm.style.setProperty('display','none','')
}
static remove(){
Pacman.elm.parentNode.removeChild(Pacman.elm)
}
static close_mouse(){
// Pacman.elm.setAttribute('data-status' , 'mouse-close')
}
}
画面表示イメージ
デモプレイ
HTML版Pacman
github Pagesで公開しています。
スマホでアクセスして、遊んでみてください。
知財
パックマンは、バンダイナムコ社の登録商標です。
PAC-MAN™ & ©1980 BANDAI NAMCO Entertainment Inc.
0 件のコメント:
コメントを投稿