先放下我玩游戏的效果图:



关于游戏最后的结束部分其实我还没有截图,看着挺好看的,后面的效果

再放作者大大的项目地址:https://github.com/panruiplay/PixelFire

接下来我们一起学习项目哇哇哇

这个项目用到了webpack,其实这个webpack的功能我觉得这个项目中用到了就是因为可以使用localhost:8080直接打开这种

我们可以仔细研究代码看看是不是我这样认为的

index.html中,有所有会在界面上渲染的静态页面效果

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>元素射击</title>
</head>
<body> <div class="root">
<!--加载面板-->
<div class="panel loading" id="loading">
<div class="box">
<div class="text">正在加载</div>
</div>
</div> <!--加载面板-->
<div class="panel intoGame hide" id="intoGame">
<div class="btn" id="intoBtn">点击进入游戏</div>
</div> <!--菜单面板-->
<div class="panel menu hide" id="menu">
<h1 class="animated fadeIn">元素射击</h1>
<h2 class="animated fadeIn" style="animation-delay: .2s">Ver 1.0</h2>
<ul>
<li id="btn-start" class="btn animated flipInX" style="animation-delay: 1s">开始游戏</li>
<li id="btn-help" class="btn animated flipInX" style="animation-delay: 1.1s">操作说明</li>
<li id="btn-shop" class="btn animated flipInX" style="animation-delay: 1.2s">游戏商店</li>
</ul>
</div> <!--帮助面板-->
<div class="panel help hide">
<div class="dir-box">
<div class="center">W</div>
<section>
<div>A</div>
<div>S</div>
<div>D</div>
</section>
</div>
<p>WASD控制移动,鼠标控制射击方向。</p>
<div class="btn" id="back-menu2">返回</div>
</div>
</div> </body>
</html>

index.js中定义了游戏运行的环境,以及引入相应的css文件和主运行文件game.js

//index.js
import './style/index.css'
import './style/block.css'
import './style/animation.css'
import './style/pointer.css'
import Game from './script/class/Game' Game.init() if(window.env === 'dev'){
window.game = Game
}

在game.js中,引入很多功能性js文件,实现Ui界面,背景音乐播放,还有按键控制以及我方和敌方输和赢的原则

//game.js
import UI from './UI'
import { $, addEventLock, addEventOnce } from '../utils'
import Music from './Music'
import Chain from 'func-chain'
import Rect from './Rect'
import Green from './Block/Unit/Green'
import Control from './Control'
import { pointerExpansion } from './Block/decorators'
import { pointDeg } from '../math'
import UnitFactory from './Block/Unit/Factory'
import Data from '../Data/index'
import QuadTree from './QuadTree'
import Block from './Block/Block'
import Combination from './Block/Combination' class Game {
width = 1000
height = 600
centerX = 500
centerY = 300
domRoot = $('.root') // DOM根节点对象
user = null // 用户角色 userGroup = [] // 用户组block
enemyGroup = [] // 敌方组block
bounds = new Rect(0, 0, this.width, this.height)
enemyGroupQuadTree = new QuadTree(this.bounds) // 初始化
init() {
this.UI = new UI()
this.Music = new Music()
this.Control = new Control() this.Music.loadMusic(() => {
this.UI.change('intoGame')
addEventOnce('#intoBtn', 'click', () => {
this.UI.change('menu')
setTimeout(() => {
this.Music.playBgm('bgm_main')
}, 700)
}) // this.Music.playBgm('bgm_main')
// this.UI.change('menu')
}) // 用户单位
this.user = new Green(this.centerX, this.centerY).speedClear()
pointerExpansion(this.user, 's1') // 注册基本按钮事件
this.eventBase()
// 注册用户角色方向键控制
this.userControl()
} // 基本按钮事件
eventBase() {
// 开始游戏
addEventLock('#btn-start', 'click', (e, unLock) => {
setTimeout(unLock, 1000)
this.startGame()
})
} // 用户键控
userControl() {
this.Control.disableDirectionKey()
this.Control.onDirChange((count, dir) => {
if(count) {
this.user.setAngle(dir)
} else {
this.user.speedClear()
}
}) this.Control.disableMouse = true
} //--------------------------// // 开始游戏(关卡)
startGame(k = 1) {
let { user, Control: control, UI: ui, Music: music, userGroup, enemyGroup } = this,
{ width, height } = this,
data = Data['k' + k],
time = 0 Chain()
> ui.hide
> function (next) { // 音乐切换
music.playBgm('bgm_bat')
next()
}
> user.birth.args(true)
> function () { // 打开用户控制
control.enableDirectionKey()
control.disableMouse = false
userGroup.push(user)
requestAnimationFrame(loop)
}
|| Chain.go() let createEnemy = (time) => {
let tmp = []
for(let i = data.length - 1; i >= 0; i--) {
let v = data[i]
if(v.createTime && time >= v.createTime) {
let { x, y } = v if(x === 'user') {
x = this.userX()
y = this.userY()
} let enemy = UnitFactory(v.enemy, x || this.randomX(), y || this.randomY())
if(enemy instanceof Combination) {
// 组合敌人
enemy.done((enemyArr) => {
for(let j = 0; j < enemyArr.length; j++) {
let enemyArrElement = enemyArr[j]
enemyArrElement.birth(true, () => enemyGroup.push(enemyArrElement))
}
})
} else {
// 单个敌人
enemy.birth(true, () => enemyGroup.push(enemy))
}
continue
}
tmp.push(v)
}
data = tmp
} let loop = () => {
let { centerX, centerY } = user.rect
let { userGroup, enemyGroup } = this
let enemyGroupQuadTree = new QuadTree(this.bounds) // 生产敌人
createEnemy(time) // 敌人行动
for(let i = enemyGroup.length - 1; i >= 0; i--) {
let enemy = enemyGroup[i],
{ rect } = enemy // 如果已经死亡
if(enemy.isDestroy) {
enemyGroup.splice(i, 1)
continue
} // 杀死所有超出边界的单位
if(rect.x > width || rect.y > height || rect.x + rect.width < 0 || rect.y + rect.height < 0) {
enemy.destroy(false)
enemyGroup.splice(i, 1)
continue
} enemy.next().update()
enemyGroupQuadTree.insert(rect)
}
// 用户行动 碰撞:用户组所有单位与敌人组进行碰撞检测
for(let i = userGroup.length - 1; i >= 0; i--) {
let friend = userGroup[i],
{ rect } = friend // 如果已经死亡
if(friend.isDestroy) {
userGroup.splice(i, 1)
continue
}
// 杀死所有超出边界的单位
if(rect.x > width || rect.y > height || rect.x + rect.width < 0 || rect.y + rect.height < 0) {
friend.destroy(false)
userGroup.splice(i, 1)
continue
} friend.next().update() let arr = enemyGroupQuadTree.retrieve(friend.rect) for(let j = arr.length - 1; j >= 0; j--) {
let enemyRect = arr[j]
// 如果发生碰撞
if(Block.isCollision(enemyRect, friend.rect)) {
Block.collision(enemyRect.block, friend)
}
}
} // 用户指针更新
user.pointer.angle = pointDeg(centerX, centerY, control.mouseX, control.mouseY) time++
requestAnimationFrame(loop)
}
} //--------------------------//
randomX() {
return Math.random() * this.width >> 0
}
randomY() {
return Math.random() * this.height >> 0
}
userX() { return this.user.rect.centerX }
userY() { return this.user.rect.centerY }
} export default new Game()

接下来我们来分析引入的js文件

utils.js中封装了一些公共方法

/* -------------∽-★-∽---元素 & 事件---∽-★-∽------------- */
// 搜索器
export function $(selector) {
return document.querySelector(selector)
}
// 搜索器(全部)
export function $$(selector) {
return document.querySelectorAll(selector)
}
// 创建dom元素
export function createDom(name, cls = '') {
let dom = document.createElement(name)
dom.className = cls
return dom
}
// 事件代理 dom可以是元素或者字符串
export function addEventAgent(dom, targetCls, type, fn) {
let reg = RegExp('(^| )' + targetCls + '($| )')
let _fn = function (e) {
if(reg.test(e.target.className)) fn(e)
} if(typeof dom === 'string') dom = $(dom) dom.addEventListener(type, _fn) return function () {
dom.removeEventListener(type, _fn)
}
}
// 添加事件 事件触发一次后上锁,1秒后解锁
export function addEventLock(dom, type, fn) {
if(typeof dom === 'string') dom = $(dom) let lock = false let _fn = function (e) {
if(lock) return
lock = true
setTimeout(() => lock = false, 1000) fn(e)
} dom.addEventListener(type, _fn) return function () {
dom.removeEventListener(type, _fn)
}
}
// 添加一次性事件
export function addEventOnce(dom, type, fn) {
if(typeof dom === 'string') dom = $(dom) let _fn = function (e) {
fn(e)
dom.removeEventListener(type, _fn)
} dom.addEventListener(type, _fn)
} /* -------------∽-★-∽---样式类---∽-★-∽------------- */
// 是否包含某个样式名
export function hasClass(classNameStr, targetCls) {
let reg = RegExp('(^| )' + targetCls + '($| )')
return reg.test(classNameStr)
}
// 添加样式
export function addClass(dom, ...cls) {
dom.className += ' ' + cls.join(' ')
}
// 删除样式
export function removeClass(dom, ...cls) {
let className = dom.className
for(let i = 0; i < cls.length; i++) {
className = className.replace(RegExp('(^| )' + cls[i] + '($| )', 'g'), ' ')
}
dom.className = className
} /* -------------∽-★-∽---其它---∽-★-∽------------- */
// 取得文件名,不包含后缀
export function getFileName(url) {
return url.split('/').pop().split('.')[0]
}

ui.js主要是控制是否显示对应的界面

//ui.js
import { $$, addClass, addEventAgent, hasClass, removeClass } from '../utils'
import Game from './Game'
import Chain from 'func-chain' class UI {
panels = {} // 所有的面板
current = null // 当前显示的面板
transitionTime = 700 // 面板过度时间 constructor() {
let arr = Array.from($$('.panel')) arr.forEach(v => {
v.style.transition = `opacity ${this.transitionTime}ms`
this.panels[v.id] = v
}) // 当前显示面板
this.current = arr.find(v => !hasClass(v.className, 'hide')) // 注册所有Btn点击音效(所有有btn样式的元素点击时,产生音效)
addEventAgent(Game.domRoot, 'btn', 'click', () => Game.Music.play('click'))
} // 隐藏当前面板
hide = (cb) => {
addClass(this.current, 'opacity0')
setTimeout(() => {
addClass(this.current, 'hide')
removeClass(this.current, 'opacity0')
cb && cb()
}, this.transitionTime)
}
// 显示面板
show = (name, cb) => {
let target = this.panels[name],
time = this.transitionTime
this.current = target addClass(target, 'opacity0')
removeClass(target, 'hide') Chain()
> function (next) { setTimeout(next, 50) }
> function (next) {
addClass(target, 'opacity1')
setTimeout(next, time)
}
> function () {
removeClass(target, 'opacity1', 'opacity0')
cb && cb()
}
|| Chain.go()
}
// 切换面板
change = (name, cb) => {
Chain()
> this.hide
> this.show.args(name)
> cb
|| Chain.go()
}
} export default UI

music控制音乐的播放

import { createDom, getFileName } from '../utils'

// 取得所有音乐文件
let context = require.context('../../assets/ogg', false),
allOgg = context.keys().map(key => context(key)) class Music {
listMap = {} // 所有的音乐对象
currentBgm = null // 当前播放的bgm constructor() {
allOgg.forEach(v => this.listMap[getFileName(v)] = '/' + v)
} // 加载所有音乐
loadMusic(cb) {
let listMap = this.listMap,
all = [] for(let listMapKey in listMap) {
all.push(new Promise((resolve, reject) => {
let audio = createDom('audio') audio.oncanplay = () => {
listMap[listMapKey] = audio
resolve()
}
audio.onerror = reject
audio.src = listMap[listMapKey]
})) } Promise.all(all)
.then(() => cb && cb(true))
.catch(() => cb && cb(false))
} // 播放音效
play(name) {
let music = this.listMap[name]
music.currentTime = 0
music.loop = false
music.play()
}
// 播放背景音乐(自动循环)
playBgm(name) {
if(this.currentBgm) this.currentBgm.pause() let music = this.listMap[name]
this.currentBgm = music
music.currentTime = 0
music.loop = true
music.play()
}
} export default Music

创建的方形对象?

//src\script\class\Rect.js
/**
* 矩形对象
* @property {number} x - 起始位置x
* @property {number} y - 起始位置y
* @property {number} centerX - 中心x位置
* @property {number} centerY - 中心y位置
* @property {number} width - 宽度
* @property {number} height - 高度
*/
class Rect {
// 按中心点创建单位
static centerCreate(centerX, centerY, width, height) {
return new Rect(centerX - width / 2, centerY - height / 2, width, height)
} constructor(x, y, width, height) {
this.x = x
this.y = y
this.width = width
this.height = height
this.centerX = x + width / 2
this.centerY = y + height / 2
} // 更新中心位置
update() {
this.centerX = this.x + this.width / 2
this.centerY = this.y + this.height / 2
} /**
* 切割矩形
* @param {number} cX - 纵向切线 x坐标
* @param {number} cY - 横向切线 y坐标
* @return {Rect[]}
*/
carve(cX, cY) {
let result = [],
temp = [],
dX = cX - this.x,
dY = cY - this.y,
carveX = dX > 0 && dX < this.width,
carveY = dY > 0 && dY < this.height // 切割XY方向
if(carveX && carveY) {
temp = this.carve(cX, this.y)
while(temp.length) {
result = result.concat(temp.shift().carve(this.x, cY))
}
// 只切割X方向
} else if(carveX) {
result.push(
new Rect(this.x, this.y, dX, this.height),
new Rect(cX, this.y, this.width - dX, this.height)
)
// 只切割Y方向
} else if(carveY) {
result.push(
new Rect(this.x, this.y, this.width, dY),
new Rect(this.x, cY, this.width, this.height - dY)
)
} return result
}
} export default Rect
//定义小方块样式
//src\script\class\Block\Unit\Green.js
import { ShrinkGreedM } from '../../Animation/Pre'
import { BoomGreen } from '../../Animation/Boom'
import { SpreadGreen } from '../../Animation/Spread'
import Block from '../Block'
import { LaunchBullet } from '../../Skill/LaunchBullet'
import BaseBullet from './BaseBullet'
import { BoundsLimit } from '../decorators' class Green extends Block {
static FactoryName = 'Green' className = 'background-green' // 方块样式
preAni = new ShrinkGreedM() // 预警动画
birthAni = new SpreadGreen() // 出生动画
deathAni = new BoomGreen() // 死亡动画
hp = 1
atk = 10 decorators = [BoundsLimit] skill = [[LaunchBullet, BaseBullet, 'user']] speed = 2
angle = 0
constructor(x, y) {
super(x, y, 10, 10)
}
} export default Green
//移动的方法
//src\script\class\Animation\Animation.js
import Game from '../Game'
import { createDom } from '../../utils' /**
* 动画类: new Animation().show()
*/
class Ani {
static baseClass = 'ani' dom = null // dom对象
music = '' // 音乐
width = 0
height = 0 constructor(width, height, className, music) {
this.dom = createDom('div', Ani.baseClass + ' ' + className)
this.width = width
this.height = height
this.music = music
} show = (x, y, cb) => {
x = x - this.width / 2
y = y - this.height / 2 this.dom.style.cssText = `left: ${x}px; top: ${y}px;` let end = () => {
Game.domRoot.removeChild(this.dom)
this.dom.removeEventListener('webkitAnimationEnd', end)
this.dom.removeEventListener('animationend', end)
cb && cb()
} this.dom.addEventListener('webkitAnimationEnd', end)
this.dom.addEventListener('animationend', end) if(this.music) Game.Music.play(this.music)
Game.domRoot.appendChild(this.dom)
}
} export default Ani

创建各种各样移动的类对象?

//src\script\class\Animation\Boom.js
import Ani from './Animation' class BoomRed extends Ani {
constructor() { super(10, 10, 'boom boom-red', 'del1') }
}
class BoomGreen extends Ani {
constructor() { super(10, 10, 'boom boom-green', 'del1') }
}
class BoomYellow extends Ani {
constructor() { super(10, 10, 'boom boom-yellow', 'del1') }
}
class BoomOrange extends Ani {
constructor() { super(10, 10, 'boom boom-orange', 'del1') }
}
class BoomBlue extends Ani {
constructor() { super(10, 10, 'boom boom-blue', 'del1') }
}
class BoomSBlue extends Ani {
constructor() { super(10, 10, 'boom boom-s-blue', 'del2') }
} export {
BoomRed,
BoomGreen,
BoomYellow,
BoomOrange,
BoomBlue,
BoomSBlue,
}

定义了各种spread类型

//src\script\class\Animation\Spread.js
import Ani from './Animation' class SpreadRed extends Ani {
constructor() { super(10, 10, 'spread spread-red') }
}
class SpreadGreen extends Ani {
constructor() { super(10, 10, 'spread spread-green') }
}
class SpreadBlue extends Ani {
constructor() { super(10, 10, 'spread spread-blue') }
} export {
SpreadRed,
SpreadGreen,
SpreadBlue,
}

方块类,可以碰撞的对象

//src\script\class\Block\Block.js
import { createDom } from '../../utils'
import Game from '../Game'
import Chain from 'func-chain'
import Rect from '../Rect' let pix = Math.PI / 180 /**
* 方块类:屏幕上一个可参与碰撞的基本单位
*/
class Block {
static baseClass = 'block-base' // 判断两个block是否碰撞
static isCollision(rect1, rect2) {
if(rect1.block.isDestroy || rect2.block.isDestroy) return false let b1x = rect1.x,
b1y = rect1.y,
b2x = rect2.x,
b2y = rect2.y return !(
b1y > rect2.height + b2y ||
rect1.width + b1x < b2x ||
rect1.height + b1y < b2y ||
b1x > rect2.width + b2x
)
} // 撞击
static collision(block1, block2) {
block1.hp -= block2.atk
block2.hp -= block1.atk if(block1.hp <= 0) block1.destroy()
if(block2.hp <= 0) block2.destroy()
} dom = null // DOM对象
rect = null // 矩形对象
className = '' // 方块样式
preAni = null // 预警动画
birthAni = null // 出生动画
birthMusic = null // 出生音乐
deathAni = null // 死亡动画
skill = [] // 技能
decorators = [] // 装饰
destroyEvt = [] // 死亡事件列表
hp = 1 // 生命值
atc = 10 // 攻击力
isDestroy = false // 是否已经死亡 angle = 0 // 角度
radian = 0 // 弧度
speed = 0 // 速度
vx = 0 // x轴移动速度
vy = 0 // y轴移动速度 constructor(centerX, centerY, width, height) {
this.rect = Rect.centerCreate(centerX, centerY, width, height)
} // 初始化
init() {
let r = this.rect
this.rect.block = this
this.dom = createDom('div', Block.baseClass + ' ' + this.className)
this.dom.style.cssText = `left: ${r.x}px; top: ${r.y}px; width: ${r.width}px; height: ${r.height}px` this.skill = this.skill.map(v => {
if(v instanceof Array) {
let [constructor, ...args] = v
return new constructor(this, ...args)
} else {
return new v(this)
}
}) this.decorators.forEach(v => {
if(v instanceof Array) {
let [fn, ...args] = v
fn(this, ...args)
} else {
v(this)
}
}) this.decomposeSpeed()
return this
} // 在屏幕上显示
birth = (hasAni = true, cb) => {
// 如果还没有创建dom则自动init
if(!this.dom) this.init() let that = this Chain()
> function (next) {
if(hasAni && that.preAni) {
that.preAni.show(that.rect.centerX, that.rect.centerY, next)
} else {
next()
}
}
> function () {
if(hasAni && that.birthAni){
that.birthAni.show(that.rect.centerX, that.rect.centerY)
}
if(that.birthMusic) Game.Music.play(that.birthMusic)
Game.domRoot.appendChild(that.dom)
that.isDestroy = false
cb && cb()
}
|| Chain.go() return this
}
// 销毁
destroy = (hasAni = true) => {
this.isDestroy = true
if(hasAni && this.deathAni) this.deathAni.show(this.rect.centerX, this.rect.centerY)
Game.domRoot.removeChild(this.dom)
this.destroyEvt.forEach(v => v())
}
// 行动(下一帧)
next = () => {
this.skill.forEach(v => v.next())
this.rect.x += this.vx
this.rect.y += this.vy
this.rect.update()
return this
}
// 更新显示效果
update = () => {
let rect = this.rect
this.dom.style.left = `${rect.x}px`
this.dom.style.top = `${rect.y}px`
return this
}
// 死亡事件
onDestroy = (fn) => {
this.destroyEvt.push(fn)
return this
} // 速度设为0
speedClear() {
this.vx = 0
this.vy = 0
return this
}
// 设置角度
setAngle(deg) {
this.angle = deg
this.radian = deg * pix
this.decomposeSpeed()
return this
}
// 设置弧度
setRadian(radian) {
this.radian = radian
this.decomposeSpeed()
return this
}
// 速度分解
decomposeSpeed() {
this.vx = Math.cos(this.radian) * this.speed
this.vy = Math.sin(this.radian) * this.speed
return this
}
} export default Block
//src\script\class\Skill\LaunchBullet.js
import Skill from './Skill'
import Game from '../Game' // 用户专用
class LaunchBullet extends Skill {
step = 0
cd = 14 constructor(block, bulletClass) {
super(block)
this.bulletClass = bulletClass
} action(block) {
let { pointer } = block,
{ rect: userRect } = Game.user let bullet = new this.bulletClass(userRect.centerX, userRect.centerY) bullet.init().birth(false, () => {
bullet.setAngle(pointer.angle || 0)
Game.userGroup.push(bullet)
})
}
} export {
LaunchBullet
}

定义的子弹

//src\script\class\Block\Unit\BaseBullet.js
import Block from '../Block'
import { directionExpansion } from '../decorators' class BaseBullet extends Block {
className = 'san1'
birthMusic = 'fire1'
decorators = [[directionExpansion, 45]]
speed = 6
hp = 10
atk = 10 constructor(x, y) {
super(x, y, 10, 10)
}
} export default BaseBullet
//src\script\class\Block\decorators.js
import Pointer from './Pointer'
import Game from '../Game' /* 类装饰 */
// block的显示样式会根据自身angle属性进行旋转
export function directionExpansion(block, angleFix = 0) {
let _update = block.update
block.update = () => {
block.dom.style.transform = `rotate(${block.angle + angleFix}deg)`
_update.call(block)
}
}
// 边界限制,方法不可以超出边界
export function BoundsLimit(block) {
let _next = block.next
block.next = () => {
_next.call(block)
let is = false,
rect = block.rect,
{ width, height } = Game.bounds if(rect.x < 0) rect.x = 0, is = true
if(rect.x + rect.width > width) rect.x = width - rect.width, is = true
if(rect.y + rect.height > height) rect.y = height - rect.height, is = true
if(rect.y < 0) rect.y = 0, is = true if(is) rect.update()
return block
}
}
// 边界反弹
export function BoundsRebound(block) {
let _next = block.next
block.next = () => {
_next.call(block)
let is = false,
rect = block.rect,
{ width, height } = Game.bounds if(rect.x < 0) rect.x = 0, block.vx *= -1, is = true
if(rect.y < 0) rect.y = 0, block.vy *= -1, is = true
if(rect.x + rect.width > width) rect.x = width - rect.width, block.vx *= -1, is = true
if(rect.y + rect.height > height) rect.y = height - rect.height, block.vy *= -1, is = true if(is) rect.update()
return block
}
} /* 实例装饰 */
// block添加指针
export function pointerExpansion(block, className) {
block.pointer = new Pointer(block, className) let _init = block.init
block.init = () => {
block.pointer.init()
_init.call(block)
} let _birth = block.birth
block.birth = (hasAni = true, cb) => {
let _cb = cb _birth.call(block, hasAni, () => {
block.pointer.birth()
_cb && _cb()
})
} let _update = block.update
block.update = () => {
block.pointer.update()
_update.call(block)
} let _destroy = block.destroy
block.destroy = () => {
block.pointer.destroy()
_destroy.call(block)
}
}
//src\script\class\Control.js
//控制鼠标移动
import Rect from './Rect'
import Game from './Game' /**
* 用户控制器,监听键盘事件,鼠标事件等
*/
class Control {
disabled = [] // 禁用按键数组
disableMouse = false // 禁用鼠标监听
keyEvt = {
// key: [fn, fn, fn]
} OL = 0 // root容器与浏览器左边距离
OT = 0 // root容器与浏览器顶边距离 // 方向键监听使用
count = 0
x = 0
y = 0
deg = {
'v00': 0,
'v10': 0,
'v1-1': 45,
'v0-1': 90,
'v-1-1': 135,
'v-10': 180,
'v11': -45,
'v01': -90,
'v-11': -135
} constructor() {
window.onresize = () => {
let { left, top } = Game.domRoot.getBoundingClientRect()
this.OL = left + 5
this.OT = top + 5
}
window.onresize() document.onkeydown = this._down_interface.bind(this)
document.onkeyup = this._up_interface.bind(this)
document.onmousemove = this._mouse_interface.bind(this) // 监听方向键
this.onKeyDown(87, () => this.dirKeyDown(1))
this.onKeyDown(65, () => this.dirKeyDown(4))
this.onKeyDown(68, () => this.dirKeyDown(2))
this.onKeyDown(83, () => this.dirKeyDown(3))
this.onKeyUp(87, () => this.dirKeyUp(1))
this.onKeyUp(65, () => this.dirKeyUp(4))
this.onKeyUp(68, () => this.dirKeyUp(2))
this.onKeyUp(83, () => this.dirKeyUp(3))
} // keydown接口
_down_interface(evt) {
let code = evt.keyCode
if(this.disabled.find(v => v == code)) return
code = 'k' + code
let evtArr = this.keyEvt[code]
if(evtArr) {
evtArr.forEach(v => v())
}
}
// keyup接口
_up_interface(evt) {
let code = evt.keyCode
if(this.disabled.find(v => v == code)) return
code = 'c' + code
let evtArr = this.keyEvt[code]
if(evtArr) {
evtArr.forEach(v => v())
}
}
// 鼠标接口
_mouse_interface(evt) {
if(this.disableMouse) return this.mouseX = evt.pageX - this.OL
this.mouseY = evt.pageY - this.OT let evtArr = this.keyEvt['mouseMove']
if(evtArr) {
evtArr.forEach(v => v(this.mouseX, this.mouseY))
}
} // 方向键监听 & 控制
dirReset = () => {
this.count = this.x = this.y = 0
this.dirTrigger(this.count, undefined)
}
dirKeyDown = (key) => {
if(key == 1) {
if(this.y == 0) this.count++
this.y = 1
} else if(key == 2) {
if(this.x == 0) this.count++
this.x = 1
} else if(key == 3) {
if(this.y == 0) this.count++
this.y = -1
} else {
if(this.x == 0) this.count++
this.x = -1
}
this.dirTrigger(this.count, this.deg['v' + this.x + this.y])
}
dirKeyUp = (key) => {
if(key == 1 && this.y == 1 || key == 3 && this.y == -1) {
this.y = 0
this.count--
} else if(key == 2 && this.x == 1 || key == 4 && this.x == -1) {
this.x = 0
this.count--
}
this.dirTrigger(this.count, this.deg['v' + this.x + this.y])
}
dirTrigger = (count, deg) => {
let evtArr = this.keyEvt['dirChange']
if(evtArr) {
evtArr.forEach(v => v(count, deg))
}
} // 禁用方向键监听
disableDirectionKey() {
this.disabled = this.disabled.concat(['87', '65', '68', '83'])
this.dirReset()
}
// 启用方向键监听
enableDirectionKey() {
this.disabled = this.disabled.filter(v => v != 87 && v != 65 && v != 68 && v != 83)
this.dirReset()
} /**
* 注册方向键变化事件,当方向键变化时,调用回调函数,传入当前按下的方向键数和方向(deg)
* @param {function(count:number, dir:number)} fn - 回调函数
*/
onDirChange(fn) {
let key = 'dirChange'
if(this.keyEvt[key]) {
this.keyEvt[key].push(fn)
} else {
this.keyEvt[key] = [fn]
}
}
/**
* 注册按下键事件
* @param {string|number} key - 键值
* @param {function} fn - 回调函数
*/
onKeyDown(key, fn) {
key = 'k' + key
if(this.keyEvt[key]) {
this.keyEvt[key].push(fn)
} else {
this.keyEvt[key] = [fn]
}
}
/**
* 注册松开键事件
* @param {string|number} key - 键值
* @param {function} fn - 回调函数
*/
onKeyUp(key, fn) {
key = 'c' + key
if(this.keyEvt[key]) {
this.keyEvt[key].push(fn)
} else {
this.keyEvt[key] = [fn]
}
}
/**
* 注册鼠标移动事件
* @param {function(number:x, number:y)} fn - 回调函数
*/
onMouseMove(fn) {
let key = 'mouseMove'
if(this.keyEvt[key]) {
this.keyEvt[key].push(fn)
} else {
this.keyEvt[key] = [fn]
}
}
} export default Control
//点到另一个点的角度
//src\script\math.js
let { atan2 } = Math,
tmp2 = 180 / Math.PI // 点到另一个点的角度
export function pointDeg(x1, y1, x2, y2) {
return atan2(y2 - y1, x2 - x1) * tmp2
}
//src\script\class\Block\Unit\Factory.js
let context = require.context('./', false, /\.js$/),
all = context.keys().filter(item => item !== './Factory.js').map(key => context(key)),
map = {} all.forEach(v => {
map[v.default.FactoryName] = v.default
}) export default function (name, x, y) {
return new map[name](x, y)
}
//src\script\Data\index.js
import { getFileName } from '../utils' let ctx = require.context('./', false, /\.js$/),
all = ctx.keys().filter(item => item != './index.js' && item != './utils.js').map(key => {
return {
name: getFileName(key),
value: ctx(key).default
}
}),
map = {} all.forEach(v => { map[v.name] = v.value}) export default map
//src\script\class\QuadTree.js
/*!
该部分代码参考以下文章
作者:lxjwlt
链接:http://blog.lxjwlt.com/front-end/2014/09/04/quadtree-for-collide-detection.html
來源:个人博客
*/ import Rect from './Rect' /**
* 四叉树对象,用于碰撞检测
* @property {Rect[]} objects - 保存在该节点本身的物体对象
* @property {QuadTree[]} nodes - 子节点
* @property {Rect} bounds - 该节点矩形范围
*/
class QuadTree {
// 每个节点最大物体数量
static MAX_OBJECTS = 7 // 判断矩形是否在象限范围内
static isInner = function (rect, bound) {
return rect.x >= bound.x &&
rect.x + rect.width <= bound.x + bound.width &&
rect.y >= bound.y &&
rect.y + rect.height <= bound.y + bound.height
} /**
* @constructor
* @param {Rect} rect - 边界对象
*/
constructor(rect) {
this.objects = []
this.nodes = []
this.bounds = rect
} /**
* 判断物体属于哪个象限
* @param {Rect} rect - 需要判断的矩形
* @return {number}
* 0 - 象限一
* 1 - 象限二
* 2 - 象限三
* 3 - 象限四
* -1 - 物体跨越多个象限
*/
getIndex(rect) {
let bounds = this.bounds,
onTop = rect.y + rect.height <= bounds.centerY,
onBottom = rect.y >= bounds.centerY,
onLeft = rect.x + rect.width <= bounds.centerX,
onRight = rect.x >= bounds.centerX if(onTop) {
if(onRight) {
return 0
} else if(onLeft) {
return 1
}
} else if(onBottom) {
if(onLeft) {
return 2
} else if(onRight) {
return 3
}
} return -1
} /**
* 划分为4个子象限
*/
split() {
let bounds = this.bounds,
x = bounds.x,
y = bounds.y,
sWidth = bounds.width / 2,
sHeight = bounds.height / 2 this.nodes.push(
new QuadTree(new Rect(bounds.centerX, y, sWidth, sHeight)),
new QuadTree(new Rect(x, y, sWidth, sHeight)),
new QuadTree(new Rect(x, bounds.centerY, sWidth, sHeight)),
new QuadTree(new Rect(bounds.centerX, bounds.centerY, sWidth, sHeight))
)
} /**
* 插入物体
* @param {Rect} rect - 需要插入的物体
*
* - 如果当前节点存在子节点,则检查物体到底属于哪个子节点,
* 如果能匹配到子节点,则将该物体插入到该子节点中,否则保存在节点自身
*
* - 如果当前节点不存在子节点,将该物体存储在当前节点。
* 随后,检查当前节点的存储数量,如果超过了最大存储数量,则对当前节点进行划分,
* 划分完成后,将当前节点存储的物体重新分配到四个子节点中。
*/
insert(rect) {
let objects = this.objects,
i, index // 如果该节点下存在子节点
if(this.nodes.length) {
index = this.getIndex(rect)
if(index !== -1) {
this.nodes[index].insert(rect)
return
}
} // 否则存储在当前节点下
objects.push(rect) // 如果当前节点没有分裂过 并且 存储的数量超过了MAX_OBJECTS
if(!this.nodes.length && this.objects.length > QuadTree.MAX_OBJECTS) {
this.split() for(i = objects.length - 1; i >= 0; i--) {
index = this.getIndex(objects[i])
if(index !== -1) {
this.nodes[index].insert(objects.splice(i, 1)[0])
}
}
}
} /**
* 检索功能:
* 给出一个物体对象,将该物体可能发生碰撞的所有物体选取出来。
* 该函数先查找物体所属的象限,该象限下的物体都是有可能发生碰撞的,然后再递归地查找子象限。
* @param {Rect} rect - 需要检索的矩形对象
* @return {Rect[]}
*/
retrieve(rect) {
let result = [],
arr, i, index if(this.nodes.length) {
index = this.getIndex(rect)
if(index !== -1) {
result = result.concat(this.nodes[index].retrieve(rect))
} else {
// 切割矩形
arr = rect.carve(this.bounds.centerX, this.bounds.centerY) for(i = arr.length - 1; i >= 0; i--) {
index = this.getIndex(arr[i])
result = result.concat(this.nodes[index].retrieve(rect))
}
}
} result = result.concat(this.objects) return result
} /**
* 移除目标矩形对象,如果在自身没有找到,则递归查找子象限
* @param {Rect} rect - 要删除伯矩形对象
* @return {boolean} 是否成功删除目标对象
*/
remove(rect) {
let objects = this.objects,
nodes = this.nodes let target = objects.findIndex(v => v === rect) if(target !== -1) {
objects.splice(target, 1)
return true
} else if(nodes.length) {
for(let i = 0; i < nodes.length; i++) {
let node = nodes[i]
if(node.remove(rect)) {
return true
}
}
} return false
} /**
* 动态刷新
* 从根节点深入四叉树,检查四叉树各个节点存储的物体是否依旧属于该节点(象限)的范围之内,如果不属于,则重新插入该物体。
* @param {QuadTree} root - 当前检索的节点对象
*/
refresh(root = this) {
let objects = this.objects,
rect, index, i, len for(i = objects.length - 1; i >= 0; i--) {
rect = objects[i]
index = this.getIndex(rect)
// 如果矩形不属于该象限,则将该矩形重新插入
if(!QuadTree.isInner(rect, this.bounds)) {
if(this !== root) {
root.insert(objects.splice(i, 1)[0])
}
// 如果矩形属于该象限 且 该象限具有子象限,则
// 将该矩形安插到子象限中
} else if(this.nodes.length && index !== -1) {
this.nodes[index].insert(objects.splice(i, 1)[0])
}
} // 递归刷新子象限
for(i = 0, len = this.nodes.length; i < len; i++) {
this.nodes[i].refresh(root)
}
}
} export default QuadTree

【默默努力】PixelFire的更多相关文章

  1. 2018.5.2(7:20到的办公室开始早课 阮一峰的JS) 所有的默默努力都是为了让自己看起来毫不费力

    continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环. break语句用于跳出代码块或循环. 标签(label) JavaScript 语言允许,语句的前面有标签(label) ...

  2. 【默默努力】fishingGame

    这个捕鱼游戏挺有意思的,通过发射子弹,打鱼.打鱼的子弹会消耗金币,但是打鱼如果打到了鱼,就会奖励金币的数量. 我如果写这个的话,应该会画一个 背景海底,然后生成很多鱼的图片,还要有一个大炮,金币.大炮 ...

  3. 【默默努力】h5-game-heroVSmonster

    先放下作者大大的项目地址:https://github.com/yangyunhe369/h5-game-heroVSmonster 然后游戏的效果为 截动图的按键与游戏按键应该冲突,我就截几张图片了 ...

  4. 【默默努力】h5-game-blockBreaker

    先放下游戏的效果,我不太会玩游戏 然后放下无私开源的作者大大的地址:https://github.com/yangyunhe369/h5-game-blockBreaker 这个游戏的话,我觉得应该是 ...

  5. 【默默努力】ig-wxz-and-hotdog

    这个是一个非常跟热点的小游戏,思聪吃热狗.这个游戏的话,我感觉思路还挺简单的,天上会掉热狗和障碍物, 思聪在下面张开嘴巴,进行左右移动,接热狗.如果吃到的是热狗就得一分,如果思聪吃到的不是热狗,是障碍 ...

  6. 【默默努力】react-drag-grid

    先放项目地址:https://github.com/Bilif/react-drag-grid 项目运行效果 感谢无私开源的程序员 先看项目入口文件 //index.js import React f ...

  7. 【默默努力】vue-pc-app

    最近在github上面看到了一个团队的项目,真的非常赞.他们进行vue-cli的二次开发,将项目用自己的方式打包. 今天的这个开源项目地址为:https://github.com/tffe-team/ ...

  8. Shader的学习方法总结

    最近网友candycat1992的新书<Unity Shader入门精要>出版了,估计万千的中国unity开发者又要掀起一波学Shader热潮了.我也想把自己这几年学习Shader的一些历 ...

  9. VIM移动

    VIM移动   断断续续的使用VIM也一年了,会的始终都是那么几个命令,效率极低 前几个星期把Windows换成了Linux Mint,基本上也稳定了下来 就今晚,我已经下定决心开始新的VIM之旅,顺 ...

随机推荐

  1. NX二次开发-更改图纸尺寸内容(编辑文本)uc5566

    #include <uf.h> #include <uf_drf.h> #include <uf_obj.h> #include <uf_part.h> ...

  2. NX二次开发-UFUN获取对象的显示属性(图层,颜色,空白状态,线宽,字体,高亮状态)UF_OBJ_ask_display_properties

    NX9+VS2012 #include <uf.h> #include <uf_modl.h> #include <uf_obj.h> UF_initialize( ...

  3. NX二次开发-UF_MODL_ask_point_containment获取一个点是在体(面,边)的边界内部,外部,还是边界上

    NX9+VS2012 #include <uf.h> #include <uf_modl.h> #include <uf_curve.h> #include < ...

  4. NX二次开发-UFUN将工程图转成CGM和PDF文件UF_CGM_export_cgm

    文章转载自唐康林NX二次开发论坛,原文出处: http://www.nxopen.cn/thread-126-1-1.html 刚才有同学问到这个问题,如果是用NXOpen来做,直接录制一下就可以了: ...

  5. spring 配置bean以及配置依赖 (2)

    目录 一.使用ref引用其他对象 二.通过有参构造器创建对象 1 通过index精确定位参数顺序 三.引用bean 1 使用内部bean 2 使用list,set 3 声明集合类型 四.其他 1 使用 ...

  6. Python sorted list的实现

    Python sorted list的实现 具体思路是用二分保list有序+插入 class SortedList(list): K = -1 def __init__(self, K=-1): li ...

  7. VMware Workstation 添加磁盘 挂载目录(centos)

    参考文档: Linux命令大全 需求:测试环境虚拟机某个目录下空间不足,准备通过添加一块磁盘,并将该目录挂载到该磁盘 前面几步在测试服务器上操作的,截图困难,所以网上找了几张设置图 关闭虚拟机(没测试 ...

  8. Mysql命令增加、修改、删除表字段

    alter add 命令用来增加表的字段: alter add命令格式:alter table 表名 add字段 类型 其他:如下所示: ) comment '单位' alter drop 命令删除表 ...

  9. 在Linux下解压xz压缩文件

    1.安装xz命令 # yum install xz -y 2.将xz文件解压为tar文件 # xz -d example.tar.xz 3.将tar文件解压 # tar xf example.tar ...

  10. iOS组件化开发-发布私有库

    远程索引库 将远程索引库添关联到本地 pod repo 查看本地已关联仓库源 pod repo add 本地索引库名称 远程索引库仓库地址 pod repo update 索引库名称 pod repo ...