目录
HTML+JS+websocket 实例,联机“游戏王”对战 1
HTML+JS+websocket 实例,联机“游戏王”对战 2 - 联机模式
HTML+JS+websocket 实例,联机“游戏王”对战 3 - 界面布局
HTML+JS+websocket 实例,联机“游戏王”对战 4 - 卡组系统
HTML+JS+websocket 实例,联机“游戏王”对战 5 - 卡片选中系统
HTML+JS+websocket 实例,联机“游戏王”对战 6 - 卡片放置,战场更新
HTML+JS+websocket 实例,联机“游戏王”对战 7 - 墓地,副控制面板
HTML+JS+websocket 实例,联机“游戏王”对战 8 - 返回手卡,卡组
HTML+JS+websocket 实例,联机“游戏王”对战 9 - 实现简单 websocket 通信
HTML+JS+websocket 实例,联机“游戏王”对战 10 - 搭建游戏服务端
HTML+JS+websocket 实例,联机“游戏王”对战 11 - 客户端消息的收发
HTML+JS+websocket 实例,联机“游戏王”对战 12 - 消息发送具体场景
HTML+JS+websocket 实例,联机“游戏王”对战 13 - 实机演示
这章开始我们结合 js 代码看看游戏具体功能的实现。大致的规划是先介绍贯穿整个游戏对局的基础功能,之后再针对不同的功能按键和功能板块来逐一介绍。函数中涉及联机的功能会放在实现联机的章节具体介绍。
卡组系统
首先来介绍下整个游戏存放素材的主体,卡组的实现。
1.卡组结构:
游戏的卡组本质上是一个简单的字符串数组,在游戏初始化时就加载存储了每一张卡牌图片的路径(或者URL)。卡组在结构上是一个堆栈,遵循后进先出(LIFO)的原则,即每次送入牌组的卡片默认放在牌组的最上方,若不进行洗牌,那么最后被放入的卡片将在最近的一次抽卡中率先被抽到,就像枪械弹匣一样。
堆栈有两种基本操作,POP(出栈)与 PUSH(入栈)。抽卡时我们执行 POP 操作,将卡组最上方的卡片从卡组中剔除,发到玩家手牌。当碰到某些效果需将手牌或场上的卡片放回卡组最上方时我们则执行 PUSH 操作,将指定的卡片压入卡组的最上方。如需要放回卡组后再切洗,则在执行完 PUSH 操作后再执行洗牌函数即可。
当然,有时候我们会需要从卡组中选取指定卡牌并拿出的效果,这时候被指定的卡片需要从卡组中剔除,如果只是单纯的 POP 操作我们就得一张一张的将卡片取出直到想要的卡片为止,非常麻烦。JS中提供了一个 splice() 方法,它允许我们直接删除或替换数组中的一个或多个元素,只需提供元素的索引值以及需删除元素的个数。在这个方法里我们可提供指定卡牌在卡组中的序号,并且只删除一个元素,即被选中的那张卡牌会被剔除卡组。剔除后该位置不会留空,而是被后面的其他卡向上填补,这意味着卡组中部分卡牌的索引也会发生变化,这个功能我们也会在以后详细说到。
2.卡组的加载:
在游戏对局开始前双方玩家需要加载各自的卡组。
首先要用到几个全局变量:
P1DeckName 用于指示我们此次对局要加载哪一副卡组的卡组名称。一个卡组名称实质上代表一个卡组文件夹,我们可以有多个卡组文件夹,自行填写本次对局要用的那个:
var P1DeckName = "Deck_KaiMa"; //我方牌组名
P1DeckNum 用于指示卡组中的卡牌数量(暂时还未实现自动读取文件夹中的所有文件,实现了可以不用这个变量):
var P1DeckNum = 50; //我方牌组卡片数量
P1Deck 用于存储卡组所有图片url的字符串数组:
var P1Deck = []; //我方牌组(储存我方所有卡片src)
之后通过循环的方法将卡组文件夹中的每一个图片路径给push到字符串数组中:
//储存P1卡组所有卡片路径
for (var i=0; i
当然在这之前我们已经将卡组文件夹中所有卡牌的文件名改成了数字:
加载完成后还要再执行一下洗牌操作,不然每次顺序都是固定的:
/*生成随机数 */
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
/*克隆数组 */
function cloneArr(arr) {
// 从第一个字符就开始 copy
// slice(start,end) 方法可从已有的数组中返回选定的元素。
return arr.slice(0);
}
/*数组洗牌 */
function shuffle(arr) {
let newArr = [];
newArr = arr;
for (let i = 0; i < newArr.length; i++) {
let j = getRandom(0, i);
let temp = newArr[i];
newArr[i] = newArr[j];
newArr[j] = temp;
}
return newArr;
}
P1Deck = shuffle(cloneArr(P1Deck)); //牌组洗牌
3.卡组抽卡的实现:
从卡组抽卡是将卡组数组中的卡牌url传到手牌卡槽当中,实现这个js函数之前我们先回顾下html文件中的手牌区域内容:
我方手卡区中有8个这样的img标签,分别对应8个手卡卡槽,每个标签有独立的id,且src属性默认是留空的,其他属性暂且不管。
每次执行抽卡函数时,我们需要确定一个空的手牌卡槽并且获取它的id,之后给它的src属性赋上抽出卡片的图片url,赋值后网页里才会显示卡牌的图片。
为确保即将赋值的卡槽是空卡槽,我们需要对每个卡槽的src值进行逐一判断。由于src属性留空后它的值并不是“”或者null,而是默认为所属html文件存放的路径,所以我这里又设置了一个全局变量 emptysrc,用于存放此默认路径,并在html文件加载完成后从任意一个空的img元素中获取该路径用于其他函数对于空卡槽的判定。
//获取空的img src路径,方便其他函数判断卡槽是否为空
//window.onload 使函数在html完全加载后执行
var emptysrc;
window.onload = function() {
var handID = 'p1-field0'; //选取任意卡槽id
element = document.getElementById(handID);
emptysrc = element.src;
P1Deck = shuffle(cloneArr(P1Deck)); //洗牌
}
注:此处局部变量 handID 命名有误,这里我选的是一个战场卡槽的 id 而非手卡卡槽,但是不影响函数的运行效果,空src的默认值是相同的。
接下来实现抽卡函数 drawCard () :
遍历我方所有8个手卡卡槽:
获取当前卡槽id;
通过卡槽id获取当前卡槽对象;
如果(卡槽对象是空卡槽):
对卡组数组P1Deck执行POP操作,将卡组最上方的图片url推出并赋值给卡槽对象的src属性;
立刻停止遍历;
/*抽取牌组最上方一张卡至手卡 */
function drawCard() {
for (var i=0; i<8; i++) {
var handID = 'p1-hand' + i.toString();
element = document.getElementById(handID);
if (element.src == emptysrc) { //如果该卡槽为空
element.src = P1Deck.pop();
/*触发抽卡音效 */
var snd = new Audio("sound/draw.wav");
snd.play();
/**
* 告知对手哪张手卡卡槽添加了一张卡
*/
messageHand('add', i);
break;
}
}
}
注:代码中关于联机的函数将放到实现联机的章节一起汇总介绍。
最后一步,触发 drawCard() 的条件是玩家点击了游戏界面卡组区的那个代表卡组的卡背图片,我们找到html中对应的那个元素:
这也是一个img标签,src设置了卡片背面图片的url,onclick属性设置我们写好的抽卡函数drawCard(),这样在点击这个卡背图片时就会向手牌区的空卡槽添加一张卡片了。
其他功能后面的章节来继续介绍。