尝试用 Vue.js 开发网页小游戏的过程

准备

首先去官方下载并安装 VSCODE,下载地址 https://code.visualstudio.com/。安装后打开会发现是英文版的,需要去安装插件来汉化。具体是在扩展插件搜索 chinese,选择第一个安装然后重启软件,这样打开就是中文界面了。

网页

去这个网站 https://getbootstrap.com/docs/4.4/examples/album/ 将源代码 Copy 下来,然后打开 VSCODE 选择项目文件夹

4193235229.png

在你的项目目录新建个 src 文件夹用来存放源代码,并在 src 下新建个 index.html 文件,将复制的代码拷贝进去

2898594965.png

如果想要实时看到页面可以在扩展插件中安装个 live server 作为本地服务器

2214346588.png

安装后在回到项目文件夹里的 index.html 文件,右键选择 Open with live server 就可以查看了,但是你会发现样式乱了这时候需要去修改一下代码,将代码第 15 行修改成下面的样子

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">

保存后再次打开网页就好了

1969623702.png

但是你会发现导航按钮好像不能使用这是因为没用正确的引用 js,还需要修改一下代码。替换 </body> 前面的三个 <script> 标签,替换代码如下:

<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>

这样保存之后导航菜单就可以使用了。接下来新建一个 img 文件夹在 src 目录,并放入这张图片

forest.jpg

再新建一个 style.css 样式文件在 src 目录,填入如下内容

#dog, #fox{
    position: absolute;
    font-size: xx-large;
}

#forest{
    background-image: url("./img/forest.jpg");
    height: 160px;
}

span.arrow-key {
    font-size: xx-large;
    cursor: pointer
}

开始

现在正式开始进入 Vue 开发,这是 Vue 的开发文档 https://cn.vuejs.org/v2/guide/。在 index.html</body> 前面添加

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="custom.js"></script>

代码中 custom.js 文件需要在项目中自己建一个,接下来替换 <div class="album py-5 bg-light">

<div class="album py-5 bg-light" id="app"> 

<div id="forest">...</div> 添加如下内容

<span id="dog" :style="{left: dog.x + 'px', top: dog.y + 'px'}">🐶</span>

顺便把这里修改下

<small class="text-muted">抓捕时间:{{ dog.抓捕时间 }}</small>

文件 custom.js 内容如下

var app = new Vue({
    el: '#app',
    data: {
        forest: {
            width: $('#forest')[0].offsetWidth,
            height: $('#forest')[0].offsetHeight
        },
        dog: {
            width: $('#dog')[0].offsetWidth,
            height: $('#dog')[0].offsetHeight,
            x: 0,
            y: 0,
            step: {
                x: 10,
                y: 2
            },
            抓捕时间: 0
        }
    },
    methods: {
        move_dog(){
            this.dog.x += this.dog.step.x
            this.dog.y += this.dog.step.y
            this.dog.抓捕时间 = Math.round((this.dog.抓捕时间 + 0.1) * 10) / 10
        }
    },
    mounted: function(){
        this.timer1 = setInterval(this.move_dog, 100)
    },
    watch: {
        'dog.x': function(){
            let x = this.dog.x
            let w = this.forest.width
            if(x <= 0 || x >= (w - this.dog.width)){
                this.dog.step.x = - this.dog.step.x
            }
        },
        'dog.y': function(){
            let y = this.dog.y
            let h = this.forest.height
            if(y <= 0 || y >= (h - this.dog.height)){
                this.dog.step.y = - this.dog.step.y
            }
        }
    }
})

保存后预览就会发现狗狗动起来了。接下来实现按键的功能,将操作说明的内容改为

<p class="card-text"> 操作说明: 点击
<span class="arrow-key" @click="change_dog_direct(37)"></span>
<span class="arrow-key" @click="change_dog_direct(39)"></span>
<span class="arrow-key" @click="change_dog_direct(38)"></span>
<span class="arrow-key" @click="change_dog_direct(40)"></span> 控制狗狗方向 </p> 

然后将 js 的内容改成

var app = new Vue({
    ...
    methods: {
        ...
        change_dog_direct(k) {
            if (k === 37 && this.dog.step.x > 0 || k === 39 && this.dog.step.x < 0) {
                this.dog.step.x = -this.dog.step.x
            }
            if (k === 38 && this.dog.step.y > 0 || k === 40 && this.dog.step.y < 0) {
                this.dog.step.y = -this.dog.step.y
            }
        },
        change_dog_direct_keyboard(event) {
            this.change_dog_direct(event.keyCode)
        }
    },
    mounted: function(){
        ...
        window.addEventListener('keyup', this.change_dog_direct_keyboard)
    },
    ...
})

保存后就会发现可以通过键盘或者点击图标来控制狗狗移动了。接下来要在森林中添加狐狸,在狗狗图标下添加

<span id="fox" :class="fox.class" :style="{ left: fox.x + 'px', top: fox.y + 'px'}">🦊</span>

并且添加动画样式文件

<link href="https://cdn.bootcss.com/animate.css/3.7.2/animate.css" rel="stylesheet">

js 文件定义狐狸对象,并且让狐狸随机移动且“得瑟起来”

var app = new Vue({
    ...
    data: {
        ...
        fox: {
            width: $('#fox')[0].offsetWidth,
            height: $('#fox')[0].offsetHeight,
            x: 0,
            y: 100,
            class: ''
        },
    },
    methods: {
        ...
        random_fox() {
            this.fox.x = Math.ceil(Math.random() * this.forest.width / 2)
            this.fox.y = Math.ceil(Math.random() * (this.forest.height - this.fox.height))
            this.fox.class = this.fox.class ? '': 'animated jello'
        },
    },
    mounted: function(){
        this.timer1 = setInterval(this.move_dog, 100)
        this.timer2 = setInterval(this.random_fox, 1000)
        window.addEventListener('keyup', this.change_dog_direct_keyboard)
    },
    ...
})

可以看到动画其效果了。那么继续接下来就是如何抓捕狐狸,我们需要在 js 文件中添加功能

var app = new Vue({
    ...
    data: {
        ...
        dog: {
            ...
            与狐狸的距离: 999,
            抓捕成功: false,
            class: ''
        },
        ...
    },
    methods: {
        move_dog(){
            if (this.dog.抓捕成功) return
            ...
            this.dog.与狐狸的距离 = getDistanceBetweenTwoPoints(
                this.dog.x, 
                this.dog.y,
                this.fox.x,
                this.fox.y
            )
        },
        ...
        bounce_dog() {
            if (this.dog.抓捕成功) {
                this.dog.class = this.dog.class ? '' : 'animated bounce'
            }
        },
        ...
        random_fox() {
            if (this.dog.抓捕成功) return
            ...
        },
    },
    mounted: function(){
        this.timer1 = setInterval(this.move_dog, 100)
        this.timer2 = setInterval(this.random_fox, 1000)
        this.timer3 = setInterval(this.bounce_dog, 2000);
        window.addEventListener('keyup', this.change_dog_direct_keyboard)
    },
    watch: {
        ...
        'dog.与狐狸的距离': function(val) {
            let min_d = this.dog.width / 2 + this.fox.width / 2
            if(val < min_d){
                this.dog.抓捕成功 = true
            }
        },
        'dog.抓捕成功': function(val) {
            if (val) {
                this.fox.class = 'animated rotateOut delay-2s'
            } else {
                this.fox.class = ''
                this.dog.class = ''
            }
        }
    }
})

//计算平面中两点距离
function getDistanceBetweenTwoPoints(x1, y1, x2, y2) {
    var a = x1 - x2;
    var b = y1 - y2;

    // c^2 = a^2 + b^2
    // a^2 = Math.pow(a, 2)
    // b^2 = Math.pow(b, 2)
    var result = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));

    return Math.round(result);
}

保存刷新一下界面,可以看到当狗狗碰到狐狸的时候会被捕获成功监听器捕获到。接下来开发再来一次功能,把 html 代码中的再来一次按钮修改成

<button type="button" :class="btn_play_again.class" @click="play_again" class="btn btn-sm btn-outline-secondary">
	<span v-if=" !dog.抓捕成功 ">正在抓捕 。。。</span>
	<span v-if=" dog.抓捕成功 "> 抓捕成功!再来一次</span>
</button> 

然后是 js 文件需要为再来一次添加功能

var app = new Vue({
   ...
    data: {
        ...
        btn_play_again: {
            class: ''
        }
    },
    methods: {
        ...
        bounce_dog() {
            if (this.dog.抓捕成功) {
                this.dog.class = this.dog.class ? '' : 'animated bounce'
                this.btn_play_again.class = this.btn_play_again.class ? '' : 'animated swing delay-1s'
            }
        },
		...
        play_again() {
            if (!this.dog.抓捕成功) return

            this.dog.抓捕成功 = false
            this.dog.抓捕时间 = 0
            this.fox.x = 0
            this.fox.y = 100
        }
    },
    ...
})

...

然后开发记录抓捕时间功能,下面我直接贴出完整的 js 代码和 html 代码

index.html

<!DOCTYPE html>
<html lang="en">
 <head> 
  <meta charset="utf-8" /> 
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> 
  <meta name="description" content="" /> 
  <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors" /> 
  <meta name="generator" content="Jekyll v3.8.6" /> 
  <title>🐶 Dog & 🦊 Fox - 美图博客</title>
  <link rel="canonical" href="https://getbootstrap.com/docs/4.4/examples/album/" /> 
  <link href="https://cdn.bootcss.com/animate.css/3.7.2/animate.css" rel="stylesheet" /> 
  <!-- Bootstrap core CSS --> 
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous" /> 
  <!-- Favicons --> 
  <meta name="theme-color" content="#563d7c" /> 
  <style>
      .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
      }

      @media (min-width: 768px) {
        .bd-placeholder-img-lg {
          font-size: 3.5rem;
        }
      }
    </style> 
  <!-- Custom styles for this template --> 
  <link href="style.css" rel="stylesheet" /> 
 </head> 
 <body> 
  <header> 
   <div class="collapse bg-dark" id="navbarHeader"> 
    <div class="container"> 
     <div class="row"> 
      <div class="col-sm-8 col-md-7 py-4"> 
       <h4 class="text-white">故事</h4> 
       <p class="text-muted">通常,人们认为,狗狗是我们人类的朋友,而狐狸则相反,它代表着奸诈,狡猾,人们都想除掉它,让美丽的森林恢复宁静 ... 于是人们派出了狗狗,开始了一场铲除邪恶的战斗 ...</p> 
      </div> 
     </div> 
    </div> 
   </div> 
   <div class="navbar navbar-dark bg-dark shadow-sm"> 
    <div class="container d-flex justify-content-between"> 
     <a href="#" class="navbar-brand d-flex align-items-center"> 
      <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" aria-hidden="true" class="mr-2" viewbox="0 0 24 24" focusable="false"> 
       <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" /> 
       <circle cx="12" cy="13" r="4" /> 
      </svg> <strong>🐶 Dog & 🦊 Fox</strong> </a> 
     <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarHeader" aria-controls="navbarHeader" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> 
    </div> 
   </div> 
  </header> 
  <main role="main"> 
   <section class="jumbotron text-center"> 
    <div class="container"> 
     <h1>通过游戏学编程</h1> 
     <p class="lead text-muted">这是一个由Vue.js开发的游戏小案例。课程链接 https://www.bilibili.com/video/BV1V7411R7dP?p=5</p> 
     <p> <a href="https://www.meitubk.com/" class="btn btn-primary my-2">访问我的博客</a></p> 
    </div> 
   </section> 
   <div class="album py-5 bg-light" id="app"> 
    <div class="container"> 
     <div class="row"> 
      <div class="col-md-12"> 
       <div class="card mb-4 shadow-sm"> 
        <div id="forest"> 
         <span id="dog" :class="dog.class" :style="{left: dog.x + 'px', top: dog.y + 'px'}">🐶</span> 
         <span id="fox" :class="fox.class" :style="{left: fox.x + 'px', top: fox.y + 'px'}">🦊</span> 
        </div> 
        <div class="card-body"> 
         <p class="card-text"> 操作说明: 点击 <span class="arrow-key" @click="change_dog_direct(37)"></span> <span class="arrow-key" @click="change_dog_direct(39)"></span> <span class="arrow-key" @click="change_dog_direct(38)"></span> <span class="arrow-key" @click="change_dog_direct(40)"></span> 控制狗狗方向 </p> 
         <div class="d-flex justify-content-between align-items-center"> 
          <div class="btn-group"> 
           <button type="button" :class="btn_play_again.class" @click="play_again" class="btn btn-sm btn-outline-secondary"> <span v-if=" !dog.抓捕成功 ">正在抓捕 。。。</span> <span v-if=" dog.抓捕成功 "> 抓捕成功!再来一次</span> </button> 
          </div> 
          <small class="text-muted">抓捕时间:{{ dog.抓捕时间 }}</small> 
         </div> 
        </div> 
       </div> 
      </div> 
     </div> 
     <div class="row"> 
      <div class="col-md-12"> 
       <table class="table table-striped"> 
        <thead> 
         <tr> 
          <th>No.</th> 
          <th>抓捕时间(秒)</th> 
         </tr> 
        </thead> 
        <tbody> 
         <tr v-for="(seconds, index) of dog.logs"> 
          <td>{{index+1}}</td> 
          <td>{{seconds}}</td> 
         </tr> 
        </tbody> 
        <tfoot> 
         <tr> 
          <td>平均时间:</td> 
          <td>{{dog.平均抓捕时间}}</td> 
         </tr> 
        </tfoot> 
       </table> 
      </div> 
     </div> 
    </div> 
   </div> 
  </main> 
  <footer class="text-muted"> 
   <div class="container py-4"> 
    <p class="float-right"> <a href="#"></a> </p> 
    <p>祝您玩得愉快。</p> 
    <p>www.meitubk.com</p> 
   </div> 
  </footer> 
  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> 
  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> 
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> 
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> 
  <script src="custom.js"></script>  
 </body>
</html>

custom.js

var app = new Vue({
    el: '#app',
    data: {
        forest: {
            width: $('#forest')[0].offsetWidth,
            height: $('#forest')[0].offsetHeight
        },
        dog: {
            width: $('#dog')[0].offsetWidth,
            height: $('#dog')[0].offsetHeight,
            x: 0,
            y: 0,
            step: {
                x: 10,
                y: 2
            },
            抓捕时间: 0,
            与狐狸的距离: 999,
            抓捕成功: false,
            class: '',
            平均抓捕时间: 0,
            logs: []
        },
        fox: {
            width: $('#fox')[0].offsetWidth,
            height: $('#fox')[0].offsetHeight,
            x: 0,
            y: 100,
            class: ''
        },
        btn_play_again: {
            class: ''
        }
    },
    methods: {
        move_dog(){
            if (this.dog.抓捕成功) return

            this.dog.x += this.dog.step.x
            this.dog.y += this.dog.step.y
            this.dog.抓捕时间 = Math.round((this.dog.抓捕时间 + 0.1) * 10) / 10
            this.dog.与狐狸的距离 = getDistanceBetweenTwoPoints(
                this.dog.x, 
                this.dog.y,
                this.fox.x,
                this.fox.y
            )
        },
        change_dog_direct(k) {
            if (k === 37 && this.dog.step.x > 0 || k === 39 && this.dog.step.x < 0) {
                this.dog.step.x = -this.dog.step.x
            }
            if (k === 38 && this.dog.step.y > 0 || k === 40 && this.dog.step.y < 0) {
                this.dog.step.y = -this.dog.step.y
            }
        },
        change_dog_direct_keyboard(event) {
            this.change_dog_direct(event.keyCode)
        },

        bounce_dog() {
            if (this.dog.抓捕成功) {
                this.dog.class = this.dog.class ? '' : 'animated bounce'
                this.btn_play_again.class = this.btn_play_again.class ? '' : 'animated swing delay-1s'
            }
        },

        random_fox() {
            if (this.dog.抓捕成功) return

            this.fox.x = Math.ceil(Math.random() * this.forest.width / 2)
            this.fox.y = Math.ceil(Math.random() * (this.forest.height - this.fox.height))
            this.fox.class = this.fox.class ? '': 'animated jello'
        },

        play_again() {
            if (!this.dog.抓捕成功) return

            this.dog.抓捕成功 = false
            this.dog.抓捕时间 = 0
            this.fox.x = 0
            this.fox.y = 100
        }
    },
    mounted: function(){
        this.timer1 = setInterval(this.move_dog, 100)
        this.timer2 = setInterval(this.random_fox, 1000)
        this.timer3 = setInterval(this.bounce_dog, 2000);
        window.addEventListener('keyup', this.change_dog_direct_keyboard)
    },
    watch: {
        'dog.x': function(){
            let x = this.dog.x
            let w = this.forest.width
            if(x <= 0 || x >= (w - this.dog.width)){
                this.dog.step.x = - this.dog.step.x
            }
        },
        'dog.y': function(){
            let y = this.dog.y
            let h = this.forest.height
            if(y <= 0 || y >= (h - this.dog.height)){
                this.dog.step.y = - this.dog.step.y
            }
        },
        'dog.与狐狸的距离': function(val) {
            let min_d = this.dog.width / 2 + this.fox.width / 2
            if(val < min_d){
                this.dog.抓捕成功 = true
                this.dog.logs.push(this.dog.抓捕时间)
            }
        },
        'dog.抓捕成功': function(val) {
            if (val) {
                this.fox.class = 'animated rotateOut delay-2s'
            } else {
                this.fox.class = ''
                this.dog.class = ''
            }
        },
        'dog.logs': function(val) {
            let sum = 0
            for (s of val) sum += s
            this.dog.平均抓捕时间 = Math.round(sum / val.length * 100) / 100
        }
    }
})

//计算平面中两点距离
function getDistanceBetweenTwoPoints(x1, y1, x2, y2) {
    var a = x1 - x2;
    var b = y1 - y2;

    // c^2 = a^2 + b^2
    // a^2 = Math.pow(a, 2)
    // b^2 = Math.pow(b, 2)
    var result = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));

    return Math.round(result);
}

这样一来这个小游戏就开发完成了!

预览

如果想要预览该项目可以在 https://www.meitubk.com/my/demo1/ 此链接访问。

本文章是根据 https://www.bilibili.com/video/BV1V7411R7dP?p=10 教程所记录的笔记。

  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    186 引用 • 602 回帖 • 547 关注
  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    99 引用 • 720 回帖 • 26 关注
回帖
请输入回帖内容...