夏には、スイカやエアコンだけでなく、スポーツや発汗もあります。プログラマー"jsゲームは暑いです、一緒に来て実行してください

詳しくは「初夏クリエイティブコンテスト」に参加しておりますので、こちらをご覧ください:初夏クリエイティブコンテスト

オンライン体験アドレス:summer.pkec.net/ソース
アドレス:gitee.com/ihope_top/j…

序文

いつの間にか夏がまたやってくる夏になるとどう思いますか?エアコン、スイカ、アイスクリーム?しかし、夏にはこれら以上のものがあり、スポーツ、発汗、そしてあなたの体を見せることがあります。毎年夏のスポーツミーティングを覚えていますか?その夏の毎日の涼しいランニングを覚えていますか?今日はjsを使ってパルクールミニゲームをお届けします-「走れ!プログラマー」、あなたがそれを好きになることを願っています

ゲーム紹介

特殊機能

特殊機能はバックエンドインターフェースのサポートを伴うため、イベント中にのみ有効になります。書き込みイベント後に無効になる可能性がありますが、ゲームには引き続き正常にアクセスできます。悪意のある攻撃やその他の制御できない要因が発生した場合は、この機能は事前に失敗する可能性があります。

グレードのアップロード

image.png

そうです、今回はスコアのアップロードをサポートするためにフロントエンドとバックエンドを開放しました。ゲーム終了後、スコアをアップロードするオプションがあります。このスコアをアップロードするには、自分のナゲッツIDを入力するだけです。 。ナゲッツIDをアップロードする主な目的は、ユーザーが不正なニックネームをランダムに入力しないようにすることです。そのため、ここでは、ユーザーのナゲッツIDに従ってユーザーのナゲッツニックネームを取得する必要があります。

ミニゲームは娯楽専用なので、不正行為防止策はなく、誰もが不正行為をしてはいけませんが、誤操作を防ぐために、同じ結果を繰り返しアップロードできないように制限されています。つまり、500メートルを2回走りました。 、および2回目結果をアップロードできません。1回目が500メートル、2回目が100メートルの場合、両方の結果をアップロードできます。

ナゲットIDを取得する方法:

image.png

個人のホームページを入力してください。アドレスバーの後ろの数字はあなたのIDです。

すべてのスタッフが実行します

image.png

ゲーム終了後、ユーザーがアップロードしたスコアはデータベースに保存され、ホームページにはすべてのユーザーがアップロードしたスコアの合計が表示されます

リーダーボード

image.png

ホームページにはリーダーボードのエントリが表示され、リーダーボードにはすべてのユーザーの単一プロセスランキングが表示されます。ユーザーはリストに複数回参加できます。最高スコアは800メートルで、1位と2位は張さんです。

ルールの紹介

image.png

ゲーム開始後、キャラクターは自動的に前に進み、走っている絵の中で小悪魔に遭遇します。ユーザーは小悪魔を避けて前に進み続ける必要があります。小悪魔に遭遇した場合、ゲームは終了します。

操作方法:

跳跃:按 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,人物处于屏幕水平中央

したがって、判断される障害物の左側の距離は、画面の幅の半分以上、文字の幅の半分、障害物自体の幅の半分になります。

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
      }
    }
  }
复制代码

検出対象が見つかった場合、それをいつ検出するか、つまり、障害物と画面の左側の間の距離<(画面幅の半分+文字の半分)の場合、障害物がちょうど始まるときも知る必要があります。障害物と重なるようにします。障害物の上下位置(生成時に保存される)とキャラクターの状態から判断すると、衝突の有無を判断できます。

// 碰撞检测
  // 当距离最近的障碍物处于检测区时,进行碰撞检测
  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
      }
    }
  }
复制代码

衝突検出は障害物の動きで行われ、完全なコードは次のとおりです。

// 整体障碍物移动
    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)
    },
复制代码

これがミニゲームの紹介です。みんなが気に入って、自分の旗を立ててください。トップ3に入ることができるなら、自分でMacを購入してください。トップ10に入ることができるなら、自分でiPadを購入してください。 。トップ10に入ることができるなら、自分でiPadを購入してください。行かないと、何も購入しません。

おすすめ

転載: juejin.im/post/7103423600660578341