自機を動かせるようになって、臨場感が増してきたと思います。
今回はステージ内の壁を判定して、衝突判定(コリジョン)を組み込んでみます。
ステージ内を壁に沿って動けるようになると、それだけでもパックマンゲームの醍醐味が味わえてしまいます。
今回の目的
- ステージの壁判定マップ作成
- キャラクター(自機)座標と壁マップの判定処理
- キャラクター(自機)移動処理の判定追加
- キャラクター(自機)が壁に当たるまで動き続ける処理
- 連続カーソル切り替え対応
対象ファイル一覧
- js/control.js
- js/frame.js
- js/pacman.js
ソースコード
カーソルキーのイベントコントロールをする処理を書き換えました。
部分的なんですが、保持するオブジェクトの仕様を変更したので、全部入れ替えて下さい。
[全部更新] 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.direction = name
Pacman.move(Control.direction)
}
keyup(e){
if(!Control.direction){return}
const name = Control.key2name(e.keyCode)
if(Control.direction !== name){return}
delete Control.direction
}
}
[全部更新] js/frame.js
壁の判定をするためのオブジェクトを作る、set_collision()という関数を作って、座標からコリジョン判定をする処理を追加しました。
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)
}
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)
}
}
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(){
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'){
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){
return Frame.map[map.y][map.x]
}
}
[全部更新] js/pacman.js
moved()関数で"Frame.is_collision()"を判定して、次に進む座標が壁かどうかの判定をしています。
壁の場合は進めないようにすると、一気にゲーム感が増します。
import { Frame } from './frame.js'
import { Control } from './control.js'
export class Pacman{
// 初期表示座標処理
constructor(){
Pacman.anim_speed = 300
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(direction){
if(Pacman.direction){
return
}
Pacman.direction = direction
this.elm.setAttribute('data-anim' , "true")
this.moving()
}
static moving(){
const next_pos = Pacman.next_pos(Pacman.direction)
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: Pacman.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)
if(Control.direction && Control.direction !== Pacman.direction){
const temp_pos = Pacman.next_pos(Control.direction)
if(!Frame.is_collision(temp_pos)){
Pacman.direction = Control.direction
}
}
Pacman.moving()
}
static next_pos(name){
const next_pos = {
x : Pacman.coodinates.x,
y : Pacman.coodinates.y,
}
switch(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
}
return next_pos
}
}
画面キャプチャ
あとがき
うまく、壁判定して、キャラクターを操作することができましたか?
前回からの変更点は3ファイルだけですが、都度内容を大きく変えてしまっているのは、自分の事前設計の甘いところですねwww。
それでも、なるべく分かりやすい関数を心がけているので、中を読み解いてもっと簡単にできるように改造してみてください。
※この時点で改造してしまうと、この後の処理がうまく続かなくなるので、このシリーズが終わったあとで改修するか、gitで管理して、フォークして遊んでみて下さい。
操作系は、まだ完璧ではなくて、ステージ内にあるワープエリアで、座標をワープさせる必要があるので、次回はその処理を追加します。
知財
パックマンは、バンダイナムコ社の登録商標です。
PAC-MAN™ & ©1980 BANDAI NAMCO Entertainment Inc.
0 件のコメント:
コメントを投稿