在vue中实现接物类小游戏
先放张效果图
<template>
<div id="container">
<div id="guidePanel"></div>
<div id="gamepanel">
<div class="score-wrap">
<div class="heart"></div>
<span id="score">0</span>
</div>
<canvas v-show="isShowCanvas" id="stage"></canvas>
</div>
<div id="gameoverPanel"></div>
<div id="resultPanel">
<!-- <div class="weixin-share"></div> -->
<a href="javascript:void(0)" class="replay"></a>
<div id="fenghao"></div>
<!-- <div id="scorecontent">
您在<span id="stime" class="lighttext">2378</span>秒内抢到了<span id="sscore" class="lighttext">21341</span>个月饼<br>超过了<span id="suser" class="lighttext">31%</span>的用户!
</div> -->
<!-- <div class="textc">
<span class="btn1 share">请小伙伴吃月饼</span>
</div> -->
</div>
<audio style="display: none;" id="media" src="https://ppt-mp3cdn.hrxz.com/d/file/filemp3/hrxz.com-owjof3jyx1p46949.mp3"></audio>
<img :class="{ 'isRotate': isRotateMusic }" class="music_btn" :src="isPlayMusic ? '/static/image/active/musicOn.png' : '/static/image/active/musicOff.png'" alt="音乐播放" @click="playPause">
</div>
</template>
<script>
export default {
name: 'activeTest',
data () {
return {
isShowCanvas: true,
isRotateMusic: false,
isPlayMusic: true
}
},
mounted () {
//
const self = this
// 实现canvas铺满整个屏幕
var canvas = document.getElementById('stage')
canvas.setAttribute('width', window.innerWidth)
canvas.setAttribute('height', window.innerHeight)
// 接物类
function Ship (ctx) {
// 预加载图片
// gameMonitor.im.loadImage(['/static/game/static/img/player.png'])
this.width = 70
this.height = 70
this.left = gameMonitor.w/2 - this.width/2
this.top = gameMonitor.h - this.height
// this.player = gameMonitor.im.createImage('/static/game/static/img/player.png')
let myImg = new Image()
myImg.src = '/static/game/static/img/player.png'
this.player = myImg
this.paint = function () {
// 取消上下移动
ctx.drawImage(this.player, this.left, gameMonitor.h - this.height, this.width, this.height)
}
// 保存接收物移动的位置
this.setPosition = function (event) {
var tarL = event.changedTouches[0].clientX
// 取消上下移动
// var tarT = event.changedTouches[0].clientY
// 测试减少计算 能否减少移动飞船时的重影
this.left = tarL - 35
// this.top = tarT - this.height/2
if (this.left < 0) {
this.left = 0
}
if (this.left > gameMonitor.w - this.width) {
this.left = gameMonitor.w - this.width
}
// if (this.top > gameMonitor.h - this.height) {
// this.top = gameMonitor.h - this.height
// }
// if (this.top < gameMonitor.h - this.height) {
// this.top = gameMonitor.h - this.height
// }
this.paint()
}
// 监听touch时间,来调用setPosition来计算位置
this.controll = function () {
const _this = this
const stage = $('#gamepanel')
let move = false
stage.on(gameMonitor.eventType.start, function (event) {
_this.setPosition(event)
move = true
}).on(gameMonitor.eventType.end, function () {
move = false
}).on(gameMonitor.eventType.move, function (event) {
event.preventDefault()
if(move) {
_this.setPosition(event)
}
})
}
// 每次帧刷新时做碰撞检测(两圆心的距离小于半径之和,则认为发生了碰撞)
this.eat = function (foodlist) {
for(let i = foodlist.length-1; i >= 0; i--) {
let f = foodlist[i]
if (f) {
const l1 = this.top+this.height/2 - (f.top+f.height/2)
const l2 = this.left+this.width/2 - (f.left+f.width/2)
const l3 = Math.sqrt(l1*l1 + l2*l2)
if (l3 <= this.height/2 + f.height/2) {
foodlist[f.id] = null
if (f.type == 0) {
gameMonitor.stop()
$('#gameoverPanel').show()
self.isShowCanvas = false
setTimeout(function () {
$('#gameoverPanel').hide()
$('#resultPanel').show()
gameMonitor.getScore()
}, 2000)
} else {
document.querySelector('#media').currentTime = 0
document.querySelector('#media').play()
$('#score').text(++gameMonitor.score)
$('.heart').removeClass('hearthot').addClass('hearthot')
setTimeout(function () {
$('.heart').removeClass('hearthot')
}, 200)
}
}
}
}
}
}
/**
* 掉落物类
* type 0 == 炸弹 1 == 好东西
*/
function Food (type, left, id) {
// 加速的时间间隔
this.speedUpTime = 600
this.id = id
this.type = type
this.width = 50
this.height = 50
this.left = left
this.top = -50
// 掉落时间
this.speed = 0.01 * Math.pow(1.2, Math.floor(gameMonitor.time/this.speedUpTime))
console.log(this.speed);
this.loop = 0
const p = this.type == 0 ? '/static/game/static/img/food1.png' : '/static/game/static/img/food2.png'
this.pic = gameMonitor.im.createImage(p)
}
Food.prototype.paint = function (ctx) {
ctx.drawImage(this.pic, this.left, this.top, this.width, this.height)
}
Food.prototype.move = function (ctx) {
if (gameMonitor.time % this.speedUpTime == 0) {
this.speed *= 1.2
}
this.top += ++this.loop * this.speed
if (this.top>gameMonitor.h) {
gameMonitor.foodList[this.id] = null
} else {
// 去掉上下移动
// this.paint(ctx)
}
}
// 图片加载器,让浏览器预加载图片,方便直接调用
function ImageMonitor () {
let imgArray = []
return {
// 返回当前数组中对应的图片,如果不存在该图片,则new一个来返回
createImage: function (src) {
return typeof imgArray[src] != 'undefined' ? imgArray[src] : (imgArray[src] = new Image(), imgArray[src].src = src, imgArray[src])
},
// 接收一个数组和一个回调函数,把数组中的图片路径逐一加载,保存到一个数组中,最后一张图片加载完后执行一个回调函数
loadImage: function (arr, callback) {
for(let i = 0, l = arr.length; i < l; i++) {
let img = arr[i]
imgArray[img] = new Image()
imgArray[img].onload = function () {
if(i == l-1 && typeof callback == 'function') {
callback()
}
}
imgArray[img].src = img
}
}
}
}
var gameMonitor = {
w: window.innerWidth,
h: window.innerHeight,
bgWidth: window.innerWidth,
bgHeight: window.innerHeight,
time: 0,
timmer: null,
bgSpeed: 2,
bgloop: 0,
score: 0,
im: new ImageMonitor(),
foodList: [],
bgDistance: 0,//背景位置
eventType: {
start : 'touchstart',
move : 'touchmove',
end : 'touchend'
},
init: function(){
var _this = this;
var canvas = document.getElementById('stage')
var ctx = canvas.getContext('2d')
//绘制背景
var bg = new Image()
_this.bg = bg
bg.onload = function(){
ctx.drawImage(bg, 0, 0, _this.bgWidth, _this.bgHeight);
}
bg.src = '/static/game/static/img/bg.jpg'
_this.initListener(ctx)
},
initListener: function (ctx) {
const _this = this
const body = $(document.body)
$(document).on(gameMonitor.eventType.move, function(event){
event.preventDefault()
})
body.on(gameMonitor.eventType.start, '.replay, .playagain', function(){
$('#resultPanel').hide()
self.isShowCanvas = true
const canvas = document.getElementById('stage')
const ctx = canvas.getContext('2d')
_this.ship = new Ship(ctx)
_this.ship.controll()
_this.reset()
_this.run(ctx)
})
body.on(gameMonitor.eventType.start, '#frontpage', function(){
$('#frontpage').css('left', '-100%')
})
body.on(gameMonitor.eventType.start, '#guidePanel', function(){
$(this).hide()
self.isRotateMusic = true
document.querySelector('#media').playbackRate = 1
_this.ship = new Ship(ctx)
_this.ship.paint()
_this.ship.controll()
gameMonitor.run(ctx)
})
},
// 生成滚动的背景
rollBg: function(ctx){
// if(this.bgDistance>=this.bgHeight){
// this.bgloop = 0
// }
// this.bgDistance = ++this.bgloop * this.bgSpeed
// ctx.drawImage(this.bg, 0, this.bgDistance-this.bgHeight, this.bgWidth, this.bgHeight)
ctx.drawImage(this.bg, 0, 0, this.bgWidth, this.bgHeight)
},
run: function(ctx){
var _this = gameMonitor
ctx.clearRect(0, 0, _this.bgWidth, _this.bgHeight)
_this.rollBg(ctx)
//绘制飞船
_this.ship.paint()
_this.ship.eat(_this.foodList)
//产生月饼
_this.genorateFood()
//绘制月饼
for (let i = _this.foodList.length-1; i>=0; i--) {
var f = _this.foodList[i]
if (f) {
f.paint(ctx)
f.move(ctx)
}
}
// 通过setTimeout来控制帧率
_this.timmer = setTimeout(function () {
gameMonitor.run(ctx)
}, Math.round(1000/200))
_this.time++
},
stop: function () {
var _this = this
$('#stage').off(gameMonitor.eventType.start + ' ' +gameMonitor.eventType.move)
setTimeout(function () {
clearTimeout(_this.timmer)
}, 0)
},
// 生成食物
genorateFood: function () {
var genRate = 70 //产生月饼的频率
var random = Math.random()
if (random*genRate>genRate-1) {
var left = Math.random()*(this.w - 50)
// 随机生成食物类型 bad good
var type = Math.floor(left) % 2 == 0 ? 0 : 1
var id = this.foodList.length
var f = new Food(type, left, id)
this.foodList.push(f)
}
},
// 点击在玩一次后 重置所有数据
reset: function(){
this.foodList = []
this.bgloop = 0
this.score = 0
this.timmer = null
this.time = 0
$('#score').text(this.score)
},
getScore: function () {
// var time = Math.floor(this.time/60)
// var score = this.score
// var user = 1
// if(score==0){
// $('#scorecontent').html('真遗憾,您竟然<span class="lighttext">一个</span>月饼都没有抢到!')
// $('.btn1').text('大侠请重新来过').removeClass('share').addClass('playagain')
// $('#fenghao').removeClass('geili yinhen').addClass('yinhen')
// return
// }
// else if(score<10){
// user = 2
// }
// else if(score>10 && score<=20){
// user = 10
// }
// else if(score>20 && score<=40){
// user = 40
// }
// else if(score>40 && score<=60){
// user = 80;
// }
// else if(score>60 && score<=80){
// user = 92
// }
// else if(score>80){
// user = 99
// }
$('#fenghao').removeClass('geili yinhen').addClass('geili')
// $('#scorecontent').html('您在<span id="stime" class="lighttext">2378</span>秒内抢到了<span id="sscore" class="lighttext">21341</span>个月饼<br>超过了<span id="suser" class="lighttext">31%</span>的用户!')
// $('#stime').text(time)
// $('#sscore').text(score)
// $('#suser').text(user+'%')
// $('.btn1').text('请小伙伴吃月饼').removeClass('playagain').addClass('share')
},
}
gameMonitor.init()
},
methods: {
playPause () {
const music = document.querySelector('#media')
this.isRotateMusic = !this.isRotateMusic
this.isPlayMusic = !this.isPlayMusic
document.querySelector('#media').muted = !this.isPlayMusic
}
}
}
</script>
<style scoped>
#container{
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
}
#startgame{
position: absolute;
right: 20px;
bottom: 20px;
}
#gamepanel{
width: 100%;
height: 100%;
}
#stage{
background-color: #CCC;
}
.score-wrap {
background: url('/static/game/static/img/scorebg.png') no-repeat;
background-size: 100%;
color: #FFF;
/*display: none;*/
font-family: "Helvetica","Microsoft YaHei",sans-serif;
font-style: italic;
font-size: 17px;
font-weight: 700;
height: 32px;
letter-spacing: 2px;
padding: 7px 10px;
position: absolute;
right: 20px;
text-align: right;
text-shadow: 1.5px 0 0 #613209,-1.5px 0 0 #613209,0 1px 0 #613209,0 -1.5px 0 #613209,1px 1px 0 #613209,-1px 1px 0 #613209,1px -1px 0 #613209,-1px -1px 0 #613209;
top: 10px;
width: 105px;
z-index: 1005
}
.score-wrap div {
background: url('/static/game/static/img/heart.png') no-repeat;
background-size: 100%;
height: 26px;
left: 2px;
position: absolute;
top: 2px;
width: 26px;
z-index: 1009
}
div.hearthot {
-webkit-animation: fire .2s linear;
-o-animation: fire .2s linear;
animation: fire .2s linear
}
@-webkit-keyframes fire {
0% {
opacity: 1;
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-ms-transform: scale(1.1);
-o-transform: scale(1.1);
transform: scale(1.1)
}
100% {
opacity: 0;
-webkit-transform: scale(3.0);
-moz-transform: scale(3.0);
-ms-transform: scale(3.0);
-o-transform: scale(3.0);
transform: scale(3.0)
}
}
@keyframes fire {
0% {
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
-ms-transform: scale(1.1);
-o-transform: scale(1.1);
transform: scale(1.1)
}
100% {
-webkit-transform: scale(1.0);
-moz-transform: scale(1.0);
-ms-transform: scale(1.0);
-o-transform: scale(1.0);
transform: scale(1.0)
}
}
#guidePanel {
background: rgba(0,0,0,0.6) url('/static/game/static/img/startbg.png') center 50% no-repeat;
background-size: 219px 369px;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
z-index: 10000
}
#gameoverPanel {
background: rgba(0,0,0,0.8) url('/static/game/static/img/gameover.jpeg') center 30% no-repeat;
background-size: 230px 260px;
top: 0
}
#gameoverPanel,#resultPanel {
display: none;
height: 100%;
position: absolute;
width: 100%;
z-index: 10000
}
#resultPanel{
background:url('/static/game/static/img/endpage.jpg') center top no-repeat;
background-size: 100% 100%;
}
#resultPanel,#resultPanel .weixin-share {
left: 0;
top: 0
}
#resultPanel .weixin-share {
background: rgba(0,0,0,.8) url('/static/game/static/img/weixin.png') right top no-repeat;
background-size: 212px 196px;
display: none;
height: 100%;
position: absolute;
width: 100%;
z-index: 100
}
#resultPanel .replay {
background: url('/static/game/static/img/replay.png') 0 0 no-repeat;
height: 36px;
line-height: 36px;
right: 24px;
overflow: hidden;
position: absolute;
top: 11px;
width: 86px;
z-index: 10;
color: #E44324;
text-align: right;
padding-right: 6px;
font-weight: 700;
font-size: 12px;
}
#resultPanel .panel,#scoreBoard .score-result {
display: none;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%
}
#fenghao{
height: 68px;
margin-top: 90px;
}
#scorecontent{
font-size: 16px;
font-weight: 700;
color: #FFF;
text-align: center;
line-height: 1.8em;
margin-top: 5px;
}
.lighttext{
color: #F6DE0A;
}
.geili{
background: url('/static/game/static/img/geili.png') center no-repeat;
}
.yinhen{
background: url('/static/game/static/img/yinhen.png') center no-repeat;
}
.textc{
text-align: center;
}
.btn1, .btn2{
display: inline-block;
width: 196px;
height: 54px;
line-height: 54px;
color: #FFF;
font-size: 20px;
border-radius: 5px;
text-align: center;
}
.btn1{
margin-top: 22px;
background-color: #E8722C;
}
.btn2{
margin-top: 12px;
border: 1px solid #6A6B6D;
}
#container .music_btn {
position: absolute;
left: 20px;
top: 20px;
width: 36px;
height: 36px;
display: block;
}
#container .isRotate {
animation: rotating 1.2s linear infinite;
}
@keyframes rotating {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style>