这个是一个非常跟热点的小游戏,思聪吃热狗。这个游戏的话,我感觉思路还挺简单的,天上会掉热狗和障碍物,

思聪在下面张开嘴巴,进行左右移动,接热狗。如果吃到的是热狗就得一分,如果思聪吃到的不是热狗,是障碍物,就结束游戏。

如果要衍生的话,其实可以将热狗的下落方向不定的,就像俄罗斯方块那样,思聪的嘴巴也可以除了向上接热狗,还可以所有咬热狗那种。

感觉很简单很好玩的游戏哇,先放游戏的效果。



我们看动图可以发现,其实这个里面除了热狗障碍物,还有一个是得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-hotdog\app\utils\index.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-hotdog\app\utils\constant.js
export const PLYAYER_OPTIONS = {
img: require('@/assets/images/sicong.jpg'),
width: 70,
height: 70,
} export const DIALOG_OPTIONS = {
width: 250,
height: 170,
} // 坠落到底事件
export const CHECK_FALL_EVENT = 'checkFall'
//定义的一些设备
//ig-wxz-and-hotdog\app\utils\device.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-hotdog\app\utils\dom.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-hotdog\app\utils\event.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-hotdog\app\store\index.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-hotdog\app\modules\bounus-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;
width: 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-hotdog\app\modules\dialog.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: ${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;
width: 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-hotdog\app\modules\fall.js
/**
* 掉落物
*/
import {
screenWidth,
screenHeight,
PLYAYER_OPTIONS,
CHECK_FALL_EVENT,
removeNode,
eventEmitter,
} from '@/utils' // 每次下落的距离
const INTERVAL_DISTANCE = 15 const CUP = {
width: 50,
height: 100,
img: require('@/assets/images/cup.jpg'),
bounus: 5,
} const HOT_DOG = {
width: 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: ${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-hotdog\app\modules\player.js
/**
* 人物
*/
import { screenWidth, safeHeight, addEvent, PLYAYER_OPTIONS, isUndef } from '@/utils' const { width: 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;
width: ${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-hotdog\app\modules\score-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;
width: 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-hotdog\app\modules\time-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;
width: 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}秒`

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

【默默努力】ig-wxz-and-hotdog的更多相关文章

  1. 2018.5.2(7:20到的办公室开始早课 阮一峰的JS) 所有的默默努力都是为了让自己看起来毫不费力

    continue语句用于立即终止本轮循环,返回循环结构的头部,开始下一轮循环. break语句用于跳出代码块或循环. 标签(label) JavaScript 语言允许,语句的前面有标签(label) ...

  2. 【默默努力】PixelFire

    先放下我玩游戏的效果图: 关于游戏最后的结束部分其实我还没有截图,看着挺好看的,后面的效果 再放作者大大的项目地址:https://github.com/panruiplay/PixelFire 接下 ...

  3. 【默默努力】fishingGame

    这个捕鱼游戏挺有意思的,通过发射子弹,打鱼.打鱼的子弹会消耗金币,但是打鱼如果打到了鱼,就会奖励金币的数量. 我如果写这个的话,应该会画一个 背景海底,然后生成很多鱼的图片,还要有一个大炮,金币.大炮 ...

  4. 【默默努力】h5-game-heroVSmonster

    先放下作者大大的项目地址:https://github.com/yangyunhe369/h5-game-heroVSmonster 然后游戏的效果为 截动图的按键与游戏按键应该冲突,我就截几张图片了 ...

  5. 【默默努力】h5-game-blockBreaker

    先放下游戏的效果,我不太会玩游戏 然后放下无私开源的作者大大的地址:https://github.com/yangyunhe369/h5-game-blockBreaker 这个游戏的话,我觉得应该是 ...

  6. 【默默努力】react-drag-grid

    先放项目地址:https://github.com/Bilif/react-drag-grid 项目运行效果 感谢无私开源的程序员 先看项目入口文件 //index.js import React f ...

  7. 【默默努力】vue-pc-app

    最近在github上面看到了一个团队的项目,真的非常赞.他们进行vue-cli的二次开发,将项目用自己的方式打包. 今天的这个开源项目地址为:https://github.com/tffe-team/ ...

  8. Shader的学习方法总结

    最近网友candycat1992的新书<Unity Shader入门精要>出版了,估计万千的中国unity开发者又要掀起一波学Shader热潮了.我也想把自己这几年学习Shader的一些历 ...

  9. VIM移动

    VIM移动   断断续续的使用VIM也一年了,会的始终都是那么几个命令,效率极低 前几个星期把Windows换成了Linux Mint,基本上也稳定了下来 就今晚,我已经下定决心开始新的VIM之旅,顺 ...

随机推荐

  1. Unicode与MultiByte之间的练习: Process Cleaner

    #include <cstdio> #include <cstring> #include <windows.h> #include <tlhelp32.h& ...

  2. (转)poi操作Excel, 各种具体操作和解释

    原文地址http://hi.baidu.com/j_changhong/item/981fa58d05fa755926ebd96b注原文是3.6 此文是3.9 java读取excel文件的顺序是: E ...

  3. spring关于bean的一些配置说明

    <context:annotation-config> 是用于激活那些已经在spring容器里注册过的bean上面的注解.该标签主要向容器中掩式定的注了 AutowiredAnnotati ...

  4. 面向XX程序设计到底是个啥

    面向过程编程:面向(对着)-->过程(流程步骤)-->编程(码代码) IPO是啥 input(输入)-->process(过程处理)-->output(输出) 未来码代码的目的 ...

  5. iOS逆向系列-动态调试

    Xcode调试App原理 Mac安装了Xcode Xcode的安装包中包含了debugserver 可执行类型的Mach-O文件,iPhone第一次连接Xcode调试会将Xcode中的debugser ...

  6. 笔记:简单的面向对象-web服务器

    import socket import re import multiprocessing import time import mini_frame class WSGIServer(object ...

  7. ASP.NET打开项目错误:将指定的计数添加到该信号量中会导致其超过最大计数。

    1.错误如图 2.解决方案 重启IIS即可,运行-> 输入IISRESET 命令 即可重启IIS,如图

  8. 前端常用的库和实用技术之JavaScript高级技巧

    javascript高级技巧 变量作用域和闭包 <!DOCTYPE html> <html lang="en"> <head> <meta ...

  9. PHP算法之两数之和

    给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样的元 ...

  10. 传统的dom的渲染方式

    DOM渲染的过程大致分为三个阶段: 后端渲染 前端渲染 独立DOM渲染(前后端相结合渲染) 1.后端渲染:DOM树的生成完全是在后端服务器中完成的,后端服务器的程序会把需要的数据拼合成一个类似于前端D ...