zoukankan      html  css  js  c++  java
  • 【默默努力】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}秒`
    

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

  • 相关阅读:
    常见保护方式简介
    各种保护机制绕过手法
    ShellCode框架(Win32ASM编写)
    单例模式
    HTTP1.0、HTTP1.1、HTTP2.0的关系和区别
    java集合提供的排序算法
    Dubbox以及微服务
    进程栈帧
    java线程池
    Java多态的实现
  • 原文地址:https://www.cnblogs.com/smart-girl/p/11446279.html
Copyright © 2011-2022 走看看