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

这个是一个非常跟热点的小游戏,思聪吃热狗。这个游戏的话,我感觉思路还挺简单的,天上会掉热狗和障碍物,
思聪在下面张开嘴巴,进行左右移动,接热狗。如果吃到的是热狗就得一分,如果思聪吃到的不是热狗,是障碍物,就结束游戏。
如果要衍生的话,其实可以将热狗的下落方向不定的,就像俄罗斯方块那样,思聪的嘴巴也可以除了向上接热狗,还可以所有咬热狗那种。
感觉很简单很好玩的游戏哇,先放游戏的效果。

我们看动图可以发现,其实这个里面除了热狗障碍物,还有一个是得5分的大能量。
先放作者的github地址:https://github.com/sl1673495/ig-wxz-and-hotdog
接下来我们一起分析代码
项目入口为

//index.js中会初始化游戏
import Scheduler from './modules/scheduler'

function initGame() {
    new Scheduler()
}

initGame()

utils文件夹中是工具函数

//ig-wxz-and-hotdogapputilsindex.js
export const isUndef = (v) =>  v === null || v === undefined
export const noop = () => {}
export * from './constant'
export * from './dom'
export * from './device'
export { default as eventEmitter } from './event'
//ig-wxz-and-hotdogapputilsconstant.js
export const PLYAYER_OPTIONS = {
    img: require('@/assets/images/sicong.jpg'),
     70,
    height: 70,
}

export const DIALOG_OPTIONS = {
     250,
    height: 170,
}

// 坠落到底事件
export const CHECK_FALL_EVENT = 'checkFall'

//定义的一些设备
//ig-wxz-and-hotdogapputilsdevice.js
const ua = window.navigator.userAgent
const dpr = window.devicePixelRatio
const w = window.screen.width
const h = window.screen.height
// iPhone X、iPhone XS
const isIPhoneX = /iphone/gi.test(ua) && dpr && dpr === 3 && w === 375 && h === 812
// iPhone XS Max
const isIPhoneXSMax = /iphone/gi.test(ua) && dpr && dpr === 3 && w === 414 && h === 896
// iPhone XR
const isIPhoneXR = /iphone/gi.test(ua) && dpr && dpr === 2 && w === 414 && h === 896

const needSafe = isIPhoneX || isIPhoneXSMax || isIPhoneXR

export const safeHeight = needSafe ? 45 : 0

export const screenHeight = window.innerHeight - safeHeight

export const screenWidth = Math.max(window.innerWidth, 300)
//ig-wxz-and-hotdogapputilsdom.js
import { isUndef } from './index'

const supportsPassive = (function () {
    let support = false
    try {
        const opts = Object.defineProperty({}, 'passive', {
            get: function () {
                support = true
            }
        })
        window.addEventListener('test', null, opts)
    } catch (e) { }
    return support
})()

export const addEvent = (
    node,
    event,
    fn,
    options = {}
) => {
    let { capture, passive } = options

    capture == isUndef(capture) ? false : capture
    passive = isUndef(passive) ? true : passive

    if (typeof node.addEventListener == 'function') {
        if (supportsPassive) {
            node.addEventListener(event, fn, {
                capture,
                passive,
            })
        } else {
            node.addEventListener(event, fn, capture)
        }
    }
    else if (typeof node.attachEvent == 'function') {
        node.attachEvent('on' + event, fn);
    }
}

export const removeEvent = function (node, event, fn) {
    if (typeof node.removeEventListener == 'function') {
        node.removeEventListener(event, fn);
    }
    else if (typeof node.detatchEvent == 'function') {
        node.detatchEvent('on' + event, fn);
    }
}

export const removeNode = (node) => node.parentNode.removeChild(node)

事件函数

//ig-wxz-and-hotdogapputilsevent.js
class EventEmitter {
    constructor() {
        this._event = {}
        this._listeners = []
    }

    on(name, callback) {
        (this._event[name] || (this._event[name] = [])).push(callback)
    }

    emit(name, payload) {
        const cbs = this._event[name] || []
        for (let i = 0, len = cbs.length; i < len; i++) {
            cbs[i](payload)
        }
        if (this._listeners.length) {
            for (let { trigger, callback } of this._listeners) {
                if (trigger(name)) {
                    callback()
                }
            }
        }
    }

    remove(name) {
        this._event[name] = null
    }

    clear() {
        this._event = {}
    }

    // 监听某些事件时使用
    listen(condition, callback) {
        let trigger
        if (condition instanceof RegExp) {
            trigger = eventName => condition.test(eventName)
        } else if (typeof condition === 'string') {
            trigger = eventName => eventName.includes(condition)
        }
        this._listeners.push({
            trigger,
            callback
        })
    }
}

export default new EventEmitter()
//ig-wxz-and-hotdogappstoreindex.js
//游戏中的状态管理
/**
 * 全局状态管理
 */
class ReactiveStore {
  constructor() {
    this._store = {}
    this._listeners = {}
  }

  // currying
  createAction(key) {
    const set = (val) => {
      this.set(key, val)
    }

    const get = () => {
      return this.get(key)
    }

    const subscribe = (fn) => {
      return this.subscribe(key, fn)
    }

    return {
      get,
      set,
      subscribe,
    }
  }

  // set的时候触发subscribe的方法
  set(key, val) {
    this._store[key] = val
    const listeners = this._listeners[key]
    if (listeners) {
      listeners.forEach(fn => fn())
    }
  }

  get(key) {
    return this._store[key]
  }

  // 订阅某个key的set执行fn回调
  subscribe(key, cb) {
    (this._listeners[key] || (this._listeners[key] = [])).push(cb)

    // return unsubscribe
    return () => {
      const cbs = this._listeners[key]
      const i = cbs.findIndex(f => cb === f)
      cbs.splice(i, 1)
    }
  }
}

const store = new ReactiveStore()

const { set: setScore, get: getScore, subscribe: subscribeScore } = store.createAction('score')
const { set: setSeconds, get: getSeconds, subscribe: subscribeSeconds } = store.createAction('seconds')

export {
  setScore,
  getScore,
  subscribeScore,
  setSeconds,
  getSeconds,
  subscribeSeconds,
}
//ig-wxz-and-hotdogappmodulesounus-point.js
/**
 * 得分提示
 */
import { PLYAYER_OPTIONS, safeHeight, addEvent, removeNode } from '@/utils'

const { height: playerHeight } = PLYAYER_OPTIONS

export default class BounusPoint {
  constructor(x, bounus) {
    this.$el = null
    this.left = x
    this.bottom = safeHeight + playerHeight
    this.bounus = bounus
    this.init()
    this.initEvent()
  }

  init() {
    const el = document.createElement('div')
    el.style.cssText = `
       position: fixed;
       z-index: 2;
        auto;
       height: 20px;
       text-align: center;
       left: ${this.left}px;
       bottom: ${this.bottom}px;
       font-weight: 700;
       font-size: 18px;
       animation:bounus 1s;
    `
    const text = document.createTextNode(`+${this.bounus}`)
    el.appendChild(text)
    document.body.appendChild(el)
    this.$el = el
  }

  initEvent() {
    addEvent(this.$el, 'animationend', () => {
      removeNode(this.$el)
    })
  }
}
//动态创建弹框
//ig-wxz-and-hotdogappmodulesdialog.js
/**
 * 游戏结束对话框
 */
import { screenWidth, DIALOG_OPTIONS, addEvent, removeNode, noop } from '@/utils'
import {
    getScore,
} from 'store'
const { width, height } = DIALOG_OPTIONS

export default class Dialog {
    constructor(onLeftClick, onRightclick) {
        this.onLeftClick = onLeftClick ? () => {
            this.destory()
            onLeftClick()
        } : noop
        this.onRightClick = onRightclick || noop
        this.initDialog()
    }

    initDialog() {
        const dialog = document.createElement('div')
        dialog.style.cssText = `
            position: fixed;
            z-index: 2;
             ${width}px;
            height: ${height}px;
            padding-top: 20px;
            border: 2px solid black;
            text-align: center;
            left: ${screenWidth / 2 - width / 2}px;
            top: 200px;
            font-weight: 700;
        `
        const endText = createText('游戏结束', 'font-size: 30px;')
        const scoreText = createText(`${getScore()}分`, 'font-size: 30px;')

        const restartBtn = createButton('replay', this.onLeftClick, 'left: 20px;')
        const starBtn = createButton('❤star', this.onRightClick, 'right: 20px;')

        dialog.appendChild(endText)
        dialog.appendChild(scoreText)
        dialog.appendChild(restartBtn)
        dialog.appendChild(starBtn)

        document.body.appendChild(dialog)
        this.$el = dialog
    }

    destory() {
        removeNode(this.$el)
    }
}


const createText = (text, extraCss) => {
    const p = document.createElement('p')
    p.style.cssText = `
        font-weight: 700;
        text-align: center;
        margin-bottom: 8px;
        ${extraCss}
    ` 
    const textNode = document.createTextNode(text)
    p.appendChild(textNode)
    return p
}

const createButton = (text, fn, extraCss) => {
    const button = document.createElement('div')
    button.style.cssText = `
        position: absolute;
         90px;
        bottom: 20px;
        border: 2px solid black;
        font-weight: 700;
        font-size: 20px;
        ${extraCss}
    `
    const textNode = document.createTextNode(text)
    button.appendChild(textNode)
    addEvent(button,'click', fn)
    return button
}

这个是下落部分的

//ig-wxz-and-hotdogappmodulesfall.js
/**
 * 掉落物
 */
import {
    screenWidth,
    screenHeight,
    PLYAYER_OPTIONS,
    CHECK_FALL_EVENT,
    removeNode,
    eventEmitter,
} from '@/utils'

// 每次下落的距离
const INTERVAL_DISTANCE = 15

const CUP = {
     50,
    height: 100,
    img: require('@/assets/images/cup.jpg'),
    bounus: 5,
}

const HOT_DOG = {
     20,
    height: 50,
    img: require('@/assets/images/hotdog.jpg'),
    bounus: 1,
}

const {
    height: playerHeight,
} = PLYAYER_OPTIONS
export default class Fall {
    constructor() {
        this.img = null
        this.bounus = 0
        this.width = 0
        this.height = 0
        this.posY = 0
        this.moveTimes = 0
        this.randomFallItem()
        this.calcTimePoint()
        this.initFall()
        this.startFall()
    }

    randomFallItem() {
        const fallItem = Math.random() <= 0.08
            ? CUP
            : HOT_DOG
        const { img, bounus, width, height } = fallItem
        this.img = img
        this.bounus = bounus
        this.width = width
        this.height = height
    }

    // 计算开始碰撞的时间点
    calcTimePoint() {
        const { width, height } = this
        // 从生成到落到人物位置需要的总移动次数
        this.timesToPlayer = Math.floor((screenHeight - playerHeight - height) / INTERVAL_DISTANCE)
        // 从生成到落到屏幕底部需要的总移动次数
        this.timesToEnd = Math.floor(screenHeight / INTERVAL_DISTANCE)
    }

    initFall() {
        this.posX = getScreenRandomX(this.width)
        const { width, height, posX } = this
        const fall = document.createElement('img')
        this.$el = fall
        fall.src = this.img
        fall.style.cssText = `
            position: fixed;
             ${width}px;
            height: ${height}px;
            left: ${posX}px;
            transform: translateY(0px);
            z-index: 0;
        `
        document.body.appendChild(fall)
    }

    updateY() {
        this.moveTimes++

        // 进入人物范围 生成高频率定时器通知外部计算是否碰撞
        if (this.moveTimes === this.timesToPlayer) {
            if (!this.emitTimer) {
                this.emitTimer = setInterval(() => {
                    eventEmitter.emit(CHECK_FALL_EVENT, this)
                }, 4)
            }
        }

        // 到底部了没有被外部通知销毁 就自行销毁
        if (this.moveTimes === this.timesToEnd) {
            this.destroy()
            return
        }

        const nextY = this.posY + INTERVAL_DISTANCE
        this.$el.style.transform = `translateY(${nextY}px)`
        this.posY = nextY
    }

    destroy() {
        this.emitTimer && clearInterval(this.emitTimer)
        this.fallTimer && clearInterval(this.fallTimer)
        removeNode(this.$el)
    }

    startFall() {
        this.fallTimer = setInterval(() => {
            this.updateY()
        }, 16)
    }
}

function getScreenRandomX(width) {
    return Math.random() * (screenWidth - width)
}
//ig-wxz-and-hotdogappmodulesplayer.js
/**
 * 人物
 */
import { screenWidth, safeHeight, addEvent, PLYAYER_OPTIONS, isUndef } from '@/utils'

const {  playerWidth, height: playerHeight, img } = PLYAYER_OPTIONS

export default class Player {
    constructor() {
        // 初始化位置 屏幕正中
        this.posX = screenWidth / 2 - playerWidth / 2
        this.initPlayer()
        this.initMoveEvent()
    }
    //初始化图像
    initPlayer() {
        const el = this.$el = document.createElement('img')
        el.src = img
        el.style.cssText = `
            position: fixed;
            bottom: ${safeHeight}px;
             ${playerWidth}px;
            height: ${playerHeight}px;
            transform: translateX(${ screenWidth / 2 - playerWidth / 2}px);
            z-index: 1;
        `
        document.body.appendChild(el)
    }
    //移动事件
    initMoveEvent() {
        const body = document.body
        
        addEvent(
            body,
            'touchstart',
            e => {
                setPositionX(this, e)
            })

        const moveEvent = 'ontouchmove' in window ? 'touchmove' : 'mousemove'
        addEvent(
            body,
            moveEvent,
            e => {
                e.preventDefault()
                setPositionX(this, e)
            },
            {
                passive: false
            }
        )

    }
}
//设置位置
const setPositionX = (player, e) => {
    let x = e.pageX
    if (isUndef(x)) {
        x = e.touches[0].clientX
    }
    const { $el } = player
    $el.style.transform = `translateX(${checkScreenLimit(x - (playerWidth / 2))}px)`
    player.posX = x
}
//设置位置限制
const checkScreenLimit = (x) => {
    const leftLimit = 0 - (playerWidth / 2)
    const rightLimit = screenWidth - (playerWidth / 2)
    return x < leftLimit
        ? leftLimit
        : x > rightLimit
            ? rightLimit
            : x
}

分数

//ig-wxz-and-hotdogappmodulesscore-board.js
/**
 * 计分板
 */
import {
    setScore,
    getScore,
    subscribeScore,
    getSeconds
} from 'store'
class Score {
    constructor() {
        this.$el = null
        this.initScore()
        subscribeScore(this.renderScore.bind(this))
    }

    initScore() {
        const score = document.createElement('div')
        score.style.cssText = `
            position: fixed;
            z-index: 2;
             100px;
            height: 50px;
            line-height: 50px;
            text-align: center;
            right: 0;
            top: 0;
            font-size: 30px;
            font-weight: 700;
        `
        this.$el = score
        document.body.appendChild(score)
    }

    addScore(bounus) {
        const seconds = getSeconds()
        if (seconds !== 0) {
            setScore(getScore() + bounus)
        }
    }
    
    renderScore() {
        this.$el.innerText = getScore()
    }
}

export default Score

计时板

//ig-wxz-and-hotdogappmodules	ime-board.js

/**
 * 计时板
 */
import { subscribeSeconds, getSeconds } from 'store'
export default class TimeBoard {
  constructor() {
    this.$el = null
    this.initTimerBoard()
    subscribeSeconds(this.renderTimerText.bind(this))
  }

  initTimerBoard() {
    const board = document.createElement('div')
    board.style.cssText = `
            position: fixed;
            z-index: 2;
             200px;
            height: 50px;
            line-height: 50px;
            text-align: center;
            left: 0;
            top: 0;
            font-size: 30px;
            font-weight: 700;
        `
    document.body.appendChild(board)
    this.$el = board
  }

  renderTimerText() {
    this.$el.innerText = createTimerText(getSeconds())
  }
}

const createTimerText = (seconds) => `剩余时间${seconds}秒`

后记,我没有看懂到底怎么写的

原文地址:https://www.cnblogs.com/smart-girl/p/11446279.html