摘要:話不多說,先上和項目源碼有趣的是,在我寫完這篇文章之后,發現愛編程的李先森也寫了一篇手寫辨色力小游戲實現方式有所不同,可以對比下。
1. 背景
之前寫過一篇文章 ES6 手寫一個“辨色”小游戲, 感覺好玩挺不錯。豈料評論區大神頻出,其中有人指出,打開控制臺,輸入以下代碼:
setInterval( ()=>document.querySelector("#special-block").click(),1)
即可破解,分數蹭蹭上漲,這不就是bug嗎?同時評論區 【愛編程的李先森】建議,讓我用 canvas 來畫會更簡單,因此有了這篇文章。
話不多說,先上 Demo 和 項目源碼
有趣的是,在我寫完這篇文章之后,發現【愛編程的李先森】也寫了一篇canvas手寫辨色力小游戲,實現方式有所不同,可以對比下。
2. 實現本項目基于 typescript 和 canvas 實現
(1) 首先定義配置項一個canvas標簽,游戲總時長time, 開始函數start, 結束函數end
interface BaseOptions { time?: number; end?: Function; start?: Function; canvas?: HTMLCanvasElement; }
定義類 ColorGame 實現的接口 ColorGameType, init()初始化方法,nextStep()下一步,reStart()重新開始方法
interface ColorGameType { init: Function; nextStep: Function; reStart: Function; }
定義一個坐標對象,用于儲存每個色塊的起始點
interface Coordinate { x: number; y: number; }(2) 實現類 ColorGame
定義好了需要用到的接口,再用類去實現它
class ColorGame implements ColorGameType { option: BaseOptions; step: number; // 步 score: number; // 得分 time: number; // 游戲總時間 blockWidth: number; // 盒子寬度 randomBlock: number; // 隨機盒子索引 positionArray: Array(3)實現 init() 方法; // 存放色塊的數組 constructor(userOption: BaseOptions) { // 默認設置 this.option = { time: 30, // 總時長 canvas: document.getElementById("canvas"), start: () => { document.getElementById("result").innerHTML = ""; document.getElementById("screen").style.display = "block"; }, end: (score: number) => { document.getElementById("screen").style.display = "none"; document.getElementById( "result" ).innerHTML = ` `; // @ts-ignore addEvent(document.getElementById("restart"), "click", () => { this.reStart(); }); } // 結束函數 }; this.init(userOption); // 初始化,合并用戶配置 } init(userOption: BaseOptions) { } nextStep() {} // 重新開始其實也是重新init()一次 reStart() { this.init(this.option); } }您的得分是: ${score}
點擊重新玩一次
init() 方法實現參數初始化,執行 start() 方法,并在最后執行 nextStep() 方法,并監聽 canvas的 mousedown 和 touchstart 事件。
這里用到 canvas.getContext("2d").isPointInPath(x, y) 判斷點擊點是否處于最后一次繪畫的矩形內,因此特殊顏色的色塊要放在最后一次繪制
init(userOption: BaseOptions) { if (this.option.start) this.option.start(); this.step = 0; // 步驟初始化 this.score = 0;// 分數初始化 this.time = this.option.time; // 倒計時初始化 // 合并參數 if (userOption) { if (Object.assign) { Object.assign(this.option, userOption); } else { extend(this.option, userOption, true); } } // 設置初始時間和分數 document.getElementsByClassName( "wgt-score" )[0].innerHTML = `得分:${this.score} 時間:${this.time}`; // 開始計時 ((4)實現 nextStep() 方法window).timer = setInterval(() => { if (this.time === 0) { clearInterval(( window).timer); this.option.end(this.score); } else { this.time--; document.getElementById("timer").innerHTML = this.time.toString(); } }, 1000); this.nextStep(); // 下一關 ["mousedown", "touchstart"].forEach(event => { this.option.canvas.addEventListener(event, e => { let loc = windowToCanvas(this.option.canvas, e); // isPointInPath 判斷是否在最后一次繪制矩形內 if (this.option.canvas.getContext("2d").isPointInPath (loc.x, loc.y)) { this.nextStep(); this.score++; document.getElementById("score").innerHTML = this.score.toString(); } }); }); }
nexStep() 這里實現的是每一回合分數增加,以及畫面的重新繪畫,這里我用了 this.blockWidth 存放每一級色塊的寬度, this.randomBlock 存放隨機特殊顏色色塊的index, this.positionArray 用于存放每個色塊的左上角坐標點,默認設置色塊之間為2像素的空白間距。
有一個特殊的地方是在清除畫布時ctx.clearRect(0, 0, canvas.width, canvas.width);,需要先 ctx.beginPath();清除之前記憶的路徑。否則會出現以下的效果:
nextStep() { // 記級 this.step++; let col: number; // 列數 if (this.step < 6) { col = this.step + 1; } else if (this.step < 12) { col = Math.floor(this.step / 2) * 2; } else if (this.step < 18) { col = Math.floor(this.step / 3) * 3; } else { col = 16; } let canvas = this.option.canvas; let ctx = canvas.getContext("2d"); ctx.beginPath(); ctx.clearRect(0, 0, canvas.width, canvas.width); // 清除畫布 ctx.closePath(); // 小盒子寬度 this.blockWidth = (canvas.width - (col - 1) * 2) / col; // 隨機盒子index this.randomBlock = Math.floor(col * col * Math.random()); // 解構賦值獲取一般顏色和特殊顏色 let [normalColor, specialColor] = getColor(this.step); this.positionArray = []; for (let i = 0; i < col ** 2; i++) { let row = Math.floor(i / col); let colNow = i % col; let x = colNow * (this.blockWidth + 2), y = row * (this.blockWidth + 2); this.positionArray.push({ x, y }); if (i !== this.randomBlock) drawItem(ctx, normalColor, x, y, this.blockWidth, this.blockWidth); } ctx.beginPath(); drawItem( ctx, specialColor, this.positionArray[this.randomBlock].x, this.positionArray[this.randomBlock].y, this.blockWidth, this.blockWidth ); ctx.closePath(); }
drawItem()用于繪制每一個色塊, 這里需要指出的是,isPointInPath 是判斷是否處于矩形的路徑上,只有使用 context.fill() 才能使整個矩形成為判斷的路徑。
function drawItem( context: Context, color: string, x: number, y: number, width: number, height: number ): void { context.fillStyle = `#${color}`; context.rect(x, y, width, height); context.fill(); //替代fillRect(); }(5) 其他共用方法 gameMethods.ts 和 utils.ts
// gameMethods.ts /** * 根據關卡等級返回相應的一般顏色和特殊顏色 * @param {number} step 關卡 */ export function getColor(step: number): Array{ let random = Math.floor(100 / step); let color = randomColor(17, 255), m: Array = color.match(/[da-z]{2}/g); for (let i = 0; i < m.length; i++) m[i] = parseInt(String(m[i]), 16); //rgb let specialColor = getRandomColorNumber(m[0], random) + getRandomColorNumber(m[1], random) + getRandomColorNumber(m[2], random); return [color, specialColor]; } /** * 返回隨機顏色的一部分值 * @param num 數字 * @param random 隨機數 */ export function getRandomColorNumber( num: number | string, random: number ): string { let temp = Math.floor(Number(num) + (Math.random() < 0.5 ? -1 : 1) * random); if (temp > 255) { return "ff"; } else if (temp > 16) { return temp.toString(16); } else if (temp > 0) { return "0" + temp.toString(16); } else { return "00"; } } // 隨機顏色 min 大于16 export function randomColor(min: number, max: number): string { var r = randomNum(min, max).toString(16); var g = randomNum(min, max).toString(16); var b = randomNum(min, max).toString(16); return r + g + b; } // 隨機數 export function randomNum(min: number, max: number): number { return Math.floor(Math.random() * (max - min) + min); }
// utils.ts /** * 合并兩個對象 * @param o 默認對象 * @param n 自定義對象 * @param override 是否覆蓋默認對象 */ export function extend(o: any, n: any, override: boolean): void { for (var p in n) { if (n.hasOwnProperty(p) && (!o.hasOwnProperty(p) || override)) o[p] = n[p]; } } /** * 事件兼容方法 * @param element dom元素 * @param type 事件類型 * @param handler 事件處理函數 */ export function addEvent(element: HTMLElement, type: string, handler: any) { if (element.addEventListener) { element.addEventListener(type, handler, false); // @ts-ignore } else if (element.attachEvent) { // @ts-ignore element.attachEvent("on" + type, handler); } else { // @ts-ignore element["on" + type] = handler; } } /** * 獲取點擊點于canvas內的坐標 * @param canvas canvas對象 * @param e 點擊事件 */ export function windowToCanvas(canvas: HTMLCanvasElement, e: any) { let bbox = canvas.getBoundingClientRect(), x = IsPC() ? e.clientX || e.clientX : e.changedTouches[0].clientX, y = IsPC() ? e.clientY || e.clientY : e.changedTouches[0].clientY; return { x: x - bbox.left, y: y - bbox.top }; } /** * 判斷是否為 PC 端,若是則返回 true,否則返回 flase */ export function IsPC() { let userAgentInfo = navigator.userAgent, flag = true, Agents = [ "Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod" ]; for (let v = 0; v < Agents.length; v++) { if (userAgentInfo.indexOf(Agents[v]) > 0) { flag = false; break; } } return flag; }3. 使用
將代碼打包構建后引入 html 后,新建 new ColorGame(option) 即可實現。前提是頁面結構如下:
總結canvas 辨色小游戲
這里主要是對 isPointInPath 的使用實踐,在之后的文章《canvas繪制九宮格》也會用到此方法,敬請期待!
更多推薦前端進階小書(advanced_front_end)
前端每日一題(daily-question)
webpack4 搭建 Vue 應用(createVue)
Canvas 進階(一)二維碼的生成與掃碼識別
Canvas 進階(二)寫一個生成帶logo的二維碼npm插件
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105439.html
摘要:背景最近接觸到的需求,前端生成一個帶企業的二維碼,并支持點擊下載它。 背景 最近接觸到的需求,前端生成一個帶企業logo的二維碼,并支持點擊下載它。 實現 在前面的文章有講到如何用 canvas 畫二維碼,在此基礎上再畫一個公司logo,并提供下載的方法供調用,再封裝成 npm 插件 模塊名稱: qrcode-with-logos github地址:https://github.com...
摘要:基本介紹一款移動端貪吃蛇大作戰游戲。主要的游戲邏輯有貪吃蛇移動碰撞檢測貪吃蛇碰撞碰撞墻壁和吃食物。貪吃蛇的身體如何跟隨頭部移動需要分為兩種情況,在單位時間內貪吃蛇移動一單位長度和貪吃蛇移動多單位長度。 基本介紹 一款移動端貪吃蛇大作戰游戲。(只支持移動端) 這是一個臨近 deadline 的課設項目,為了方便地使用TS,我直接使用angular-cli生成了TypeScript的項...
摘要:本文由云社區發表使用一個簡單的游戲開發示例,由淺入深,介紹了如何用引擎開發微信小游戲。前段時間正好抽空研究了一下這塊的內容,現做一個總結,針對如何使用引擎開發微信小游戲給大家做一下介紹。 本文由云+社區發表 使用一個簡單的游戲開發示例,由淺入深,介紹了如何用Laya引擎開發微信小游戲。 showImg(https://segmentfault.com/img/remote/146000...
摘要:前言依稀記得幾年前朋友圈流行的辨色小游戲,找出顏色不同的矩形。前些天突發奇想,打算自己手寫一個類似的游戲,話不多說,先上。顏色由三色構成,三色值越接近,則顏色顯示越接近。 showImg(https://segmentfault.com/img/bVbhaOC?w=1003&h=474); 1. 前言 依稀記得幾年前朋友圈流行的辨色小游戲,找出顏色不同的矩形。前些天突發奇想,打算自己手...
閱讀 909·2021-09-09 09:32
閱讀 2849·2021-09-02 10:20
閱讀 2685·2021-07-23 11:24
閱讀 824·2019-08-30 15:54
閱讀 3631·2019-08-30 15:54
閱讀 1346·2019-08-30 11:02
閱讀 2844·2019-08-26 17:40
閱讀 1122·2019-08-26 13:55