In summer, there are not only watermelons and air conditioners, but also sports and sweating, "Run! Programmer" js game is hot, come and run together

I am participating in the "Early Summer Creative Contest" for details, please see: Early Summer Creative Contest

Online experience address: summer.pkec.net/source
address: gitee.com/ihope_top/j…

foreword

Before you know it, summer is here again. What do you think of when it comes to summer? Air conditioning, watermelon and ice cream? But there are more than these in summer, there are sports, sweating, and showing your body. Remember the sports meeting every summer? Remember the cool running every day that summer? Today I will use js to bring you a parkour mini game - "Run! Programmer", I hope you like it

game introduction

special function

The special function involves the support of the back-end interface, so it only takes effect during the event. It may become invalid after the writing event, but the game can still be accessed normally. If it encounters malicious attacks or other uncontrollable factors, this function may fail in advance.

Grade upload

image.png

That's right, this time we have opened up the front and back ends and supported the upload of scores. After the game is over, there will be an option to upload scores. You only need to fill in your own Nuggets ID to upload this score. Upload the Nuggets ID's The main purpose is to prevent users from randomly filling in illegal nicknames, so it is necessary to obtain the user's Nuggets nickname based on the user's Nuggets id.

The mini game is only for entertainment, so there is no anti-cheating measure, everyone must not cheat, but in order to prevent misoperation, it is still restricted that the same results cannot be uploaded repeatedly, that is, they ran 500 meters twice, and the second time The results cannot be uploaded. If you run 500 meters for the first time and run 100 meters for the second time, you can upload both results.

How to get Nuggets ID:

image.png

Enter the personal homepage, the number behind the address bar is your id

All staff run

image.png

After the game is over, the scores uploaded by users will be stored in the database, and the home page will display the sum of the scores uploaded by all users

Leaderboard

image.png

The homepage will display the entry of the leaderboard, and the leaderboard will display the single process ranking of all users. A user can be on the list multiple times. The highest score is 800 meters, then the first and second places are Zhang San.

Rules Introduction

image.png

After starting the game, the character will automatically run forward, and the running picture will encounter a little devil. The user must avoid the little devil and continue to run forward. If the little devil is encountered, the game is over.

Operation method:

跳跃:按 w 键或 键进行跳跃躲避下方的小恶魔

image.png

下滑:按 s 键或 键进行下滑躲避上方的小恶魔

image.png

随着里程的增加,人物奔跑的速度会越来越快,小恶魔的数量也会越来越多(有上限),规则介绍就到这里啦,快去体验一下游戏吧。

游戏开发

场景开发

白云开发

image.png

单个的白云其实就是一个圆角矩形,然后用伪类元素做两个圆叠加起来形成的,代码如下

.cloud-item {
  position: absolute;
  width: 175px;
  height: 55px;
  margin: 50px;
  border-radius: 100px;
  background: #fff;
}
.cloud-item::before, .cloud-item::after {
  content: '';
  display: block;
  background: #fff;
  position: absolute;
}
.cloud-item::before {
  content: '';
  display: block;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  top: -90%;
  right: 10%;
}
.cloud-item::after {
  content: '';
  display: block;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  top: -54%;
  left: 14%;
  transform: rotate(-25deg);
}
复制代码

下面就是随机生成白云,之后给白云一个从右往左移动的动画,为了生成的白云更符合观感,我们给它一个随机的大小,然后根据这个大小再来一个对应的移动速度,就变成了下面这样

2.gif

附上代码

<div class="cloud-wrap" ref="cloudWrap"></div>
复制代码
screenWidth: document.documentElement.clientWidth,
lastCreateTime: 0,
cloudFrequency: 10,
cloudSpeed: 1

// 生成云朵
createCloud() {
  let now = new Date().getTime()
  if (now - this.lastCreateTime > 3000) {
    // 创建云朵
    let cloudItem = document.createElement('div')
    cloudItem.className = 'cloud-item'
    // 设置云朵变化系数
    cloudItem.cloudScale = Math.random()
    // 设置云朵大小
    cloudItem.style.transform = 'scale('+cloudItem.cloudScale+')'
    // 设置云朵透明度
    cloudItem.style.opacity = cloudItem.cloudScale
    // 设置云朵位置
    let _left = this.screenWidth
    cloudItem.style.left = _left + 'px'
    let _top = Math.random() * 400
    cloudItem.style.top = _top + 'px'

    this.$refs.cloudWrap.appendChild(cloudItem)
    // 云朵移动
    let cloudMove = () => {
      // 云朵越大,移动速度越快
      let moveX = this.cloudSpeed * cloudItem.cloudScale
      let _left = +cloudItem.style.left.slice(0, -2)
      cloudItem.style.left = _left - moveX + 'px'

      // 如果云朵距离屏幕顶部距离大于等于屏幕高度,则移除此云朵
      if (cloudItem.offsetLeft < (-cloudItem.offsetWidth)) {
        this.$refs.cloudWrap.removeChild(cloudItem)
      } else {
        requestAnimationFrame(cloudMove)
      }
    }
    cloudMove()
    cloudMove()
    this.lastCreateTime = now
  }
  requestAnimationFrame(this.createCloud)
},
复制代码

下面是代码块版本

地面开发

ground.png

地面部分就是用这样的小方块平铺形成的,如果高度不够的话,就再来一个颜色相近的背景色

<!-- 地面 -->
<div class="ground-wrap"></div>
复制代码
.ground-wrap {
  height: 150px;
  box-sizing: border-box;
  background: url('./assets/images/ground.png') #685166;
  background-repeat: repeat-x;
  background-size: 50px;
}
复制代码

我们都知道,游戏里的人物运动其实并不是人物本身在动,而是场景的移动衬托出人物在运动,这里我们需要地面也在不断的运动,衬托出人物在运动。由于我们这里使用的是背景图片,所以只需要控制背景图片的定位即可

因为随着游戏的进行,人物会移动的越来越快,所以这里地面的移动速度也需要随着speed(全局速度控制变量)变化,另外我们对于人物的奔跑距离也在这里进行计算,当人物奔跑到一定距离时改变速度

 // 地面背景横向滚动
    groundScroll() {
      let ground = document.querySelector('.ground-wrap');
      let _left = 0
      
      ground.style.backgroundPositionX = _left +'px';
      let cityMove = () => {
        if (_left <= -600) {
          _left = 0
        }
        _left -= this.speed * 3
        this.total += (this.speed / 10)
        if (this.total >= 40000) {
          this.speed = 6
        } else if (this.total >= 4000) {
          this.speed = 5.5
        } else if (this.total >= 2000) {
          this.speed = 5
        } else if (this.total >= 1000) {
          this.speed = 4.5
        } else if (this.total >= 500) {
          this.speed = 4
        } else if (this.total >= 200) {
          this.speed = 3.5
        }
        ground.style.backgroundPositionX = _left +'px';
        this.groundMoveInterval = requestAnimationFrame(cityMove)
      }
      cityMove()
    },
复制代码

人物开发

image.png

人物跑动其实就是来回切换这样的几张静态图片,之所以没有用gif,是因为我还要控制人物跑动的速度,gif我没找到怎么控制速度的,我们先来看一下不同速度的跑动动画

3.gif

4.gif

下面是代码

先加载一下跑动的图片数组

for (let index = 0; index < 10; index++) {
  let img = require('@/assets/images/user/run/Run_00'+index+'.png')
  this.userRunList.push(img)
}
复制代码

跑步时切换图片

// 动画
run() {
  let _this = this
  let _index = 0

  let _run = () => {
    let now = new Date().getTime()
    if (now - this.lastRunTime > 120 - (this.speed * 20)) {
      if (_index > (this.userRunList.length - 1) ) {
        _index = 0
      }
      _this.userPic = this.userRunList[_index]
      _index++
      this.lastRunTime = now
    }
    this.runInterval = requestAnimationFrame(_run)
  }
  _run()
},
复制代码

不同于跑步的动画处理,跳跃和下滑我都没有做动画处理,一是因为它的时间不好控制,二是太多的图片也会导致,所以这里我们就用两张静态图片,展示两种状态

slide () {
  this.userPic = this.userSlidePic
},
jump () {
  this.userPic = this.userJumpPic
}
复制代码

障碍物开发

这种飞行物的开发其实原理都一样,从我最开始的年兽大作战中的弹幕到抗疫的汤圆中的柱子,又或者是刚才说过的天空中的云朵,都一样,都是按规定的时间生成一个物体,然后给它一个移动的动画。

这里不一样的地方是每个障碍物里包含的小恶魔数量不一样,障碍物可能在上面也可能在下面。

针对数量这个问题,我是根据speed(全局速度变量)来随机生成的,最少一个,最多四个,数量越多,当然难度也就越大。

针对上下这个问题,无非就是写一个靠上和靠下的样式,然后生成的时候随机进行生成,赋予相应的样式,注意,这里需要将这个状态保存下来,因为进行碰撞检测的时候要用。

image.png

// 生成障碍物
    createObstruction () {
      let obsList = document.createElement('div')
      // 让障碍物随机在上方或者下方
      let state = Math.random() > 0.5 ? 'top' : 'bottom'
      obsList.className = 'obs-list-' + state
      obsList.state = state
      // 根据速度等级,随机生成相应数量的小恶魔
      let random = Math.ceil(Math.random() * (this.speed - 2))
      for (let index = 0; index < random; index++) {
        let obsItem = document.createElement('div')
        obsItem.className = 'obs-item'
        obsList.appendChild(obsItem)
      }
      obsList.style.left = this.screenWidth + 'px'
      // obsList.createNext = false // 是否已创建下一个障碍物
      obsList.nextSpace = Math.random() * (this.obsInterval[1] - this.obsInterval[0]) + this.obsInterval[0] // 下一个障碍物间隔

      this.$refs.obstructionWrap.appendChild(obsList)

    },
复制代码

这里障碍物的生成还借鉴了弹幕那里的生成方案,那就是一个障碍物出来多久后,自动加载下一个,而不是定时进行创建,这里有点忘了当初怎么想的了,想起来再补充吧。

之后就是障碍物的移动,这里障碍物的移动速度设置的和地面是一样的,这样有一种障碍物是漂浮在地面上的感觉,另外和白云不一样的是这里障碍物的移动是控制的整体障碍物的移动,而不是给单一障碍物添加的移动动画,原因是因为这里的障碍物不需要给每个障碍物不同的移动速度,另一个原因是我们需要在障碍物碰到人的时候停止所有障碍物的移动,如果是给单一障碍物添加移动动画,显然是很难达到这个需求的。

  // 获取所有障碍物
  let obsDoms = this.$refs.obstructionWrap.children
  let obsList = Array.from(obsDoms)

  let nextItem = null

  // 给每个障碍物添加移动
  for (let index = 0; index < obsList.length; index++) {
    let item = obsList[index]
    if (item.offsetLeft < -item.offsetWidth) {
      this.$refs.obstructionWrap.removeChild(item)
    } else {
      item.style.left = item.offsetLeft - this.speed * 3 + 'px'
    }
  }
复制代码

玩法开发

玩法无非就是人物运动+障碍物运动+碰撞检测+躲避障碍物,人物运动和障碍物运动刚才都说过了,这里主要说的是碰撞检测和躲避障碍物,这个可以放到一起来说,因为只要没碰到那就是躲过去了。

这里的碰撞检测其实相当于抗疫的汤圆中的碰撞检测改版。其实准确的说,这里用的不是碰撞检测,是状态检测,因为这里的任务不能自由的移动,只有三种状态,跳跃、奔跑、下滑,所以我们只需要在合适的时候判断它处于什么状态就可以了,比如当上面的小恶魔过来的时候,判断人物是否处于下滑状态,如果不是,则判定为碰撞

首先我们需要找到和谁进行碰撞,因为同一时间障碍物可能有多个,我们给每一个障碍物添加碰撞检测,显然会浪费性能,所以我们需要找到距离离人物最近且没有完全经过人物的障碍物进行检测如下图所示

image.png

我们需要找到第一个自身没有完全经过人物的障碍物进行检测

已知人物宽度为120,人物处于屏幕水平中央

Therefore, the distance to the left of the obstacle to be judged is > half the width of the screen - half the width of the character - the width of the obstacle itself

let nextItem = null

// 给每个障碍物添加移动
  for (let index = 0; index < obsList.length; index++) {
    // 由于只需要找到第一个符合条件的障碍物即可,所以这里需要进行判断
    if (!nextItem) {
      // 找到人物右侧最近的障碍物,进行碰撞检测
      // 需要进行检测的障碍物需满足条件:距离屏幕左侧距离>人物距离左侧距离+自身宽度
      // 人物宽度为120,人物距离左侧距离为屏幕的一半减去自身的一半
      if (item.offsetLeft > (this.screenWidth / 2 - 60 - item.offsetWidth)) {
        nextItem = item
      }
    }
  }
复制代码

For the detection target found, we also need to know when to detect it, that is, when the distance between the obstacle and the left side of the screen < (half of the screen width + half of the character), the obstacle just begins to overlap with the obstacle. When judging according to the upper and lower positions of the obstacles (saved when they are generated) and the state of the characters, you can judge whether there is a collision.

// 碰撞检测
  // 当距离最近的障碍物处于检测区时,进行碰撞检测
  if (nextItem.offsetLeft < (this.screenWidth / 2 + 60)) {
    if (nextItem.state === 'top') {
      if (this.userStatus !== 'slide') {
        this.$emit('gameOver')
        // 游戏结束
        // alert('游戏结束')
        // this.gameOver()
        return
      }
    } else {
      if (this.userStatus !== 'jump') {
        // 游戏结束
        this.$emit('gameOver')
        //  alert('游戏结束')
        //  this.gameOver()
        return
      }
    }
  }
复制代码

The collision detection is carried out in the obstacle movement, and the complete code is as follows.

// 整体障碍物移动
    obsMove () {
      // 获取所有障碍物
      let obsDoms = this.$refs.obstructionWrap.children
      let obsList = Array.from(obsDoms)

      let nextItem = null

      // 给每个障碍物添加移动
      for (let index = 0; index < obsList.length; index++) {
        let item = obsList[index]
        if (item.offsetLeft < -item.offsetWidth) {
          this.$refs.obstructionWrap.removeChild(item)
        } else {
          item.style.left = item.offsetLeft - this.speed * 3 + 'px'
        }

        // 由于只需要找到第一个符合条件的障碍物即可,所以这里需要进行判断
        if (!nextItem) {
          // 找到人物右侧最近的障碍物,进行碰撞检测
          // 需要进行检测的障碍物需满足条件:距离屏幕左侧距离>人物距离左侧距离+自身宽度
          // 人物宽度为120,人物距离左侧距离为屏幕的一半减去自身的一半
          if (item.offsetLeft > (this.screenWidth / 2 - 60 - item.offsetWidth)) {
            nextItem = item
          }
        }
      }

      // 碰撞检测
      // 当距离最近的障碍物处于检测区时,进行碰撞检测
      if (nextItem.offsetLeft < (this.screenWidth / 2 + 60)) {
        if (nextItem.state === 'top') {
          if (this.userStatus !== 'slide') {
            this.$emit('gameOver')
            // 游戏结束
            // alert('游戏结束')
            // this.gameOver()
            return
          }
        } else {
          if (this.userStatus !== 'jump') {
            // 游戏结束
            this.$emit('gameOver')
            //  alert('游戏结束')
            //  this.gameOver()
            return
          }
        }
      }

      // 找到最后一个障碍物,创建下一个障碍物
      let lastChild = obsList[obsList.length - 1]
      // console.log(lastChild.nextSpace);
      if (lastChild.offsetLeft < (this.screenWidth - lastChild.offsetWidth - lastChild.nextSpace)) {
        this.createObstruction()
      }
      this.obsMoveInterval = requestAnimationFrame(this.obsMove)
    },
复制代码

This is the introduction of the mini-game. I hope everyone likes it, and set a flag for yourself. If you can make it into the top three, buy yourself a mac. If you can make it into the top ten, buy yourself an iPad. If you can make it into the top ten, buy yourself an iPad. If you don't go, you won't buy anything.

Guess you like

Origin juejin.im/post/7103423600660578341
run