当前位置: 首页 > news >正文

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

在这里插入图片描述

这是一个没有套路的前端博主,热衷各种前端向的骚操作,经常想到哪就写到哪,如果有感兴趣的技术和前端效果可以留言~博主看到后会去代替大家踩坑的~接下来的几篇都是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传入的敌机组件
在这里插入图片描述

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

<!-- 敌机 -->
<Enemy
  v-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

相关文章:

  • HTML生日快乐代码 html生日快乐网站制作 html烟花表白网站制作
  • 环境卫生学重点笔记
  • 改进搜索机制的单纯形法引导麻雀搜索算法-附代码
  • 带你吃透Servlet核心编程下篇(完整图文教程)
  • 化工原理 --- 流体流动3
  • 07. 使用子表达式
  • Kubernetes_14_静态Pod网关apiserver底层都是restful接口
  • 信息学奥赛一本通:1091:求阶乘的和
  • less和sass的区别[简洁易懂]
  • 爆肝一周——PYTHON 算法基础
  • (附源码)计算机毕业设计高校学生选课系统
  • 【Neo4j】第 11 章 :在您的 Web 应用程序中使用 Neo4j
  • Deformable Convolution 可变形卷积
  • Go-Excelize API源码阅读(二十七)——SetRowOutlineLevel、SetColOutlineLevel
  • 详细介绍LinkedList
  • [ JavaScript ] 数据结构与算法 —— 链表
  • 【剑指offer】让抽象问题具体化
  • CEF与代理
  • IP路由与转发
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • Otto开发初探——微服务依赖管理新利器
  • Spark RDD学习: aggregate函数
  • SpiderData 2019年2月25日 DApp数据排行榜
  • SQLServer之索引简介
  • WebSocket使用
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 简单基于spring的redis配置(单机和集群模式)
  • Spring第一个helloWorld
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • # include “ “ 和 # include < >两者的区别
  • #、%和$符号在OGNL表达式中经常出现
  • #define与typedef区别
  • %3cli%3e连接html页面,html+canvas实现屏幕截取
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)甲方乙方——赵民谈找工作
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • *_zh_CN.properties 国际化资源文件 struts 防乱码等
  • ./configure,make,make install的作用
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .Net mvc总结
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .NET 材料检测系统崩溃分析
  • /etc/fstab和/etc/mtab的区别
  • @Conditional注解详解
  • [ web基础篇 ] Burp Suite 爆破 Basic 认证密码
  • [] 与 [[]], -gt 与 > 的比较
  • [20170728]oracle保留字.txt
  • [Angular] 笔记 21:@ViewChild
  • [BeginCTF]真龙之力
  • [BZOJ5125]小Q的书架(决策单调性+分治DP+树状数组)
  • [c#基础]DataTable的Select方法