在上一篇《Chrome自带恐龙小游戏的源码研究(三)》中实现了让游戏昼夜交替,这一篇主要研究如何绘制障碍物。

  障碍物有两种:仙人掌和翼龙。仙人掌有大小两种类型,可以同时并列多个;翼龙按高、中、低的随机飞行高度出现,不可并行。仙人掌和地面有着相同的速度向左移动,翼龙则快一些或慢一些,因为添加了随机的速度修正。我们使用一个障碍物列表管理它们,当它们移出屏幕外时则将其从列表中移除。同时再用一个列表记录它们的类型:

 Obstacle.obstacles = [];    //存储障碍物的数组
Obstacle.obstacleHistory = []; //记录障碍物数组中障碍物的类型

障碍物的出现不能太频繁,也不能太稀少,太频繁立刻就gameover了,太稀少则没有挑战性,因此需要一定的规则来生成障碍物。每组障碍物之间应该有一段间隔作为落脚点,新生成的障碍物在这个间隔之外生成。如示意图所示:

aaarticlea/png;base64,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" alt="" />

因此,先定义一个最大间距系数,下面会用这个系数生成随机间距:

Obstacle.MAX_GAP_COEFFICIENT = 1.5; //障碍物最大间距系数

另外,还需要对障碍物进行一些约束及配置:

 //每组障碍物的最大数量
Obstacle.MAX_OBSTACLE_LENGTH = 3;
//相邻的障碍物类型的最大重复数
Obstacle.MAX_OBSTACLE_DUPLICATION = 2; Obstacle.types = [
{
type: 'CACTUS_SMALL', //小仙人掌
width: 17, //宽
height: 35, //高
yPos: 105, //在画布上的y坐标
multipleSpeed: 4,
minGap: 120, //最小间距
minSpeed: 0 //最低速度
},
{
type: 'CACTUS_LARGE', //大仙人掌
width: 25,
height: 50,
yPos: 90,
multipleSpeed: 7,
minGap: 120,
minSpeed: 0
},
{
type: 'PTERODACTYL', //翼龙
width: 46,
height: 40,
yPos: [ 100, 75, 50 ], //有高、中、低三种高度
multipleSpeed: 999,
minSpeed: 8.5,
minGap: 150,
numFrames: 2, //有两个动画帧
frameRate: 1000/6, //动画帧的切换速率,这里为一秒6帧
speedOffset: .8 //速度修正
}
];

障碍物的所有实现由构造函数Obstacle完成,下面是它的实现代码:

 /**
* 绘制障碍物构造函数
* @param canvas
* @param type 障碍物的类型
* @param spriteImgPos 雪碧图坐标
* @param dimensions 屏幕尺寸
* @param gapCoefficient 障碍物间隙
* @param speed 障碍物移动速度
* @param opt_xOffset 障碍物水平偏移量
* @constructor
*/
function Obstacle(canvas,type,spriteImgPos,dimensions,gapCoefficient,speed,opt_xOffset) {
this.ctx = canvas.getContext('2d');
this.spritePos = spriteImgPos;
//障碍物类型(仙人掌、翼龙)
this.typeConfig = type;
this.gapCoefficient = gapCoefficient;
//每个障碍物的数量(1~3)
this.size = getRandomNum(1,Obstacle.MAX_OBSTACLE_LENGTH);
this.dimensions = dimensions;
//表示该障碍物是否可以被移除
this.remove = false;
//水平坐标
this.xPos = dimensions.WIDTH + (opt_xOffset || 0);
this.yPos = 0;
this.width = 0;
this.gap = 0;
this.speedOffset = 0; //速度修正 //障碍物的动画帧
this.currentFrame = 0;
//动画帧切换的计时器
this.timer = 0; this.init(speed);
}
``` 实例方法:
```javascript
Obstacle.prototype = {
init:function(speed) {
//如果随机障碍物是翼龙,则只出现一只
//翼龙的multipleSpeed是999,远大于speed
if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
this.size = 1;
}
//障碍物的总宽度等于单个障碍物的宽度乘以个数
this.width = this.typeConfig.width * this.size; //若障碍物的纵坐标是一个数组
//则随机选取一个
if (Array.isArray(this.typeConfig.yPos)) {
var yPosConfig = this.typeConfig.yPos;
this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
} else {
this.yPos = this.typeConfig.yPos;
} this.draw(); //对翼龙的速度进行修正,让它看起来有的飞得快一些,有些飞得慢一些
if (this.typeConfig.speedOffset) {
this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
-this.typeConfig.speedOffset;
} //障碍物之间的间隙,与游戏速度有关
this.gap = this.getGap(this.gapCoefficient, speed);
},
//障碍物之间的间隔,gapCoefficient为间隔系数
getGap: function(gapCoefficient, speed) {
var minGap = Math.round(this.width * speed +
this.typeConfig.minGap * gapCoefficient);
var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
return getRandomNum(minGap, maxGap);
},
//判断障碍物是否移出屏幕外
isVisible: function() {
return this.xPos + this.width > 0;
},
draw:function() {
//障碍物宽高
var sourceWidth = this.typeConfig.width;
var sourceHeight = this.typeConfig.height; //根据障碍物数量计算障碍物在雪碧图上的x坐标
//this.size的取值范围是1~3
var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) +
this.spritePos.x; // 如果当前动画帧大于0,说明障碍物类型是翼龙
// 更新翼龙的雪碧图x坐标使其匹配第二帧动画
if (this.currentFrame > 0) {
sourceX += sourceWidth * this.currentFrame;
}
this.ctx.drawImage(imgSprite,
sourceX, this.spritePos.y,
sourceWidth * this.size, sourceHeight,
this.xPos, this.yPos,
sourceWidth * this.size, sourceHeight);
},
//单个障碍物的移动
update:function(deltaTime, speed) {
//如果障碍物还没有移出屏幕外
if (!this.remove) {
//如果有速度修正则修正速度
if (this.typeConfig.speedOffset) {
speed += this.speedOffset;
}
//更新x坐标
this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime); // Update frame
if (this.typeConfig.numFrames) {
this.timer += deltaTime;
if (this.timer >= this.typeConfig.frameRate) {
//在两个动画帧之间来回切换以达到动画效果
this.currentFrame =
this.currentFrame == this.typeConfig.numFrames - 1 ?
0 : this.currentFrame + 1;
this.timer = 0;
}
}
this.draw(); if (!this.isVisible()) {
this.remove = true;
}
}
},
//管理多个障碍物移动
updateObstacles: function(deltaTime, currentSpeed) {
//保存一个障碍物列表的副本
var updatedObstacles = Obstacle.obstacles.slice(0); for (var i = 0; i < Obstacle.obstacles.length; i++) {
var obstacle = Obstacle.obstacles[i];
obstacle.update(deltaTime, currentSpeed); //移除被标记为删除的障碍物
if (obstacle.remove) {
updatedObstacles.shift();
}
}
Obstacle.obstacles = updatedObstacles; if(Obstacle.obstacles.length > 0) {
//获取障碍物列表中的最后一个障碍物
var lastObstacle = Obstacle.obstacles[Obstacle.obstacles.length - 1]; //若满足条件则添加障碍物
if (lastObstacle &&
lastObstacle.isVisible() &&
(lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
this.dimensions.WIDTH) {
this.addNewObstacle(currentSpeed);
}
} else {//若障碍物列表中没有障碍物则立即添加
this.addNewObstacle(currentSpeed);
}
},
//随机添加障碍
addNewObstacle:function (currentSpeed) {
//随机选取一种类型的障碍
var obstacleTypeIndex = getRandomNum(0,Obstacle.types.length - 1);
var obstacleType = Obstacle.types[obstacleTypeIndex]; //检查随机取到的障碍物类型是否与前两个重复
//或者检查其速度是否合法,这样可以保证游戏在低速时不出现翼龙
//如果检查不通过,则重新再选一次直到通过为止
if(this.duplicateObstacleCheck(obstacleType.type) || currentSpeed < obstacleType.minSpeed) {
this.addNewObstacle(currentSpeed);
} else {
//检查通过后,获取其雪碧图中的坐标
var obstacleSpritePos = this.spritePos[obstacleType.type];
//生成新的障碍物并存入数组
Obstacle.obstacles.push(new Obstacle(c,obstacleType,obstacleSpritePos,this.dimensions,
this.gapCoefficient,currentSpeed,obstacleType.width));
//同时将障碍物的类型存入history数组
Obstacle.obstacleHistory.unshift(obstacleType.type);
} //若history数组的长度大于1,则清空最前面的两个
if (Obstacle.obstacleHistory.length > 1) {
Obstacle.obstacleHistory.splice(Obstacle.MAX_OBSTACLE_DUPLICATION);
}
},
//检查障碍物是否超过允许的最大重复数
duplicateObstacleCheck:function(nextObstacleType) {
var duplicateCount = 0;
//与history数组中的障碍物类型比较,最大只允许重得两次
for(var i = 0; i < Obstacle.obstacleHistory.length; i++) {
duplicateCount = Obstacle.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0;
}
return duplicateCount >= Obstacle.MAX_OBSTACLE_DUPLICATION;
}
};

最后在此前的基础上添加一段测试代码:

 window.onload = function () {
var h = new HorizonLine(c,spriteDefinition.HORIZON);
var cloud = new Cloud(c,spriteDefinition.CLOUD,DEFAULT_WIDTH);
var night = new NightMode(c,spriteDefinition.MOON,DEFAULT_WIDTH);
var obstacle = new Obstacle(c,Obstacle.types[0],spriteDefinition,{WIDTH:600},0.6,1);
var startTime = 0;
var deltaTime;
var speed = 3;
(function draw(time) {
gameFrame++;
if(speed < 13.5) {
speed += 0.01;
}
ctx.clearRect(0,0,600,150);
time = time || 0;
deltaTime = time - startTime;
h.update(deltaTime,speed);
cloud.updateClouds(0.2);
night.invert(deltaTime);
obstacle.updateObstacles(deltaTime,speed);
startTime = time;
window.requestAnimationFrame(draw,c);
})();
};

最终得到的效果:

 

// this.bumpThreshold ? this.dimensions.WIDTH : 0;
},
draw:function() {
this.ctx.drawImage(imgSprite,
this.sourceXPos[0], this.spritePos.y,
this.dimensions.WIDTH, this.dimensions.HEIGHT,
this.xPos[0],this.yPos,
this.dimensions.WIDTH,this.dimensions.HEIGHT);

this.ctx.drawImage(imgSprite,
this.sourceXPos[1], this.spritePos.y,
this.dimensions.WIDTH, this.dimensions.HEIGHT,
this.xPos[1],this.yPos,
this.dimensions.WIDTH,this.dimensions.HEIGHT);

},
updateXPos:function(pos,increment) {
var line1 = pos,
line2 = pos === 0 ? 1 : 0;

this.xPos[line1] -= increment;
this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;

if(this.xPos[line1] = 0; i--) {
Cloud.clouds[i].update(speed);
}
var lastCloud = Cloud.clouds[numClouds - 1];
if(numClouds lastCloud.cloudGap &&
Cloud.config.CLOUD_FREQUENCY > Math.random()) {
this.addCloud();
}

Cloud.clouds = Cloud.clouds.filter(function(obj){
return !obj.remove;
});
} else {
this.addCloud();
}
},
update:function(speed) {
if(!this.remove) {
//向左移动
this.xPos -= Math.ceil(speed);
this.draw();

if(!this.isVisible()) {
this.remove = true;
}
}

},
//判断云朵是否移出屏幕外
isVisible:function() {
return this.xPos + Cloud.config.WIDTH > 0;
},
addCloud:function () {
var cloud = new Cloud(this.canvas,spriteDefinition.CLOUD,DEFAULT_WIDTH);
Cloud.clouds.push(cloud);
}
};
//endregion

//region NightMode
NightMode.config = {
FADE_SPEED: 0.035, //淡入淡出速度
HEIGHT: 40, //月亮高度
MOON_SPEED: 0.25, //月亮移动速度
NUM_STARS: 2, //星星数量
STAR_SIZE: 9, //星星宽度
STAR_SPEED: 0.3,//星星速度
STAR_MAX_Y: 70, //星星在画布上出现的位置
WIDTH: 20 //半个月度宽度
};
//月亮在不同时期有不同的位置
NightMode.phases = [140,120,100,60,40,20,0];

NightMode.invertTimer = 0;
NightMode.inverted = false;
NightMode.invertTrigger = false;
NightMode.INVERT_FADE_DURATION = 5000;

function NightMode(canvas,spritePos,containerWidth) {
this.spritePos = spritePos;
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.containerWidth = containerWidth;
this.xPos = containerWidth - 50; //月亮的x坐标
this.yPos = 30; //月亮的y坐标
this.currentPhase = 0;
this.opacity = 0;
this.stars = []; //用于存储星星
this.drawStars = false; //是否绘制星星
this.placeStars(); //放置星星
}

NightMode.prototype = {
update:function(activated) {
//若夜晚模式处于激活状态且opacity为0时
//对月亮周期进行更新
if(activated && this.opacity == 0) {
this.currentPhase++;
if(this.currentPhase >= NightMode.phases.length) {
this.currentPhase = 0;
}
}

//淡入
if(activated && (this.opacity 0) {//淡出
this.opacity -= NightMode.config.FADE_SPEED;
}

//当opacity大于0时移动月亮位置
if(this.opacity > 0) {
this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED);

//移动星星
if(this.drawStars) {
for (var i = 0; i NightMode.INVERT_FADE_DURATION) {
NightMode.invertTimer = 0;
NightMode.invertTrigger = false;
NightMode.inverted = a.classList.toggle('inverted',NightMode.invertTrigger);
} else if(NightMode.invertTimer) {
NightMode.invertTimer += deltaTime;
} else {
//每500帧触发黑夜,这里只是为了模拟效果,完整游戏中是每700米触发一次黑夜
NightMode.invertTrigger = !(gameFrame % 500);
if(NightMode.invertTrigger && NightMode.invertTimer === 0) {
NightMode.invertTimer += deltaTime;
NightMode.inverted = a.classList.toggle('inverted',NightMode.invertTrigger);
}
}
},
reset: function() {
this.currentPhase = 0;
this.opacity = 0;
this.update(false);
}
};
//endregion

//region Obstacle
Obstacle.MAX_GAP_COEFFICIENT = 1.5; //障碍物最大间距系数
Obstacle.MAX_OBSTACLE_LENGTH = 3; //障碍物的最大数量
Obstacle.obstacles = []; //存储障碍物的数组
Obstacle.obstacleHistory = []; //记录障碍物数组中障碍物的类型
Obstacle.MAX_OBSTACLE_DUPLICATION = 2; //障碍物的最大重复数量

//障碍物类型的相关配置
Obstacle.types = [
{
type: 'CACTUS_SMALL', //小仙人掌
width: 17,
height: 35,
yPos: 105,
multipleSpeed: 4,
minGap: 120, //最小间距
minSpeed: 0 //最低速度
},
{
type: 'CACTUS_LARGE', //大仙人掌
width: 25,
height: 50,
yPos: 90,
multipleSpeed: 7,
minGap: 120,
minSpeed: 0
},
{
type: 'PTERODACTYL', //翼龙
width: 46,
height: 40,
yPos: [ 100, 75, 50 ], //有高、中、低三种高度
multipleSpeed: 999,
minSpeed: 8.5, //最小速度
minGap: 150,
numFrames: 2, //有两个动画帧
frameRate: 1000/6, //动画帧的切换速率
speedOffset: .8 //速度修正
}
];

/**
* 绘制障碍物构造函数
* @param canvas
* @param type 障碍物的类型
* @param spriteImgPos 雪碧图坐标
* @param dimensions 屏幕尺寸
* @param gapCoefficient 障碍物间隙
* @param speed 障碍物移动速度
* @param opt_xOffset 障碍物水平偏移量
* @constructor
*/
function Obstacle(canvas,type,spriteImgPos,dimensions,gapCoefficient,speed,opt_xOffset) {
this.ctx = canvas.getContext('2d');
this.spritePos = spriteImgPos;
//障碍物类型(仙人掌、翼龙)
this.typeConfig = type;
this.gapCoefficient = gapCoefficient;
//每个障碍物的数量(1~3)
this.size = getRandomNum(1,Obstacle.MAX_OBSTACLE_LENGTH);
this.dimensions = dimensions;
//表示该障碍物是否可以被移除
this.remove = false;
//水平坐标
this.xPos = dimensions.WIDTH + (opt_xOffset || 0);
this.yPos = 0;
this.width = 0;
this.gap = 0;
this.speedOffset = 0; //速度修正

//障碍物的动画帧
this.currentFrame = 0;
//动画帧切换的计时器
this.timer = 0;

this.init(speed);
}

Obstacle.prototype = {
init:function(speed) {
//如果随机障碍物是翼龙,则只出现一只
//翼龙的multipleSpeed是999,远大于speed
if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
this.size = 1;
}
//障碍物的总宽度等于单个障碍物的宽度乘以个数
this.width = this.typeConfig.width * this.size;

//若障碍物的纵坐标是一个数组
//则随机选取一个
if (Array.isArray(this.typeConfig.yPos)) {
var yPosConfig = this.typeConfig.yPos;
this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
} else {
this.yPos = this.typeConfig.yPos;
}

this.draw();

//对翼龙的速度进行修正,让它看起来有的飞得快一些,有些飞得慢一些
if (this.typeConfig.speedOffset) {
this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
-this.typeConfig.speedOffset;
}

//障碍物之间的间隙,与游戏速度有关
this.gap = this.getGap(this.gapCoefficient, speed);
},
//障碍物之间的间隔,gapCoefficient为间隔系数
getGap: function(gapCoefficient, speed) {
var minGap = Math.round(this.width * speed +
this.typeConfig.minGap * gapCoefficient);
var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
return getRandomNum(minGap, maxGap);
},
//判断障碍物是否移出屏幕外
isVisible: function() {
return this.xPos + this.width > 0;
},
draw:function() {
//障碍物宽高
var sourceWidth = this.typeConfig.width;
var sourceHeight = this.typeConfig.height;

//根据障碍物数量计算障碍物在雪碧图上的x坐标
//this.size的取值范围是1~3
var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) +
this.spritePos.x;

// 如果当前动画帧大于0,说明障碍物类型是翼龙
// 更新翼龙的雪碧图x坐标使其匹配第二帧动画
if (this.currentFrame > 0) {
sourceX += sourceWidth * this.currentFrame;
}
this.ctx.drawImage(imgSprite,
sourceX, this.spritePos.y,
sourceWidth * this.size, sourceHeight,
this.xPos, this.yPos,
sourceWidth * this.size, sourceHeight);
},
//单个障碍物的移动
update:function(deltaTime, speed) {
//如果障碍物还没有移出屏幕外
if (!this.remove) {
//如果有速度修正则修正速度
if (this.typeConfig.speedOffset) {
speed += this.speedOffset;
}
//更新x坐标
this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);

// Update frame
if (this.typeConfig.numFrames) {
this.timer += deltaTime;
if (this.timer >= this.typeConfig.frameRate) {
//在两个动画帧之间来回切换以达到动画效果
this.currentFrame =
this.currentFrame == this.typeConfig.numFrames - 1 ?
0 : this.currentFrame + 1;
this.timer = 0;
}
}
this.draw();

if (!this.isVisible()) {
this.remove = true;
}
}
},
//管理多个障碍物移动
updateObstacles: function(deltaTime, currentSpeed) {
//保存一个障碍物列表的副本
var updatedObstacles = Obstacle.obstacles.slice(0);

for (var i = 0; i 0) {
//获取障碍物列表中的最后一个障碍物
var lastObstacle = Obstacle.obstacles[Obstacle.obstacles.length - 1];

//若满足条件则添加障碍物
if (lastObstacle &&
lastObstacle.isVisible() &&
(lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) 1) {
Obstacle.obstacleHistory.splice(Obstacle.MAX_OBSTACLE_DUPLICATION);
}
},
//检查障碍物是否超过允许的最大重复数
duplicateObstacleCheck:function(nextObstacleType) {
var duplicateCount = 0;
//与history数组中的障碍物类型比较,最大只允许重复两次
for(var i = 0; i = Obstacle.MAX_OBSTACLE_DUPLICATION;
}
};
//endregion

window.onload = function () {
var h = new HorizonLine(c,spriteDefinition.HORIZON);
var cloud = new Cloud(c,spriteDefinition.CLOUD,DEFAULT_WIDTH);
var night = new NightMode(c,spriteDefinition.MOON,DEFAULT_WIDTH);
var obstacle = new Obstacle(c,Obstacle.types[0],spriteDefinition,{WIDTH:600},0.6,1);
var startTime = 0;
var deltaTime;
var speed = 3;
(function draw(time) {
gameFrame++;
if(speed

Chrome自带恐龙小游戏的源码研究(四)的更多相关文章

  1. Chrome自带恐龙小游戏的源码研究(五)

    在上一篇<Chrome自带恐龙小游戏的源码研究(四)>中实现了障碍物的绘制及移动,从这一篇开始主要研究恐龙的绘制及一系列键盘动作的实现. 会眨眼睛的恐龙 在游戏开始前的待机界面,如果仔细观 ...

  2. Chrome自带恐龙小游戏的源码研究(一)

    目录 Chrome自带恐龙小游戏的源码研究(一)——绘制地面 Chrome自带恐龙小游戏的源码研究(二)——绘制云朵 Chrome自带恐龙小游戏的源码研究(三)——昼夜交替 Chrome自带恐龙小游戏 ...

  3. Chrome自带恐龙小游戏的源码研究(七)

    在上一篇<Chrome自带恐龙小游戏的源码研究(六)>中研究了恐龙的跳跃过程,这一篇研究恐龙与障碍物之间的碰撞检测. 碰撞盒子 游戏中采用的是矩形(非旋转矩形)碰撞.这类碰撞优点是计算比较 ...

  4. Chrome自带恐龙小游戏的源码研究(完)

    在上一篇<Chrome自带恐龙小游戏的源码研究(七)>中研究了恐龙与障碍物的碰撞检测,这一篇主要研究组成游戏的其它要素. 游戏分数记录 如图所示,分数及最高分记录显示在游戏界面的右上角,每 ...

  5. Chrome自带恐龙小游戏的源码研究(六)

    在上一篇<Chrome自带恐龙小游戏的源码研究(五)>中实现了眨眼睛的恐龙,这一篇主要研究恐龙的跳跃. 恐龙的跳跃 游戏通过敲击键盘的Spacebar或者Up来实现恐龙的跳跃.先用一张图来 ...

  6. Chrome自带恐龙小游戏的源码研究(三)

    在上一篇<Chrome自带恐龙小游戏的源码研究(二)>中实现了云朵的绘制和移动,这一篇主要研究如何让游戏实现昼夜交替. 昼夜交替的效果主要是通过样式来完成,但改变样式的时机则由脚本控制. ...

  7. Chrome自带恐龙小游戏的源码研究(二)

    在上一篇<Chrome自带恐龙小游戏的源码研究(一)>中实现了地面的绘制和运动,这一篇主要研究云朵的绘制. 云朵的绘制通过Cloud构造函数完成.Cloud实现代码如下: Cloud.co ...

  8. WinFom中经典小游戏(含源码)

    最近整理了若干经典的小游戏,无聊时可以打发时间.程序本身不大,练手非常不错,主要是GDI编程,主界面地址如下图所示 源码下载方式 1,关注微信公众号:小特工作室(也可直接扫描签名处二维码) 2,发送: ...

  9. github下载下来的C#控制台小游戏[含源码]

    早就听说了github是世界最大的源码库,但自己却不是很懂,今天去研究了下,注册了一个帐号,然后在上面搜索了一下C# game,然后发现有许多的游戏. 随意地选择了一个,感觉比较简单,于是就下载了下来 ...

随机推荐

  1. hdu 4502 dp

    吉哥系列故事——临时工计划 Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Tot ...

  2. [bzoj 1782] [Usaco2010 Feb]slowdown慢慢游

    [bzoj 1782] [Usaco2010 Feb]slowdown慢慢游 Description 每天Farmer John的N头奶牛(1 <= N <= 100000,编号1-N)从 ...

  3. 学习good taste代码

    Linux 的创始人,在采访中提及了关于代码的 “good taste”.Linus Torvalds 展示了一一些代码: void remove_list_entry(entry){ prev = ...

  4. svn安装配置使用小总结

    1svn:版本控制系统服务端与客户端协作服务端:subversion客户端:eclipse_svn_site-1.10.5.zip插件1安装问题:    1subversion版本过高    会出现版 ...

  5. 微信公众号开发C#系列-12、微信前端开发利器:WeUI

    1.前言 通过前面系列文章的学习与讲解,相信大家已经对微信的开发有了一个全新的认识.后端基本能够基于盛派的第三方sdk搞定大部分事宜,剩下的就是前端了.关于手机端的浏览器的兼容性问题相信一直是开发者们 ...

  6. POJ 3710 Christmas Game [博弈]

    题意:略. 思路:这是个删边的博弈游戏. 关于删边游戏的预备知识:http://blog.csdn.net/acm_cxlove/article/details/7854532 学习完预备知识后,这一 ...

  7. java程序容错

    程序最怕出错的方式就是直接闪退 编程应该以这种方式进行,保证结构不出错,数据可容错的方式 比如 fungetsonmfrominternet(){变量 a a=从网络返回数据 return a } 在 ...

  8. ios iPhone 如何将应用程序名称本地化

    iPhone的应用程序名称也可以本地化,可以按照以下步骤来实施: 1. 修改项目目录下的’ -info.plist’文件名 将’ -info.plist’ 修改为 Info.plist 2. 将Inf ...

  9. 11G在用EXP导出时,空表不能导出

    11G中有个新特性,当表无数据时,不分配segment,以节省空间 解决方法: 1.insert一行,再rollback就产生segment了. 该方法是在在空表中插入数据,再删除,则产生segmen ...

  10. 深入Java----集合----BitSet

    BitSet类    大小可动态改变, 取值为true或false的位集合.用于表示一组布尔标志. java中有三种移位运算符 <<      :     左移运算符,num <&l ...