[Javascript] パックマンをHTMLで作ってみるブログ#10 敵キャラ移動

2023年7月4日

Javascript

eyecatch いよいよ敵キャラを動かして、本格的にゲームっぽくしてみたいと思います。

今回の目的

今回はかなりやることが多いです。 仕様変更などをする必要も出てきて、修正するモジュールも多くなってしまいました。
- ランダム移動 - 方向切替(曲がり角の度にランダム選択) - 袋小路の場合は、バックする - ワープ処理 - 敵基地出口処理 (map: TU(Through Up) , TD(Through Down), TL(Through Left), TR(Through right))

対象ファイル一覧

  1. index.html
  2. assets/frame.json
  3. css/frame.css
  4. css/ghost.css
  5. js/frame.js
  6. js/ghost.js
  7. js/main.js
  8. js/pacman.js

ソースコード

敵キャラをHTMLでベタ書きしていたのを、システムで書き込む仕様に変更したので、下記赤部分をコメントアウトします。

[部分更新] index.html

<!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>

[部分更新] 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","P1","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","P1","S4","S6","P1","S7","S8","S8","S9","P1","D6", "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", "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","TU","TU","D8","D8","S3","S5","S4","S6","P1","D6","S5","S5","S5","S5","S5", "D2","D2","D2","D2","D2","S9","P1","S7","S9","S5","D6","SX","SX","S5","SX","SX","SX","D4","S5","S7","S9","P1","S7","D2","D2","D2","D2","D2", "W1","S5","S5","S5","S5","S5","P1","S5","S5","S5","D6","SX","S5","S5","S5","S5","SX","D4","S5","S5","S5","P1","S5","S5","S5","S5","S5","W1", "D8","D8","D8","D8","D8","S3","P1","S1","S3","S5","D6","SX","SX","SX","SX","SX","SX","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" ]

[追加] css/frame.css

敵キャラの始動エリアの出口にフタをするパーツを配置したので、次のセレクター部分を追加。 /* Through */ .frame-area .TU::before{ position:absolute; bottom:calc(var(--block) / 4); left:0; width : 100%; height: calc(var(--block) / 4); background-color:pink; }

[全部更新] css/ghost.css

71行目あたりの2箇所(赤字箇所)を下記のように修正。 top,bottomというcssの記述をjavascriptのup,downに変更して、名称統一しました。 ※めんどくさい場合は、下記コードをファイルごと入れ替えて下さい。 .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)); } .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='up'] > *::before{ margin-top : 0; margin-bottom : 50%; } .ghost:not([data-status='weak']) > .face[data-direction='down'] > *::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); }

[部分更新] js/frame.js

85行目あたりの次の関数を少し変更しました。 赤字の箇所が更新されています。 ステージパーツを追加したので、コリジョン判定の処理を追加しました。 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 } get cols_count(){ return ~~(Frame.root.offsetWidth / Frame.block_size) } static get_elm(num){ return Frame.root.querySelector(`[data-num='${num}']`) } 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() this.set_collision() this.finish() } }).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) p.setAttribute('data-num' , i) } } 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` , '') } // 壁座標に1を設置 set_collision(type){ const cols_count = this.cols_count const maps = [] let row_count = 0 for(const frame_data of this.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] } static is_through(map , direction){ // if(!map || !Frame.map || !Frame.map[map.y] || !Frame.map[map.y][map.x]){return} const through_item = Frame.frame_datas[Frame.get_pos2num(map)] // console.log(direction,through_item) 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 } }

[全部更新] js/ghost.js

敵キャラ用のモジュールは大幅に書き換えたため、ファイル内を全部入れ替えます。 念のため、変更点を赤字にしてあります。 import { Main } from './main.js' import { Frame } from './frame.js' export class Ghost{ constructor(){ this.put_element() } static datas = [ { id : 1, direction : null, coodinate : { x : 12, y : 11 }, }, { id : 2, direction : null, coodinate : { x : 15, y : 11 }, }, { id : 3, direction : null, coodinate : { x : 12, y : 14 }, }, { id : 4, direction : null, coodinate : { x : 15, y : 14 }, }, ] 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 } put_element(){ for(const data of Ghost.datas){ const elm = document.createElement('div') elm.className = 'ghost' elm.setAttribute('data-color' , data.id) Frame.root.appendChild(elm) } this.load_asset() } 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.set_ghost() setTimeout(this.set_move.bind(this) , 1000) } }).bind(this) xhr.send() } set_ghost(){ 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 = this.asset } } set_move(){ const elm_ghosts = Ghost.elm_ghosts for(const elm_ghost of elm_ghosts){ this.move(elm_ghost) } } move(elm_ghost){ if(!elm_ghost){return} const data = Ghost.get_data(elm_ghost) const coodinate = Ghost.get_coodinate(elm_ghost) const directions = Ghost.get_enable_directions(coodinate , data.direction) || Ghost.get_enable_directions(coodinate) const direction = Ghost.get_direction(directions) Ghost.set_direction(elm_ghost , direction) const next_pos = Frame.next_pos(direction , coodinate) this.moving(elm_ghost , next_pos) } moving(elm_ghost , next_pos){ if(!elm_ghost || !next_pos){return} const data = Ghost.get_data(elm_ghost) if(!data){return} //warp if(Frame.is_warp(next_pos)){ data.coodinate = Frame.get_another_warp_pos(next_pos) next_pos = Frame.next_pos(data.direction , data.coodinate) } if(!next_pos){return} const before_pos = { x : data.coodinate.x * Frame.block_size, y : data.coodinate.y * Frame.block_size, } const after_pos = { x : next_pos.x * Frame.block_size, y : next_pos.y * Frame.block_size, } elm_ghost.animate( [ { left : `${before_pos.x}px`, top : `${before_pos.y}px`, }, { left : `${after_pos.x}px`, top : `${after_pos.y}px`, } ], { duration: Main.anim_speed } ) Promise.all(elm_ghost.getAnimations().map(e => e.finished)) .then(this.moved.bind(this , elm_ghost , next_pos)) } moved(elm_ghost , pos){ elm_ghost.style.setProperty('left' , `${pos.x * Frame.block_size}px` , '') elm_ghost.style.setProperty('top' , `${pos.y * Frame.block_size}px` , '') const data = Ghost.get_data(elm_ghost) data.coodinate = pos const directions = Ghost.get_enable_directions(data.coodinate , data.direction) const direction = Ghost.get_direction(directions) Ghost.set_direction(elm_ghost , direction) const next_pos = Frame.next_pos(data.direction , data.coodinate) if(Frame.is_collision(next_pos)){ this.move(elm_ghost) } else{ this.moving(elm_ghost , next_pos) } } static get_direction(directions){ if(!directions || !directions.length){return null} const direction_num = Math.floor(Math.random() * directions.length) return directions[direction_num] || null } // 移動可能な方向の一覧を取得する static get_enable_directions(pos , direction){ 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') && 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') && 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') && 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') && direction !== 'up'){ directions.push('down') } if(directions.length){ return directions } else{ 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 face = elm_ghost.querySelector('.face') if(!face){return} face.setAttribute('data-direction' , direction) } }

[部分更新] 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 = { anim_speed : 200 } 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 { Main } from './main.js' import { Frame } from './frame.js' import { Control } from './control.js' import { Feed } from './feed.js' export class Pacman{ // 初期表示座標処理 constructor(){ Pacman.coodinates = this.start_coodinates Frame.put(this.elm, Pacman.coodinates) this.elm.style.setProperty('--anim-speed' , `${Main.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(direction){ if(Pacman.direction){ return } Pacman.direction = direction this.elm.setAttribute('data-anim' , "true") this.moving() } static moving(){ let next_pos = Pacman.next_pos(Pacman.direction , Pacman.coodinates) //warp if(Frame.is_warp(next_pos)){ Pacman.coodinates = Frame.get_another_warp_pos(next_pos) next_pos = Pacman.next_pos(Pacman.direction , Pacman.coodinates) } if(Frame.is_collision(next_pos)){ this.elm.setAttribute('data-anim' , "") delete Pacman.direction return } this.elm.setAttribute('data-direction' , Pacman.direction) 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: Main.anim_speed } ) Promise.all(this.elm.getAnimations().map(e => e.finished)).then(()=>{ Pacman.moved(next_pos) }) } static moved(next_pos){ Pacman.coodinates = next_pos Frame.put(this.elm, Pacman.coodinates) Feed.move_map() if(Control.direction && Control.direction !== Pacman.direction){ const temp_pos = Pacman.next_pos(Control.direction , Pacman.coodinates) if(!Frame.is_collision(temp_pos)){ Pacman.direction = Control.direction } } Pacman.moving() } static next_pos(direction){ return Frame.next_pos(direction , Pacman.coodinates) } }

画面キャプチャ

解説

今回はかなり大掛かりな更新になりました。 書き換え箇所が多いのと、元々の仕様が足りていなかったというのが足かせになりましたが、無事に敵キャラが動くようになったので、上記ソースコードを更新した手元の環境で確認してみてください。 あ、そういえば、これまで説明していなかったのですが、ローカル環境のjavascriptでも、今回のようにscript type='module'という、モジュールクラスで書かれている場合、 html(s)://から始まるURLでアクセスする必要があります。 どうやればいいか分からないという人は、GoogleChromeの機能拡張で簡単に実装できるので、下記サイトを参考にしてみてください。 [GoogleChrome] ローカルhttp表示できる拡張機能「Web Server for Chrome」 ページを開くと、敵キャラ4体が見事に動いているのが確認できると思います。 敵キャラの動きは、単純に曲がり角に差し掛かると、バック以外の方向のどれかをランダムで選択するようにしています。 ただし、行き止まりの場合は、バックするという処理も追加して、常に動き回る敵キャラができて、本番さながらの挙動ができました。 本番では、曲がり角で、より自キャラに近いほうを選択する割合を増やしてより難易度をあげると良いかもですね。 今のところは単なるランダムでも、ソレっぽく見えます。

次回予告

次回は、パワーエサを食べた時に、敵キャラが青くなるモードを作ってみます。

知財

パックマンは、バンダイナムコ社の登録商標です。 PAC-MAN™ & ©1980 BANDAI NAMCO Entertainment Inc.

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。