正篇 — 用前端重返童年
我之前没有用过canvas,也是一点一点写的,代码质量不要吐槽哈~因为我知道这一次代码质量不行🌟
Hancao present to Game Science in 2021.
既然要出的是一个有情怀的前端作品,我也是花了很长时间在设计上,也试了很多方法去体现我的创意,但是效果并不是很好,边coding边摸索,最终就出来了本次作品的设计,正如我的标题所说,梦回童年的前端作品,效仿的便是老一代插卡游戏机中游戏的开机动画,不知道现在从事前端行业的伙伴年纪如何,但是像我这样刚入行一年的新人都见过,大家应该,八成,或许,大概,也都见过吧...
下面让我们一起见证,寒草用 前端技术 将 黑神话悟空 与 童年游戏经典开始动画结合的作品吧
这是前端工程师寒草在2021年献给游戏科学创作者们的礼物
重返80年代马赛克化的悟空
既然我们要重返童年,那么我们这个图片要紧跟时代啊,哪可以这么清晰。回想一下那个年代的游戏都是一个色块一个色块的马赛克,所以我们需要把悟空'马赛克化'
我们先搞一个canvas
<canvas id="my-canvas-monkey-king"></canvas>
之后我们在画布导入图片,并获取点阵信息,并间隔12像素去获取颜色并绘制
// 显示孙悟空 let canvas2 = document.getElementById("my-canvas-monkey-king"); let ctx2 = canvas2.getContext("2d"); var image2 = new Image(); image2.src = "monkey-king.jpeg"; image2.width = 700; image2.height = 700; image2.onload = function () { canvas2.width = image2.width; canvas2.height = image2.height; ctx2.drawImage(image2, 0, 0); var imageData2 = ctx2.getImageData(0, 0, image2.width, image2.height).data; // 将画布背景图黑 ctx2.fillStyle = "#000"; ctx2.fillRect(0, 0, image2.width, image2.height); var gap = 12; for (var h = 0; h < image2.height; h += gap) { for (var w = 0; w < image2.width; w += gap) { var position = (image2.width * h + w) * 4; var r = imageData2[position], g = imageData2[position + 1], b = imageData2[position + 2]; // 因为我用的原图背景不是纯黑的,所以我直接筛除rgb相加小于165的点(为什么是165呢,因为600是整数,255 * 3 - 600 = 165) if (765 - (r + g + b) < 600) { ctx2.fillStyle = `rgb(${r}, ${g}, ${b})`; ctx2.fillRect(w, h, gap, gap); } } } }
文字动效设计:悟空!出来吧~
这里做了一个logo马赛克化并且逐行绘制的效果,马赛克化的方法和之前一致,这个逐行绘制其实加入了一些细节,越到上面绘制起来越快,有点像一层层摞起来的文字一样~
我们再搞一个canvas
<canvas id="my-canvas-wukong"></canvas>
之后我们获取图片信息,按行存储在数组里,之后用定时器去获取每一行的信息,逐行绘制,每一次执行定时器的回调方法的时候也要记得减少定时器的时长。
let canvas = document.getElementById("my-canvas-wukong"); let ctx = canvas.getContext("2d"); var image = new Image(); image.src = "wukong.png"; image.width = 240; image.height = 240; image.onload = function () { canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); var imageData = ctx.getImageData(0, 0, image.width, image.height).data; ctx.fillStyle = "#000"; ctx.fillRect(0, 0, image.width, image.height); let pointPixels = []; let rowPoints = []; for (var h = image.height - 3; h >= 0; h -= 3) { if (h !== image.height - 3) { pointPixels.push(rowPoints); rowPoints = []; } for (var w = image.width - 3; w >= 0; w -= 3) { var position = (image.width * h + w) * 4; var r = imageData[position], g = imageData[position + 1], b = imageData[position + 2]; if (r + g + b !== 0) { rowPoints.push([w, h]); } } } ctx.fillStyle = "#fff"; let index = 0; const length = pointPixels.length; let delay = 30; const fn = () => { let timer = setTimeout(() => { clearTimeout(timer); if (index != length) { const rowPoints = pointPixels[index]; for (const rowPoint of rowPoints) { ctx.fillRect(rowPoint[0], rowPoint[1], 3, 3); } delay = delay - 0.125; index++; fn(); } else { const dom = document.getElementById('operation'); dom.style.opacity = 1; } }, delay) } fn(); }
梦回童年的游戏菜单
之后我们来设计一下我们的操作界面,我们先去回顾一下那些老游戏的界面:
emm,看上去我们已经把上面的title做完了,要设计下面操作菜单了,我也是按照经典的来:
这里文字用了文字阴影效果。下面的单人游玩和多人游玩使用了金箍棒作为选择指针~我想金箍棒也是悟空的标志了。下面还要留下我的署名:Hancao present to Game Science in 2021.
,是不是就有游戏厂商注册商标那感觉了~
<style> #operation { position: absolute; left: 30vw; top: 55vh; width: 40vw; height: 300px; text-align: center; transition: all 2s; opacity: 0; } #title { color: white; font-size: 40px; font-weight: 800; text-shadow: 8px 8px 8px #888888; cursor: pointer; text-decoration: none; } .select { margin-top: 32px; text-align: left; padding-left: 160px; } .option { overflow: hidden; } .op { display: inline-block; overflow: hidden; line-height: 45px; font-size: 30px; font-weight: 600; text-shadow: 8px 8px 8px #888888; color: #fff; } .jingubang { display: inline-block; height: 20px; width: 8px; background-color: #555151; border-top: 10px solid #7c7469; border-bottom: 10px solid #7c7469; margin-right: 16px; } .placeholder { display: inline-block; height: 20px; width: 8px; margin-right: 16px; } .hancao { font-size: 8px; margin-top: 48px; color: #fff; } </style> <div id="operation"> <a id="title" href="https://www.heishenhua.com"> BLACKMYTH WUKONG </a> <div class="select"> <div class="option"> <div class="jingubang"></div> <div class="op"> 1 PLAYER </div> </div> <div class="option"> <div class="placeholder"></div> <div class="op"> 2 PLAYERS </div> </div> </div> <div class="hancao"> Hancao present to Game Science in 2021. </div> </div>
完整代码在此,复制粘贴,与我一同重返童年
完整代码在此,大家可以复制粘贴,记得 vscode 下载live server插件来运行,否则,canvas存在图片代理问题
tip: 完整效果见头图。代码质量堪忧,首先是canvas不太会,而且我是边设计边编码的,所以代码结构没有经过设计~
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>wukong</title> <style> body { margin: 0; background-color: #000; } #my-canvas-wukong { position: absolute; margin-left: 50vw; margin-top: 20vh; width: 16vw; height: 32vh; } #my-canvas-monkey-king { position: absolute; margin-left: 30vw; margin-top: 20vh; width: 16vw; height: 32vh; } #operation { position: absolute; left: 30vw; top: 55vh; width: 40vw; height: 300px; text-align: center; transition: all 2s; opacity: 0; } #title { color: white; font-size: 40px; font-weight: 800; text-shadow: 8px 8px 8px #888888; cursor: pointer; text-decoration: none; } .select { margin-top: 32px; text-align: left; padding-left: 160px; } .option { overflow: hidden; } .op { display: inline-block; overflow: hidden; line-height: 45px; font-size: 30px; font-weight: 600; text-shadow: 8px 8px 8px #888888; color: #fff; } .jingubang { display: inline-block; height: 20px; width: 8px; background-color: #555151; border-top: 10px solid #7c7469; border-bottom: 10px solid #7c7469; margin-right: 16px; } .placeholder { display: inline-block; height: 20px; width: 8px; margin-right: 16px; } .hancao { font-size: 8px; margin-top: 48px; color: #fff; } </style> </head> <body> <canvas id="my-canvas-wukong"> </canvas> <canvas id="my-canvas-monkey-king"> </canvas> <div id="operation"> <a id="title" href="https://www.heishenhua.com"> BLACKMYTH WUKONG </a> <div class="select"> <div class="option"> <div class="jingubang"></div> <div class="op"> 1 PLAYER </div> </div> <div class="option"> <div class="placeholder"></div> <div class="op"> 2 PLAYERS </div> </div> </div> <div class="hancao"> Hancao present to Game Science in 2021. </div> </div> <script> setTimeout(() => { let canvas = document.getElementById("my-canvas-wukong"); let ctx = canvas.getContext("2d"); var image = new Image(); image.src = "wukong.png"; image.width = 240; image.height = 240; image.onload = function () { canvas.width = image.width; canvas.height = image.height; ctx.drawImage(image, 0, 0); var imageData = ctx.getImageData(0, 0, image.width, image.height).data; ctx.fillStyle = "#000"; ctx.fillRect(0, 0, image.width, image.height); let pointPixels = []; let rowPoints = []; for (var h = image.height - 3; h >= 0; h -= 3) { if(h !== image.height - 3 ){ pointPixels.push(rowPoints); rowPoints = []; } for (var w = image.width - 3; w >= 0; w -= 3) { var position = (image.width * h + w) * 4; var r = imageData[position], g = imageData[position + 1], b = imageData[position + 2]; if (r + g + b !== 0) { rowPoints.push([w, h]); } } } ctx.fillStyle = "#fff"; let index = 0; const length = pointPixels.length; let delay = 30; const fn = () => { let timer = setTimeout(() => { clearTimeout(timer); if(index != length){ const rowPoints = pointPixels[index]; for(const rowPoint of rowPoints){ ctx.fillRect(rowPoint[0], rowPoint[1], 3, 3); } delay = delay - 0.125; index++; fn(); } else { const dom = document.getElementById('operation'); dom.style.opacity = 1; } }, delay) } fn(); } // 显示孙悟空 let canvas2 = document.getElementById("my-canvas-monkey-king"); let ctx2 = canvas2.getContext("2d"); var image2 = new Image(); image2.src = "monkey-king.jpeg"; image2.width = 700; image2.height = 700; image2.onload = function () { console.log(image2, canvas2, ctx2); canvas2.width = image2.width; canvas2.height = image2.height; ctx2.drawImage(image2, 0, 0); var imageData2 = ctx2.getImageData(0, 0, image2.width, image2.height).data; console.log(imageData2) ctx2.fillStyle = "#000"; ctx2.fillRect(0, 0, image2.width, image2.height); var gap = 12; for (var h = 0; h < image2.height; h+=gap) { for(var w = 0; w < image2.width; w+=gap){ var position = (image2.width * h + w) * 4; var r = imageData2[position], g = imageData2[position + 1], b = imageData2[position + 2]; if(765 - (r + g + b) < 600) { ctx2.fillStyle = `rgb(${r}, ${g}, ${b})`; ctx2.fillRect(w,h,gap,gap); } } } } }, 5000); </script> </body> </html>
结束语 — 热爱,所以期待
首先,文章中关于游戏和前端的看法仅仅代表我的主观想法,欢迎评论区指正,以及感谢大帅老师的文章与代码对我有思路上的指引~
本篇文章到此就结束了,我们不需要去捧杀黑神话悟空,持续期待就好了。以及大家或许能从我的文章看出我们可以用技术去完成很多很多好玩的事情,我也希望我的存在可以为前端开发者带来更多的创造力,更多的idea,让大家对这个行业更热爱~
这可能就是我现在作为一个有趣的,有奇奇怪怪的点子的前端工程师可以为社区带来的东西吧。(当然我也有硬核内容,比如寒草的编译原理哈哈哈)