JS實現(xiàn)羊了個羊小游戲?qū)嵗?/h1>
更新時間:2022年09月17日 11:46:32 作者:夕水
這篇文章主要為大家介紹了JS實現(xiàn)羊了個羊小游戲示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
引言
這兩天火爆全場的《羊了個羊》游戲,相信大家都玩過了,那么在玩這個游戲的同時,我想大家都會好奇這個游戲的實現(xiàn),本文就帶大家使用css,html,js來實現(xiàn)一個動物版的游戲。
首先我用到了2個插件,第一個插件就是flexible.js,這個插件就是對不同設(shè)備設(shè)置根元素字體大小,也就是一個移動端的適配方案。
因為這里使用了rem布局,針對移動端做了自適應(yīng),所以這里選擇采用rem布局方案。
rem布局方案
還有一個彈框插件,我很早自行實現(xiàn)的,就是popbox.js,關(guān)于這個插件,本文不打算講解實現(xiàn)原理,只講解一下使用原理:
popbox.js使用原理
ewConfirm({
title: "溫馨提示", //彈框標題
content: "游戲結(jié)束,別灰心,你能行的!", //彈框內(nèi)容
sureText: "重新開始", //確認按鈕文本
isClickModal:false, //點擊遮罩層是否關(guān)閉彈框
sure(context) {
context.close();
//點擊確認按鈕執(zhí)行的邏輯
},//點擊確認的事件回調(diào)
})
引入了這個js之后,會在window對象上綁定一個ewConfirm方法,這個方法傳入一個自定義對象,對象的屬性有title,content,sureText,cancelText,cancel,sure,isClickModal
這幾個屬性,當然這里沒有用到cancel按鈕,所以不細講。
正如注釋所說,每個屬性代表的意思,這里不做贅述。
html代碼
<!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>羊了個羊《動物版》</title>
<link rel="stylesheet" href="./style.css" rel="external nofollow" >
</head>
<body>
</body>
<script src="https://www.eveningwater.com/static/plugin/popbox.min.js"></script>
<script src="https://www.eveningwater.com/test/demo/flexible.js"></script>
<script src="./script.js"></script>
</html>
可以看到html代碼是什么都沒有的,因為里面的DOM元素,我們都放在js代碼里面動態(tài)生成了,所以script.js這里的代碼是核心,這個后續(xù)會講到,接下來看樣式代碼,也比較簡單。
樣式代碼
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body,
html {
height: 100%;
width: 100%;
overflow: hidden;
}
body {
background: url('https://www.eveningwater.com/my-web-projects/js/21/img/2.gif') no-repeat center / cover;
display: flex;
justify-content: center;
align-items: center;
}
.ew-box {
position: absolute;
width: 8rem;
height: 8rem;
}
.ew-box-item {
width: 1.6rem;
height: 1.6rem;
border-radius: 4px;
border: 1px solid #535455;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
cursor: pointer;
transition: all .4s cubic-bezier(0.075, 0.82, 0.165, 1);
}
.ew-collection {
width: 8rem;
height: 2.4rem;
display: flex;
align-items: center;
justify-content: center;
padding: 0 1rem;
background: url('https://www.eveningwater.com/static/dist/20d6c430c2496590f224.jpg') no-repeat center/cover;
position: fixed;
margin: auto;
overflow: auto;
bottom: 10px;
}
.ew-collection > .ew-box-item {
margin-right: 0.3rem;
}
.ew-left-source,
.ew-right-source {
width: 2.6rem;
height: 1.2rem;
position: absolute;
top: 0;
}
.ew-left-source {
left: 0;
}
.ew-right-source {
right: 0;
}
.ew-shadow {
box-shadow: 0 0 50px 10px #535455 inset;
}
首先是通配選擇器'*'代表匹配所有的元素,并且設(shè)置樣式初始化,然后是html和body元素設(shè)置寬高為100%,并且隱藏溢出的內(nèi)容,然后給body元素設(shè)置了一個背景圖,并且body元素采用彈性盒子布局,水平垂直居中。
接下來是中間消除的盒子元素box,也很簡單就是設(shè)置定位,和固定寬高為8rem。
接下來是box-item,代表每一個塊元素,也就是消消樂的每一塊元素,接著羊了個羊底部有一個存儲選中塊元素的收集盒子元素,也就是ew-collection,然后是左右的看不到層級的卡牌容器元素。
最后就是為了讓塊元素看起來有層疊效果而添加的陰影效果。
javascript代碼
css核心代碼也就比較簡單,接下來我們來看javascript代碼。
導入圖片素材列表
在開始之前,我們需要先導入圖片素材列表,這里如下:
const globalImageList = [
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/1.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/2.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/3.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/4.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/5.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/6.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/7.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/8.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/9.jpg',
'https://www.eveningwater.com/my-web-projects/jQuery/7/img/10.jpg'
]
然后在onload也就是頁面加載時調(diào)用我們封裝好的Game類,將這個素材列表傳入其中。如下:
window.onload = () => {
const game = new Game(globalImageList);
}
接下來,我們來看Game類的核心代碼吧,首先定義這個類:
class Game {
constructor(originSource, bindElement){
//核心代碼
}
}
這個類名有2個參數(shù),第一個參數(shù)就是素材列表,第二個參數(shù)則是綁定的DOM元素,默認如果不傳入的話,就綁定到document.body上。也因此,我們在構(gòu)造函數(shù)里面初始化一些后續(xù)需要用到的變量。如下:
//constructor內(nèi)部
this.doc = document;
this.originSource = originSource;
this.bindElement = bindElement || this.doc.body;
// 存儲隨機打亂的元素
this.source = [];
// 存儲點擊的元素
this.temp = {};
// dom元素
this.box = null; //存儲消消樂塊元素的容器盒子元素
this.leftSource = null; //左邊素材容器元素
this.rightSource = null; //右邊素材容器元素
this.collection = null; //收集素材的容器元素
// 需要調(diào)用bind方法修改this指向
this.init().then(this.startHandler.bind(this)); //startHandler為游戲開始的核心邏輯函數(shù),init初始化方法
這里存儲了document對象,存儲了原始素材列表,以及綁定的dom元素,然后還定義了source用來存儲被打亂后的素材列表,以及temp用來存儲點擊的元素,方便做消除,添加陰影這些操作。
還有四個變量,其實也就是存儲dom元素的,如注釋所述。
接下來init方法就是做初始化的一些操作,這個方法返回一個Promise所以才能調(diào)用then方法,然后startHandler是游戲開始的核心邏輯函數(shù),這個后面會講到,注意這里有一個有意思的點,那就是bind(this),因為在then方法內(nèi)部的this并不是指Game這個實例,所以需要調(diào)用bind方法修改this綁定,接下來我們來看init方法做了什么。
init() {
return new Promise(resolve => {
const template = `<div class="ew-box" id="ew-box"></div>
<div class="ew-left-source" id="ew-left-source"></div>
<div class="ew-right-source" id="ew-right-source"></div>
<div class="ew-collection" id="ew-collection"></div>`;
const div = this.create('div');
this.bindElement.insertBefore(div, document.body.firstChild);
this.createElement(div, template);
div.remove();
resolve();
})
}
很顯然這個方法如前所述返回了一個Promise,內(nèi)部定義了template模板代碼,也就是頁面的結(jié)構(gòu),然后調(diào)用create方法創(chuàng)建一個容器元素,并且向body元素的首個子元素之前插入這個元素,然后在這個容器元素之前插入創(chuàng)建好的頁面結(jié)構(gòu),刪除這個容器元素,并且resolve出去,從而達到將頁面元素添加到body元素內(nèi)部。這里涉及到了兩個工具函數(shù),我們分別來看看它們,如下:
create(name) {
return this.doc.createElement(name);
}
create方法其實也就是調(diào)用createElement方法來創(chuàng)建一個DOM元素,this.doc指的就是document文件對象,也就是說,create方法只是document.createElement的一個封裝而已。來看createElement方法。
createElement(el, str) {
return el.insertAdjacentHTML('beforebegin', str);
}
createElement方法傳入2個參數(shù),第一個參數(shù)是一個DOM元素,第二個參數(shù)是一個DOM元素字符串,表示在第一個DOM元素之前插入傳入的模板元素。這個方法可以參考code-segment。
startHandler函數(shù)實現(xiàn)
init方法說白了就是動態(tài)創(chuàng)建元素的一個實現(xiàn),接下來就是startHandler函數(shù)的實現(xiàn)。
startHandler() {
this.box = this.$('#ew-box');
this.leftSource = this.$('#ew-left-source');
this.rightSource = this.$('#ew-right-source');
this.collection = this.$('#ew-collection');
this.resetHandler();
//后續(xù)還有邏輯
}
startHandler是核心實現(xiàn),所以不可能只有上面那么點代碼,但是我們要寫一步步的拆分,以上的代碼就做了2個邏輯,獲取DOM元素和重置。這里涉及到了一個$方法,如下:
$(selector, el = this.doc) {
return el.querySelector(selector);
}
$方法傳入2個參數(shù),第一個參數(shù)為選擇器,是一個字符串,第二個參數(shù)為DOM元素,實際上就是document.querySelector的一個封裝。當然還有一個$$方法,類似,如下:
$$(selector, el = this.doc) {
return el.querySelectorAll(selector);
}
接下來是resetHandler方法,如下:
resetHandler() {
this.box.innerHTML = '';
this.leftSource.innerHTML = '';
this.rightSource.innerHTML = '';
this.collection.innerHTML = '';
this.temp = {};
this.source = [];
}
可以看到resetHandler方法確實是如其定義的那樣,就是做重置的,我們要重置用到的數(shù)據(jù)以及DOM元素的子節(jié)點。
讓我們繼續(xù),在startHandler也就是resetHandler方法的后面,添加這樣的代碼:
startHandler() {
this.box = this.$('#ew-box');
this.leftSource = this.$('#ew-left-source');
this.rightSource = this.$('#ew-right-source');
this.collection = this.$('#ew-collection');
this.resetHandler();
for (let i = 0; i < 12; i++) {
this.originSource.forEach((src, index) => {
this.source.push({
src,
index
})
})
}
this.source = this.randomList(this.source);
//后續(xù)還有邏輯
}
可以看到這里實際上就是對素材數(shù)據(jù)做了一個添加和轉(zhuǎn)換操作,randomList方法顧名思義,就是打亂素材列表的順序。
randomList 工具方法
讓我們來看這個工具方法的源碼:
/**
* 打亂順序
* @param {*} arr
* @returns
*/
randomList(arr) {
const newArr = [...arr];
for (let i = newArr.length - 1; i >= 0; i--) {
const index = Math.floor(Math.random() * i + 1);
const next = newArr[index];
newArr[index] = newArr[i];
newArr[i] = next;
}
return newArr;
}
這個函數(shù)的作用就是將素材列表隨機打亂以達到隨機的目的,接下來,讓我們繼續(xù)。
startHandler() {
this.box = this.$('#ew-box');
this.leftSource = this.$('#ew-left-source');
this.rightSource = this.$('#ew-right-source');
this.collection = this.$('#ew-collection');
this.resetHandler();
for (let i = 0; i < 12; i++) {
this.originSource.forEach((src, index) => {
this.source.push({
src,
index
})
})
}
this.source = this.randomList(this.source);
//后續(xù)還有邏輯
for (let k = 5; k > 0; k--) {
for (let i = 0; i < 5; i++) {
for (let j = 0; j < k; j++) {
const item = this.create('div');
item.setAttribute('x', i);
item.setAttribute('y', j);
item.setAttribute('z', k);
item.className = `ew-box-item ew-box-${i}-${j}-${k}`;
item.style.position = 'absolute';
const image = this.source.splice(0, 1);
// 1.44為item設(shè)置的寬度與高度
item.style.left = 1.44 * j + Math.random() * .1 * k + 'rem';
item.style.top = 1.44 * i + Math.random() * .1 * k + 'rem';
item.setAttribute('index', image[0].index);
item.style.backgroundImage = `url(${image[0].src})`;
const clickHandler = () => {
// 如果是在收集框里是不能夠點擊的
if(item.parentElement.className === 'ew-collection'){
return;
}
// 沒有陰影效果的元素才能夠點擊
if (!item.classList.contains('ew-shadow')) {
const currentIndex = item.getAttribute('index');
if (this.temp[currentIndex]) {
this.temp[currentIndex] += 1;
} else {
this.temp[currentIndex] = 1;
}
item.style.position = 'static';
this.collection.appendChild(item);
// 重置陰影效果
this.$$('.ew-box-item',this.box).forEach(item => item.classList.remove('ew-shadow'));
this.createShadow();
// 等于3個就消除掉
if (this.temp[currentIndex] === 3) {
this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove());
this.temp[currentIndex] = 0;
}
let num = 0;
for (let i in this.temp) {
num += this.temp[i];
}
if (num > 7) {
item.removeEventListener('click', clickHandler);
this.gameOver();
}
}
}
item.addEventListener('click', clickHandler)
this.box.append(item);
}
}
}
}
這里的代碼很長,但是總結(jié)下來就二點,添加塊元素,并為每個塊元素綁定點擊事件。我們知道羊了個羊每一個消除的塊元素都會有層疊的效果,那么我們這里也要實現(xiàn)同樣的效果,如何實現(xiàn)呢?
答案就是定位,我們應(yīng)該知道定位會分為層級關(guān)系,層級越高就會占上面,這里也是采用同樣的道理,這里之所以用3個循環(huán),就是盒子元素是分成5行5列的,所以也就是為什么循環(huán)是5的原因。
然后在循環(huán)內(nèi)部,我們就是創(chuàng)建每一個塊元素,每個元素都設(shè)置了x,y,z三個屬性,并且還添加了ew-box-${i}-${j}-${k}
類名,很顯然這里的x,y,z屬性和這個類名關(guān)聯(lián)上了,這方便我們后續(xù)對元素進行操作。
同樣的每個塊元素我們也設(shè)置了樣式,類名是'ew-box-item',同樣的每個塊元素也設(shè)置為絕對定位。
PS: 大家可能有些好奇為什么每個元素我都加一個'ew-'的前綴,其實也就是我個人喜歡給自己寫的代碼加的一個前綴,代表這是我自己寫的代碼的一個標志。
接下來從素材列表中取出單個素材,取出的數(shù)據(jù)結(jié)構(gòu)應(yīng)該是{ src:'圖片路徑',index:'索引值' }這樣。然后將該元素設(shè)置背景圖,就是素材列表的圖片路徑,以及index屬性,還有l(wèi)eft和top偏移值,這里的left和top偏移值之所以是隨機的,也就是因為每一個塊元素都是隨機的。
接下來是clickHandler也就是點擊塊元素執(zhí)行的回調(diào),這個我們先不詳細敘述,我們繼續(xù)往后看,就是為該元素添加事件,利用addEventListener方法,并且將塊元素添加到box盒子元素中。
clickHandler函數(shù)內(nèi)部
讓我們繼續(xù)來看clickHandler函數(shù)內(nèi)部。
首先這里有這樣一個判斷:
if(item.parentElement.className === 'ew-collection'){
return;
}
很簡單,當我們的收集框里面點擊該元素,是不能觸發(fā)點擊事件的,所以這里要做判斷。
然后又是一個判斷,有陰影效果的都是多了一個類名'ew-shadow',有陰影效果代表它的層級最小,被疊加遮蓋住了,所以無法被點擊。
接下來獲取當前點擊塊元素的index索引值,這也是為什么在添加塊元素之前會設(shè)置一個index屬性的原因。
然后判斷點擊的次數(shù),如果點擊的是同一個,則在temp對象里面存儲點擊的索引值,否則點擊的是不同的塊元素,索引值就是1。
然后將該元素的定位設(shè)置為靜態(tài)定位,也就是默認值,并且添加到收集框容器元素當中去。
createShadow方法
接下來就是重置陰影效果,并且重新添加陰影效果。這里有一個createShadow方法,讓我們來揭開它的神秘面紗。如下:
createShadow(){
this.$$('.ew-box-item',this.box).forEach((item,index) => {
let x = item.getAttribute('x'),
y = item.getAttribute('y'),
z = item.getAttribute('z'),
ele = this.$$(`.ew-box-${x}-${y}-${z - 1}`),
eleOther = this.$$(`.ew-box-${x + 1}-${y + 1}-${z - 1}`);
if (ele.length || eleOther.length) {
item.classList.add('ew-shadow');
}
})
}
這里很顯然通過獲取x,y,z屬性設(shè)置的類名來確定是否需要添加陰影,因為通過這三個屬性值可以確定元素的層級,如果不是在最上方,就能夠獲取到該元素,所以就添加陰影。注意$$方法返回的是一個NodeList集合,所以可以拿到length屬性。
接下來就是通過存儲的索引值等于3個,代表選中了3個相同的塊,那就要從收集框里面移除掉該三個塊元素,并且重置對應(yīng)的index索引值為0。
接下來的for...in循環(huán)所做的操作當然是統(tǒng)計收集框里面的塊元素,如果達到了7個代表槽位滿了,然后游戲結(jié)束,并且移除塊元素的點擊事件。我們來看游戲結(jié)束這個方法的實現(xiàn):
gameOver() {
const self = this;
ewConfirm({
title: "溫馨提示",
content: "游戲結(jié)束,別灰心,你能行的!",
sureText: "重新開始",
isClickModal:false,
sure(context) {
context.close();
self.startHandler();
}
})
}
這也是最開始提到的彈框插件的用法,在點擊確認的回調(diào)里面調(diào)用startHandler方法表示重新開始游戲,這沒什么好說的。
到這里,我們實現(xiàn)了中間盒子元素的每一個塊元素與槽位容器元素的對應(yīng)邏輯,接下來還有2點,那就是被遮蓋看不到層級的兩邊塊元素集合。所以繼續(xù)看startHandler后續(xù)的邏輯。
startHandler后續(xù)的邏輯
startHandler() {
this.box = this.$('#ew-box');
this.leftSource = this.$('#ew-left-source');
this.rightSource = this.$('#ew-right-source');
this.collection = this.$('#ew-collection');
this.resetHandler();
for (let i = 0; i < 12; i++) {
this.originSource.forEach((src, index) => {
this.source.push({
src,
index
})
})
}
this.source = this.randomList(this.source);
for (let k = 5; k > 0; k--) {
for (let i = 0; i < 5; i++) {
for (let j = 0; j < k; j++) {
const item = this.create('div');
item.setAttribute('x', i);
item.setAttribute('y', j);
item.setAttribute('z', k);
item.className = `ew-box-item ew-box-${i}-${j}-${k}`;
item.style.position = 'absolute';
const image = this.source.splice(0, 1);
// 1.44為item設(shè)置的寬度與高度
item.style.left = 1.44 * j + Math.random() * .1 * k + 'rem';
item.style.top = 1.44 * i + Math.random() * .1 * k + 'rem';
item.setAttribute('index', image[0].index);
item.style.backgroundImage = `url(${image[0].src})`;
const clickHandler = () => {
// 如果是在收集框里是不能夠點擊的
if(item.parentElement.className === 'ew-collection'){
return;
}
// 沒有陰影效果的元素才能夠點擊
if (!item.classList.contains('ew-shadow')) {
const currentIndex = item.getAttribute('index');
if (this.temp[currentIndex]) {
this.temp[currentIndex] += 1;
} else {
this.temp[currentIndex] = 1;
}
item.style.position = 'static';
this.collection.appendChild(item);
// 重置陰影效果
this.$$('.ew-box-item',this.box).forEach(item => item.classList.remove('ew-shadow'));
this.createShadow();
// 等于3個就消除掉
if (this.temp[currentIndex] === 3) {
this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove());
this.temp[currentIndex] = 0;
}
let num = 0;
for (let i in this.temp) {
num += this.temp[i];
}
if (num >= 7) {
item.removeEventListener('click', clickHandler);
this.gameOver();
}
}
}
item.addEventListener('click', clickHandler)
this.box.append(item);
}
}
}
//從這里開始分析
let len = Math.ceil(this.source.length / 2);
this.source.forEach((item, index) => {
let div = this.create('div');
div.classList.add('ew-box-item')
div.setAttribute('index', item.index);
div.style.backgroundImage = `url(${item.src})`;
div.style.position = 'absolute';
div.style.top = 0;
if (index > len) {
div.style.right = `${(5 * (index - len)) / 100}rem`;
this.rightSource.appendChild(div);
} else {
div.style.left = `${(5 * index) / 100}rem`;
this.leftSource.appendChild(div)
}
const clickHandler = () => {
if(div.parentElement.className === 'ew-collection'){
return;
}
const currentIndex = div.getAttribute('index');
if (this.temp[currentIndex]) {
this.temp[currentIndex] += 1;
} else {
this.temp[currentIndex] = 1;
}
div.style.position = 'static';
this.collection.appendChild(div);
if (this.temp[currentIndex] === 3) {
this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove());
this.temp[currentIndex] = 0;
}
let num = 0;
for (let i in this.temp) {
num += this.temp[i];
}
if (num >= 7) {
div.removeEventListener('click', clickHandler);
this.gameOver();
}
}
div.addEventListener('click', clickHandler);
});
this.createShadow();
}
這里很顯然取的是source素材列表的一般來分別生成對應(yīng)的左右素材列表,同理,這里面的塊元素點擊事件邏輯應(yīng)該是和塊容器元素里面的邏輯是很相似的,所以沒什么好說的。我們主要看以下這段代碼:
let div = this.create('div');
div.classList.add('ew-box-item');
div.setAttribute('index', item.index);
div.style.backgroundImage = `url(${item.src})`;
div.style.position = 'absolute';
div.style.top = 0;
if (index > len) {
div.style.right = `${(5 * (index - len)) / 100}rem`;
this.rightSource.appendChild(div);
} else {
div.style.left = `${(5 * index) / 100}rem`;
this.leftSource.appendChild(div)
}
其實這里也很好理解,也就是同樣的創(chuàng)建塊元素,這里根據(jù)index > len來確定是添加到右邊素材容器元素還是左邊素材元素,并且它們的top偏移量應(yīng)該是一致的,主要不同在left和right而已,計算方式也很簡單。
注意這里是不需要設(shè)置x,y,z屬性的,因為不需要用到設(shè)置陰影的函數(shù)。
到此為止,我們一個《羊了個羊——動物版》的小游戲就完成了。
如有興趣可以參考源碼。
更多關(guān)于jS 羊了個羊小游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
-
js前端表單數(shù)據(jù)處理表單數(shù)據(jù)校驗
這篇文章主要為大家介紹了js前端表單數(shù)據(jù)處理表單數(shù)據(jù)校驗示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪 2022-07-07
-
微信小程序獲取循環(huán)元素id以及wx.login登錄操作
這篇文章主要介紹了微信小程序獲取循環(huán)元素id以及wx.login登錄操作的相關(guān)資料,這里提供實例幫助大家實現(xiàn)該功能,需要的朋友可以參考下 2017-08-08
-
網(wǎng)站申請不到支付寶接口、微信接口,免接口收款實現(xiàn)方式幾種解決辦法
這篇文章主要介紹了網(wǎng)站申請不到支付寶接口、微信接口,免接口收款實現(xiàn)方式幾種解決辦法的相關(guān)資料,需要的朋友可以參考下 2016-12-12
最新評論
引言
這兩天火爆全場的《羊了個羊》游戲,相信大家都玩過了,那么在玩這個游戲的同時,我想大家都會好奇這個游戲的實現(xiàn),本文就帶大家使用css,html,js來實現(xiàn)一個動物版的游戲。
首先我用到了2個插件,第一個插件就是flexible.js,這個插件就是對不同設(shè)備設(shè)置根元素字體大小,也就是一個移動端的適配方案。
因為這里使用了rem布局,針對移動端做了自適應(yīng),所以這里選擇采用rem布局方案。
rem布局方案
還有一個彈框插件,我很早自行實現(xiàn)的,就是popbox.js,關(guān)于這個插件,本文不打算講解實現(xiàn)原理,只講解一下使用原理:
popbox.js使用原理
ewConfirm({ title: "溫馨提示", //彈框標題 content: "游戲結(jié)束,別灰心,你能行的!", //彈框內(nèi)容 sureText: "重新開始", //確認按鈕文本 isClickModal:false, //點擊遮罩層是否關(guān)閉彈框 sure(context) { context.close(); //點擊確認按鈕執(zhí)行的邏輯 },//點擊確認的事件回調(diào) })
引入了這個js之后,會在window對象上綁定一個ewConfirm方法,這個方法傳入一個自定義對象,對象的屬性有title,content,sureText,cancelText,cancel,sure,isClickModal
這幾個屬性,當然這里沒有用到cancel按鈕,所以不細講。
正如注釋所說,每個屬性代表的意思,這里不做贅述。
html代碼
<!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>羊了個羊《動物版》</title> <link rel="stylesheet" href="./style.css" rel="external nofollow" > </head> <body> </body> <script src="https://www.eveningwater.com/static/plugin/popbox.min.js"></script> <script src="https://www.eveningwater.com/test/demo/flexible.js"></script> <script src="./script.js"></script> </html>
可以看到html代碼是什么都沒有的,因為里面的DOM元素,我們都放在js代碼里面動態(tài)生成了,所以script.js這里的代碼是核心,這個后續(xù)會講到,接下來看樣式代碼,也比較簡單。
樣式代碼
* { margin: 0; padding: 0; box-sizing: border-box; } body, html { height: 100%; width: 100%; overflow: hidden; } body { background: url('https://www.eveningwater.com/my-web-projects/js/21/img/2.gif') no-repeat center / cover; display: flex; justify-content: center; align-items: center; } .ew-box { position: absolute; width: 8rem; height: 8rem; } .ew-box-item { width: 1.6rem; height: 1.6rem; border-radius: 4px; border: 1px solid #535455; background-position: center; background-size: cover; background-repeat: no-repeat; cursor: pointer; transition: all .4s cubic-bezier(0.075, 0.82, 0.165, 1); } .ew-collection { width: 8rem; height: 2.4rem; display: flex; align-items: center; justify-content: center; padding: 0 1rem; background: url('https://www.eveningwater.com/static/dist/20d6c430c2496590f224.jpg') no-repeat center/cover; position: fixed; margin: auto; overflow: auto; bottom: 10px; } .ew-collection > .ew-box-item { margin-right: 0.3rem; } .ew-left-source, .ew-right-source { width: 2.6rem; height: 1.2rem; position: absolute; top: 0; } .ew-left-source { left: 0; } .ew-right-source { right: 0; } .ew-shadow { box-shadow: 0 0 50px 10px #535455 inset; }
首先是通配選擇器'*'代表匹配所有的元素,并且設(shè)置樣式初始化,然后是html和body元素設(shè)置寬高為100%,并且隱藏溢出的內(nèi)容,然后給body元素設(shè)置了一個背景圖,并且body元素采用彈性盒子布局,水平垂直居中。
接下來是中間消除的盒子元素box,也很簡單就是設(shè)置定位,和固定寬高為8rem。
接下來是box-item,代表每一個塊元素,也就是消消樂的每一塊元素,接著羊了個羊底部有一個存儲選中塊元素的收集盒子元素,也就是ew-collection,然后是左右的看不到層級的卡牌容器元素。
最后就是為了讓塊元素看起來有層疊效果而添加的陰影效果。
javascript代碼
css核心代碼也就比較簡單,接下來我們來看javascript代碼。
導入圖片素材列表
在開始之前,我們需要先導入圖片素材列表,這里如下:
const globalImageList = [ 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/1.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/2.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/3.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/4.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/5.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/6.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/7.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/8.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/9.jpg', 'https://www.eveningwater.com/my-web-projects/jQuery/7/img/10.jpg' ]
然后在onload也就是頁面加載時調(diào)用我們封裝好的Game類,將這個素材列表傳入其中。如下:
window.onload = () => { const game = new Game(globalImageList); }
接下來,我們來看Game類的核心代碼吧,首先定義這個類:
class Game { constructor(originSource, bindElement){ //核心代碼 } }
這個類名有2個參數(shù),第一個參數(shù)就是素材列表,第二個參數(shù)則是綁定的DOM元素,默認如果不傳入的話,就綁定到document.body上。也因此,我們在構(gòu)造函數(shù)里面初始化一些后續(xù)需要用到的變量。如下:
//constructor內(nèi)部 this.doc = document; this.originSource = originSource; this.bindElement = bindElement || this.doc.body; // 存儲隨機打亂的元素 this.source = []; // 存儲點擊的元素 this.temp = {}; // dom元素 this.box = null; //存儲消消樂塊元素的容器盒子元素 this.leftSource = null; //左邊素材容器元素 this.rightSource = null; //右邊素材容器元素 this.collection = null; //收集素材的容器元素 // 需要調(diào)用bind方法修改this指向 this.init().then(this.startHandler.bind(this)); //startHandler為游戲開始的核心邏輯函數(shù),init初始化方法
這里存儲了document對象,存儲了原始素材列表,以及綁定的dom元素,然后還定義了source用來存儲被打亂后的素材列表,以及temp用來存儲點擊的元素,方便做消除,添加陰影這些操作。
還有四個變量,其實也就是存儲dom元素的,如注釋所述。
接下來init方法就是做初始化的一些操作,這個方法返回一個Promise所以才能調(diào)用then方法,然后startHandler是游戲開始的核心邏輯函數(shù),這個后面會講到,注意這里有一個有意思的點,那就是bind(this),因為在then方法內(nèi)部的this并不是指Game這個實例,所以需要調(diào)用bind方法修改this綁定,接下來我們來看init方法做了什么。
init() { return new Promise(resolve => { const template = `<div class="ew-box" id="ew-box"></div> <div class="ew-left-source" id="ew-left-source"></div> <div class="ew-right-source" id="ew-right-source"></div> <div class="ew-collection" id="ew-collection"></div>`; const div = this.create('div'); this.bindElement.insertBefore(div, document.body.firstChild); this.createElement(div, template); div.remove(); resolve(); }) }
很顯然這個方法如前所述返回了一個Promise,內(nèi)部定義了template模板代碼,也就是頁面的結(jié)構(gòu),然后調(diào)用create方法創(chuàng)建一個容器元素,并且向body元素的首個子元素之前插入這個元素,然后在這個容器元素之前插入創(chuàng)建好的頁面結(jié)構(gòu),刪除這個容器元素,并且resolve出去,從而達到將頁面元素添加到body元素內(nèi)部。這里涉及到了兩個工具函數(shù),我們分別來看看它們,如下:
create(name) { return this.doc.createElement(name); }
create方法其實也就是調(diào)用createElement方法來創(chuàng)建一個DOM元素,this.doc指的就是document文件對象,也就是說,create方法只是document.createElement的一個封裝而已。來看createElement方法。
createElement(el, str) { return el.insertAdjacentHTML('beforebegin', str); }
createElement方法傳入2個參數(shù),第一個參數(shù)是一個DOM元素,第二個參數(shù)是一個DOM元素字符串,表示在第一個DOM元素之前插入傳入的模板元素。這個方法可以參考code-segment。
startHandler函數(shù)實現(xiàn)
init方法說白了就是動態(tài)創(chuàng)建元素的一個實現(xiàn),接下來就是startHandler函數(shù)的實現(xiàn)。
startHandler() { this.box = this.$('#ew-box'); this.leftSource = this.$('#ew-left-source'); this.rightSource = this.$('#ew-right-source'); this.collection = this.$('#ew-collection'); this.resetHandler(); //后續(xù)還有邏輯 }
startHandler是核心實現(xiàn),所以不可能只有上面那么點代碼,但是我們要寫一步步的拆分,以上的代碼就做了2個邏輯,獲取DOM元素和重置。這里涉及到了一個$方法,如下:
$(selector, el = this.doc) { return el.querySelector(selector); }
$方法傳入2個參數(shù),第一個參數(shù)為選擇器,是一個字符串,第二個參數(shù)為DOM元素,實際上就是document.querySelector的一個封裝。當然還有一個$$方法,類似,如下:
$$(selector, el = this.doc) { return el.querySelectorAll(selector); }
接下來是resetHandler方法,如下:
resetHandler() { this.box.innerHTML = ''; this.leftSource.innerHTML = ''; this.rightSource.innerHTML = ''; this.collection.innerHTML = ''; this.temp = {}; this.source = []; }
可以看到resetHandler方法確實是如其定義的那樣,就是做重置的,我們要重置用到的數(shù)據(jù)以及DOM元素的子節(jié)點。
讓我們繼續(xù),在startHandler也就是resetHandler方法的后面,添加這樣的代碼:
startHandler() { this.box = this.$('#ew-box'); this.leftSource = this.$('#ew-left-source'); this.rightSource = this.$('#ew-right-source'); this.collection = this.$('#ew-collection'); this.resetHandler(); for (let i = 0; i < 12; i++) { this.originSource.forEach((src, index) => { this.source.push({ src, index }) }) } this.source = this.randomList(this.source); //后續(xù)還有邏輯 }
可以看到這里實際上就是對素材數(shù)據(jù)做了一個添加和轉(zhuǎn)換操作,randomList方法顧名思義,就是打亂素材列表的順序。
randomList 工具方法
讓我們來看這個工具方法的源碼:
/** * 打亂順序 * @param {*} arr * @returns */ randomList(arr) { const newArr = [...arr]; for (let i = newArr.length - 1; i >= 0; i--) { const index = Math.floor(Math.random() * i + 1); const next = newArr[index]; newArr[index] = newArr[i]; newArr[i] = next; } return newArr; }
這個函數(shù)的作用就是將素材列表隨機打亂以達到隨機的目的,接下來,讓我們繼續(xù)。
startHandler() { this.box = this.$('#ew-box'); this.leftSource = this.$('#ew-left-source'); this.rightSource = this.$('#ew-right-source'); this.collection = this.$('#ew-collection'); this.resetHandler(); for (let i = 0; i < 12; i++) { this.originSource.forEach((src, index) => { this.source.push({ src, index }) }) } this.source = this.randomList(this.source); //后續(xù)還有邏輯 for (let k = 5; k > 0; k--) { for (let i = 0; i < 5; i++) { for (let j = 0; j < k; j++) { const item = this.create('div'); item.setAttribute('x', i); item.setAttribute('y', j); item.setAttribute('z', k); item.className = `ew-box-item ew-box-${i}-${j}-${k}`; item.style.position = 'absolute'; const image = this.source.splice(0, 1); // 1.44為item設(shè)置的寬度與高度 item.style.left = 1.44 * j + Math.random() * .1 * k + 'rem'; item.style.top = 1.44 * i + Math.random() * .1 * k + 'rem'; item.setAttribute('index', image[0].index); item.style.backgroundImage = `url(${image[0].src})`; const clickHandler = () => { // 如果是在收集框里是不能夠點擊的 if(item.parentElement.className === 'ew-collection'){ return; } // 沒有陰影效果的元素才能夠點擊 if (!item.classList.contains('ew-shadow')) { const currentIndex = item.getAttribute('index'); if (this.temp[currentIndex]) { this.temp[currentIndex] += 1; } else { this.temp[currentIndex] = 1; } item.style.position = 'static'; this.collection.appendChild(item); // 重置陰影效果 this.$$('.ew-box-item',this.box).forEach(item => item.classList.remove('ew-shadow')); this.createShadow(); // 等于3個就消除掉 if (this.temp[currentIndex] === 3) { this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove()); this.temp[currentIndex] = 0; } let num = 0; for (let i in this.temp) { num += this.temp[i]; } if (num > 7) { item.removeEventListener('click', clickHandler); this.gameOver(); } } } item.addEventListener('click', clickHandler) this.box.append(item); } } } }
這里的代碼很長,但是總結(jié)下來就二點,添加塊元素,并為每個塊元素綁定點擊事件。我們知道羊了個羊每一個消除的塊元素都會有層疊的效果,那么我們這里也要實現(xiàn)同樣的效果,如何實現(xiàn)呢?
答案就是定位,我們應(yīng)該知道定位會分為層級關(guān)系,層級越高就會占上面,這里也是采用同樣的道理,這里之所以用3個循環(huán),就是盒子元素是分成5行5列的,所以也就是為什么循環(huán)是5的原因。
然后在循環(huán)內(nèi)部,我們就是創(chuàng)建每一個塊元素,每個元素都設(shè)置了x,y,z三個屬性,并且還添加了ew-box-${i}-${j}-${k}
類名,很顯然這里的x,y,z屬性和這個類名關(guān)聯(lián)上了,這方便我們后續(xù)對元素進行操作。
同樣的每個塊元素我們也設(shè)置了樣式,類名是'ew-box-item',同樣的每個塊元素也設(shè)置為絕對定位。
PS: 大家可能有些好奇為什么每個元素我都加一個'ew-'的前綴,其實也就是我個人喜歡給自己寫的代碼加的一個前綴,代表這是我自己寫的代碼的一個標志。
接下來從素材列表中取出單個素材,取出的數(shù)據(jù)結(jié)構(gòu)應(yīng)該是{ src:'圖片路徑',index:'索引值' }這樣。然后將該元素設(shè)置背景圖,就是素材列表的圖片路徑,以及index屬性,還有l(wèi)eft和top偏移值,這里的left和top偏移值之所以是隨機的,也就是因為每一個塊元素都是隨機的。
接下來是clickHandler也就是點擊塊元素執(zhí)行的回調(diào),這個我們先不詳細敘述,我們繼續(xù)往后看,就是為該元素添加事件,利用addEventListener方法,并且將塊元素添加到box盒子元素中。
clickHandler函數(shù)內(nèi)部
讓我們繼續(xù)來看clickHandler函數(shù)內(nèi)部。
首先這里有這樣一個判斷:
if(item.parentElement.className === 'ew-collection'){ return; }
很簡單,當我們的收集框里面點擊該元素,是不能觸發(fā)點擊事件的,所以這里要做判斷。
然后又是一個判斷,有陰影效果的都是多了一個類名'ew-shadow',有陰影效果代表它的層級最小,被疊加遮蓋住了,所以無法被點擊。
接下來獲取當前點擊塊元素的index索引值,這也是為什么在添加塊元素之前會設(shè)置一個index屬性的原因。
然后判斷點擊的次數(shù),如果點擊的是同一個,則在temp對象里面存儲點擊的索引值,否則點擊的是不同的塊元素,索引值就是1。
然后將該元素的定位設(shè)置為靜態(tài)定位,也就是默認值,并且添加到收集框容器元素當中去。
createShadow方法
接下來就是重置陰影效果,并且重新添加陰影效果。這里有一個createShadow方法,讓我們來揭開它的神秘面紗。如下:
createShadow(){ this.$$('.ew-box-item',this.box).forEach((item,index) => { let x = item.getAttribute('x'), y = item.getAttribute('y'), z = item.getAttribute('z'), ele = this.$$(`.ew-box-${x}-${y}-${z - 1}`), eleOther = this.$$(`.ew-box-${x + 1}-${y + 1}-${z - 1}`); if (ele.length || eleOther.length) { item.classList.add('ew-shadow'); } }) }
這里很顯然通過獲取x,y,z屬性設(shè)置的類名來確定是否需要添加陰影,因為通過這三個屬性值可以確定元素的層級,如果不是在最上方,就能夠獲取到該元素,所以就添加陰影。注意$$方法返回的是一個NodeList集合,所以可以拿到length屬性。
接下來就是通過存儲的索引值等于3個,代表選中了3個相同的塊,那就要從收集框里面移除掉該三個塊元素,并且重置對應(yīng)的index索引值為0。
接下來的for...in循環(huán)所做的操作當然是統(tǒng)計收集框里面的塊元素,如果達到了7個代表槽位滿了,然后游戲結(jié)束,并且移除塊元素的點擊事件。我們來看游戲結(jié)束這個方法的實現(xiàn):
gameOver() { const self = this; ewConfirm({ title: "溫馨提示", content: "游戲結(jié)束,別灰心,你能行的!", sureText: "重新開始", isClickModal:false, sure(context) { context.close(); self.startHandler(); } }) }
這也是最開始提到的彈框插件的用法,在點擊確認的回調(diào)里面調(diào)用startHandler方法表示重新開始游戲,這沒什么好說的。
到這里,我們實現(xiàn)了中間盒子元素的每一個塊元素與槽位容器元素的對應(yīng)邏輯,接下來還有2點,那就是被遮蓋看不到層級的兩邊塊元素集合。所以繼續(xù)看startHandler后續(xù)的邏輯。
startHandler后續(xù)的邏輯
startHandler() { this.box = this.$('#ew-box'); this.leftSource = this.$('#ew-left-source'); this.rightSource = this.$('#ew-right-source'); this.collection = this.$('#ew-collection'); this.resetHandler(); for (let i = 0; i < 12; i++) { this.originSource.forEach((src, index) => { this.source.push({ src, index }) }) } this.source = this.randomList(this.source); for (let k = 5; k > 0; k--) { for (let i = 0; i < 5; i++) { for (let j = 0; j < k; j++) { const item = this.create('div'); item.setAttribute('x', i); item.setAttribute('y', j); item.setAttribute('z', k); item.className = `ew-box-item ew-box-${i}-${j}-${k}`; item.style.position = 'absolute'; const image = this.source.splice(0, 1); // 1.44為item設(shè)置的寬度與高度 item.style.left = 1.44 * j + Math.random() * .1 * k + 'rem'; item.style.top = 1.44 * i + Math.random() * .1 * k + 'rem'; item.setAttribute('index', image[0].index); item.style.backgroundImage = `url(${image[0].src})`; const clickHandler = () => { // 如果是在收集框里是不能夠點擊的 if(item.parentElement.className === 'ew-collection'){ return; } // 沒有陰影效果的元素才能夠點擊 if (!item.classList.contains('ew-shadow')) { const currentIndex = item.getAttribute('index'); if (this.temp[currentIndex]) { this.temp[currentIndex] += 1; } else { this.temp[currentIndex] = 1; } item.style.position = 'static'; this.collection.appendChild(item); // 重置陰影效果 this.$$('.ew-box-item',this.box).forEach(item => item.classList.remove('ew-shadow')); this.createShadow(); // 等于3個就消除掉 if (this.temp[currentIndex] === 3) { this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove()); this.temp[currentIndex] = 0; } let num = 0; for (let i in this.temp) { num += this.temp[i]; } if (num >= 7) { item.removeEventListener('click', clickHandler); this.gameOver(); } } } item.addEventListener('click', clickHandler) this.box.append(item); } } } //從這里開始分析 let len = Math.ceil(this.source.length / 2); this.source.forEach((item, index) => { let div = this.create('div'); div.classList.add('ew-box-item') div.setAttribute('index', item.index); div.style.backgroundImage = `url(${item.src})`; div.style.position = 'absolute'; div.style.top = 0; if (index > len) { div.style.right = `${(5 * (index - len)) / 100}rem`; this.rightSource.appendChild(div); } else { div.style.left = `${(5 * index) / 100}rem`; this.leftSource.appendChild(div) } const clickHandler = () => { if(div.parentElement.className === 'ew-collection'){ return; } const currentIndex = div.getAttribute('index'); if (this.temp[currentIndex]) { this.temp[currentIndex] += 1; } else { this.temp[currentIndex] = 1; } div.style.position = 'static'; this.collection.appendChild(div); if (this.temp[currentIndex] === 3) { this.$$(`div[index="${currentIndex}"]`, this.collection).forEach(item => item.remove()); this.temp[currentIndex] = 0; } let num = 0; for (let i in this.temp) { num += this.temp[i]; } if (num >= 7) { div.removeEventListener('click', clickHandler); this.gameOver(); } } div.addEventListener('click', clickHandler); }); this.createShadow(); }
這里很顯然取的是source素材列表的一般來分別生成對應(yīng)的左右素材列表,同理,這里面的塊元素點擊事件邏輯應(yīng)該是和塊容器元素里面的邏輯是很相似的,所以沒什么好說的。我們主要看以下這段代碼:
let div = this.create('div'); div.classList.add('ew-box-item'); div.setAttribute('index', item.index); div.style.backgroundImage = `url(${item.src})`; div.style.position = 'absolute'; div.style.top = 0; if (index > len) { div.style.right = `${(5 * (index - len)) / 100}rem`; this.rightSource.appendChild(div); } else { div.style.left = `${(5 * index) / 100}rem`; this.leftSource.appendChild(div) }
其實這里也很好理解,也就是同樣的創(chuàng)建塊元素,這里根據(jù)index > len來確定是添加到右邊素材容器元素還是左邊素材元素,并且它們的top偏移量應(yīng)該是一致的,主要不同在left和right而已,計算方式也很簡單。
注意這里是不需要設(shè)置x,y,z屬性的,因為不需要用到設(shè)置陰影的函數(shù)。
到此為止,我們一個《羊了個羊——動物版》的小游戲就完成了。
如有興趣可以參考源碼。
更多關(guān)于jS 羊了個羊小游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js前端表單數(shù)據(jù)處理表單數(shù)據(jù)校驗
這篇文章主要為大家介紹了js前端表單數(shù)據(jù)處理表單數(shù)據(jù)校驗示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07微信小程序獲取循環(huán)元素id以及wx.login登錄操作
這篇文章主要介紹了微信小程序獲取循環(huán)元素id以及wx.login登錄操作的相關(guān)資料,這里提供實例幫助大家實現(xiàn)該功能,需要的朋友可以參考下2017-08-08網(wǎng)站申請不到支付寶接口、微信接口,免接口收款實現(xiàn)方式幾種解決辦法
這篇文章主要介紹了網(wǎng)站申請不到支付寶接口、微信接口,免接口收款實現(xiàn)方式幾種解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12