先放下我玩游戏的效果图:
关于游戏最后的结束部分其实我还没有截图,看着挺好看的,后面的效果
再放作者大大的项目地址: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
来源:https://www.cnblogs.com/smart-girl/p/11463201.html