其他分享
首页 > 其他分享> > 【默默努力】PixelFire

【默默努力】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