扫雷游戏应该怎样制作

757 阅读2分钟

我正在参加掘金社区游戏创意投稿大赛团队赛,详情请看:游戏创意投稿大赛

大帅 带队,莫石 参与开发《九霄雷霆之扫雷》的扫雷游戏

体验方式 小程序搜索 九霄雷霆之扫雷, 仓库地址

我这边主要是更改了游戏界面,先来几张图

image.png

image.png

一、游戏介绍及思路分析

首先,我先简单的介绍一下扫雷游戏的基本规则(了解规则的跳过即可)

  • 1、创建一个X行X列的二维数组。
  • 2、创建雷:随机行数 i 和列数 j,根据随机到 i、j 从二维数组中取出对应的元素,将取到的元素设置一个属性type等于1,表示当前元素已经是雷,并且递增雷计数器,然后递归调用;如果取到的元素已经是雷了,则跳过继续执行,雷计数器达到设定的最大值就跳出递归
  • 3、计算每个元素周围的雷数量,当前位置显示对应的数字
  • 4、同样根据这个二维数组来创建遮罩的小方块,正好盖住之前创建的图形。
  • 5、点击这个遮罩的小方块则触发揭开,揭开后根据对应的数字或者雷做不同的操作。

二、静态页面制作(核心代码)

<view class="placeInRow" :class="classMax" v-for="(row,i) in mask" :key="'row-'+i">
 <view class="content" v-for="(block,j) in row" :key="'block-'+j">
  <!-- 0 表示没有探过 1 表示探过 -1表示立flag 2表示flag错了 -->
  <view v-if="block === 1" class="block">
   <view class="open" v-if="maps[i][j] > 0" >{{maps[i][j]}}</view>
   <view v-else-if="maps[i][j] === 0" class="none" ></view>
   <view class="boom" v-else></view>
  </view>
  <view v-else-if="block === 0" class="block "
   @tap="setMask(i,j,'open')" @longpress="setMask(i,j,'mask')"
  ></view>
  <view v-else-if="block === -1" class="block " @longpress="setMask(i,j,'mask')">
   <image src="../../static/imgs/flag.png"></image>
  </view>
  <view v-else-if="block === 2" class="block ">
   <image src="../../static/imgs/error.png"></image>
  </view>
 </view>
</view>

三、创建雷及显示对应的图片(雷、数字)

1.随机row 和 col,并从二维数组中获取到这个对象;

2.判断他是否是雷,如果是则跳过当前;

3.如果当前不是雷,则标记当前对象为雷对象,并且更改图片;

4.递归,当达到设定的数量时跳出。

 this.maps = []
 this.mask = []
 this.isGameOver = false
 this.isGameSuccess = false
 this.booms = []
 
 /*      
 1.随机row 和 col,并从二维数组中获取到这个对象       
 2.判断他是否是雷,如果是则跳过当前      
 3.如果当前不是雷,则标记当前对象为雷对象,并且更改图片     
 4.递归,当达到设定的数量时跳出   
 */

 for (let i=0;i<this.width;i++){
  this.maps.push([])
  this.mask.push([])
  for (let j=0;j<this.height;j++){
   this.maps[i].push(0)
   this.mask[i].push(0)
  }
 }
 let initBooms = []
 while (initBooms.length < this.boomNum){
  let xy = [
   parseInt(Math.random()*this.width), 
   parseInt(Math.random()*this.height)
  ]
  let hasSame = false;
  for (let b =0; b<initBooms.length;b++){
   if(initBooms[b][0] == xy[0] && initBooms[b][1] == xy[1]){
    hasSame = true;
    break;
   }
  }
  if (!hasSame){
   initBooms.push(xy)
   this.maps[xy[0]][xy[1]] = -1;
  }
 }
 this.booms = initBooms;
 // 计算周围雷的数量
 for (let i=0;i<this.width;i++){
  for (let j=0;j<this.height;j++){
   if(this.maps[i][j] !== -1){
    let boomSum = 0;
    if(i > 0) {
     if (j > 0 && this.maps[i-1][j-1] == -1) boomSum ++;
     if (this.maps[i-1][j] == -1) boomSum ++;
     if (j < this.height - 1 && this.maps[i-1][j+1] == -1) boomSum ++;
    }
    if(i < this.width - 1) {
     if (j > 0 && this.maps[i+1][j-1] == -1) boomSum ++;
     if (this.maps[i+1][j] == -1) boomSum ++;
     if (j < this.height - 1 && this.maps[i+1][j+1] == -1) boomSum ++;
    }
    if (j > 0 && this.maps[i][j-1] == -1) boomSum ++;
    if (j < this.height - 1 && this.maps[i][j+1] == -1) boomSum ++;
    this.maps[i][j] = boomSum
   }
  }
 }
 this.$emit('init',{maps:this.maps})
console.log(this.booms,this.maps);

四、增加音乐 及对应音乐的操作(开始,停止,暂停,切换音乐等)

initMusic(){
    !this.startAudio&& (this.startAudio = uni.createInnerAudioContext())
 this.startAudio.src ='https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3c5dd365-9db5-4687-8b00-e7c4a23710e1/d53274e0-321b-4e55-9ebc-e4dd86ef3e9b.mp3';    ;    // 开场音效
 !this.didaAudio  &&(this.didaAudio = uni.createInnerAudioContext())
 this.didaAudio.loop=true;
 
 this.didaAudio.src= 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3c5dd365-9db5-4687-8b00-e7c4a23710e1/edbc4906-940d-4add-b99b-94f014525cdc.mp3'// 氛围音效x
 
  //   启动之后开始氛围渲染
  this.startAudio.onEnded( ()=>{
   this.playDida()
  })
  
 
    !this.flagAudio && (this.flagAudio = uni.createInnerAudioContext())
 this.flagAudio.src='https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3c5dd365-9db5-4687-8b00-e7c4a23710e1/a0a4d7c4-1b53-4554-9e71-7c79844458b8.wav';    // 插旗音效
 !this.boomAudio &&(this.boomAudio = uni.createInnerAudioContext())
 this.boomAudio.src='https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3c5dd365-9db5-4687-8b00-e7c4a23710e1/d9ce405b-9410-4595-939b-7207cb8e2f72.mp3';    // 炸雷音效
 // 没踩到雷的
 !this.normalAudio && (this.normalAudio = uni.createInnerAudioContext() );
 this.normalAudio.src = 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3c5dd365-9db5-4687-8b00-e7c4a23710e1/4b6ffade-9811-43e6-b378-fefc879e49b9.mp3'
}
switchMusic(){
 this.quiet = !this.quiet ;
 let volume = Number(!this.quiet)
 this.startAudio.volume = volume  ;
 this.didaAudio.volume = volume  ;
 this.flagAudio.volume = volume  ;
 this.boomAudio.volume = volume  ;
},
playStart(){ //播放音乐
    this.startAudio.play()
} ,
playDida(){ 
    this.didaAudio.play()
} ,
pauseDida(){ //暂停音乐
    this.didaAudio.pause()
} ,
playFlag(){ 
    this.flagAudio.play()
} ,
playBoom(){
    this.boomAudio.play()
} ,
playNormal(){ 
    this.normalAudio.play()
} ,

五、块的点击事件

setMask(i,j,action){
 // action 可以是 open 或 mask
 this.lastAction = action;
 if (this.isGameOver || this.isGameSuccess){
  return;
 }
 else if (action === 'open'){
  if (this.maps[i][j] === -1){
   // 踩到雷了
   for(let b=0;b<this.booms.length;b++){
    let theBoomXY = this.booms[b];
    if(this.mask[theBoomXY[0]][theBoomXY[1]] != -1){
     this.mask[theBoomXY[0]][theBoomXY[1]] = 1
    }
   }
 console.log(this.mask)

   this.isGameOver = true;
   this.startAudio.pause() // 有可能片头曲还没完就炸了
   this.pauseDida()
   this.playBoom()
   for (let i=0;i<this.width;i++){
    for (let j=0;j<this.height;j++){
     if(this.mask[i][j] == -1 && this.maps[i][j] != -1) this.mask[i][j] = 2;
    }
   }
   this.$forceUpdate()
   this.$emit('result', {code:-1, msg:'failed'})
  }
  else{
   this.playNormal() // 先声夺人
   this.canIOpen(i,j);
   console.log(this.mask)
  }
 }
 else{
  this.playFlag()
  if(this.mask[i][j] == 0)
   this.mask[i][j] = -1;
  else if(this.mask[i][j] == -1)
   this.mask[i][j] = 0;
  // console.log(i,j,this.mask[i][j])
  this.$nextTick(function(){
   this.$forceUpdate()
  })
 }
}

主要涉及的知识点:

1、雷区和数字的制作与分布 2、侦听用户的点击及对相应事件进行处理