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


    在这里插入图片描述

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

    一. 前言

    上一篇中主要实现的是敌机模型的实现,如果我们可以将敌机模型视作为一个JavaScript的类,它这个类上包含了,创建,坐标生成,位移,碰撞检测等等方法,这些方法完整的构成了一个敌机模型,上一篇别的我们都介绍了,唯独位移这个没有细说,那么本篇主要介绍的就是位移的实现以及其实现方法:requestAnimationFrame;

    耐心看完,或许你会所有收获~

    二. 阅读对象与难度

    本文难度属于:中级,主要分享的内容为 模型位移的实现,其实不仅仅是敌机模型,我方控制的飞机,子弹,都是需要位移,通过文本你可以大致了解到一下内容

    • requestAnimationFrame以及其使用方式;
    • 敌机位移的实现;

    具体内容可以参考以下的思维导图:
    在这里插入图片描述

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

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

    四. requestAnimationFrame

    4.1 介绍

    首先来说说什么是 requestAnimationFrame?
    Mozilla官方解释:window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行;这段话怎么理解呢?
    首先,我们可以明确:requestAnimationFrame是JavaScript上的原生方法,虽然用的时候可以这么用,如下

    requestAnimationFrame();
    
    // 实际上的全写
    window.requestAnimationFrame()
    
    • 1
    • 2
    • 3
    • 4

    它是挂载在window对象上的,因此,在没有window对象的环境下,该方法不可使用;

    其次,原文说到:你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画,这就是说requestAnimationFrame用于执行动画的,它有点类似于使用 setTimeoutsetInterval,通过无限循环执行实现动画;

    最后,原文说到:该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
    意思是它接受一个函数作为参数,这个函数用于执行动画的,比如在函数中修改y轴坐标,那么无限循环下DOM在Y轴上的坐标将不断变化,从而实现了一个动画~

    到这里,我们大致知道了 requestAnimationFrame 这是一个啥,简单的说,就是 渲染动画的函数,它接收一个具体执行DOM变化的函数作为参数,通过无限循环执行这个函数达到了动画的效果~

    4.2 异步还是同步?

    看一个函数首先就是 异步还是同步的问题,requestAnimationFrame 是异步还是同步?做一个实验

    setTimeout(() => {
        console.log('--- setTimeout 1 ---');
    });
    
    window.requestAnimationFrame(() => {
        console.log('--- requestAnimationFrame 2 ---');
    });
    
    async function testAsync() {
        console.log('--- testAsync 3 ---');
        await testAsync2();
        console.log('--- testAsync 4 ---');
    }
    
    async function testAsync2() {
        console.log('--- testAsync 5 ---');
    }
    
    testAsync();
    
    new Promise((resolve) => {
        console.log('--- promise 6 ---');
        resolve();
        console.log('--- promise 7 ---');
    }).then(() => {
        console.log('--- promise 8 ---');
    });
    
    console.log('--- promise 9 ---');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    结果如下:
    在这里插入图片描述

    结果很明显,requestAnimationFrame它是一个异步函数

    4.3 JS实现动画

    在具体聊 requestAnimationFrame 之前,我们先思考一下,假如现在有一个面试题,或者是有一个需求,要求是是使用JS实现一个动画,该怎么做?
    正常情况下,我们第一时间可能会想到 setTimeoutsetInterval,假设动画的帧率是60帧,那么我只需要这么写就行

    setInterval(()=>{
    	// 具体执行的代码
    }, 1000 / 60)
    
    • 1
    • 2
    • 3

    通过setInterval实现了一个循环动画,为什么参数是 1000 / 60,那就说到另外一个原理了,动画的本质其实就是将一张张切片连接起来,当这个连接或者说切换的速度够快时,人眼将无法区分是单独的每一张,从而形成一个不卡顿的连续动画,看起来像是活了一样,动起来了;拿这边举例,当1000毫秒也就是1秒内,连续移动超过60次,那么自然而然人眼看起来就是一副完整的动画;
    因此,如果使用setInterval实现了一个简易动画,我们可以这么写

    <template>
        <div class="demo" :style="{ left: x + 'px', top: y + 'px' }"> div>
    template>
    
    <script lang="ts" setup>
        import { ref } from 'vue';
    
        const x = ref(0);
        const y = ref(40);
    
        setInterval(() => {
            x.value = x.value + 1;
        }, 1000 / 60);
    script>
    
    <style scoped lang="less">
        .demo {
            position: absolute;
    
            width: 100px;
            height: 100px;
            background-color: red;
        }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    其效果如下:
    在这里插入图片描述

    既然setInterval已经可以实现动画,那么requestAnimationFrame 的意义是什么呢?不着急,继续往下试验;

    4.4 requestAnimationFrame 实现动画

    依然是上方那个动画,如果使用requestAnimationFrame怎么实现呢,大致如下:

    <template>
        <div class="demo" :style="{ left: x + 'px', top: y + 'px' }"> div>
    template>
    
    <script lang="ts" setup>
        import { ref } from 'vue';
    
        // setInterval(() => {
        //     x.value = x.value + 1;
        //     console.log(x.value);
        //     // y.value = y.value + 1;
        // }, 1000 / 60);
    
        const move = () => {
            x.value = x.value + 1;
    
            window.requestAnimationFrame(move);
        };
    
        move();
    script>
    
    <style scoped lang="less">
        .demo {
            position: absolute;
    
            width: 100px;
            height: 100px;
            background-color: red;
        }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    对应效果图如下:
    在这里插入图片描述

    从效果图上看两者感觉差不多,从用法上看,requestAnimationFrame 的用法貌似更接近setTimeout,而不是setInterval,当然,用法还是既然已经有了setTimeout 和 setInterval了,为什么还要requestAnimationFrame?那当然是为了解决一些瓶颈的问题;

    4.5 requestAnimationFrame优点

    第一个优点:执行函数的时间更加精准

    我们知道 setTimeout 和 setInterval 都是借助于浏览器的定时器线程执行的,因此,当界面上存在大量异步的时候,会堵塞异步代码的执行,在这种情况下setTimeout 和 setInterval 将变得不准时,反映到界面上的效果就是动画执行的过程中会有卡顿,动画整体不再流畅;这是一个硬伤,如果出现的频繁,那么用户体验将变得极差,不可接受;

    那requestAnimationFrame 呢?上面我们测试过了,requestAnimationFrame 也是一个异步函数,它会不会也有这种问题,实际上不会,最大的原因是,它执行的时机造成的,requestAnimationFrame 的执行时机是由系统决定的,每一次页面进行刷新时都会执行一次requestAnimationFrame 函数,举个例子吧,可能容易明白点;
    比如:我们的电脑刷新率是60Hz,那么这就代表1秒内刷新了60次,刷新的间隔为 1000 / 60 毫秒,每一次进行刷新的时候,浏览器会主动去执行一次 requestAnimationFrame 函数,这将大大的提升精准度,那如果电脑的刷新率是75Hz呢,那刷新间隔是 1000 / 75;
    所以,在精准度上,requestAnimationFrame 的效果远好于 setTimeout 和 setInterval,因为它的执行间隔不由代码决定,而由系统决定,适用性更强;

    第二个优点:性能更好

    这点体现在执行时页面的重绘与回流,我们知道重绘与回流是影响DOM性能最大的因素之一,当使用 setTimeout 和setInterval 等时,就是正常的重绘与回流,但requestAnimationFrame不同,它会把每一帧中需要执行的操作或者说动画步骤都给收集起来,在一次重绘或回流中就完成全部的操作;
    除此之外,当网页处于hidden状态时,requestAnimationFrame会被冻结,不再执行,这也会节省电脑的CPU和GPU资源;

    五. 敌机模型位移的实现

    到这里,我们再回看一下上一篇中关于敌机位移的实现,代码如下:

    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    一共两个方法:init 和 move ,我们分别看一下
    init函数
    代码不复杂,通过requestAnimationFrame实现了一个无限循环动画,该动画主要实现的位移逻辑是由this.move()实现的;

    move函数
    首先是一个if判断,判断当前在y轴上的坐标值是否小于300,如果小于300修改DOM在y轴上的坐标,修改值也可以称作与加速度,其值取决于敌机的类型,敌机的类型如果是小飞机,那么每次加速度是 默认速度+0.5,大飞机则是 默认速度,这也就实现了不同的敌机类型飞行速度不一致的效果;
    那么这个300是什么意思?这个300在这里只是一个演示值,正常情况下应该是 屏幕的高度,作用是:当飞机在屏幕的y坐标大于屏幕时,就应该将超出屏幕的飞机移除,毕竟我们不可能让飞机永远存在,当其超出屏幕的情况下,我方飞机依然不可能击毁它,因此我们需要及时销毁;
    故以上代码最终能实现的效果图就是如下:
    在这里插入图片描述

    六. 小结

    本文主要概述了requestAnimationFrame,通过本文我们知道:

    • requestAnimationFrame这事一个 原生的,帧动画函数,它可以让我们通过它来实现JS动画;
    • 相比 setTimeout 和 setInterval,不管从性能上,还是动画的流程度上requestAnimationFrame都更优,因此,如果是需要使用JS实现动画,可以选择requestAnimationFrame;
    • requestAnimationFrame是 异步函数,它执行的时机在于每次系统帧刷新前;

    最后,我们重新回看了一下敌机的位移实现,发现其实并不复杂,就是利用requestAnimationFrame不断改变敌机在y轴上的坐标从而实现了敌机的位移动画效果,当然,至少这个阶段的敌机实现还并不复杂~

  • 相关阅读:
    django 链接mysql数据库问题
    selenium中webdriver常用的ChromeOptions参数
    如何将几张图片转换为GIF动图?
    万字详解 | Java 函数式编程
    AI加速(五)| 一个例子看懂流水——从指令到算法
    详解基于栈的算法
    kafka_3.7.0(sasl+acl)+管理工具redpanda
    vue项目npm run build报错Error: Cannot find module ‘@vue/cli-plugin-babel‘的解决方法
    32 位计算机时间戳溢出的思考 —— 整数的二进制表示
    2022高考季征文获奖名单公布
  • 原文地址:https://blog.csdn.net/zy21131437/article/details/127377916