HTML5游戏实战之拼图游戏(包含关卡)

拼图游戏是每个人小时候都玩过的经典休闲游戏,依托Hola Studio强大的图片功能和事件回调体系,实现一个游戏性十足的拼图游戏并不难。本文就介绍整个游戏的制作过程,本游戏包含完整的游戏元素,包括广告、关卡、分享等元素,你可以自行添加其他游戏元素,也可以将素材替换成你需要的元素。

游戏截图
HTML5游戏实战之拼图游戏(包含关卡)_第1张图片

扫描二维码

游戏链接
点击链接打开游戏

导入为你的项目
点击链接导入项目

游戏界面的编辑

游戏主要有四个界面

我们只介绍游戏界面的编辑,其他界面的编辑在之前的博客中已经都介绍过,可以说,用Hola Studio编辑界面简直是再容易不过的事,甚至可以交给策划来做。

游戏界面

游戏界面的控件树形图如图所示:
HTML5游戏实战之拼图游戏(包含关卡)_第2张图片

而界面效果图:
HTML5游戏实战之拼图游戏(包含关卡)_第3张图片

可以说界面元素不太多,里面主要用到两个UIGridViewX控件,作为拼图游戏小图块的起始和目的。

图片的操作

拼图游戏免不了跟图片的打交道,关于图片更多的便捷操作和技巧,可以参考jim写的番外关于图片不得不说的秘密。

图片的存储

在我们的拼图游戏中,是分为多关卡的,每个关卡一张图片,这些关卡的图片都可以存在图片控件的备用图片中。如下图所示:
HTML5游戏实战之拼图游戏(包含关卡)_第4张图片

图片的切割

我们的游戏中,每个图片都被切割成4x4个小图片,然后拼成一张大的,我们不可能又傻又天真,真用PS之类工具去切割成图片。借助Hola Studio强大的图片操作,我们可以采用如下方式切割图片:

        var selection = "#r"+rows+"c"+cols+"i"+index;
        image.setImageSrc(imageSrc + selection);

其中rows是你向切割的总行数,cols是总列数,index是次序,imageSrc是图片的链接,image就是图片控件了,是不是非常简单。另一种切割图片的方法是:

        var selection = "#x"+x+"y"+y+"w"+w+"h"+h;
        image.setImageSrc(imageSrc + selection);

其中x,y就是起始点相对于原始图片原点的位置,w,h就是切割的宽高了。

图片的切换

每个关卡完成以后,要切换到下一张图片,可以采用如下方法

image.setValue(imageIndex);

这里的imageIndex就是备用图片的序号了。参考UIImage

图片的拖动

如果你熟悉Hola Studio就知道Hola Studio有强大的事件代码入口,比如onClick,onBeforeOpen等等,你只需要将代码写道事件里即可。但是对于资深的游戏开发者来说,这种方式显得有点不够“高大上“,今天就着重介绍另一种熟知编辑事件回调的方法——addEventListener

在我们的游戏中,这些个小图片封装成了一个类PImage

function PImage(element) {
    this.element = element; //element就是图片控件
    //处理拖动事件
    element.addEventListener("pointerdown", this.onDragStart.bind(this));
    element.addEventListener("pointermove", this.onDragMove.bind(this));
    element.addEventListener("pointerup", this.onDragEnd.bind(this));

    element.pImage = this;
}

//跟另外一个PImage交换图片资源
PImage.prototype.exchangeImage = function(pImage) {
    var src = this.element.getImageSrc();
    this.element.setImageSrc(pImage.element.getImageSrc());
    pImage.element.setImageSrc(src)
}

PImage.prototype.canHandleEvent = function(event) {
    //如果图片为空,不处理
    if(event.beforeChild || !this.element.getImageSrc()) {
        return false;
    }
    var container = this.element.getParent();
    var controll = container.seats.getControll();
    return !controll.startInfo || controll.startInfo.element == this.element;
}

PImage.prototype.onDragStart = function(event) {
    if(!this.canHandleEvent(event)) {
        return;
    }
    this.startP = {x:event.point.x, y:event.point.y};
    var container = this.element.getParent();
    var point = this.convertEventPoint(event.point);
    container.seats.getControll().onPick(this, point);

    this.element.setOpacity(0);
    this.onDragMove(event);
};

PImage.prototype.onDragMove = function(event) {
    if(!this.canHandleEvent(event)) {
        return;
    }

    var point = this.convertEventPoint(event.point);
    point.x -= this.startP.x;
    point.y -= this.startP.y;
    var container = this.element.getParent();
    container.seats.getControll().onMove(this, point);
};

//转换坐标,因为拖动控件是相对当前小图片,要转换为相对于场景坐标
PImage.prototype.convertEventPoint = function(point) {
    var p = this.element.getPositionInWindow();
    var parent = this.element.getParent();
    //因为父控件是UIGridViewX,有偏移量,需要减去。
    var x = p.x + point.x - parent.getXOffset();
    var y = p.y + point.y - parent.getYOffset() ;
    p = {x:x, y: y};

    return p;
};

PImage.prototype.onDragEnd = function(event) {
    console.log(this.element.name, "pointup");
    if(!this.canHandleEvent(event)) {
        return;
    }
    var point = this.convertEventPoint(event.point);

    var container = this.element.getParent();

    container.seats.getControll().onPut(this, point);
};

关于beforeChild,参考onPointerDown

图片的容器封装

拼图游戏中的起始容器和目的容器分别是一个UIGridViewX控件,需要做一些封装。

//这是公共类
function Seats(container) {
    this.container = container;
    if(container) {
        container.seats = this;
    }
    return this;
}

Seats.prototype.initContainer = function() {
    return this;    
}

Seats.prototype.onPut = function(pImage, point) {
}

Seats.prototype.getControll = function() {
    return this.controll;
}

Seats.prototype.setControll = function(controll) {
    this.controll = controll;
}

Seats.prototype.onPick = function(pImage, point) {
}

Seats.prototype.onMove = function(pImage, point) {
}

Seats.prototype.onCancel = function(pImage, point) {
}

Seats.prototype.onRemove = function(element) {
}
//这是起始容器
function SourceSeats(container) {
    Seats.call(this, container);
    this.pImages = [];
    return this;
}

SourceSeats.prototype = new Seats();
SourceSeats.prototype.constructor = SourceSeats;

SourceSeats.prototype.init = function(baseImage, rows, cols) {
    console.log("SourceSeats.prototype.init");
    var container = this.container;
    var pImages = this.pImages;
    var indexs = [];
    var num = rows * cols;
    for(var i = 0; i < num; i++) {
        indexs.push(i);
    }
    //随机位置
    indexs.sort(function() {
        return Math.random() > 0.5 ? 1 : -1;
    });
    var imageSrc = baseImage.getImageSrc();
    baseImage.setVisible(true).setEnable(true);
    indexs.forEach(function(index) {
        var child = baseImage.clone();
        container.addChild(child);
        child.setImageScale(0, 0);
        //切割图片
        var selection = "#r"+rows+"c"+cols+"i"+index;
        child.setImageSrc(imageSrc + selection);
        pImages.push(new PImage(child));
    });
    container.relayoutChildren();
}

SourceSeats.prototype.onRemove = function(element) {
    if(element.getParent() != this.container) {
        return;
    }
    var index = this.pImages.indexOf(element.pImage);
    this.pImages.splice(index, 1);
    element.remove(true);
    setTimeout(function() {
        this.container.relayoutChildren();
    }.bind(this), 0);
}

SourceSeats.prototype.onPut = function(startInfo, point) {
    var element = startInfo.element;
    var index = startInfo.index;
    var elementAtPoint = element.getWindow().findChildByPoint(point, true);

    if(startInfo.seats != this)  {
        //从dest拖到source,创建一个PImage
        var c = element.clone();
        element.setImageSrc(null);
        c.setOpacity(100);
        this.container.addChild(c, elementAtPoint ? elementAtPoint.getZIndex() - 1 : this.container.getChildren().length + 1);
        this.pImages.push(new PImage(c));
        setTimeout(function() {
            this.container.relayoutChildren();
        }.bind(this), 0);
        this.getControll().onPuttedDone(element, null);
    } else {
        element.setImageSrc(startInfo.imageSrc);
        this.getControll().onPuttedDone(element, elementAtPoint);
    }
}
//这是目标容器
function DestSeats(container) {
    Seats.call(this, container);
    this.pImages = [];
    return this;
}

DestSeats.prototype = new Seats();
DestSeats.prototype.constructor = DestSeats;

DestSeats.prototype.init = function(baseImage, rows, cols) {
    var container = this.container;
    var pImages = this.pImages;
    var num = rows * cols;
    container.setCols(cols);
    container.setRows(rows);
    baseImage.setEnable(true).setVisible(true);
    for(var i = 0; i < num; i++) {
        var child = baseImage.clone();
        child.setImageSrc(null);
        child.setImageScale(0, 0);
        container.addChild(child);
        pImages.push(new PImage(child));
    }
    container.relayoutChildren();
}

DestSeats.prototype.onPut = function(startInfo, point) {
    var element = startInfo.element;
    var index = startInfo.index;
    var elementAtPoint = element.getWindow().findChildByPoint(point, true);
    var removeElement = false;

    element.pImage.exchangeImage(elementAtPoint.pImage);
    this.getControll().onPuttedDone(element, elementAtPoint);
}
//游戏控制类,处理游戏逻辑的
GameControll = function(win) {
    this.win = win;
    this.gameTime = 0;
    win.find("计时器控件").stop();
    win.find("计时器").setValue("00:00s");
}

GameControll.prototype.initGame = function(rows, cols) {
    var win = this.win;

    this.sourceSeats = new SourceSeats(win.find("选图框").getChild(0));
    this.destSeats = new DestSeats(win.find("底图框架").getChild(0));

    this.cols = cols;
    this.rows = rows;
    this.sourceSeats.setControll(this);
    this.destSeats.setControll(this);
    var level = this.readGameLevel();
    var baseImage = win.find("selecting-image");
    var MAX_LEVEL = 11;
    if(level >= MAX_LEVEL) {
        level = 0;
    }
    baseImage.setValue(level);
    //初始化两个容器
    this.sourceSeats.init(baseImage, rows,  cols);
    this.destSeats.init(baseImage, rows, cols);
    baseImage.setVisible(false).setEnable(false);
    this.baseImage = baseImage;
    this.gameLevel = level;
    win.find("迷宫答案").setImageSrc(baseImage.getImageSrc());
};

GameControll.prototype.readGameLevel = function() {
    var value = WebStorage.get("game-level");
    value = parseInt(value);
    return isNaN(value) ? 0 : value; 
}

GameControll.prototype.writeGameLevel = function(level) {
    //存储游戏进度
    WebStorage.set("game-level", level);
}

GameControll.prototype.showGameTime = function() {
    this.gameTime++;
    var win = this.win;
    var min = 0;
    var sec = 0;
    min = Math.floor(this.gameTime/60);
    sec = this.gameTime%60;

    if (min < 10) {
        min = "0"+min;
    }
    if (sec < 10) {
        sec = "0"+sec;
    }
    //显示游戏时长
    win.find("计时器").setValue(min+":"+sec+"s");
}

GameControll.prototype.onPick = function(pImage) {
    if(this.startInfo) {
        return;
    }
    var element = pImage.element;
    this.startInfo = {
        index : element.getZIndex(),
        seats : element.getParent().seats,
        imageSrc : element.getImageSrc(),
        element : element,
    };

    element.getParent().seats.onPick(pImage);

    var baseImage = this.baseImage;
    baseImage.setImageScale(0, 0);
    baseImage.setImageSrc(pImage.element.getImageSrc());
    baseImage.setVisible(true);
}

GameControll.prototype.pointInRect = function(rect, point) {
    return (point.x >= rect.x && point.x <= rect.x + rect.w 
        && point.y >= rect.y && point.y <= rect.y + rect.h);
};

GameControll.prototype.hitElement = function(element, point) {
    var p = element.getPositionInWindow();
    var rect = {x:p.x, y:p.y, w:element.w, h:element.h};
    return this.pointInRect(rect, point);
};

GameControll.prototype.pointInSeats = function(seats, point) {
    return this.hitElement(seats.container, point);
}

GameControll.prototype.onMove = function(pImage, point) {
    var baseImage = this.baseImage;
    baseImage.setVisible(true);
    baseImage.setPosition(point.x, point.y);
}

GameControll.prototype.onPut = function(pImage, point) {
    var win = pImage.element.getWindow();
    if(this.pointInSeats(this.sourceSeats, point)) {
        this.sourceSeats.onPut(this.startInfo, point);
    } else if(this.pointInSeats(this.destSeats, point)) {
        this.destSeats.onPut(this.startInfo, point);
    } else {
        //没有点到网格上,取消替换
        this.onCancel(pImage, point);
    }

    if(this.checkWin()) {
        this.destSeats.container.setEnable(false);
        this.sourceSeats.container.setEnable(false);
        this.onGameWin();
    }
}

GameControll.prototype.setBaseImageSrc = function(src) {
    this.baseImage.setImageSrc(src);
    return this;
}

GameControll.prototype.animateBaseImageToStart = function(onDone) {
    var startInfo = this.startInfo;
    if(!startInfo) {
        return;
    }
    var element = startInfo.element;
    var p = element.getPositionInWindow();
    var sourceContainer = this.sourceSeats.container;
    if(element.getParent() == sourceContainer) {
        p.x -= sourceContainer.getXOffset();
        p.y += sourceContainer.getYOffset();
    }

    var cfg = {xEnd: p.x, yEnd: p.y, duration:300, interpolator:'l'};
    var baseImage = this.baseImage;
    this.animateImage(cfg, baseImage, function() {
        element.setImageSrc(baseImage.getImageSrc());
        element.setOpacity(100);
        baseImage.setVisible(false);
        onDone && onDone();
    }.bind(this))
}

GameControll.prototype.onCancel = function(pImage, point) {
    this.animateBaseImageToStart(function() {
        this.startInfo = null;
    }.bind(this));
}

GameControll.prototype.animateImage = function(cfg, image, onDone) {
    image.setZIndex(100);
    image.animate(cfg, function() {
        if(onDone) onDone();
    });
};

GameControll.prototype.onGameWin = function() {
    var initData = {}
    this.writeGameLevel(this.gameLevel + 1);
    this.win.openWindow("游戏结束界面", initData);
    this.win.find("计时器控件").stop();
}

GameControll.prototype.setBaseImageVisibility = function(visible) {
    this.baseImage.setVisible(visible);
}

GameControll.prototype.confirmPImage = function(pImage) {
    var seats = pImage.element.getParent().seats();
}

GameControll.prototype.checkWin = function() {
    var container = this.destSeats.container;
    var num = this.rows * this.cols;
    for(var i = 0; i < num; i++) {
        var selection = "#r"+this.rows+"c"+this.cols+"i"+i;
        var element = container.getChild(i);
        var src = element.getImageSrc();
        //判断是否位置与图片后缀一致
        if(!src || src.indexOf(selection) < 0) {
            return false;
        }
    }
    return true;
}

GameControll.prototype.onPuttedDone = function(from, to) {
    if(!to) {
        from.setOpacity(100);
        this.baseImage.setVisible(false);
        this.startInfo = null;
        return;
    }
    to.setVisible(true);
    if(!from.getImageSrc()) {
        var element = this.startInfo.element;
        this.startInfo.seats.onRemove(element);
        this.baseImage.setVisible(false);
        from.setOpacity(100);
        this.startInfo = null;
    } else {
        this.setBaseImageSrc(from.getImageSrc()).animateBaseImageToStart(function() {
            from.setOpacity(100);
            this.startInfo = null;
        }.bind(this));
    }
}

你可能感兴趣的:(游戏,html5,拼图,休闲游戏,游戏界面)