js_拳皇(上)

发布于:2024-07-30 ⋅ 阅读:(111) ⋅ 点赞:(0)

架构设计:一图胜千言

拳皇
地图
KOF
角色
键盘操作
gif

绪论

『一个人的自身越是丰富,就越难以忍受世俗常规的安排。』—— 叔本华「人生的智慧」

在这里插入图片描述

不能正常加载动图

用注释的最后一行代码不能正常加载图片

#kof {
    /* 前面是用 id 标记的 div ,所以这里选择出来 */
    width: 1280px;
    height: 720px;
    background-image: url(/static/images/background/0.gif);
    /* background-image: url('static/images/background/0.gif'); */
}

调整了一下图片的大小
在这里插入图片描述

设计的思路

需要有三个对象,背景,左边玩家,右边玩家,这三个对象都需要每秒钟刷新 60 次,实现动态效果

我们的游戏需要读取键盘输入,那么我们需要让我们的 canvas(画布) 能够聚焦

渲染画布

我们需要在每一帧结束的时候把该帧清空,这样下一帧在展示的时候才不会出现重影的现象,但是清空的时间太快了,人眼察觉不到,所以我们把画布渲染为黑色,验证我们是不是可以正确渲染和清空

在这里插入图片描述
渲染的函数如下

render() {
        //表示清空画布的内容,也就是清空一帧的内容
        //clearRect表示的是清空矩形
        //参数是左上角的坐标,矩形的宽,矩形的高
        //this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
        //console.log(this.$canvas.width());


        //把填充的颜色设置为黑色
        this.ctx.fillStyle = 'black';
        //填充矩形,参数是左上角的坐标,宽度,高度
        this.ctx.fillRect(0, 0, this.$canvas.width(), this.$canvas.height());
    }

开发感想

笔者之前做 django 项目,因为跟着教程做,出现了一些问题,没有很好的解决,现在做这个项目,因为花了很多时间,把这个里面的逻辑搞得非常清楚,所以开发的时候非常开心,因为自己可能确实能够理解这里面每一行代码的具体含义。而且敲击键盘的时候,给自己带来了一些之前没有怎么体会过的成就感

角色抽象为矩形

我们首先把底层的逻辑实现好,等逻辑实现完成之后再去考虑角色的美术优化还有一些动态的效果
在这里插入图片描述

ctx 是 canvas 的对象

笔者比较疑惑,canvas 是画布,ctx 表示的是画布的内容,这两者之间的关系是什么

canvas 和 context 的关系 画布和画笔的关系

这个讲的非常清楚,画布相当于背景,画笔相当于画布上面的图案,以这个项目为例,画布就是背景图片,画笔绘制的内容就是两个角色,因为角色要不停地在画布上面移动,表示的意思是,需要不停地在画布上绘制图片

键盘控制角色

//需要用键盘来操作这个游戏

//每一个类都需要暴露出来
export class Controller {
    //需要控制的是角色,所以需要先把画布传进来
    constructor($canvas) {
        //需要实现的是,键盘控制当前的角色,按下一个键就会产生相应的反应

        //把画布存下来
        this.$canvas = $canvas;

        //下面是自己手动实现按键盘
        //Set是一个 js 的数据结构,里面可以存任何数据类型的数据
        this.pressed_keys = new Set();

        // 下面是调用初始函数
        this.start();
    }

    //只在开始的时候执行一次
    start() {
        //outer 表示的是这个 controller 
        //我们在后面重新定义了一个函数,这个函数会有一个新的 this
        //this  指向的是类的实例
        //这里主要是因为之后要用前面定义的这个实例
        let outer = this;
        //keydown 表示的是键盘是不是被按住,会连续触发
        //但是这个不是我们需要的效果,我们需要的效果是,按住就发生变化,松开就结束变化
        //不需要连续触发这个事件
        this.$canvas.keydown(function (e) {
            //Set 添加元素是用 add()
            outer.pressed_keys.add(e.key);
            //输出调试一下
            console.log(e.key);
        });
    }
}

在这里插入图片描述
我们可以看到控制台可以输出我们键盘按了什么键,这说明可以准确的识别我们操作的是哪一个键位

Set

js 的 Set 里面不能有重复的元素,就是集合的一个概念

add():添加元素
has():返回是否包含某个元素
size:返回元素数量
delete():删除某个元素
clear():删除所有元素

我们项目需要用到的是,往集合里面添加一个键盘输入,再从集合里面删除一个键盘输入,实现的效果是按住某一个键,立即生效,松开某一个键,立即失效

键盘事件

keydown:某个键是否被按住,事件会连续触发
event.code:返回按的是哪个键
event.altKey、event.ctrlKey、event.shiftKey分别表示是否同时按下了alt、ctrl、shift键。
keyup:某个按键是否被释放
keypress:紧跟在keydown事件后触发,只有按下字符键时触发。适用于判定用户输入的字符。

流程图

对象
地图
角色
主类
拳皇
键盘输入

我们在编辑文章的时候可以多添加一些图片,增强文章的可读性

在 canvas 里面使用 gif 图片

学会把一个函数封装好,给用户使用,而不是把一些底层的东西让用户去使用,这个非常关键,就像是我们需要用一个结果去证明自己,而不是用一个假努力的过程去欺骗自己,欺骗别人,最好用一个努力奋斗的过程取得的一个结果展示给别人看,比如说辛辛苦苦练了几年的肌肉

假设我们写的项目大部分都是市面上已有的函数接口,那么我们的项目没有什么创新点

我们这里就使用一个现有的函数接口帮助我们实现播放动图的功能

继承

class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y); // 这里的super表示父类的构造函数
        this.color = color;
    }

    toString() {
        return this.color + ' ' + super.toString(); // 调用父类的toString()
    }
}

我们在设计一个角色的时候,需要把抽象的矩形变成具体的游戏角色,所以在设计这个角色的时候需要继承玩家这个类,然后设计一些具体的东西,机制其实都是一样的,王者不断新出的英雄,多个地图,等等

存储动作

我们把动作存到一个 Map 里面,保存的是键值对

动作
静止
往右
往左
跳跃
攻击
被攻击
被击败

ReferenceError: gif is not defined

其实在开发的时候我遇到了挺多问题,忘记记录下来了

自己在使用 gif 的时候把 gif 放在了循环外面,索引不到,所以就报错了,解决之后,出现了更多的问题

TypeError: Cannot read properties of undefined (reading ‘length’)

obj.frame_cnt = gif.frame.length;

找到的原因是函数接口里面定义的是 frames ,虽然是这种简单的拼写错误,但是在开发项目的时候,遇到这种问题也是非常无奈的,要是找不出来,项目就无法正常运行

显示贴图

现在控制台没有任何报错,但是显示不了贴纸

笔者认为可能是引用的路径不是绝对路径的原因,也就是不是从 / 开始引用的,现在修改一下试试

把玩家和具体角色的引用路径都改为了绝对路径,没有效果

没有任何问题,但是出现不了贴纸,这是为什么呢

怀疑是 gif 的接口函数没有调用好,重新使用一遍接口函数

  • 我在实现两个抽象矩形移动的时候是没有问题的,到贴纸这一步出现的问题,

找了挺久还是没有找到原因,准备从正常移动那里开始看问题出在了哪里,今天先睡觉,明天和它战斗,两个多小时的教程,看了两天了,还没有搞完

从矩形抽象开始

笔者把整个教程完整看了一遍,现在从上一步卡住的地方开始做起

目前初步的构想是,实现几个项目,并且部署到拥有独立域名的网站上面,方便展示,并且绘制几张流程图梳理整个项目的逻辑

多动手多搜索

我们知道,gif 是动图,所以和帧有一定的关系,我们在矩形抽象的时候,和帧有关系的部分只有动画,也有一定的关系,我先把后面的代码都注释掉,让我们的项目可以像之前一样移动矩形

并且现在不涉及到状态机,与状态机有关的代码都可以注释掉

拳皇角色矩形抽象视频演示

视频演示

开发小技巧

因为自己是在 vscode 上面开发的,之前研究了一下怎么通过 git 把项目代码 push 到云端,可以 push 上去,但是不是很流畅,用 vim 会非常流畅的 push 上去,但是没有代码自动补全,没有自动补全,写前端代码其实是非常难受的

所以我的技巧就是多写一点注释,不删除代码,只把需要删除的代码注释掉,及时保存代码

gif 工具包

前面其实用了这个步骤,但是现在需要重新操作一遍,因为我不知道我到底是哪一块出现了问题(debug 的时候试过一次了,没有解决)

这个第三方函数是直接复制过来的,文件夹和文件名字都没有问题,也就是说调用的路径不会有问题

创建一个具体的游戏角色

继承 player 这个基类,写在 player 文件夹下

关于注释

因为自己写的注释比较多,会让人看不清楚代码结构,所以可以多画一些图片来梳理代码结构

具体角色
被调用
被调用
构造
函数
初始化动作
函数
添加
存kyo实例
Kyo
Player
GIF
constructoR
init_animations
for
gif.load
七个动作
outer
玩家
被调用
动作
构造函数
画布
键盘输入
状态机
定义开始
定义更新
定义渲染
定义键盘操作
定义移动
调用键盘
调用移动
调用渲染
根据状态机修改
从Map里读取键盘输入
考虑重力
Player
AcGameObject
construcOr
animations
ctx
pressed_keys
status
start
update
render
update_control
update_move
两名玩家
A
w
a
d
space
B
arrowup
arrowleft
arrowright
enter
gravity
vx
vy
timedelta
控制边界

通过这些图可以发现其实里面的逻辑关系比较复杂,不同的函数,变量,在不断地调用

架构设计:一图胜千言

渲染矩形

1:25:41 之前是渲染矩形
在这里插入图片描述
在这里插入图片描述
可以看到目前没有任何地报错信息

现在貌似找到之前地错误的原因了,因为一个单词拼写错了,之前没有报错,是因为没有找到这个变量,所以页面显示不了任何信息

项目能正常跑起来就算成功了一半,真的是真理呀
在这里插入图片描述

成功显示了,虽然丑丑的,但是非常开心哈哈哈,后面就是把角色放大一点儿,设置攻击,背景等

放大+调整帧率

在这里插入图片描述
很不错

竖直方向偏移量

移动的时候,我们在竖直方向会有一个偏移量,我们需要把这个竖直方向的偏移量消除,因为不同的动图的图片高度是不相等的,换句话说,往右走和静止,两者的高度是不相等的,观察可以发现,移动会比静止低一定的距离

设置一个偏移量的数组,把左右移动加上 22 的偏移量即可,这个偏移量是自己调试出来的

使用后退的动画

目前往右和往左都是同一个动画,假设我们是左边的角色,往右是前进,往左是后退,我们速度方向和 x 轴正方向不一致的时候,设置为后退状态

if (this.status === 1 && this.derection * this.vx < 0) {
        this.status = 2;
}

实现攻击的判断

在 Player 里面实现

if (space) {
                //把状态机转变为 4
                this.status = 4;
                //攻击的时候不能移动
                this.vx = 0;
                //从第零帧开始渲染,也就是渲染攻击的动态效果
                this.frame_current_cnt = 0;
            }

修改按键

因为空格键也是网页往下的按键,所以点击空格攻击的时候,网页也会往下,不太好,准备把空格键修改为 k 键,注意 space 这个名称没有修改

space = this.pressed_keys.has('k');

频闪

我们在攻击结束之后,角色会闪一下,下面我们需要解决这个问题

是因为我们图片总共是有 frame_cnt 张,我们播放到 frame_cnt-1 这张图片的时候,下一帧要直接停下来了,这样可以不会发生闪烁,丝滑过渡

 if (this.status === 4 && this.frame_current_cnt === obj.frame_rate * (obj.frame_cnt - 1)) {
            //把状态更新为静止
            this.status = 0;
            //重新开始渲染动画
            //this.frame_current_cnt = 0;
        }

使两名玩家对称

在这里插入图片描述
目前两名玩家是朝向一个方向的,我们需要让这两名玩家面对同一个方向

通过变换坐标系实现翻转,方法是网上搜 api 来实现
在这里插入图片描述
目前卡成这样子了,担心出 bug
在这里插入图片描述
现在效果还行,就是移动的时候出现了一些问题

现在完全实现了对称,办法就是查一下怎么在 canvas 里面翻转图像,然后把一些偏移量,对称的图形的一些参数改一下,目前的效果是这样子
在这里插入图片描述

修改跳跃的速率

写前端就是这样,需要多次修改一些参数,使得用户得到一个比较好的体验

//修改跳跃的参数,其他的是 5 帧播放一张图片,跳跃的时候每 4 帧播放一张图片
                //一般浏览器是一秒 60 帧,所以就是 0.0167 秒是一帧
                //5帧就是0.08秒(大概),4帧是0.07秒
                if (i === 3) {
                    obj.frame_rate = 4;
                }

修改完之后还行

修改背景图片

在这里插入图片描述
感觉还行,主要是这个背景图片占满了整个屏幕,角色无法正常落地,但是不把网页往下翻,其实是没有影响的,目前有两个选择,第一是保持现状,第二是把背景缩小一些,然后调一下参数使得角色可以落地,还是选第一个吧

结语

『这世界太大,勇敢的少年奔赴天涯。』—— 陈可心「荣耀同行」