基于lufylegend和cropper自定义图片的拼图华容道小游戏

前几天公司要做一个拼图的小游戏,我看到了《速度挑战 - 2小时完成HTML5拼图小游戏》,照着写完了一个小游戏以后,这几天使用cropperlufylegend游戏引擎制作了一款简单的可以自定义图片的拼图华容道游戏,该游戏除了实现基本的游戏功能以外,还支持游戏图片上传,剪切,以及图片过大可以进行压缩的功能。

传送门

准备

俗话说“兵马未动粮草先行”,在直接开始撸游戏之前,需要先做一些准备:
(1)cropper
cropper是一款使用简单且功能强大的图片剪裁jQuery插件,我选择使用该插件来实现图片裁剪的功能,在使用之前需要引入cropper:

<link href="https://cdn.bootcss.com/cropperjs/1.3.6/cropper.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/cropperjs/1.3.6/cropper.min.js"></script>

(2)jQuery
jQuery就不多说了,cropper就是jQuery的插件,自然需要引入,需要注意的是jQuery需要在cropper之前引入
(3)lufylegend
lufylegend是一个HTML5开源引擎,使用之前需要引入:

<script src="js/lib/lufylegend-1.10.1.simple.min.js"></script>

(4)WeUI
WeUI是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。使用这个ui库主要是使用了gallery用来展示上传的图片用于下一步的裁剪,要使用的话也需要引入:

<link rel="stylesheet" href="https://res.wx.qq.com/open/libs/weui/1.1.2/weui.min.css">
<script type="text/javascript" src="https://res.wx.qq.com/open/libs/weuijs/1.1.3/weui.min.js"></script>

开始

在做好之前的准备以后,就可以开始撸游戏了

1.布局

游戏本身只需要一个div,整个页面分为两块,图片上传模块和游戏模块,整体还是比较简单,之后可以继续增加其他的内容:

<input type="file" id="file" multiple="multiple" accept="image/*" style="display: none">
<!--图片上传按钮-->
<div class="weui-uploader__input-box getImg">
    <input id="getImg" class="weui-uploader__input">
</div>
<!--图片上传-->
<div id="uploadPhotoBox" class="page gallery js_show img" style="display: none">
    <div class="weui-gallery" style="display: block">
        <div id="photoBox" class="photo weui-gallery__img">
            <img id="photo" style="max-width: 100%" src="">
        </div>
        <div class="tool">
            <a>
                <i id="crop">确定</i>
                <i id="cancel">取消</i>
            </a>
        </div>
    </div>
</div>
<!--游戏-->
<div class="game" style="display: none">
    <div class="time">时间: <span id="time">99</span></div>
    <div class="steps">步数: <span id="steps">0</span></div>
    <div id="myGame"></div>
</div>

还有一点点样式:

.time, .steps {
            position: absolute;
            margin-left: 20%;
            margin-top: 5%;
        }

        .steps {
            margin-top: 10%;
        }

        .tool {
            position: absolute;
            z-index: 2;
            background-color: #111111;
            width: 100%;
        }

        .tool i {
            font-size: 1rem;
            color: white;
            right: 0;
            float: right;
            padding-left: 2rem;
            padding-right: 2rem;
        }

        #cancel {
            float: left;
        }

2.定义全局变量

首先需要定义一些变量,并且添加一些简单的逻辑事件,例如鼠标点击:

var imgResult;//裁剪出来图片的base64值
var imgResultImg = new Image();//裁剪出来的图片对象
var flObj = document.getElementById("file");

$('#getImg').click(function () {
    $('#file').bind('change', function () {//文件上传控件绑定监听事件
        var file = $(this).val();
        if (file.length > 0) {//文件不为空时自动提交图片
            uploadImg();//图片提交
            $('#photoBox').css('display', 'block');
            $('.getImg').css('display', 'none');
        }
    });
    $('#file').click();
    $('#uploadPhotoBox').css('display', 'block');
});

$('#cancel').click(function () {//取消图片提交
    window.location.reload();
});

上面的uploadImg()函数会在之后有定义

3.图片处理部分

需要完成图片的上传,压缩,裁剪功能

3.1 图片上传

在用户选择文件以后要先判断是否是图片,然后再判断图片大小确定是否要压缩,然后才能开始裁剪

function uploadImg() {//图片上传
    $('#photoBox').empty();
    $('#photoBox').html('<img id="photo" src="">');
    var file = flObj.files[0];//因为每次只上传了一张图片,所以获取到flObj.files[0];
    var fReader = new FileReader();
    var isImage = checkFile(file);//检查文件是否为图像类型

    if (!isImage) {
        alert("请确保文件为图像类型");
    } else {
        fReader.onload = function (e) {
            var imageSize = e.total; //图片大小
            var image = new Image();
            image.src = e.target.result;
            image.onload = function () {
                //判断是否需要压缩图片
                image = judgeCompress(image, imageSize);
                document.getElementById("photo").src = image.src;
                cropper(document.getElementById("photo"), options);
            };
        }
    }
    fReader.readAsDataURL(isImage);
};

检查文件是否为图像类型:

function checkFile(file) {//检查文件是否为图像类型
    console.log(file);
    //使用正则表达式匹配判断
    if (!/image\/\w+/.test(file.type)) {
        return false;
    }
    return file;
}

3.2 图片压缩

在图片压缩之前,需要先检查图片大小,图片过大才需要压缩:

function judgeCompress(image, imageSize) {//判断图片大小
    //判断图片是否大于300000 bit
    var threshold = 300000; //阈值,可根据实际情况调整
    if (imageSize > threshold) {
        var imageData = compress(image);//图片压缩
        var newImage = new Image();
        newImage.src = imageData;
        return newImage;
    } else {
        return image;
    }
}

图片压缩是使用canvas实现,先将图片绘制出来,然后再讲绘制出来的图片保存为图片对象以完成压缩,代码实现如下:

function compress(image) {//图片压缩
    var canvas = document.createElement("canvas");
    var ctx = canvas.getContext("2d");

    var originWidth = image.width;
    var originHeight = image.height;

    var maxWidth = 800,
        maxHeight = 800;
    // 目标尺寸
    var targetWidth = originWidth,
        targetHeight = originHeight;
    // 图片尺寸超过800x800的限制
    if (originWidth > maxWidth || originHeight > maxHeight) {
        if (originWidth / originHeight > maxWidth / maxHeight) {
            // 更宽,按照宽度限定尺寸
            targetWidth = maxWidth;
            targetHeight = Math.round(maxWidth * (originHeight / originWidth));
        } else {
            targetHeight = maxHeight;
            targetWidth = Math.round(maxHeight * (originWidth / originHeight));
        }
    }

    canvas.height = targetHeight;
    canvas.width = targetWidth;
    ctx.drawImage(image, 0, 0, targetWidth, targetHeight);
    //压缩操作
    var quality = 0.8; //图片质量  范围:0<quality<=1 根据实际需求调正
    var imageData = canvas.toDataURL("image/jpeg", quality);
    
    return imageData;
}

3.3 图片裁剪

得到了处理完成的图片以后,就可以进行图片裁剪了,首先先按照官网进行插件的配置,我把它们写在了一个对象中:

var options = {
    aspectRatio: 1,  //宽高比
    preview: '.preview',  //预览窗口
    guides: true,  //裁剪框的虚线
    autoCropArea: 0.5,  //0-1之间的数值,定义自动剪裁区域的大小,默认0.8
    dragCrop: true,  //是否允许移除当前的剪裁框,并通过拖动来新建一个剪裁框区域
    movable: true,  //是否允许移动剪裁框
    resizable: true,  //是否允许改变裁剪框的大小
    zoomable: false,  //是否允许缩放图片大小
    mouseWheelZoom: false,  //是否允许通过鼠标滚轮来缩放图片
    touchDragZoom: false,  //是否允许通过触摸移动来缩放图片
    rotatable: false,  //是否允许旋转图片
    minContainerWidth: 200,  //容器的最小宽度
    minContainerHeight: 200,  //容器的最小高度
    minCanvasWidth: 0,  //canvas 的最小宽度(image wrapper)
    minCanvasHeight: 0,  //canvas 的最小高度(image wrapper)
    strict: true,
};

然后就是图片这部分最核心的裁剪部分了,但是使用cropper裁剪出来的图片是canvas,解决的方法是将canvas使用toDataURL()方法转化为base64之后再转为img对象,用于后面的操作:

function cropper(photo, options) {//图片裁剪
    var cropper = new Cropper(photo, options);
    $('#crop').on('click', function () {
        imgResult = cropper.getCroppedCanvas().toDataURL();//裁剪出来的base64
        imgResultImg.src = imgResult;//裁剪出来的图片对象
        $('.img').css('display', 'none');
        LInit(60, "myGame", gameWidth, gameHeight, main);//游戏初始化
        $('.game').css('display', 'block');
    })
}

这样子图片处理部分就完成了,在图片裁剪完成之后就开始初始化游戏了。

4.游戏部分

接下来就开始游戏部分的制作,游戏中大致思路模仿了《速度挑战 - 2小时完成HTML5拼图小游戏》,在其基础上做了修改

4.1 定义变量

首先还是要定义游戏中需要用到的变量:

/** 初始化游戏 */
var gameWidth = 390;
var gameHeight = 390;
/** 游戏层 */
var stageLayer, gameLayer, overLayer;
/** 拼图块列表 */
var blockList;
/** 是否游戏结束 */
var isGameOver,
    isTimeOver;
/** 用时 */
var startTime, time, countTime;
/** 步数 */
var steps;
/** 图片 */
var imgBmpd, startNewGame, fail, startBitmap, Again, failBitmap, againBitmap, succeed, succeedBitmap;

var _blockList = [];//拼图序列
var datalist = [];//存放图片

4.2 游戏初始化

游戏初始化,包括素材的加载,以及游戏界面的显示:

function main() {//游戏资源初始化
    /** 全屏设置 */
    if (LGlobal.mobile) {
        LGlobal.width = gameWidth;
        LGlobal.height = gameHeight;
        LGlobal.stageScale = LStageScaleMode.SHOW_ALL;
    }
    LGlobal.screen(LGlobal.FULL_SCREEN);
    LGlobal.preventDefault = false;

    /** 添加加载提示 */
    var loadingHint = new LTextField();
    loadingHint.text = "资源加载中……";
    loadingHint.size = 20;
    loadingHint.x = (LGlobal.width - loadingHint.getWidth()) / 2;
    loadingHint.y = (LGlobal.height - loadingHint.getHeight()) / 2;
    addChild(loadingHint);

    /** 加载图片 文件*/
    LLoadManage.load(
        [
            {path: "./js/Block.js"},
            {name: "startGame", path: "./images/start.png"},
            {name: "fail", path: "./images/fail.png"},
            {name: "Again", path: "./images/challengeAgain.png"},
            {name: "succeed", path: "./images/succeed.png"},
        ],
        null,
        function (result) {
            /** 移除加载提示 */
            loadingHint.remove();

            /** 保存位图数据,方便后续使用 */
            imgBmpd = new LBitmapData(imgResultImg);

            gameInit(result);
        }
    );
}

function gameInit(e) {//游戏内容初始化
    datalist = e;
    var bitmapData = new LBitmapData(imgResultImg);
    var bitmap = new LBitmap(bitmapData);

    bitmap.scaleX = LGlobal.width / bitmap.width;
    bitmap.scaleY = LGlobal.height / bitmap.height;
    bitmap.width = LGlobal.width;
    bitmap.height = LGlobal.height;
    bitmap.x = 0;
    bitmap.y = 0;
    addChild(bitmap);

    startNewGame = new LBitmapData(datalist['startGame']);
    fail = new LBitmapData(datalist["fail"]);
    Again = new LBitmapData(datalist["Again"]);
    succeed = new LBitmapData(datalist["succeed"]);
    startBitmap = new LBitmap(startNewGame);
    failBitmap = new LBitmap(fail);
    againBitmap = new LBitmap(Again);
    succeedBitmap = new LBitmap(succeed);

    /** 初始化舞台层 */
    stageLayer = new LSprite();
    stageLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height], true, "transparent");
    addChild(stageLayer);

    /** 初始化游戏层 */
    gameLayer = new LSprite();
    stageLayer.addChild(gameLayer);

    /** 初始化最上层 */
    overLayer = new LSprite();
    stageLayer.addChild(overLayer);

    /** 添加开始界面 */
    addBeginningUI();
}

function addBeginningUI() {//游戏开始界面
    var beginningLayer = new LSprite();
    beginningLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height], true, "transparent");
    stageLayer.addChild(beginningLayer);

    /** 游戏标题 */
    var title = new LTextField();
    title.text = "拼图华容道";
    title.size = 48;
    title.weight = "bold";
    title.x = (LGlobal.width - title.getWidth()) / 2;
    title.y = 70;
    title.color = "#f8fbb5";
    title.lineWidth = 5;
    title.lineColor = "#000000";
    title.stroke = true;
    beginningLayer.addChild(title);

    /** 开始游戏提示 */
    startBitmap.scaleX = 0.7;
    startBitmap.scaleY = 0.7;
    startBitmap.x = (LGlobal.width - startBitmap.getWidth()) / 2;
    startBitmap.y = 250 + 0;
    beginningLayer.addChild(startBitmap);

    /** 初始化拼图块列表 */
    initBlockList();
    /** 打乱拼图 */
    getRandomBlockList();

    /** 开始游戏 */
    beginningLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
        beginningLayer.remove();
        startGame();
    });
}

4.3 游戏主体

接下来来到了整个游戏最重要的部分,游戏主体的实现,开始以后将拼图打乱,然后进行游戏:

(1)开始游戏

点击界面以后将游戏的各种值初始化,然后开始游戏

function startGame() {//开始游戏
    isGameOver = false;
    isTimeOver = false;

    /** 初始化时间和步数 */
    startTime = (new Date()).getTime();
    countTime = 0;
    time = 0;
    steps = 0;

    /** 显示拼图 */
    showBlock();
    /** 计时 */
    updateTimeTxt(countTime);
    /** 显示步数 */
    updateStepsTxt();

    stageLayer.addEventListener(LEvent.ENTER_FRAME, onFrame);
}
(2)显示拼图

显示拼图显然不是一张图,那么就需要拼图块,后面会定义一个类来表示这些拼图块

function showBlock() {//显示拼图
    for (var i = 0, l = blockList.length; i < l; i++) {
        var b = blockList[i];

        /** 根据序号计算拼图块位置 */
        var y = (i / 3) >>> 0, x = i % 3;

        b.setLocation(x, y);

        gameLayer.addChild(b);
    }
}
(3)初始化拼图列表

根据序号计算拼图块图片显示位置,将拼图块存放到列表中

function initBlockList() {//初始化拼图列表
    blockList = new Array();

    for (var i = 0; i < 9; i++) {
        /** 根据序号计算拼图块图片显示位置 */
        var y = (i / 3) >>> 0, x = i % 3;
        blockList.push(new Block(i, x, y));
    }
}
(4)打乱拼图

这里使用的是随机打乱拼图,然后计算序列的倒序和,如果倒序和是奇数,拼图无法完成,需要重新打乱,直到倒序和为偶数。

function getRandomBlockList() {//随机打乱拼图

    /** 随机打乱拼图 */
    blockList.sort(function () {
        return 0.5 - Math.random();
    });

    /** 计算逆序和 */
    var reverseAmount = 0;

    for (var i = 0, l = blockList.length; i < l; i++) {
        var currentBlock = blockList[i];

        for (var j = i + 1; j < l; j++) {
            var comparedBlock = blockList[j];

            if (comparedBlock.index < currentBlock.index) {
                reverseAmount++;
            }
        }
    }

    /** 检测打乱后是否可还原 */
    if (reverseAmount % 2 != 0) {
        /** 不合格,重新打乱 */
        getRandomBlockList();
    } else {
        var str = "";
        for (var i = 0; i < blockList.length; i++) {
            str = str + blockList[i].index;
        }
        console.log(str);
        if (str.substr(0, 3) == "012") {
            getRandomBlockList();
        } else {
            _blockList.push(str);
        }
    }
}

4.4 拼图块

在拼图过程中,引入了一个新的Block类,这个类用来表示并且操作拼图块:

function Block(index, x, y) {
    LExtends(this, LSprite, []);

    var bmpd = imgBmpd.clone();
    if (index != 8) {
        bmpd.setProperties(x * bmpd.width / 3, y * bmpd.width / 3, bmpd.width / 3, bmpd.width / 3);
        this.bmp = new LBitmap(bmpd);
        this.bmp.scaleX = 130 / this.bmp.width;
        this.bmp.scaleY = 130 / this.bmp.height;
        this.addChild(this.bmp);
    } else {
        var shape = new LShape();
        shape.graphics.drawRect(2, "#ffffff", [0, 0, 130, 130], true, "#ffffff");
        this.addChild(shape);
    }

    // 格子边框
    var border = new LShape();
    border.graphics.drawRect(3, "#ffffff", [0, 0, 130, 130]);
    border.graphics.drawRoundRect(3, "#ffffff", [0, 0, 130, 130, 10]);
    this.addChild(border);

    this.index = index;

    this.addEventListener(LMouseEvent.MOUSE_UP, this.onClick);
}

Block.getBlock = function (x, y) {
    return blockList[y * 3 + x];
};

Block.isGameOver = function () {
    var reductionAmount = 0, l = blockList.length;

    /** 计算还原度 */
    for (var i = 0; i < l; i++) {
        var b = blockList[i];

        if (b.index == i) {
            reductionAmount++;
        }
    }

    /** 计算是否完全还原 */
    if (reductionAmount == l) {
        /** 游戏结束 */
        gameOver();
    }
};

Block.exchangePosition = function (b1, b2) {
    var b1x = b1.locationX, b1y = b1.locationY,
        b2x = b2.locationX, b2y = b2.locationY,
        b1Index = b1y * 3 + b1x,
        b2Index = b2y * 3 + b2x;

    /** 在地图块数组中交换两者位置 */
    blockList.splice(b1Index, 1, b2);
    blockList.splice(b2Index, 1, b1);

    /** 交换两者显示位置 */
    b1.setLocation(b2x, b2y);
    b2.setLocation(b1x, b1y);

    /** 判断游戏是否结束 */
    Block.isGameOver();
};

Block.prototype.setLocation = function (x, y) {//方块位置
    this.locationX = x;
    this.locationY = y;

    this.x = x * 130;
    this.y = y * 130 + 0;
};

Block.prototype.onClick = function (e) {//方块的点击事件
    var self = e.currentTarget;

    if (isGameOver) {
        return;
    }

    var checkList = new Array();

    /** 判断右侧是否有方块 */
    if (self.locationX > 0) {
        checkList.push(Block.getBlock(self.locationX - 1, self.locationY));
    }

    /** 判断左侧是否有方块 */
    if (self.locationX < 2) {
        checkList.push(Block.getBlock(self.locationX + 1, self.locationY));
    }

    /** 判断上方是否有方块 */
    if (self.locationY > 0) {
        checkList.push(Block.getBlock(self.locationX, self.locationY - 1));
    }

    /** 判断下方是否有方块 */
    if (self.locationY < 2) {
        checkList.push(Block.getBlock(self.locationX, self.locationY + 1));
    }

    for (var i = 0, l = checkList.length; i < l; i++) {
        var checkO = checkList[i];

        /** 判断是否是空白拼图块 */
        if (checkO.index == 8) {
            steps++;
            updateStepsTxt();

            Block.exchangePosition(self, checkO);
            var str = "";
            for (var i = 0; i < blockList.length; i++) {
                str = str + blockList[i].index;
            }
            _blockList.push(str);

            break;
        }
    }
};

4.5 游戏结束

添加游戏结束功能,根据拼图完成或者计时结束判断游戏成功或是失败

(1)游戏计时计步

游戏未结束之前更新游戏的时间和步数:

function onFrame() {//计时

    if (isGameOver) {
        return;
    }
    if (isTimeOver) {
        return;
    }
    /** 获取当前时间 */
    var currentTime = (new Date()).getTime();

    /** 计算使用的时间并更新时间显示 */
    time = currentTime - startTime;

    if (countTime > 0) {// 倒计时
        updateTimeTxt();
    } else {
        timeOver();
    }
}
(2)更新时间和步数
function updateTimeTxt() {//更新时间
    $('#time').html(getTimeTxt());
}

function getTimeTxt() {
    var d = new Date(time);
    countTime = 99 - Math.floor(d / 1000);
    return countTime;
}

function updateStepsTxt() {//更新步数
    $('#steps').html(steps);
}
(3)游戏失败

游戏时间结束后视为游戏失败,弹出游戏失败界面,点击重新开始

function timeOver() {// 判断时间是否结束 失败
    isTimeOver = true;

    var resultLayer = new LSprite();
    resultLayer.filters = [new LDropShadowFilter()];
    resultLayer.graphics.drawRoundRect(3, "#BBBBBB", [0, 0, 390, 450, 5], true, "rgba(0,0,0,.6)");
    resultLayer.x = (LGlobal.width - resultLayer.getWidth()) / 2;
    resultLayer.y = LGlobal.height / 2;
    resultLayer.alpha = 0;
    overLayer.addChild(resultLayer);

    failBitmap.scaleX = 0.6;
    failBitmap.scaleY = 0.6;
    failBitmap.x = (LGlobal.width - failBitmap.getWidth()) / 2;
    failBitmap.y = 70 + 0;
    resultLayer.addChild(failBitmap);

    againBitmap.scaleX = 0.6;
    againBitmap.scaleY = 0.6;
    againBitmap.x = (LGlobal.width - againBitmap.getWidth()) / 2;
    againBitmap.y = 250 + 0;
    resultLayer.addChild(againBitmap);

    LTweenLite.to(resultLayer, 0.5, {
        alpha: 0.7,
        y: (LGlobal.height - resultLayer.getHeight()) / 2 - 15,
        onComplete: function () {
            /** 点击界面重新开始游戏 */
            stageLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
                gameLayer.removeAllChild();
                overLayer.removeAllChild();
                stageLayer.removeAllEventListener();
                main();
            });
        }
    });
}
(4)游戏成功

拼图完成时游戏成功,弹出游戏成功界面,输出游戏用时,步数,分数以及游戏情况的序列,点击重新开始

function gameOver() {// 游戏成功
    let score = 99 - getTimeTxt(time);
    let blockList = _blockList.join("-");
    let step = steps;
    isGameOver = true;
    console.log('用时:' + getTimeTxt(time) + '步数:' + step + '分数:' + score + '序列:' + blockList);

    var resultLayer = new LSprite();
    resultLayer.filters = [new LDropShadowFilter()];
    resultLayer.graphics.drawRoundRect(3, "#BBBBBB", [0, 0, 390, 450, 5], true, "rgba(0,0,0,.6)");
    resultLayer.x = (LGlobal.width - resultLayer.getWidth()) / 2;
    resultLayer.y = LGlobal.height / 2;
    resultLayer.alpha = 0;
    overLayer.addChild(resultLayer);

    succeedBitmap.scaleX = 0.6;
    succeedBitmap.scaleY = 0.6;
    succeedBitmap.x = (LGlobal.width - succeedBitmap.getWidth()) / 2;
    succeedBitmap.y = 70 + 0;
    resultLayer.addChild(succeedBitmap);

    againBitmap.scaleX = 0.6;
    againBitmap.scaleY = 0.6;
    againBitmap.x = (LGlobal.width - againBitmap.getWidth()) / 2;
    againBitmap.y = 250 + 0;
    resultLayer.addChild(againBitmap);

    LTweenLite.to(resultLayer, 0.5, {
        alpha: 0.7,
        y: (LGlobal.height - resultLayer.getHeight()) / 2 - 15,
        onComplete: function () {
            /** 点击界面重新开始游戏 */
            stageLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
                gameLayer.removeAllChild();
                overLayer.removeAllChild();
                stageLayer.removeAllEventListener();
                main();
            });
        }
    });
}

反思和总结

感受

终于撸完了这个简单的小游戏,虽然写的毛毛糙糙,很多地方写得也不够好,但是收获肯定是有的,不光是看了文档,学习了大佬们的思路,同时也增长了一些动手能力,虽然代码比较简单,但是只要坚持,日积月累肯定会有收获。

可优化项

在写这篇博客的时候,回顾代码就已经满满的槽点了,把可优化项记录下来,以后有空可以尝试去优化:

  • 1.使用jQuery和原生js两种方式获取dom元素,编码习惯不好,使代码更加混乱
  • 2.游戏白底界面过于简单
  • 3.随机打乱拼图,使游戏无法设置多种难度,缺少游戏乐趣
  • 4.图片裁剪部分布局粗糙使用WeUI来完成
  • 5.无法保留游戏记录
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_x阅读 15,967评论 3 119
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,691评论 2 59
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,431评论 25 707
  • 请备好一壶茶,听我慢慢道来。希望我的故事里有你的影子和感悟… 蝴蝶家中有三个子女,她是家里的老大。做为老大的她,坚...
    兮蔚子阅读 742评论 3 2
  • 星期三/晴 秋意渐渐浓了,即便是日头高扬的大晴天也不觉炎热,倒在每日的清晨与夜晚被丝丝缕缕的凉意侵袭。 上午在连廊...
    酒久里个丸子阅读 463评论 0 0