欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于Vue.js的2048小游戏的实现

程序员文章站 2022-04-07 19:13:30
...


游戏规则

  1. 游戏盘内有16个方格,初始状态是有两个值为2或4的数字格
  2. 指定向↑ ↓ ← →方向滑动,则所有方块都会向该方向滑动
  3. 相碰撞的两个数字格的值相等则会将其合并
  4. 每次滑动都会在随机空位上生成一个值为2或4的数字格
  5. 若游戏盘的16个方格均被填满且无法移动,则判定为游戏失败
  6. 若有一数字格的值为8192,则游戏通关

一、准备工作

1. 项目结构

基于Vue.js的2048小游戏的实现

2. 入口组件App.vue

这一块没什么好说的,就是给游戏提供入口,由于想照顾兼容更多情况引入了vue-router。

我用到了SCSS主要是为了让css写起来简洁一点,为了好看点,个人习惯用到了苹方字体,可以不必在意。

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<style lang="scss">
@import "./assets/font/font.css";
body {
  margin: 0;
  font-family: "PingFang-RE", "Montserrat-RE", "Microsoft YaHei";
  button,
  input {
    font-family: "PingFang-RE";
  }
}
#app {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>

3. 路由文件router -> index.js

这一块没什么好说的,主要是把组件引入进来应用到App里面的router-view

import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';

Vue.use(VueRouter);

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
];

const router = new VueRouter({
  routes,
});

export default router;

二、界面布局

1. 游戏盘背景 Background.vue

这里我用一个v-for循环把16个格子展现出来

background就是用的Grid布局,分成四行四列

<template>
  <div class="background">
    <div v-for="i of 16" :key="i" class="grid-cell"></div>
  </div>
</template>

<script>
export default {};
</script>

<style lang="scss" scoped>
.background {
  width: 365px;
  height: 365px;
  padding: 20px;
  background-color: #bbada0;
  border-radius: 10px;
  display: grid;
  grid-row-gap: 15px;
  grid-column-gap: 15px;
  grid-template-columns: repeat(4, 80px);
  grid-template-rows: repeat(4, 80px);

  .grid-cell {
    width: 80px;
    height: 80px;
    border-radius: 5px;
    background-color: #ccc0b3;
  }
}
</style>

2. 游戏界面的头部信息

基于Vue.js的2048小游戏的实现

这里也相当简单,我用一个header标签包裹

<header>
    <h1>2048</h1>
    <button @click="init" class="init-button">New Game</button>
    <p>
      Score: <span>{{ score }}</span>
    </p>
</header>
header {
  h1 {
    margin: 0;
    font-size: 32px;
  }
  p {
    margin: 0;
    margin-top: 10px;
    font-size: 16px;
    span {
      font-weight: bold;
    }
  }
}
.init-button {
  width: 110px;
  padding: 10px;
  background-color: #8f7a66;
  color: #fff;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  outline: none;
  font-size: 16px;
  font-weight: bold;
  &:hover {
    background-color: #9f8a77;
  }
}

3. 游戏界面主体的编写

用一个container包裹

mask是游戏通关或失败的遮罩

Background是背景以组件形式引入了的标签

number-cells是游戏的数字格子,transition-group是为了给这些格子加过渡动画,数字格我以数组形式存起来然后v-for逐个展示出来

<div class="container">
  <div class="mask" v-if="success">
    <h1>You win!</h1>
    <button @click="init" class="init-button">Try again</button>
  </div>
  <div class="mask" v-if="gameover">
    <h1>Game over!</h1>
    <button @click="init" class="init-button">Try again</button>
  </div>
  <Background />
  <div class="number-cells">
    <transition-group name="appear">
      <div
        class="number-cell"
        v-for="cell of numberCells"
        :id="`c${cell.id}`"
        :key="cell.id"
        :style="
          `
      width: 80px;
      height: 80px;
      border-radius: 5px;
      font-size: 32px;
      font-weight: bold;
      line-height: 80px;
      color: #776e65;

      position: absolute;
      z-index: ${cell.num};
      backgroundColor: ${cell.color};
      top: ${getTop(cell)};
      left: ${getLeft(cell)};
      `
        "
      >
        {{ cell.num }}
      </div>
    </transition-group>
  </div>
</div>

container包裹了background,然后整个容器用margin来做水平居中

mask以container为基准做绝对定位,用top…等定位来拉伸展开

number-cell(数字格子)也是以container为基准做绝对定位,top和left的数值通过格子的数据结构计算出。

因为合并时可能会存在大数值的格子需要覆盖小数值的格子,所以以数值来作为z-index

然后就是用到了vue的transition以及CSS3的transition,前者是用来在DOM生成时的一个从无到有的动画,后者是数字格的滑动

.container {
  width: 405px;
  height: 405px;
  margin: 20px auto;
  position: relative;
  .mask {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 9999;
    background: rgba(238, 228, 218, 0.5);
    text-align: center;
    h1 {
      font-size: 60px;
      font-weight: 700;
      height: 60px;
      line-height: 60px;
      margin-top: 120px;
      color: #776e65;
    }
    button {
      margin-top: 30px;
    }
  }
  .number-cells {
    .number-cell {
      transition: $transitionTime top, $transitionTime left;
      // animation-fill-mode: backwards;
      // animation: appear 200ms ease-in-out;
    }
  }
}
.appear-enter-active {
  animation: appear 100ms ease-in-out;
}
.appear-leave-active {
  transition: $transitionTime top, $transitionTime left;
}
@keyframes appear {
  0% {
    opacity: 0;
    transform: scale(0);
  }
  50% {
    opacity: 0;
    transform: scale(0.5);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}
getTop(cell) {
  return `${20 + cell.y * 95}px`;
},
getLeft(cell) {
  return `${20 + cell.x * 95}px`;
},

总结

后续将完善游戏的基本逻辑