【默默努力】PixelFire
作者:互联网
先放下我玩游戏的效果图:
关于游戏最后的结束部分其实我还没有截图,看着挺好看的,后面的效果
再放作者大大的项目地址: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 - 回调函数
*/
onm ouseMove(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
标签:return,努力,dom,默默,let,rect,import,PixelFire,block 来源: https://www.cnblogs.com/smart-girl/p/11463201.html