這個(gè)厲害了,用前端重返童年??
點(diǎn)擊上方關(guān)注 前端技術(shù)江湖,一起學(xué)習(xí),天天進(jìn)步
前言 — 背景介紹
國產(chǎn) 3A 游戲情懷
了解我的朋友都知道,我是一個(gè)很喜歡玩游戲的人,我甚至還六一兒童節(jié)發(fā)表過兒童節(jié)雜談?dòng)螒蛴洃洝渡C(jī)5》[1],小時(shí)候玩的游戲也不少,從插卡游戲機(jī)到ps3,ps4也都有經(jīng)歷過。
我們這些玩著國外3A游戲長大的孩子一直有一個(gè)小小的愿望(其實(shí)是大大的夢(mèng)想),就是玩到屬于我們的國產(chǎn)3A游戲,并且不僅僅是由國人開發(fā),國人制作,而是包含著中國的文化底蘊(yùn),我們有著五千年深厚的歷史文化,我們的游戲應(yīng)該做到文化輸出,讓世界的游戲玩家看到中國的游戲,中國的文化,中國的魅力。
大家會(huì)不會(huì)覺得上面的話很熟悉,沒錯(cuò),國產(chǎn)動(dòng)畫同理,小時(shí)候看著日漫美漫長大的孩子們一直也是在期待著國產(chǎn)動(dòng)畫崛起,突然那一年,大圣歸來,身穿虎虎生風(fēng)黃金甲,身披炙熱烈焰火紅袍,手掄天河定底神珍鐵。為國產(chǎn)動(dòng)漫電影打開了新的道路,但是后面的大圣歸來游戲不盡如人意,然而去年游戲科學(xué)出了黑神話悟空的預(yù)告,今年又出了虛幻5引擎的新版預(yù)告。看得我熱血沸騰,上次是大圣,這是又是大圣,悟空要開辟我熱愛的游戲產(chǎn)業(yè)的國產(chǎn)3A新道路了。「此處因?yàn)楹?? 太過熱血沸騰,于是有了這篇文章,和這幾百行代碼」

玩游戲的孩子長大了,成為了工程師
時(shí)間回到現(xiàn)在,沒錯(cuò),寒草看完演示視頻久久不能平靜的現(xiàn)在,現(xiàn)在的寒草?? 褪去稚氣,成為了一個(gè)在前端行業(yè)摸爬滾打一年的工程師,今天中午和朋友去吃好吃的(沒錯(cuò),周末一定要有好吃的啊~),和他我說了很多黑神話悟空的消息,我越說越激動(dòng),心想,那我用我的技術(shù),來表達(dá)我那一份對(duì)國產(chǎn)游戲的期待與對(duì)游戲?qū)η岸诵袠I(yè)的熱愛吧。
為大家?guī)碛腥さ那岸藘?nèi)容,用前端去做更好玩的事情,一直是我的初心。一路以來,感謝有你們??
正篇 — 用前端重返童年

我之前沒有用過canvas,也是一點(diǎn)一點(diǎn)寫的,代碼質(zhì)量不要吐槽哈~因?yàn)槲抑肋@一次代碼質(zhì)量不行??
Hancao present to Game Science in 2021.
既然要出的是一個(gè)有情懷的前端作品,我也是花了很長時(shí)間在設(shè)計(jì)上,也試了很多方法去體現(xiàn)我的創(chuàng)意,但是效果并不是很好,邊coding邊摸索,最終就出來了本次作品的設(shè)計(jì),正如我的標(biāo)題所說,夢(mèng)回童年的前端作品,效仿的便是老一代插卡游戲機(jī)中游戲的開機(jī)動(dòng)畫,不知道現(xiàn)在從事前端行業(yè)的伙伴年紀(jì)如何,但是像我這樣剛?cè)胄幸荒甑男氯硕家娺^,大家應(yīng)該,八成,或許,大概,也都見過吧...
下面讓我們一起見證,寒草用 前端技術(shù) 將 黑神話悟空 與 童年游戲經(jīng)典開始動(dòng)畫結(jié)合的作品吧
這是前端工程師寒草在2021年獻(xiàn)給游戲科學(xué)創(chuàng)作者們的禮物
重返80年代馬賽克化的悟空

既然我們要重返童年,那么我們這個(gè)圖片要緊跟時(shí)代啊,哪可以這么清晰。回想一下那個(gè)年代的游戲都是一個(gè)色塊一個(gè)色塊的馬賽克,所以我們需要把悟空'馬賽克化'
我們先搞一個(gè)canvas
<canvas id="my-canvas-monkey-king"></canvas>
復(fù)制代碼
之后我們?cè)诋嫴紝?dǎo)入圖片,并獲取點(diǎn)陣信息,并間隔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];
// 因?yàn)槲矣玫脑瓐D背景不是純黑的,所以我直接篩除rgb相加小于165的點(diǎn)(為什么是165呢,因?yàn)?00是整數(shù),255 * 3 - 600 = 165)
if (765 - (r + g + b) < 600) {
ctx2.fillStyle = `rgb(${r}, ${g}, ${b})`;
ctx2.fillRect(w, h, gap, gap);
}
}
}
}
復(fù)制代碼
文字動(dòng)效設(shè)計(jì):悟空!出來吧~

這里做了一個(gè)logo馬賽克化并且逐行繪制的效果,馬賽克化的方法和之前一致,這個(gè)逐行繪制其實(shí)加入了一些細(xì)節(jié),越到上面繪制起來越快,有點(diǎn)像一層層摞起來的文字一樣~
我們?cè)俑阋粋€(gè)canvas
<canvas id="my-canvas-wukong"></canvas>
復(fù)制代碼
之后我們獲取圖片信息,按行存儲(chǔ)在數(shù)組里,之后用定時(shí)器去獲取每一行的信息,逐行繪制,每一次執(zhí)行定時(shí)器的回調(diào)方法的時(shí)候也要記得減少定時(shí)器的時(shí)長。
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();
}
復(fù)制代碼
夢(mèng)回童年的游戲菜單
之后我們來設(shè)計(jì)一下我們的操作界面,我們先去回顧一下那些老游戲的界面:

emm,看上去我們已經(jīng)把上面的title做完了,要設(shè)計(jì)下面操作菜單了,我也是按照經(jīng)典的來:

這里文字用了文字陰影效果。下面的單人游玩和多人游玩使用了金箍棒作為選擇指針~我想金箍棒也是悟空的標(biāo)志了。下面還要留下我的署名:Hancao present to Game Science in 2021.,是不是就有游戲廠商注冊(cè)商標(biāo)那感覺了~
<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>
復(fù)制代碼
完整代碼在此,復(fù)制粘貼,與我一同重返童年
完整代碼在此,大家可以復(fù)制粘貼,記得 vscode 下載live server插件來運(yùn)行,否則,canvas存在圖片代理問題
tip: 完整效果見頭圖。代碼質(zhì)量堪憂,首先是canvas不太會(huì),而且我是邊設(shè)計(jì)邊編碼的,所以代碼結(jié)構(gòu)沒有經(jīng)過設(shè)計(jì)~
<!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>
復(fù)制代碼
結(jié)束語 — 熱愛,所以期待

首先,文章中關(guān)于游戲和前端的看法僅僅代表我的主觀想法,歡迎評(píng)論區(qū)指正,以及感謝大帥老師[2]的文章與代碼對(duì)我有思路上的指引~
本篇文章到此就結(jié)束了,我們不需要去捧殺黑神話悟空,持續(xù)期待就好了。以及大家或許能從我的文章看出我們可以用技術(shù)去完成很多很多好玩的事情,我也希望我的存在可以為前端開發(fā)者帶來更多的創(chuàng)造力,更多的idea,讓大家對(duì)這個(gè)行業(yè)更熱愛~
這可能就是我現(xiàn)在作為一個(gè)有趣的,有奇奇怪怪的點(diǎn)子的前端工程師可以為社區(qū)帶來的東西吧。(當(dāng)然我也有硬核內(nèi)容,比如寒草的編譯原理哈哈哈)
最后,熱愛,所以期待
踏過三界寶剎,閱過四洲繁華。
笑過五蘊(yùn)癡纏,舍過六根牽掛。
為情義披戰(zhàn)甲,為愛人挑擔(dān)、牽馬、送晚霞。
怕什么欲念不休,怕什么浪跡天涯。
步履不停,便是得救之法。
——敢問路在何方?路在腳下。
伙伴們,如果喜歡我的文章,可以點(diǎn)贊 ?? 關(guān)注? ,這是對(duì)我最大的支持。
加我微信:hancao97,邀你進(jìn)群,了解寒草?? 的github小組現(xiàn)狀,一起學(xué)習(xí)前端,成為更優(yōu)秀的工程師~(群二維碼在這里->前端晚晚睡[3], 二維碼過期了的話看鏈接沸點(diǎn)中的評(píng)論,我會(huì)把最新的二維碼放在評(píng)論區(qū),當(dāng)然也可以加我微信我拉你進(jìn)群,畢竟我也是有趣的前端,認(rèn)識(shí)我也不賴??~)
關(guān)于本文
作者:寒草
https://juejin.cn/post/6999413377692860430
The End
歡迎自薦投稿到《前端技術(shù)江湖》,如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),記得點(diǎn)個(gè) 「在看」哦
點(diǎn)個(gè)『在看』支持下 
