这是一个没有套路的前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言~博主看到后会去代替大家踩坑的~接下来的几篇都是uni-app的小实战,有助于我们更好的去学习uni-app~
主页: oliver尹的主页
格言: 跌倒了爬起来就好~
准备篇:https://oliver.blog.csdn.net/article/details/127185461
启动页实现:https://oliver.blog.csdn.net/article/details/127217681

《uni-app》一个非canvas的飞机对战小游戏实现-敌机模型实现

  • 一. 前言
  • 二. 阅读对象与难度
  • 三. 项目地址以及最终效果
  • 四. 敌机模型的实现
    • 4.1 分析分析
    • 4.2 敌机样式
    • 4.3 敌机随机类型的实现
    • 4.4 敌机生成的实现
    • 4.5 敌机坐标的实现
    • 4.6 敌机位移的实现
  • 五. 小结

一. 前言

上一篇中主要实现的是游戏的启动页,启动页作是整个游戏的第一个界面,界面本身并不复杂,一共才4个元素,相对复杂一点的也就是小飞机的穿梭动画,其核心实现也不过就是一个 animation,但是,这个界面确是相当重要,只有当点击“开始游戏”按钮之后,我们才会开始正式游戏~

本文主要分享的内容为 敌机模型 的实现,耐心看完,或许你会所有收获~

二. 阅读对象与难度

本文难度属于:中级,本文中 主要实现的敌机模型相关的操作,包括坐标位置初始化,敌机类型初始化,敌机位移等等,通过文本你可以大致了解到一下内容

  • Vue中的基础知识,包括v-for等常规用法;
  • requestAnimationFrame以及其使用方式;
  • JavaScript相关的一些知识;

具体内容可以参考以下的思维导图:

三. 项目地址以及最终效果

文本代码已上传CSDN上的gitCode,有兴趣的小伙伴可以直接clone,项目地址:https://gitcode.net/zy21131437/planegameuni
如果有小伙伴愿意点个星,那就非常感谢了~最终效果图如下:

四. 敌机模型的实现

4.1 分析分析

根据上面的效果图,我们先分析一下要实现的功能:

  1. 通过效果图确认,敌机的类型一共有两种,小飞机大飞机,需要分别实现样式;
  2. 敌机的随机坐标生成,点击“开始游戏”的时候,敌机出现的x轴坐标是随机生成的,因此不会一成不变的在某个位置上出现~
  3. 敌机在Y轴上实现的位移;

大致上这三个功能在本文这个阶段是最主要的,再来估计一下如果要实现这几个功能可能要用到什么实现逻辑
第一个,敌机样式
敌机样式,这一块其实包含两部分:

  • 第一部分,大小飞机的UI,不管是大敌机还是小敌机,其实就是DOM元素,加上背景图,这一点应该是毋庸置疑的;
  • 第二部分,爆炸动画,当敌机(包括大飞机和小飞机)被摧毁时,会有一个爆炸的效果动画,既然是动画那肯定是animation;

第二个,随机坐标
首先说一下坐标,坐标肯定是有x和y的,从效果上看y轴上的坐标是在UI之外,这个是固定的,x轴是随机的,既然是随机的,那估计是一个 随机数*x轴的长度,生成了一个值,然后敌机在这个坐标上创建一个敌机DOM;

第三个,位移
重头戏来了,如果不知道实现,可能会觉得这个非常复杂,实际上很简单的,但是这个方式可能比较冷门,叫requestAnimationFrame,具体我们后面再看;

4.2 敌机样式

先来看看敌机,我们的敌机一共有两种:小飞机大飞机,他们的素材图如下:
小飞机

大飞机

可能看到这会有一点点疑惑,不对啊,怎么是这种图片,我们其实可以这么理解,先看一个示意图

正常情况下,默认显示最左边的敌机,也就是 正常状态下的敌机,当这个敌机模型接收到被摧毁的信号时,触发爆炸动画,此时的敌机模型只需要 通过animation将右侧隐藏的图片分步显示出来,形成一个动画,具体完整代码如下:

<template><view :class="getEnemyClass" ></view>
</template><script>
export default {props: {data: {type: Object,default: () => {return {};}},},computed: {getEnemyClass() {const classStyle = [`enemy_${this.data.type}`];const explosion = {};explosion[`enemy_${this.data.type}_effect`] = this.data.isExplosion;classStyle.push(explosion);return classStyle;}},
};
</script>
<style scoped lang="scss">
.enemy_1 {width: 59px;height: 36px;position: fixed;z-index: 1;background: url(@/static/images/enemy1.png) no-repeat left top;
}.enemy_1_effect {animation: enemy_1_animate 0.5s steps(5) both infinite;-webkit-animation: enemy_1_animate 0.5s steps(5) both infinite;
}
/* 敌机1-爆炸效果 */
@keyframes enemy_1_animate {0% {background-position: 0 0;}100% {background-position: -295px 0;}
}@-webkit-keyframes enemy_1_animate {0% {background-position: 0 0;}100% {background-position: -295px 0;}
}
/* 敌机2 */
.enemy_2 {width: 70px;height: 92px;position: fixed;z-index: 1;background: url(@/static/images/enemy2.png) no-repeat left top;
}.enemy_2_effect {animation: enemy_2_animate 0.5s steps(6) both infinite;-webkit-animation: enemy_2_animate 0.5s steps(6) both infinite;
}/* 敌机2-爆炸效果 */
@keyframes enemy_2_animate {0% {background-position: 0 0;}100% {background-position: -420px 0;}
}@-webkit-keyframes enemy_2_animate {0% {background-position: 0 0;}100% {background-position: -420px 0;}
}
</style>

完整代码差不多就是如上,我们分布看一下,首先是template

<template><view :class="getEnemyClass" ></view>
</template>

template部分非常简洁,通过一个名为 getEnemyClass的计算属性 获取完整的类名,接着这个计算属性

getEnemyClass() {const classStyle = [`enemy_${this.data.type}`];const explosion = {};explosion[`enemy_${this.data.type}_effect`] = this.data.isExplosion;classStyle.push(explosion);return classStyle;
}

通这个属性中可以看到,最终返回出去的是一个数组,数组一共有两项,第一项是一个字符串,第二项是一个对象,举个例子吧,假设this.data.type的值是1,那么这个计算属性最终返回的结果是这个

["enemy_1",{"enemy_1_effect":this.data.isExplosion}]

我们放到css中去看一下,这个两个对应的样式

.enemy_1 {width: 59px;height: 36px;position: fixed;z-index: 1;background: url(@/static/images/enemy1.png) no-repeat left top;
}

标准DOM样式,宽,高,背景图等等,另外一个

.enemy_1_effect {animation: enemy_1_animate 0.5s steps(5) both infinite;-webkit-animation: enemy_1_animate 0.5s steps(5) both infinite;
}
/* 敌机1-爆炸效果 */
@keyframes enemy_1_animate {0% {background-position: 0 0;}100% {background-position: -295px 0;}
}@-webkit-keyframes enemy_1_animate {0% {background-position: 0 0;}100% {background-position: -295px 0;}
}

这是一个动画,关于animation的用法具体可以参考上一篇,里面有详细的用法解释,简单的说,就是当 this.data.isExplosion 的值为true的时候,enemy_1_effect这个类名将会被添加到这个DOM上,当类名被添加的同时会立即执行名为enemy_1_animate的动画,该动画会在0.5秒内分成5步显示完,动画改变的背景图的坐标,将x轴从0变到了-295px,实现了动画效果;
敌机类型一共有两种,其实原理一样,相同的代码实现后最终呈现的效果图如下:

4.3 敌机随机类型的实现

既然游戏存在多种类型的敌机,那么游戏开始后,每一次敌机的生成其类型都应该是具有随机性的,因此需要有一个随机数的生成;
以本游戏为例,我们一共有两种敌机, 那么随机数的生成函数

/*** 敌机类型*/
enemyType() {let random = Math.round(Math.random() * 10);return random < 5 ? 1 : 2;
},

通过一个简单的随机数生成器或者一个 1 或者 2,目的是为了拼接字符串,比如当值为1的时候,最终拼接会拼接成

["enemy_1",{"enemy_1_effect":this.data.isExplosion}]

如果是2,最终会拼接成

["enemy_2",{"enemy_2_effect":this.data.isExplosion}]

那么到这里,我们基本可以这些写,上面的敌机模型是单独一个文件,接着通过 props 传入了敌机的类型,这个类型最终会通过计算属性getEnemyClass被拼接到dom上

4.4 敌机生成的实现

通过上面两个小节,到这里其实我们已经可以有点眉目了,大致的流程是,敌机的文件是一个单独的.vue文件,里面有敌机模型的所有参数功能,当参数创建成功后存入敌机数组,通过v-for将敌机数组遍历,其它的类似于敌机类型等参数的则是通过props传入的敌机组件

因此,在父级,我们可以这么写

<!-- 敌机 -->
<Enemyv-for="(enemy, index) in enemyData":data="enemy":key="enemy.id"
/><script>
import Enemy from '../view/enemy/enemy.vue';
export default {data() {return {enemyData: [],};},components: { Enemy },methods: {/*** 敌机类型*/enemyType() {let random = Math.round(Math.random() * 10);return random < 5 ? 1 : 2;},initEnemy() {// 创建飞机参数const createEnemyParam = () => {// 随机生成敌机类型const enemyType = this.enemyType();return {type: enemyType,id: `enemy` + new Date().getTime(),isExplosion: false};};// 创建敌机const createEnemy = () => {const param = createEnemyParam();this.enemyData.push(param);};createEnemy();this.enemyTimer = setInterval(createEnemy, 1500);},}
};
</script>

子组件,也就是敌机组件则是如下,通过props将父组件中的敌机配置参数传入子组件

<script>
export default {props: {data: {type: Object,default: () => {return {};}},},
};
</script>

4.5 敌机坐标的实现

在分析中我们有说到,敌机每次生成的坐标在y轴上都是在在屏幕外,而在x轴上则是根据屏幕宽度随机生成,大致位置如下:

因此,在y轴上我们可以将坐标固定在-敌机尺寸,比如敌机的DOM元素高度为36px,那么设定敌机在y轴上距离顶部的距离为-36px,即可把敌机在y轴彻底隐藏;
至于x轴,先看一段代码:

const x = (this.config.winWdith - (enemyType === 1 ? 59 : 70)) * Math.random();

什么意思呢,简单的说,就是屏幕宽度 - 敌机宽度之后,乘以一个0~1之间的随机数,即可获得一个大于0,小于屏幕宽度的数值,这个数值即是在x轴上的坐标,因此父组件在创建敌机参数的时候完整代码如下:

initEnemy() {// 创建飞机参数const createEnemyParam = () => {const enemyType = this.enemyType();const x = (this.config.winWdith - (enemyType === 1 ? 59 : 70)) * Math.random();return {type: enemyType,x,y: enemyType == 1 ? -36 : -90,id: `enemy` + new Date().getTime(),isExplosion: false};};// 创建敌机const createEnemy = () => {const param = createEnemyParam();this.enemyData.push(param);};createEnemy();this.enemyTimer = setInterval(createEnemy, 1500);
},

子组件接收

<template><view :class="getEnemyClass" :style="{ left: data.x + 'px', top: data.y + 'px' }"></view>
</template>

在子组件中直接设置left属性以及top属性来控制敌机的坐标方位;

4.6 敌机位移的实现

这里先简单说一下,位移的功能是通过requestAnimationFrame这个方法实现的,使用也非常简单,这是一个JavaScript原生的方法,具体是什么我们单独出一篇进行详细解释
位移的实现代码还是简单的,具体如下:

<script>
export default {props: {data: {type: Object,default: () => {return {};}},},data() {return {moveTimer: null};},methods: {move() {if (this.data.y < 300) {//敌机的加速度let speed = this.data.type === 1 ? 0 : 0.5;this.data.y += this.enemyY + speed;} else {this.remove();}},init() {this.moveTimer = () => {//敌机移动this.move();// 重绘,无限循环requestAnimationFrame(this.moveTimer);};this.moveTimer();},},
};
</script>

五. 小结

本文主要概述了敌机模型的实现,主要包含:

  • 敌机样式:其实就是设定好不同的DOM,加入背景图,预设好爆炸的CSS动画;
  • 敌机类型:当写好样式后,通过随机数生成一个样式的标志,拼接加入样式,生成对应的敌机;
  • 敌机生存:创建敌机配置参数,加入敌机的缓存数组,通过v-for指令循环生成敌机;
  • 敌机坐标:y坐标上在屏幕的上方,x坐标通过随机数生成;
  • 敌机位移:位移通过requestAnimationFrame实现;

其实从代码量来看,它的实现在这个阶段其实并不复杂,剩下的一个核心功能就是碰撞检测以及触发碰撞信号时进行敌机的移除,当然,除此之外还有一种情况敌机没有被摧毁,只是运动超出了屏幕,这种也需要将敌机移除,关于移除的功能,我们放在碰撞检测中分析;

已经看到这里了,请点个赞吧,谢谢~~~ 下一篇我们将来重点研究一下 requestAnimationFrame

《uni-app》一个非canvas的飞机对战小游戏实现-敌机模型实现相关推荐

  1. 《uni-app》一个非canvas的飞机对战小游戏实现-碰撞检测的实现

    这是一个没有套路的前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言-博主看到后会去代替大家踩坑的-接下来的几篇都是uni-app的小实战,有助于我们更好的去学 ...

  2. 《uni-app》一个非canvas的飞机对战小游戏实现(一)准备

    这是一个没有套路的前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言-博主看到后会去代替大家踩坑的-接下来的几篇都是uni-app的小实战,有助于我们更好的去学 ...

  3. 《uni-app》一个非canvas的飞机对战小游戏-启动页

    这是一个没有套路的前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言-博主看到后会去代替大家踩坑的-接下来的几篇都是uni-app的小实战,有助于我们更好的去学 ...

  4. 《uni-app》一个非canvas的飞机对战小游戏实现-我方飞机实现

    这是一个没有套路的前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言-博主看到后会去代替大家踩坑的-接下来的几篇都是uni-app的小实战,有助于我们更好的去学 ...

  5. 《uni-app》一个非canvas的飞机对战小游戏实现-requestAnimationFrame详解

    这是一个没有套路的前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言-博主看到后会去代替大家踩坑的-接下来的几篇都是uni-app的小实战,有助于我们更好的去学 ...

  6. 怎么开发联机小游戏_Q飞机游戏:空战吃鸡大乱斗游戏!好玩的联机Q飞机对战小游戏...

    20000+游戏爱好者已加入我们! 带你发现好游戏! <Q飞机>游戏小程序好玩吗? <Q飞机>小游戏怎么玩? 只有你想不到, 没有我找不到的好游戏! 「良心好游戏推荐」 搜罗了 ...

  7. 基于LabVIEW的飞机大作战小游戏(可做毕设)

    一.前言 Python是目前相当流行的一门编程语言,网上有人用Python做了一个<飞机大作战>的小游戏,并且出了一份视频教程,很有意思."基于Python的飞机大作战小游戏&q ...

  8. c语言小程序飞机大战,飞机大战微信小游戏:经典像素飞机大战小程序,点开即玩...

    50000+游戏爱好者已加入我们! 每天推荐好玩游戏! 关注我们,沐沐带你发现好游戏! <经典像素飞机大战>游戏小程序好玩吗? <经典像素飞机大战>小游戏怎么玩? 怎么进入&l ...

  9. HTML5 canvas 实现回合制战棋游戏(1):加载和绘制图形

    HTML5 canvas 实现回合制战棋游戏(1):加载和绘制图形 游戏介绍 完整代码 代码目录 游戏运行 HTML5 canvas 绘制图形 canvas 介绍 绘制函数 加载图片 生物行走动画绘制 ...

最新文章

  1. AI一分钟|阿里被曝拟参与旷视新一轮6亿美元融资;MIT设计自动力机器人,与人类卵细胞大小无异...
  2. Python应用与实践【转】
  3. OSChina 周二乱弹 —— 最要命的编程语言?
  4. 【Jmeter】 Report Dashboard 生成html图形测试报告
  5. 香帅的北大金融学课笔记12 -- 资产估值
  6. nginx解析php文件设置_nginx 解析php conf配置文件
  7. [nginx报错]---unknown directive chunkin in /XXXXX/XXXXXX:XX的几种解决方式
  8. flutter引入高德地图_Flutter笔记-调用原生IOS高德地图sdk
  9. 服务器对象错误quot;ASP 0177:80040154quot;Server.CreateObject
  10. Docker CE for CentOS的安装(对官方文档的翻译)
  11. 简述关系型数据库和非关系型数据库
  12. 把数据对象转成字符串_Android Json数据的转换
  13. excel比对两列数据
  14. 使用HTML实现用户登录界面
  15. bat批处理之for循环
  16. SIP协议-01 简介与入门
  17. wps重复上一步快捷键_word回到上一步快捷键是什么
  18. Word入门教程之插入文字批注(转)
  19. 计算机专业进银行和国家电网,银行春招与国家电网那个好?
  20. 第十四篇,STM32的CAN总线通信

热门文章

  1. CPU卡设计实例及程序设计(二十九)卡82H外部认证,ESAM04H,05H,06H外部认证
  2. 自定义view实现阻尼效果的加载动画
  3. 如何处理类别型特征?
  4. Python 学习书籍推荐
  5. A模块——iptables练习
  6. matlab的2维矩阵和3维矩阵维度变换,二维矩阵变换三维矩阵,2D矩阵转换3D矩阵
  7. 对SQL慢查询的优化(MySQL)
  8. 领结婚证了,从此告别单身
  9. 知识图谱关系抽取 python_中文知识图谱-基于规则的关系抽取
  10. Unity使用ShaderGraph,设置SRP的RenderPipelineAsset后,原有材质显示紫色的问题