CocosCreator摘星星

玩法分析

玩家操作一个反映迟钝却蹦跳不停地小怪物去触碰不断出现的星星,难以驾驭的加速度将给玩家带来很大挑战,和您的小伙伴比一比看谁能拿到更多地星星吧!

创建项目

启动CocosCreator创建项目,在资源管理器中分类创建资源文件夹以保存不同的资源。

资源文件夹 描述
texture 存放纹理资源
audio 存放音效资源
scene 存放游戏场景文件
script 存放游戏逻辑脚本文件
prefab 存放预设文件

将项目所使用的音效和纹理分别存放到对应文件夹并规范化命名。

创建场景

游戏场景(Scene)是开发时组织游戏内容的中心,也是呈现给玩家游戏内容的载体。游戏场景中一般会包含以下内容:场景图片、文字、角色、以组件形式附加在场景节点上的游戏逻辑脚本等。当玩家运行游戏时,首先会加载游戏场景,场景加载后会自动运行所包含组件的游戏脚本,以实现各种开发者设置的逻辑。

创建游戏主场景

$ vim assets/scene/main.fire

双击主场景会在场景编辑器和层级管理器(节点树)中打开,打开场景后层级管理器会显示当前场景中所有节点及其层级关系。刚新建的场景中默认只有一个名为Canvas的根节点,Canvas可以被称为画布节点或渲染根节点。默认状态下,画布节点在场景编辑器中以紫色矩形框显示区域范围。

渲染根节点

选中Canvas在右侧属性检查器的Canvas组件属性中设置画布适配选项。

Canvas组件属性
画布属性 描述
DesignResolution 游戏设计分辨率,即游戏界面的宽度和高度。
FitWidth 宽度自适应,用于在不同尺寸屏幕上运行时,按高度优先来缩放画布以适配。
FitHeight 高度自适应,用于在不同尺寸屏幕上运行时,按高度优先来缩放画布以适配。

由于提供多分辨率适配一般会将场景中所有负责图像显示的节点都存放在Canvas下面,这样当作父节点的Canvas的缩放属性scale改变时,所有子节点图像也会跟着一起缩放以适应不同屏幕大小。

设置创景图像

添加场景背景图,将资源管理器中texture纹理目录下的background图像拖拽到层级管理器Canvas节点下,即添加一个以background为贴图资源的子节点。节点会自动以贴图资源的文件名来命名,编辑场景后及时使用快捷键Ctrl+S保存。

修改背景尺寸

为了使背景图片覆盖整个屏幕,选中背景节点后点击主窗口左上角工具栏第四个按钮矩形变形工具,使用矩形变形工具可任意修改图像节点的尺寸。

矩形变形工具

为了使背景图能够覆盖主流手机整个屏幕,在背景节点的属性检查器的Node节点属性组中设置size宽高为1360 x 760。

添加地面

游戏主角在画面上是在地面上跳跃,因此需添加地面。添加方式和背景雷同。需要注意的是在层级管理器中显示在下方的节点渲染顺序是会上方节点的后面,也就是说下方节点会在上方节点之后绘制。从层级管理器上可以发现,层级管理器最下方的节点在场景编辑器中会显示在最前方。另外,子节点永远会显示在父节点前方。因此可以通过调整节点层级顺序来控制图层显示顺序。

设置地面宽高为1960x250,并沿着背景底边对齐。

地面节点

添加主角

添加主角到层级管理器,确保位于最下层显示最前面。设置主角属性,修改锚点位置,默认任何节点的锚点会在节点中心即(0.5, 0.5)位置,也就是说节点中心点所在位置就是该节点的位置。

主角跳跃时需要控制主角的底部位置来模拟在地面上跳跃的效果,所以需要将主角的锚点在底边中心即(0, 0.5)

主角锚点

到此为止,场景搭配完毕。

主角脚本

Cocos Creator开发游戏的核心理念是让内容生产和功能开发流畅地并行协作

创建驱动主角行动的脚本

$ vim assets/script/player.js

Creator中脚本名称就是组件的名称,命名大小写敏感,若组件名称大小写错误会导致无法通过名称使用组件。

cc.Class({

});

使用全局方法cc.Class()声明CocosCreator中的类

  • cc是Cocos引擎的简称也是Cocos引擎的主要命名空间,引擎代码中所有的类、函数、属性、常量都在cc这个命名空间下。
  • Class()方法是cc模块下的一个方法,用于声明CocosCreator中的类。
  • Class()方法的参数是一个原型对象,在原型对象中以键值对形式设定所需参数类型,即可创建出所需的类。

创建组件类的结构

cc.Class({
  extends:cc.Component,
  properties:{}
});

自定义组件类继承自cc.Component类,实现继承后能够挂载到到场景的节点上,提供控制节点的各种功能。

添加组件属性

properties: {
    jumpHeight:0,//主角跳跃高度,单位像素。
    jumpDuration:0,//主角跳跃持续时长,单位为秒。
    maxSpeed:0,//最大移动速度,单位像素每秒。
    accel:0,//移动加速度,单位像素每秒。
    jumpAudio:{type:cc.AudioClip, default:null},//跳跃音效
},

CocosCreator规定节点自定义属性必须写在properties代码块中,自定义属性规定主角的移动方式主要包括高度、时间、速度、加速度这四大属性,具体的属性值可使用属性检查器设置,换句话说,这种随时需要调整的属性都可以放到properties属性中。

为节点添加脚本组件

在层级管理器中选中主角节点后,在属性检查器下点击添加组件,选择用户脚本组件,选择player脚本组件,即可为主角节点添加player组件。

添加脚本

添加组件成功后即可在属性检查器中出现player组件以及自定义的属性,设置主角跳跃高度为200像素,起跳到最高点所需时间0.6秒,最大水平移动速度为400像素每秒,水平加速度250像素每秒。

属性设置

实现玩家垂直跳跃

onLoad(){
  //初始化跳跃动作并在当前节点上执行该动作
  this.jumpAction = this.setJumpAction();
  this.node.runAction(this.jumpAction);
},
//设置主角跳跃动作
setJumpAction(){
  //执行向上跳跃到最高点
  const jumpUp = cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
  //从最高点向下跳跃到原点
  const jumpDown = cc.moveBy(this.jumpDuration, cc.v2(0, -this.jumpHeight)).easing(cc.easeCubicActionOut());
  //上下跳跃时播放跳跃音效
  const callback = cc.callFunc(this.playJumpAudio, this);
  //循环执行
  return cc.repeatForever(cc.sequence(jumpUp, jumpDown, callback));
}

onLoad生命周期回调函数会在场景加载后立即自动执行,一般会将初始化相关的操作和逻辑放入其中。这里首先讲循环跳跃动作传递给jumpAction变量,然后调用组件挂载的节点的runAction方法,让主角节点循环跳跃。

当组件加载后脚本首先会执行onLoad生命周期回调函数,在onLoad()回调函数中实现主角跳跃动作,需要了解CocosCreator的动作系统(Action),CocosCreator中的动作简单来说就是节点的位移、缩放和旋转。主角上下跳跃实际上可以分解为两个动作向上移动和向下移动,然后无限重复这两个动作。这里需要实现移动和重复两个操作,需使用cc.moveBy()cc.repeatForever()方法。

移动到指定的距离

cc.moveBy(duration, deltaPos, deltaY):ActionInterval
参数 类型 描述
duration Number 移动持续秒数
deltaPos Vec2/Number 目标坐标点或X轴坐标值
deltaY number Y轴坐标值

cc.moveBy()方法用于在规定时间内移动指定的一段距离,duration参数是移动消耗的时长,deltaPos参数是一个Vec2类型(2D向量和坐标)的对象,也可以是一个Number类型的X轴坐标值。当传入的是一个Number类型是即为X轴坐标,此时就需要填写第三个参数deltaY。需要注意的是这里的坐标值是相当于节点当前的坐标位置,而非整个坐标系的绝对位置。

cc.moveBy(this.jumpDuration, cc.v2(0, this.jumpHeight))
cc.moveBy(this.jumpDuration, 0, this.jumpHeight)

cc.moveBy()方法返回值是一个ActionInterval类型的对象,ActionInterval表示时间间隔动作类,即动作在一定时间内完成。时间间隔类ActionInterval具有easing()方法,可以让时间间隔动作呈现一种缓动运动,easing()方法的参数是一个缓动对象,同时也返回一个ActionInterval时间间隔类对象。

cc.moveBy(this.jumpDuration, 0, this.jumpHeight).easing(cc.easeCubicActionOut());

cc.easeCubicActionOut()方法构建的缓动对象是以EaseCubicInOut按三次函数缓动进入并退出的动作。

重复上下跳动

cc.repeatForever()函数用于永远地重复某个动作,有限次数内重复一个动作则使用repeat函数,由于cc.repeatForever重复执行的动作不会停止,因此不能被添加到cc.sequencecc.spawn函数中。

cc.repeatForever(action:FiniteTimeAction):ActionInterval

按顺序执行动作

cc.sequence()函数用于按顺序执行动作,创建的动作将按顺序依次运行。

cc.sequence(actionOrActionArray:FiniteTimeAction|FiniteTimeAction[], tempArray:FiniteTimeAction):ActionInterval

执行回调函数

cc.callFunc(selector:function, selectorTarget:object, data:any):ActionInterval

实现玩家移动控制

键盘按下开启加速,键盘抬起关闭加速。

  • 键盘按下开启向左或向右加速
  • 键盘松开关闭向左或向右加速

为了让玩家能够通过键盘按键控制主角按水平移动,可为主角添加键盘输入,使用键盘A键和D键控制跳跃的方向。

玩家水平左右移动的过程中存在加速度,当开启方向加速度开关时向左或向右移动时会添加加速度,当关闭方向加速开关时向左或向右移动时会取消加速度。

onLoad(){
  //初始化水平加速度
  this.xSpeed = 0;
  //初始化方向加速开关
  this.accLeft = false;
  this.accRight = false;
  //监听键盘按下事件以实现玩家水平移动
  cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
  cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
onDestroy(){
  //取消监听
  cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
  cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
//键盘按键按下 启用方向加速度开关
onKeyDown(evt){
  switch(evt.keyCode){
    case cc.macro.KEY.a: this.accLeft = true; break;
    case cc.macro.KEY.d: this.accRight = true; break;
  }
}
//键盘按键抬起 关闭方向加速度开关
onKeyUp(evt){
  switch(evt){
    case cc.macro.KEY.a: this.accLeft = false; break;
    case cc.macro.KEY.d: this.accRight = false; break;
  }
}

Cocos通过systemEvent来监听系统全局事件,通过向systemEvent注册键盘响应函数,在函数中通过switch判断键盘上的A和D键是否被按下或松开,若松下就执行对应的操作。

设置主角速度和位置

  • 向左加速移动
  • 向右加速移动

游戏中根据键盘输入获得加速度方向后,需要每帧在update方法中计算主角的速度和位置。

游戏开发的一个关键点是在每一帧渲染前更新物体的行为、状态和方位,这些更新操作通常都会放在update这个生命周期回调函数中。

update(dt){
  //根据加速度方向设置每帧更新速度
  if(this.accLeft){
    this.xSpeed -= this.accel * dt;//向左加速移动
  }else if(this.accRight){
    this.xSpeed += this.accel * dt;//向右加速移动
  }
  //限制主角速度不能超过最大值
  if(Math.abs(this.xSpeed) > this.maxSpeed){
    this.xSpeed = this.maxSpeed * this.xSpeed / Math.abs(this.xSpeed);
  }
  //根据当前速度更新主角的X坐标位置
  this.node.x += this.xSpeed * dt;
}

制作星星

星星是主角的目标,会不断在场景中消失又出现。玩家需引导主角去触碰星星以收集分数。当主角触碰到星星时便会显示,然后随机位置重新出现。

星星在场景中属于重复出现又消失的节点,对于这种在节点树上重复生成的节点,应采用预制资源prefab,作为动态生成节点时所使用的模板。

  • 星星随机地出现在画布范围内
  • 星星与主角的距离一旦满足收集范围则

制作预制资源

从资源管理器中将星星纹理拖到场景中,无需修改星星节点的任何属性,只需要为其添加一个自定义的脚本组件后,在将星星节点拖放到资源管理器,此时会发现资源管理器会自动生成星星预设资源。

星星脚本组件

星星脚本组件主要实现的操作包括

  • 每一帧都判断星星和主角的距离是否达到收集半径,若满足则发生收集行为。
  • 计算星星与主角之间的距离
  • 星星被收集时计算得分并销毁
  • 星星收集时会出现渐隐的视觉效果

这些效果的实现仅仅靠单独的星星组件脚本是不行的,需要和其他组件配置实现。

$ vim assets/script/star.js
cc.Class({
    extends: cc.Component,
    properties: {
        //星星和主角之间的距离小于这个数值时会完成收集
        pickRadius:0,
    }
}

星星组件添加属性

星星组件只需要一个属性用来控制主角距离星星多远时完成收集,在properties中添加pickRadius自定义属性。

为节点添加组件

star.js脚本组件添加到star节点中,设置收集半径pickRadius属性值为60像素。

删除星星节点

将星星节点从层级管理器拖拽到资源管理器中的prefab文件夹下,自动生成star.prefab预设资源文件。接下来,删除层级管理器中的star节点。后续修改星星组件可直接双击star.prefab预设资源文件即可。

游戏控制

目前准备工作已经做完,首先创建了搭建了场景,完成了美术UI设计,接着为主角添加了player.js控制脚本组件,为星星创建了预设资源star.prefab文件。下面的中心是实现游戏主逻辑,首先添加名为game.js的脚本组件作为游戏主逻辑脚本,将game.js脚本组件添加到场景的渲染节点Canvas中,接着在将地面节点ground、玩家节点player、星星节点star作为自定义属性添加到脚本中,在依次实现生成星星、计算得分、游戏结束功能。

$ vim assets/script/player.js
cc.Class({
    extends: cc.Component,
    properties: {
        //星星产生后消失时间的随机范围
        minStarDuration:0,
        maxStarDuration:0,
        //星星预制资源
        star:{type:cc.Prefab, default:null},
        //地面节点用于确定星星生成的高度
        ground:{type:cc.Node, default:null},
        //玩家节点用于获取主角弹跳的高度和控制主角行动开关
        player:{type:cc.Node, default:null},
        //得分文本
        scoreLabel:{type:cc.Label, default:null},
        //得分音效
        scoreAudio:{type:cc.AudioClip, default:null}
    }
}

属性检查器自定义属性参数

例如:在脚本中为自定义属性除了设置type和default默认值外,还可设置其他参数。

star:{
  type:cc.Prefab, 
  default:null
},
属性参数 描述
type 限定属性的数据类型
default 设置属性的默认值,默认值在组件第一次添加到节点是才会使用。
visible 布尔值,是否在属性检查器面板中显示。
serializable 布尔值,是否序列化保存属性。
displayName 在属性检查器面板中显示的最终名字
tooltip 在属性检查器面板中添加属性提示

为场景渲染节点Canvas添加game.js游戏主逻辑控制脚本组件

将自定义属性中的节点分别设置(拖拽)到属性检查器面板对应位置

任意位置随机生成星星

场景加载后会初始化游戏主逻辑控制脚本game.js并执行onLoad生命周期回调函数,此时需要随机位置生成星星,生成星星的位置是有一定区域限制的,从坐标的角度上看,星星的最高点不能超过主角的弹跳最高点,最低点不得超过地面,左右侧不得超过屏幕。

如何使用预设资源生成星星,需要使用给定模板在场景中生成节点,并将该节点添加到Canvas节点下,最后为其设置一个随机的位置。

onLoad(){
  //随机生成星星
  this.spawnStar();
},
//随机生成星星
spawnStar(){
  //使用给定预设资源模板在场景中生成一个新的节点
  const start = cc.instantiate(this.starPrefab);
  //将新增节点添加到画布节点下
  this.node.addChild(star);
  //为新节点设置随机位置
  star.setPosition(this.getPosition());
}

这里涉及到两个问题,第一个是如何使用预设资源即模板生成节点,第二个如何设置随机位置。

使用预设资源生成节点

const star = cc.instantiate(this.starPrefab);

cc.instantiate方法的作用是克隆指定的任意类型的对象,或从Prefab预设资源实例化出新节点,返回值为Node节点或Object对象。

为星星设置随机坐标位置

节点X轴位置可根据屏幕宽度随机计算得到,节点y轴坐标可根据地面高度和主角跳跃的高度随机计算得到。

//为星星设置随机坐标位置
getPosition(){
  const x = this.node.width / 2 * (Math.random() - 0.5);
  const y = this.groundY + this.player.getComponent("player").jumpHeight * Math.random() + 50;
  return cc.v2(x, y);
}

获取地面最高点Y坐标值

this.ground.y是地面锚点的Y坐标,锚点默认位于地面的中心位置,获取高点顶点位置还需为加上地面高度的一半。

onLoad(){
  this.groundY = this.ground.y + this.ground.height / 2;
}

获取玩家的最高点Y坐标值

this.groundY + this.player.getComponent("player").jumpHeight

计算星星的随机Y轴坐标值

const y = this.groundY + this.player.getComponent("player").jumpHeight * Math.random();

添加主角碰触收集星星的行为

星星每帧获取主角节点的坐标,通过判断它们之间的距离是否小于可收集距离来执行收集行为,这里的问题是,如何在星星中获取主角节点的引用呢?由于game脚本组件中保存了player玩家节点属性即主角节点的引用,而且星星是在game脚本中动态生成的,因此只要在game脚本生成star节点实例时,将game组件的实例传入到star脚本并保存起来即可,之后即可在star脚本中通过this.game.player访问主角节点。

spawnStar(){
  //...
  //在星星组件上暂存game对象的引用
  star.getComponent("star").game = this;
}

在star脚本组件中每帧判断星星和主角的距离

$ vim assets/script/star.js
update(dt){
  //每帧判断星星与主角的距离来执行收集行为
  if(this.getDistance() < this.pickedRadis){
    this.onPicked();
    return;
  }
},
//获取星星和主角之间的距离
getDistance(){
  //获取主角的坐标
  const playerPosition = this.game.player.getPosition();
  //极端两点之间的距离
  return this.node.position.sub(playerPosition).mag();
}

计算两个cc.Vec2矢量坐标的距离时,首先对两个坐标做减法,然后求距离。这里使用到两个函数cc.Vec2.sub()cc.Vec2.mag()方法。

  • cc.Vec2.sub方法用于坐标向量减法同时返回新的向量
  • cc.Vec2.mag方法用于计算向量的长度

实现星星收集

当星星被收集时调用game脚本中的接口生成一个星星,然后在销毁当前星星节点,最后计算得分。

onPicked(){
  //生成新的星星
  this.game.spawnStar();
  //销毁当前星星
  this.node.destroy();
  //计算得分
  this.game.gainScore();
}

计算得分并显示

成功收集星星时会增加得分奖励的逻辑和显示

场景中添加得分节点

游戏开始得分为0,每次成功收集积分加1,显示积分,首先需要在场景中添加一个Label节点,并添加到画布节点下,设置Label节点位于最下层并命名为score,设置Label节点属性的位置和文字大小。最后将Label节点添加到game游戏主逻辑脚本属性的score属性中。

游戏主逻辑脚本中实现得分逻辑

$ vim assets/script/game.js
properties(){
  //得分文本显示节点
  scoreLabel:{type:cc.Label, default:null},
  //得分音效资源
  scoreAudio:{type:cc.AudioClip, default:null}
},
onLoad(){
  //初始化得分值
  this.score = 0;
},
//计算得分
gainScore(){
  //计算得分
  this.score += 1;
  //更新显示
  this.scoreLabel.string = this.score + "";
  //播放音效
  cc.audioEngine.playEffect(this.scoreAudio, false);
}

失败判断

得分可以带来成就感和补偿感能增加兴趣,但得分再多不能失败,游戏的挑战和刺激性就不够,因为没有成就感,游戏带来体验之一是成就感,这种成就感是通过不断地失败而后成功,才能极大的给玩家带来某种幸福感。如何让玩家在收集星星的过程中失败呢?星星是不断出现和消失的,在出现和消失的时间间隙完成收集则成功加分,否则失败退出。

为星星添加计时消失的逻辑

设置标准计时器

vim assets/script/game.js
onLoad(){
  //初始化计时器
  this.timer = 0;
},
update(dt){
  //更新计时器
  this.timer += dt;
},
//生成星星
spawnStar(){
  //重置计时器
  this.timer= 0;
}

设置星星消失时间范围

vim assets/script/game.js
properties:{
  //定义星星产生后消失的时间范围
  minStarDuration:0,
  maxStarDuration:0,
}
onLoad(){
  //初始化星星消失时间
  this.starDuration = 0;
}
//创建星星
spawnStar(){
  //随机设置星星消失时间
  this.starDuration = this.minStarDuration + Math.random() * (this.maxStarDuration - this.minStarDuration);
}

游戏结束判断

vim assets/script/game.js
update(dt){
  //游戏失败判断
  if(this.timer > this.starDuration){
    //游戏失败
    this.gameOver();
    return;
  }
  //更新计时器
  this.timer += dt;
}
//游戏失败
gameOver(){
  //停止玩家行为
  this.player.stopAllActions();
  //重新加载场景
  cc.director.loadScene("main");
}

为星星增加渐隐效果

$ vim assets/script/star.js
properties(){
  //透明度范围
  minOpacity:50,
  maxOpacity:255
},
update(dt){
  //根据计时器更新透明度
  const radio = 1 - this.game.timer / this.game.starDuration;
  this.node.opacity = this.minOpacity + Math.floor(radio * (this.maxOpacity - this.minOpacity));
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342