VSCode 天命人:边打代码边体验黑神话悟空

2024-11-14 08:30   重庆  

点击关注公众号,“技术干货” 及时达!

《黑神话:悟空》最近很火,明天就要正式上线了!小明我为了提前感受一下这款游戏的激燃氛围,做了个 VSCode 小扩展,让你在写代码时也能体验到《黑神话:悟空》里的打斗快感。

接下来,我会带你一步步实现这个插件,让你的编程旅程不再单调!

原理

这个插件的核心原理是利用 VSCode 的 TextEditorDecorationType 来展示逐帧动画。由于 VSCode 不支持直接播放 MP4 视频,我们需要先将视频转成图片序列,然后通过装饰的方式来显示这些图片。

具体操作是这样的:我们先用 ffmpeg 命令将视频拆解成一帧帧的图片,命令如下:

ffmpeg -i ./wukong.mp4 -vf "fps=10,scale=300:150" -compression_level 9 ./res/frames/frame%03d.png

这个命令会把 wukong.mp4 视频按每秒 10 帧的速度导出成 300x150 像素的图片,存放在 res/frames 文件夹下。然后,我们在 VSCode 中利用这些图片逐帧展示动画,营造出打字时打斗的效果。

接下来,我会带你一步步实现这个有趣的插件,让你的编程过程变得更有意思!

第一步:初始化项目

首先,我们要创建一个新的 VSCode 插件项目。如果你还没有设置好开发环境,可以参考 VSCode 官方文档 来创建你的第一个插件项目。

假设你已经创建了一个插件项目,现在我们直接进入编码阶段。

第二步:预加载动画帧

在展示动画之前,我们需要先加载所有的动画帧。这里假设我们有 100 帧图片,存放在插件的 res/frames 文件夹下。

首先,我们来写一个函数,用来加载这些帧图片并存储到内存中,方便后续使用。

const frameCache: { [key: number]: string } = {}

async function preloadFrames(extensionPath: string) {
for (let i = 1; i <= 100; i++) {
const frameUrl = getFrameUrl(i, extensionPath)
frameCache[i] = frameUrl
}
}

这个函数的逻辑很简单:我们遍历 100 个帧图片,依次读取并存储到 frameCache 对象中。这样在后面播放动画时,就可以直接使用缓存好的图片,而不用每次都去读取文件。

其中的 getFrameUrl 函数负责生成每一帧图片的路径,并将其转换为 Base64 格式的 URL:

function getFrameUrl(frameNumber: number, extensionPath: string): string {
const paddedFrameNumber = frameNumber.toString().padStart(3, '0')
const imagePath = path.join(extensionPath, 'res', 'frames', `frame${paddedFrameNumber}.png`)

try {
const imageBuffer = fs.readFileSync(imagePath)
const base64Image = imageBuffer.toString('base64')
return `data:image/png;base64,${base64Image}`
} catch (error) {
console.error(`读取图片失败: ${imagePath}`, error)
return ''
}
}

这里我们通过 fs.readFileSync 读取每一帧图片,然后将其转换为 Base64 格式,这样就可以直接在 HTML 中使用图片数据。

第三步:监听输入事件

有了预加载的动画帧,现在我们需要监听用户在编辑器中的输入事件,每当用户输入代码时,就触发动画播放。

vscode.workspace.onDidChangeTextDocument(event => {
if (event.contentChanges.length > 0) {
updateAnimation(event.document.uri)
resetInactivityTimer()
incrementComboCount()
}
})

当文档内容发生变化时,我们会调用 updateAnimation 来更新动画。同时,我们还重置了用户不活动计时器,并增加连击计数。

第四步:展示动画

核心部分来了!我们要通过 VSCode 的 TextEditorDecorationType API 来在编辑器中显示动画。

let animationDecoration: vscode.TextEditorDecorationType | undefined
let currentFrame = 1

async function showNextFrame(editor: vscode.TextEditor) {
if (!isAnimating) return

const frameUrl = frameCache[currentFrame]

if (frameUrl) {
if (animationDecoration) {
animationDecoration.dispose()
}

animationDecoration = vscode.window.createTextEditorDecorationType({
after: {
contentText: comboCount > 0 ? `${comboCount}x` : '',
margin: '0 0 0 1em',
width: '200px',
height: '100px',
textDecoration: `
none;
position: absolute;
left: -0.5rem;
top: 2rem;
z-index: 1;
pointer-events: none;
background-image: url(/cover?u=ZnV4NkdPS3JYK0NmVk1HcEJ3RHpwZz09);
background-size: 100% 100%;
background-repeat: no-repeat;
`
},
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed
})

const lastLine = editor.document.lineAt(editor.document.lineCount - 1)
const range = new vscode.Range(lastLine.range.end, lastLine.range.end)

editor.setDecorations(animationDecoration, [{ range }])

currentFrame = (currentFrame % 100) + 1
}
}

这里的 showNextFrame 函数负责将动画帧显示在编辑器中。通过设置 TextEditorDecorationType,我们可以自定义每一帧动画的样式和位置。

第五步:处理动画的停止和连击

为了让动画更有趣,我们加入了连击的概念,并且在用户停止输入一段时间后自动停止动画。

let isAnimating = false
let inactivityTimer: NodeJS.Timeout | undefined
let comboCount = 0

function resetInactivityTimer() {
if (inactivityTimer) clearTimeout(inactivityTimer)
inactivityTimer = setTimeout(() => {
stopAnimation()
resetComboCount()
}, 3000) // 3秒后停止动画并重置连击计数
}

function stopAnimation() {
isAnimating = false
if (animationDecoration) {
animationDecoration.dispose()
animationDecoration = undefined
}
}

function incrementComboCount() {
comboCount++
showNextFrame(vscode.window.activeTextEditor!)
}

function resetComboCount() {
comboCount = 0
showNextFrame(vscode.window.activeTextEditor!)
}

resetInactivityTimer 会在用户停止输入超过 3 秒后停止动画,并重置连击计数。这样,我们就不会一直占用资源来播放动画了。

第六步:将插件激活到 VSCode 中

现在,我们的动画逻辑已经写好了。接下来,我们要把它整合到 VSCode 插件的主入口中。

import * as vscode from 'vscode'
import { activateFightAnimation } from './fightAnimation'

export const activate = async (context: vscode.ExtensionContext) => {
try {
console.log('"VSCode Funny" 插件已激活!')

// 激活打斗动画功能
activateFightAnimation(context)

} catch (err) {
console.warn('插件激活失败', err)
}
}

在插件的 activate 函数中,我们调用 activateFightAnimation 来启动我们的打字动画功能。

最后一步:配置和运行插件

至此,插件的代码已经完成。你可以通过以下步骤来运行并测试这个插件:

  1. 打开 VSCode,进入插件的项目目录。
  2. 运行 npm install 来安装依赖。
  3. 使用 F5 启动插件调试环境。
  4. 在调试窗口中输入代码,享受打字战斗动画带来的乐趣!

结语:彩蛋时间 🐣

这个插件不仅能让你在敲代码时体验到《黑神话》的程序吗喽热血,还“故意”留了一个 bug——动画的位置 bug。

如果你对这个扩展感兴趣,或者想亲自体验一下这个插件,可以去我的 GitHub 仓库看看:https://github.com/nicepkg/vscode-funny

点击关注公众号,“技术干货” 及时达!


稀土掘金技术社区
掘金,一个帮助开发者成长的技术社区
 最新文章