JS开发HTML5游戏《神奇的六边形》(三)
近期出现一款魔性的消除类HTML5游戏《神奇的六边形》,今天我们一起来看看如何通过开源免费的青瓷引擎(www.zuoyouxi.com)来实现这款游戏。
(点击图片可进入游戏体验)
因内容太多,为方便大家阅读,所以分成四部分来讲解。
本文为第三部分,主要包括:
11.显示出3个形状
12.形状的拖放处理
13.形状放入棋盘的实现
14.界面管理
15.消除行
若要一次性查看所有文档,也可点击这里。
十一. 显示出3个形状
1. 在Scripts/ui创建文件:Pool.js,绘制3个形状。
var s = qc.Serializer; /**
* 3个形状的绘制
*/
var Pool = qc.defineBehaviour('qc.tetris.Pool', qc.Behaviour, function() {
var self = this; /**
* 形状的预置
*/
self.blocksPrefab = null; /**
* 记录下面3个形状的实例
*/
self.shapes = [];
}, {
blocksPrefab: s.PREFAB
}); /**
* 初始化处理
*/
Pool.prototype.awake = function() {
var self = this;
self.redraw();
}; /**
* 绘制3个形状
*/
Pool.prototype.redraw = function() {
var self = this; // 先干掉旧的形状数据
for (var i = 0; i < self.shapes.length; i++) {
self.shapes[i].destroy();
}
self.shapes = []; // 创建3个新的形状
for (i = 0; i < 3; i++) {
self.add(i);
}
self.resize();
}; /**
* 调整位置
*/
Pool.prototype.resize = function() {
var self = this, o = self.gameObject; // 计算X方向的偏移
var offset = o.width * (0.5 - 0.165);
for (var i = 0; i < 3; i++) {
var child = self.shapes[i];
if (!child) return;
child.anchoredX = offset * (i - 1);
child.anchoredY = 0;
}
}; /**
* 添加一个形状
*/
Pool.prototype.add = function(index) {
var self = this; var o = self.game.add.clone(self.blocksPrefab, self.gameObject);
var c = o.getScript('qc.tetris.BlocksUI');
c.data = qc.Tetris.Shapes.pool[index];
self.shapes[index] = o;
}; /**
* 删除一个形状
*/
Pool.prototype.remove = function(index) {
var o = this.shapes[index];
o.destroyImmediately();
this.shapes.splice(index, 1);
};
整个代码逻辑比较简单,根据3个形状的数据进行绘制。请参考注释进行理解。
2. 将此脚本挂载到UIRoot/pool节点,关联blocksPrefab属性:
3. 运行测试下效果,3个形状正确显示了:
十二. 形状的拖放处理
形状在被按下时,需要变大,如果是手机上需要向上做一定的位置偏移。拖拽时形状应该跟着鼠标或手指进行移动。
修改脚本Scripts/ui/BlocksUI.js,添加如下代码:
1. 修改reset函数,增加放大区块的逻辑:
BlocksUI.prototype.reset = function(fixToBoard) {
var self = this, o = self.gameObject;
for (var pos in self._blocks) {
var p = qc.Tetris.readPos(pos);
var pt = qc.Tetris.board.toWorld(p, fixToBoard ? qc.Tetris.BLOCK_H : qc.Tetris.POOL_DISTANCE_NORMAL);
var block = self._blocks[pos];
block.anchoredX = pt.x;
block.anchoredY = pt.y; var scale = fixToBoard ? 1.13 : 1;
block.find('shadow').scaleX = scale;
block.find('shadow').scaleY = scale;
block.find('block').scaleX = scale;
block.find('block').scaleY = scale;
}
};
2. 添加按下的逻辑处理,放大区块:
/**
* 鼠标按下:放大区块
*/
BlocksUI.prototype.onDown = function(e) {
var self = this, o = self.gameObject;
self.drop = false;
self.reset(true); // 在手机下,需要往上做点偏移
o.y -= self.offsetY;
};
- drop标记当前区块是否被放到棋盘了,刚开始按下清理下环境
- 按下时需要向上做偏移offsetY
3. 添加鼠标松开或触摸结束的处理,还原区块的位置和大小:
/**
* 鼠标松开:重置区块大小
*/
BlocksUI.prototype.onUp = function() {
var self = this;
self.reset();
};
4. 添加开始拖拽的处理:
/**
* 拖拽开始
*/
BlocksUI.prototype.onDragStart = function(e) {
var self = this;
self.drop = false;
self.drag = true;
self.lastPos = '';
self.game.input.nativeMode = true;
self.reset(true); self.game.log.trace('Start drag:{0}', self.index); // 复制出可放入标记
var ob = self.flagBlocks = self.game.add.clone(self.gameObject, qc.Tetris.boardUI.gameObject);
ob.children.forEach(function(block) {
block.find('shadow').visible = false;
var b = block.find('block');
b.width = qc.Tetris.BLOCK_W;
b.height = qc.Tetris.BLOCK_H;
b.scaleX = 1;
b.scaleY = 1;
b.frame = 'dark' + b.frame;
});
ob.scaleX = 1;
ob.scaleY = 1;
ob.interactive = false;
self.hideFlag();
};
- 初始时,标记正在拖拽(drag = true),并且没有被放下(drop = false)
- 当拖拽到棋盘时,需要实时指示是否可以放下本形状。拖拽开始先清理下最近一次检测的逻辑坐标点(last = '')
- 设置输入模式nativeMode = true。确保输入事件能被实时处理(默认情况下延后一帧处理,运行效率比较高),本游戏对拖拽的实时响应比较重要。
- 拖拽开始时,放大并偏移形状(和鼠标按下的逻辑一样)
- 后续的逻辑:另外复制出本形状,并隐藏掉。这个形状在后续拖拽中,会在棋盘显示出来以指示当前是否可以放入。这个指示的格子图片,使用暗色的图片。
5. 添加拖拽的处理,每帧都会进行调度:
/**
* 拖拽中
*/
BlocksUI.prototype.onDrag = function(e) {
var self = this,
o = self.gameObject;
if (self.drag) {
// 改变节点的目标位置
var p = o.getWorldPosition();
p.x += e.source.deltaX;
p.y += e.source.deltaY;
var lp = o.parent.toLocal(p);
o.x = lp.x;
o.y = lp.y; // 计算当前对应棋盘中心点的偏移
var board = qc.Tetris.boardUI.gameObject;
p = board.toLocal(p);
p.y += board.height * 0.5; // 反算出对应的归一化坐标
var xy = qc.Tetris.board.toLocal(p);
var x = Math.round(xy.x),
y = Math.round(xy.y),
pos = qc.Tetris.makePos(x, y);
if (self.lastPos !== pos) {
self.lastPos = pos;
if (qc.Tetris.board.data[pos] &&
qc.Tetris.board.checkPutIn(pos, self.data.list)) {
self.showFlag(pos);
}
else {
self.hideFlag();
}
}
}
};
- 在拖拽的事件e中,会指明本帧到上一帧的移动偏移量(屏幕坐标),本形状加上屏幕坐标偏移,这样就移动起来了
- 然后计算本形状的中心点,对应到棋盘的逻辑坐标。并检查目标是否可以放入,如果可以就需要显示指示
- 最近一次检测的逻辑坐标需要记录下来,防止每帧都对同一逻辑坐标检查是否可以放入(白耗CPU)
6. 打开脚本Scripts/logic/Board.js,实现checkPutIn方法:
Board.prototype.checkPutIn = function(pos, list) {
var self = this;
var pt = qc.Tetris.readPos(pos),
x = pt.x,
y = pt.y; for (var i = 0; i < list.length; i++) {
var x0 = x + list[i][0],
y0 = y + list[i][1]; // 这个点应该是空的
var block = self.data[qc.Tetris.makePos(x0, y0)];
if (!block) return false;
if (block.value !== 0) return false;
}
return true;
};
7. 继续打开Scripts/ui/Blocks.js,继续实现拖拽结束的逻辑:
/**
* 拖拽结束
*/
BlocksUI.prototype.onDragEnd = function(e) {
var self = this,
o = self.gameObject;
self.drag = false; if (self.flagBlocks.visible && self.lastPos) {
// 放到这个位置中去
self.drop = true;
qc.Tetris.operation.putIn(self.index, self.lastPos, self.data);
}
else {
self.reset();
o.parent.getScript('qc.tetris.Pool').resize();
} // 显示标记可以干掉了
self.flagBlocks.destroy();
delete self.flagBlocks;
}; /**
* 隐藏指示标记
*/
BlocksUI.prototype.hideFlag = function() {
this.flagBlocks.visible = false;
}; /**
* 显示指示标记
*/
BlocksUI.prototype.showFlag = function(pos) {
this.flagBlocks.visible = true;
var pt = qc.Tetris.board.data[pos];
this.flagBlocks.anchoredX = pt.x;
this.flagBlocks.anchoredY = pt.y;
};
- 拖拽结束后,需要判定形状是否被放入目标节点
- 如果可以放入,则调用指令:qc.Tetris.operation.putIn(下步骤实现)
- 如果不能放入,则需要将位置和大小等还原
- 最后,指示对象需要被析构
8. 在Scripts/operation创建文件PutIn.js,实现放入形状指令:
/**
* 请求放入指定格子,如果成功放入返回true,否则返回false
*/
qc.Tetris.operation.putIn = function(index, pos) {
// TODO: 逻辑待实现
};
9. 在Blocks.js中,我们使用到了棋盘对象:qc.Tetris.boardUI.gameObject,但目前这个值(BoardUI)尚未被赋值。
打开BoardUI.js,在构造函数中加入代码赋值:
var BoardUI = qc.defineBehaviour('qc.tetris.BoardUI', qc.Behaviour, function() {
var self = this; // 登记下本对象
qc.Tetris.boardUI = self; /**
* 棋盘的棋子元素
*/
self.pieces = {}; ...
10. 运行测试下,形状可以随意拖拽了,并且可以反弹回原来位置。不过还无法放入(因为PutIn我们还没实现),请继续后面教程。
十三. 形状放入棋盘的实现
处理流程如下图:
打开文件Scripts/operation/PutIn.js,实现上述代码:
/**
* 请求放入指定格子,如果成功放入返回true,否则返回false
*/
qc.Tetris.operation.putIn = function(index, pos) {
var shape = qc.Tetris.Shapes.pool[index],
board = qc.Tetris.board,
ui = qc.Tetris.game.ui,
log = qc.Tetris.game.log;
log.trace('尝试将({0})放入({1})', index, pos); if (!board.checkPutIn(pos, shape.list)) {
// 禁止放入
return false;
}
log.trace('放入格子:({0})', pos); // 更新棋盘信息
board.putIn(pos, shape.list, shape.value); // 计算可以消除的行,并同时消除掉
var lines = board.getFullLines();
lines.forEach(function(flag) {
var children = ui.killLineEffect.find(flag).gameObject.children;
var pts = [];
children.forEach(function(child) { pts.push(child.name); })
board.clearLine(pts);
}); // 计算分数明细,并添加之
var scoreDetail = qc.Tetris.operation.calcScore(lines);
qc.Tetris.score.current += scoreDetail.total; // 替换为新的形状
qc.Tetris.Shapes.pool.splice(index, 1);
qc.Tetris.Shapes.pool.push(qc.Tetris.Shapes.random()); // 重新绘制棋盘
ui.board.redraw(); // 行消除与分数增加的动画表现
if (lines.length > 0) {
for (var i = 0; i < lines.length; i++) {
ui.killLineEffect.play(i, lines[i], scoreDetail.lines[i]);
}
}
else {
ui.board.getScript('qc.tetris.FlyScore').play(pos, scoreDetail.total);
} // 当前分数的动画表现
ui.currentScore.setScore(); // 形状飞入的动画表现,并将旧的形状删除掉
ui.pool.remove(index);
ui.pool.add(2);
ui.pool.flyIn(index); // 死亡检测
if (board.die) {
// 延迟显示死亡界面
log.trace('Game Over!');
qc.Tetris.game.timer.add(3000, function() {
ui.onDie();
});
} // 放入成功了
return true;
}; /**
* 计算分数明细
* total: 总分数
* lines: [各行的分数]
*/
qc.Tetris.operation.calcScore = function(lines) {
var scores = {
total: 40,
lines: []
};
if (lines.length < 1) return scores; // 计算加成
var append = Math.max(0, lines.length - 1 * 10); for (var i = 0; i < lines.length; i++) {
var flag = lines[i]; var line = qc.Tetris.game.ui.killLineEffect.find(flag);
var len = line.gameObject.children.length;
scores.lines[i] = len * 20 + append * len;
scores.total += scores.lines[i]; // 40合并到第一行去做表现
if (i === 0) {
scores.lines[i] += 40;
}
} return scores;
};
- calcScore方法为计算分数的逻辑
- 代码中出现了qc.Tetris.game.ui(即UIManager),在下文中陆续实现
- 另外,本逻辑中加入了一些动画表现,在下文中也陆续实现之
- 先大致理解下处理流程,细节可以后续章节中逐一理解
十四. 界面管理
1. 在Scripts/ui新建UIManager.js:
/**
* 负责管理所有的游戏界面
*/
var UIManager = qc.defineBehaviour('qc.tetris.UIManager', qc.Behaviour, function() {
var self = this;
self.game.ui = self; self.runInEditor = true;
}, {
bestScoreNode: qc.Serializer.NODE,
currentScoreNode: qc.Serializer.NODE,
boardNode: qc.Serializer.NODE,
poolNode: qc.Serializer.NODE,
killLineEffectNode: qc.Serializer.NODE, uiRoot: qc.Serializer.NODE,
gameOverPrefab: qc.Serializer.PREFAB
}); /**
* 初始化管理
*/
UIManager.prototype.awake = function() {
var self = this; /**
* bestScore: BestScore组件
*/
if (self.bestScoreNode)
self.bestScore = self.bestScoreNode.getScript('qc.tetris.BestScore'); /**
* currentScore: CurrentScore组件
*/
if (self.currentScoreNode)
self.currentScore = self.currentScoreNode.getScript('qc.tetris.CurrentScore'); /**
* board: 棋盘绘制组件
*/
if (self.boardNode)
self.board = self.boardNode.getScript('qc.tetris.BoardUI'); /**
* pool: 3个形状的方块
*/
if (self.poolNode)
self.pool = self.poolNode.getScript('qc.tetris.Pool'); /**
* killLineEffect: 方块消除的动画组件
*/
if (self.killLineEffectNode)
self.killLineEffect = self.killLineEffectNode.getScript('qc.tetris.KillLineEffect');
}; /**
* 游戏重新开始的界面处理
*/
UIManager.prototype.restart = function() {
var self = this; // 重新生成3个新的形状
self.pool.redraw(); // 棋盘重绘制
self.board.redraw(); // 重绘当前分数
self.currentScore.setScore();
}; /**
* 死亡的处理
*/
UIManager.prototype.onDie = function() {
// 显示失败页面
this.game.add.clone(this.gameOverPrefab, this.uiRoot);
};
- UIManager引用了几个界面逻辑,其中KillLineEffect脚本下章节再实现
- 同时,加入了死亡处理接口、重新开始游戏接口,具体的逻辑在后续章节中逐一实现
2. 将脚本挂载到UIRoot,并关联各属性:
部分属性先留空
十五. 消除行
以下的行是可以被消除的:
逻辑实现
1. 打开Scripts/logic/board.js,将上述3类型的行建立数据结构:
var Board = qc.Tetris.Board = function() {
// 省略一堆代码
... // 左斜的9条线,指明起始点坐标
self.xyLines = [
[0, -4],
[1, -4],
[2, -4],
[3, -4],
[4, -4], [4, -3],
[4, -2],
[4, -1],
[4, 0]
]; // 横向9条线,指明起始点坐标和长度
self.yLines = [
[0, -4, 5],
[-1, -3, 6],
[-2, -2, 7],
[-3, -1, 8],
[-4, 0, 9],
[-4, 1, 8],
[-4, 2, 7],
[-4, 3, 6],
[-4, 4, 5]
]; // 右斜9条线,指明起始点坐标和长度
self.xLines = [
[-4, 0, 5],
[-3, -1, 6],
[-2, -2, 7],
[-1, -3, 8],
[0, -4, 9],
[1, -4, 8],
[2, -4, 7],
[3, -4, 6],
[4, -4, 5]
];
};
2. 实现putIn接口:
Board.prototype.putIn = function(pos, list, value) {
var self = this;
var pt = qc.Tetris.readPos(pos),
x = pt.x,
y = pt.y; for (var i = 0; i < list.length; i++) {
var x0 = x + list[i][0],
y0 = y + list[i][1]; // 这个点应该是空的
var block = self.data[qc.Tetris.makePos(x0, y0)];
block.value = value;
}
};
3. 实现clearLine接口,干掉一行数据:
// 干掉一行
Board.prototype.clearLine = function(pts) {
var self = this;
pts.forEach(function(pos) {
self.data[pos].value = 0;
});
};
4. 实现getFullLines接口,将所有可以消除的行返回:
// 取得可以消除的行
Board.prototype.getFullLines = function() {
var self = this,
lines = []; // 横向9条线
var pts = self.yLines;
for (var i = 0; i < pts.length; i++) {
var start = pts[i], end = [start[0] + start[2] - 1, start[1]];
var ok = true;
for (var x = start[0], y = start[1]; x <= end[0];) {
var pos = qc.Tetris.makePos(x, y);
if (self.data[pos].value === 0) {
// 不符合,不能消除
ok = false; break;
} // 下一个点
x++;
}
if (ok) {
// 这条线可以消除,添加进来
lines.push('y' + qc.Tetris.makePos(start[0], start[1]));
}
} // 右斜9条线
var pts = self.xLines;
for (var i = 0; i < pts.length; i++) {
var start = pts[i], end = [start[0], start[1] + start[2] - 1];
var ok = true;
for (var x = start[0], y = start[1]; y <= end[1];) {
var pos = qc.Tetris.makePos(x, y);
if (self.data[pos].value === 0) {
// 不符合,不能消除
ok = false; break;
} // 下一个点
y++;
}
if (ok) {
// 这条线可以消除,添加进来
lines.push('x' + qc.Tetris.makePos(start[0], start[1]));
}
} // 左斜的9条线
var pts = self.xyLines;
for (var i = 0; i < pts.length; i++) {
var start = pts[i], end = [start[1], start[0]];
var ok = true;
for (var x = start[0], y = start[1]; true;) {
var pos = qc.Tetris.makePos(x, y);
if (self.data[pos].value === 0) {
// 不符合,不能消除
ok = false; break;
} // 下一个点
if (end[0] > start[0]) {
x++, y--;
if (x > end[0]) break;
}
else {
x--, y++;
if (x < end[0]) break;
}
}
if (ok) {
// 这条线可以消除,添加进来
lines.push('xy' + qc.Tetris.makePos(start[0], start[1]));
}
} return lines;
};
界面实现
预先将所有的行创建出来,当行被删除时直接显示出来做动画表现。以下流程中,我们首先创建一个格子的预制,再创建一个行的预置。
1. 在board节点下,创建Image对象,设置属性如下图:
2.将新创建的block节点拖入Assets/prefab目录,创建预制。然后从场景中删除。
3. 在board节点下,创建Node对象,设置属性如下图:
4. 为节点挂载TweenAlpha动画组件,消失时需要淡出:
- 透明度从1变化到0
- 耗时0.5秒
- 变化的曲线是:先平缓的做变化,然后在快速变化为0
- 图片中from和to值设置反了,请手工设置下from=1,to=0
5. 在Scripts/ui下创建脚本Line.js,控制行的绘制和表现:
/**
* 消除一行的表现界面
*/
var LineUI = qc.defineBehaviour('qc.tetris.LineUI', qc.Behaviour, function() {
var self = this; // 描述行的信息
self.flag = 'xy';
self.x = 0;
self.y = 0;
}, {
blockPrefab: qc.Serializer.PREFAB
}); Object.defineProperties(LineUI.prototype, {
/**
* 取得行标记
*/
key: {
get: function() {
return this.flag + qc.Tetris.makePos(this.x, this.y);
}
}, /**
* 取得本行的格子数量
*/
count: {
get: function() {
return this.gameObject.children.length;
}
}
}); /**
* 初始化行
*/
LineUI.prototype.init = function(flag, start, end) {
var self = this;
self.flag = flag;
self.x = start[0];
self.y = start[1]; // 创建一个格子
var createBlock = function(pos) {
var block = self.game.add.clone(self.blockPrefab, self.gameObject);
block.frame = 'white.png';
block.anchoredX = qc.Tetris.board.data[pos].x;
block.anchoredY = qc.Tetris.board.data[pos].y;
block.name = pos;
return block;
}; switch (flag) {
case 'xy':
for (var x = self.x, y = self.y; true;) {
createBlock(qc.Tetris.makePos(x, y)); // 下一个点
if (end[0] > start[0]) {
x++, y--;
if (x > end[0]) break;
}
else {
x--, y++;
if (x < end[0]) break;
}
}
break; case 'y':
for (var x = start[0], y = start[1]; x <= end[0];) {
createBlock(qc.Tetris.makePos(x, y));
x++;
}
break; case 'x':
for (var x = start[0], y = start[1]; y <= end[1];) {
createBlock(qc.Tetris.makePos(x, y));
y++;
}
} // 初始时隐藏掉
self.gameObject.name = self.key;
self.gameObject.visible = false;
}; /**
* 播放消失的动画
*/
LineUI.prototype.playDisappear = function(index) {
var self = this,
o = self.gameObject,
ta = self.getScript('qc.TweenAlpha'); o.visible = true;
o.alpha = 1; ta.delay = 0;
ta.resetToBeginning();
ta.onFinished.addOnce(function() {
// 隐藏掉
o.visible = false;
});
ta.playForward();
};
- flag和x、y属性描述了行的信息(左斜行、水平行还是右斜行,起始点的坐标)
6. 将此脚本挂载到Line节点,并设置blockPrefab为第一步骤创建的格子预置:
7. 将line拖进Assets/prefab目录,创建预制。然后从场景中删除。
8. 在Scripts/ui创建脚本KillLineEffect.js,处理行消失表现的逻辑
/**
* 行消除的动画表现
*/
var KillLineEffect = qc.defineBehaviour('qc.tetris.KillLineEffect', qc.Behaviour, function() {
var self = this; /**
* 所有的行
*/
self.lines = {}; /**
* 两行之间的播放延迟
*/
self.delay = 300;
}, {
delay: qc.Serializer.NUMBER,
linePrefab: qc.Serializer.PREFAB
}); /**
* 初始化:将用于表现的行全部创建出来放着
*/
KillLineEffect.prototype.awake = function() {
var self = this; // 创建用于消除表现的格子行
var createLine = function(flag, start, end) {
var ob = self.game.add.clone(self.linePrefab, self.gameObject);
var line = ob.getScript('qc.tetris.LineUI');
line.init(flag, start, end);
self.lines[line.key] = line;
};
var pts = qc.Tetris.board.xyLines;
for (var i = 0; i < pts.length; i++) {
var start = pts[i], end = [start[1], start[0]];
createLine('xy', start, end);
} var pts = qc.Tetris.board.yLines;
for (var i = 0; i < pts.length; i++) {
var start = pts[i], end = [start[0] + start[2] - 1, start[1]];
createLine('y', start, end); }
var pts = qc.Tetris.board.xLines;
for (var i = 0; i < pts.length; i++) {
var start = pts[i], end = [start[0], start[1] + start[2] - 1];
createLine('x', start, end);
}
}; KillLineEffect.prototype.find = function(flag) {
return this.lines[flag];
}; KillLineEffect.prototype.play = function(index, flag, score) {
var self = this;
var line = self.find(flag);
var delay = index * self.delay; var playFunc = function() {
// 冒出分数
var children = line.gameObject.children;
var pos = children[Math.round(children.length/2) - 1].name;
self.getScript('qc.tetris.FlyScore').play(pos, score); // 消失动画
line.playDisappear();
};
if (delay <= 0) {
playFunc();
}
else {
self.game.timer.add(delay, playFunc);
}
};
- 在脚本初始化时,将所有行的数据构建出来,并隐藏掉
- delay表示在多行消失时,其动画的间隔时间
- 在动画表现时,有分数表现,FlyScore下一章再补充
9. 将KillLineEffect挂载到board节点(棋盘),并设置linePrefab:
10. 运行工程,就可以看到这些“行”了:
11. 选中UIRoot节点,设置UIManager的Kill Line Effect Node属性(board节点,因为board挂载了KillLineEffect脚本):
最新文章
- inline-block元素间距
- addEventListener、attachEvent、cancelBubble兼容性随笔
- andriod一次退出所有的Activity
- Masonry+拖动
- QT--以共享的方式发布应用,QT依赖库
- smartjs - DataManager 场景示例分析 - 数据懒加载
- Oracle游标练手实例
- javascript 判断身份证的正确性
- Git - Tutorial [Lars Vogel]
- Ubuntu下部署java JDK和eclipse IDE
- JAVA调用WebService总结
- 浅尝key-value数据库(三)——MongoDB的分布式
- Internet基础
- HDU 4070 Phage War
- license.json
- Linux服务器部署javaweb项目,从环境配置,到最终系统运行
- 获取txt md5值上传文件完整性校验
- Java知多少(36)内部类及其实例化
- Oracle VM VirtualBox启动后莫名奇妙的报错
- MySql&#160;UNIX_TIMESTAMP和FROM_UNIXTIME函数讲解
热门文章
- 【mysql】关于临时表
- jdbc至sql server的两种常见方法
- JavaScript中的直接量与初始器的区别
- poj 3694 Network 边双连通+LCA
- DirectX API 编程起步 #02 窗口的诞生
- iTOP-4412开发板低功耗高性能的开源硬件平台——上手评测
- SQOOP Load Data from Oracle to Hive Table
- uva 11054 wine trading in gergovia (归纳【好吧这是我自己起的名字】)——yhx
- C++基础笔记(二)C++对C的扩展
- IO流的练习1 —— 随机获取文本中的信息