游戏开发HTML5------套圈圈丨web前端开发者

BY XIAOBAI · 2018年4月8日

前端开发者HTML5

https://www.rokub.com



前言虽然本文标题为介绍一个水压套圈h5游戏,但是窃以为仅仅如此对读者是没什么帮助的,毕竟读者们的工作生活很少会再写一个类似的游戏,更多的是面对需求的挑战。

我更希望能举一反三,给大家在编写h5游戏上带来一些启发,无论是从整体流程的把控,对游戏框架、物理引擎的熟悉程度还是在某一个小难点上的思路突破等。

因此本文将很少详细列举实现代码,取而代之的是以伪代码展现思路为主。

游戏 demo 地址:http://jdc.jd.com/fd/demo/waterful/game.html希望能给诸位读者带来的启发技术选型 整体代码布局 难点及解决思路 优化点技术选型一个项目用什么技术来实现,权衡的因素有许多。

其中时间是必须优先考虑的,毕竟效果可以减,但上线时间是死的。

本项目预研时间一周,真正排期时间只有两周。

虽然由项目特点来看比较适合走 3D 方案,但时间明显是不够的。

最后保守起见,决定采用 2D 方案尽量逼近真实立体的游戏效果。

从游戏复杂度来考虑,无须用到 Egret 或 Cocos 这些“牛刀”,而轻量、易上手、团队内部也有深厚沉淀的 CreateJS 则成为了渲染框架的首选。

另外需要考虑的是是否需要引入物理引擎,这点需要从游戏的特点去考虑。

本游戏涉及重力、碰撞、施力等因素,引入物理引擎对开发效率的提高要大于学习使用物理引擎的成本。

因此权衡再三,我引入了同事们已经玩得挺溜的 Matter.js

( Matter.js 文档清晰、案例丰富,是切入学习 web 游戏引擎的一个不错的框架)整体代码布局在代码组织上,我选择了面向对象的手法,对整个游戏做一个封装,抛出一些控制接口给其他逻辑层调用。

伪代码: 1 2 3 < ! — index . html — > < ! — 游戏入口 canvas — > < canvas id = “waterfulGameCanvas” width = “660” height = “570” > < / canvas >// game.js /** * 游戏对象 */ class Waterful { // 初始化函数 init () {} // CreateJS Tick,游戏操作等事件的绑定放到游戏对象内 eventBinding () {} // 暴露的一些方法 score () {} restart () {} pause () {} resume () {} // 技能 skillX () {} } /** * 环对象 */ class Ring { // 于每一个 CreateJS Tick 都调用环自身的 update 函数 update () {} // 进针后的逻辑 afterCollision () {} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // game.js /** * 游戏对象 */ class Waterful { // 初始化函数 init ( ) { } // CreateJS Tick,游戏操作等事件的绑定放到游戏对象内 eventBinding ( ) { } // 暴露的一些方法 score ( ) { } restart ( ) { } pause ( ) { } resume ( ) { } // 技能 skillX ( ) { } } /** * 环对象 */ class Ring { // 于每一个 CreateJS Tick 都调用环自身的 update 函数 update ( ) { } // 进针后的逻辑 afterCollision ( ) { } }// main.js // 根据业务逻辑初始化游戏,调用游戏的各种接口 const waterful = new Waterful() waterful.init({…}) 1 2 3 4 // main.js // 根据业务逻辑初始化游戏,调用游戏的各种接口 const waterful = new Waterful ( ) waterful . init ( { . . . } )初始化游戏的初始化接口主要做了4件事情:参数初始化 CreateJS 显示元素(display object)的布局 Matter.js 刚体(rigid body)的布局 事件的绑定下面主要聊聊游戏场景里各种元素的创建与布局,即第二、第三点。

一、CreateJS 结合 Matter.js阅读 Matter.js 的 demo 案例,都是用其自带的渲染引擎 Matter.Render。

但是由于某些原因(后面会说到),我们需要使用 CreateJS 去渲染每个环的贴图。

不像 Laya 配有和 Matter.js 自身用法一致的 Render,CreateJS 需要单独创建一个贴图层,然后在每个 Tick 里把贴图层的坐标同步为 Matter.js 刚体的当前坐标。

伪代码:createjs.Ticker.addEventListener(‘tick’, e => { 环贴图的坐标 = 环刚体的坐标 }) 1 2 3 createjs . Ticker . addEventListener ( ‘tick’ , e = > { 环贴图的坐标 = 环刚体的坐标 } )使用 CreateJS 去渲染后,要单独调试 Matter.js 的刚体是非常不便的。

建议写一个调试模式专门使用 Matter.js 的 Render 去渲染,以便跟踪刚体的运动轨迹。

二、环本游戏的难点是要以 2D 去模拟 3D,环是一点,进针的效果是一点,先说环。

环由一个圆形的刚体,和半径稍大一些的贴图层所组成。

如下图,蓝色部分为刚体:伪代码:class Ring { constructor () { // 贴图 this.texture = new createjs.Sprite(…) // 刚体 this.body = Matter.Bodies.circle(…) } } 1 2 3 4 5 6 7 8 class Ring { constructor ( ) { // 贴图 this . texture = new createjs . Sprite ( . . . ) // 刚体 this . body = Matter . Bodies . circle ( . . . ) } }三、刚体为什么把刚体半径做得稍小呢,这也是受这篇文章 推金币 里金币的做法所启发。

推金币游戏中,为了达到金币间的堆叠效果,作者很聪明地把刚体做得比贴图小,这样当刚体挤在一起时,贴图间就会层叠起来。

所以这样做是为了使环之间稍微有点重叠效果,更重要的也是当两个紧贴的环不会因翻转角度太接近而显得留白太多。

如图:为了模拟环在水中运动的效果,可以选择给环加一些空气摩擦力。

另外在实物游戏里,环是塑料做成的,碰撞后动能消耗较大,因此可以把环的 restitution 值调得稍微小一些。

需要注意 Matter.js 中因为各种物理参数都是没有单位的,一些物理公式很可能用不上,只能基于其默认值慢慢进行微调。

下面的 frictionAir 和 restitution 值就是我慢慢凭感觉调整出来的:this.body = Matter.Bodies.circle(x, y, r, { frictionAir: 0.02, restitution: 0.15 }) 1 2 3 4 this . body = Matter . Bodies . circle ( x , y , r , { frictionAir : 0.02 , restitution : 0.15 } )四、贴图环在现实世界中的旋转是三维的,而 CreateJS 只能控制元素在二维平面上的旋转。

对于一个环来说,二维平面的旋转是没有任何意义的,无论如何旋转,都只会是同一个样子。

想要达到环绕 x 轴旋转的效果,一开始想到的是使用 rotation + scaleY。

虽然这样能在视觉上达到目的,但是 scaleY 会导致环有被压扁的感觉,图片会失真:显然这样的效果是不能接受的,最后我采取了逐帧图的方式,最接近地还原了环的旋转姿态:注意在每个 Tick 里需要去判断环是否静止,若非静止则继续播放,并将贴图的 rotation 值赋值为刚体的旋转角度。

如果是停止状态,则暂停逐帧图的播放:// 贴图与刚体位置的小数点后几位有点不一样,需要降低精度 const x1 = Math.round(texture.x) const x2 = Math.round(body.position.x) const y1 = Math.round(texture.y) const y2 = Math.round(body.position.y) if (x1 !== x2 || y1 !== y2) { texture.paused && texture.play() texture.rotation = body.angle * 180 / Math.PI } else { !texture.paused && texture.stop() } texture.x = body.position.x texture.y = body.position.y 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 贴图与刚体位置的小数点后几位有点不一样,需要降低精度 const x1 = Math . round ( texture . x ) const x2 = Math . round ( body . position . x ) const y1 = Math . round ( texture . y ) const y2 = Math . round ( body . position . y ) if ( x1 !== x2 || y1 !== y2 ) { texture . paused && texture . play ( ) texture . rotation = body . angle * 180 / Math . PI } else { ! texture . paused && texture . stop ( ) } texture . x = body . position . x texture . y = body . position . y五、舞台舞台需要主要由物理世界、背景图,墙壁,针所组成。

1. 物理世界为了模拟真实世界环在水中的向下加速度,可以把 y 方向的 g 值调小:engine.world.gravity.y = 0.2 1 engine . world . gravity . y = 0.2左右重力感应对环的加速度影响同样可以通过改变 x 方向的 g 值达到:// 最大倾斜角度为 70 度,让用户不需要过分倾斜手机 // 0.4 为灵敏度值,根据具体情况调整 window.addEventListener(‘deviceorientation’, e => { let gamma = e.gamma if (gamma < -70) gamma = -70 if (gamma > 70) gamma = 70 this.engine.world.gravity.x = (e.gamma / 70) * 0.4 }) 1 2 3 4 5 6 7 8 // 最大倾斜角度为 70 度,让用户不需要过分倾斜手机 // 0.4 为灵敏度值,根据具体情况调整 window . addEventListener ( ‘deviceorientation’ , e = > { let gamma = e . gamma if ( gamma < – 70 ) gamma = – 70 if ( gamma > 70 ) gamma = 70 this . engine . world . gravity . x = ( e . gamma / 70 ) * 0.4 } )2. 背景图本游戏布景为游戏机及海底世界,两者可以作为父容器的背景图,把 canvas 的位置定位到游戏机内即可。

canvas 覆盖范围为下图的蓝色蒙层:3. 墙壁因为环的刚体半径比贴图半径小,因此墙壁刚体需要有一些提前位移,环贴图才不会溢出,位移量为 R – r(下图红线为墙壁刚体的一部分):4. 针为了模拟针的边缘轮廓,针的刚体由一个矩形与一个圆形所组成。

下图红线描绘了针的刚体:为什么针边缘没有像墙壁一样有一些提前量呢?这是因为进针效果要求针顶的平台区域尽量地窄。

作为补偿,可以把环刚体的半径尽可能地调得更大,这样在视觉上环与针的重叠也就不那么明显了。

进针进针是整个游戏的核心部分,也是最难模拟的地方。

进针后两个二维平面的物体交错是不能产生“穿过”效果的:除非把环分成前后两部分,这样层级关系才能得到解决。

但是由于环贴图是逐帧图,分两部分的做法并不合适。

最后找到的解决办法是利用视觉错位来达到“穿过”效果:具体做法是,当环被判定成功进针时,把环刚体去掉,环的逐帧图逐渐播放到平放的那一帧,rotation 值也逐渐变为 0。

同时利用 CreateJS 的 Tween 动画把环平移到针底。

进针后需要去掉环刚体,平移环贴图,这就是上文为什么环的贴图必须由 CreateJS 负责渲染的答案。

伪代码:/ Object Ring afterCollision (waterful) { // 平移到针底部 createjs.Tween.get(this.texture) .to({y: y}, duration) // 消去刚体 Matter.World.remove(waterful.engine.world, this.body) this.body = null // 接下来每一 Tick 的更新逻辑改变如下 this.update = function () { const texture = this.texture if 当前环贴图就是第 0 帧(环平放的那一帧){ texture.gotoAndStop(0) } else { 每 5 个 Tick 往前播放一帧(相隔多少 Tick 切换一帧可以凭感觉调整,主要是为了使切换到平放状态的过程不显得太突兀) } // 使针大概在环中央位置穿过 if (texture.x < 200) ++texture.x if (texture.x > 213 && texture.x < 300) –texture.x if (texture.x > 462) –texture.x if (texture.x > 400 && texture.x < 448) ++texture.x // 把环贴图尽快旋转到水平状态 let rotation = Math.round(texture.rotation) % 180 if (rotation < 0) rotation += 180 if (rotation > 0 && rotation <= 90) { texture.rotation = rotation – 1 } else if (rotation > 90 && rotation < 180) { texture.rotation = rotation + 1 } else if (frame === 0) { this.update = function () {} } } // 调用得分回调函数 waterful.score() } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 / Object Ring afterCollision ( waterful ) { // 平移到针底部 createjs . Tween . get ( this . texture ) . to ( { y : y } , duration ) // 消去刚体 Matter . World . remove ( waterful . engine . world , this . body ) this . body = null // 接下来每一 Tick 的更新逻辑改变如下 this . update = function ( ) { const texture = this . texture if 当前环贴图就是第 0 帧(环平放的那一帧) { texture . gotoAndStop ( 0 ) } else { 每 5 个 Tick 往前播放一帧(相隔多少 Tick 切换一帧可以凭感觉调整,主要是为了使切换到平放状态的过程不显得太突兀) } // 使针大概在环中央位置穿过 if ( texture . x < 200 ) ++ texture . x if ( texture . x > 213 && texture . x < 300 ) — texture . x if ( texture . x > 462 ) — texture . x if ( texture . x > 400 && texture . x < 448 ) ++ texture . x // 把环贴图尽快旋转到水平状态 let rotation = Math . round ( texture . rotation ) % 180 if ( rotation < 0 ) rotation += 180 if ( rotation > 0 && rotation <= 90 ) { texture . rotation = rotation – 1 } else if ( rotation > 90 && rotation < 180 ) { texture . rotation = rotation + 1 } else if ( frame === 0 ) { this . update = function ( ) { } } } // 调用得分回调函数 waterful . score ( ) }进针判断进针条件1. 到达针顶到达针顶是环进针成功的必要条件。

2. 动画帧环必须垂直于针才能被顺利穿过,水平于针时应该是与针相碰后弹开。

当然条件可以相对放宽一些,不需要完全垂直,下图红框内的6帧都被规定为符合条件:为了降低游戏难度,我规定超过针一半高度时,只循环播放前6帧:this.texture.on(‘animationend’, e => { if (e.target.y < 400) { e.target.gotoAndPlay(‘short’) } else { e.target.gotoAndPlay(‘normal’) } }) 1 2 3 4 5 6 7 this . texture . on ( ‘animationend’ , e = > { if ( e . target . y < 400 ) { e . target . gotoAndPlay ( ‘short’ ) } else { e . target . gotoAndPlay ( ‘normal’ ) } } )3. rotation 值同理,为了使得环与针相垂直,rotation 值不能太接近 90 度。

经试验后规定 0下图这种过大的倾角逻辑上是不能进针成功的:初探一开始我想的是把三维的进针做成二维的“圆球进桶”,进针的判断也就归到物理事件上面去,不需要再去考虑。

具体做法如下图,红线为针壁,当环刚体(蓝球)掉入桶内且与 Sensor (绿线)相碰,则判断进针成功。

为了使游戏难度不至于太大,环刚体必须设置得较小,而且针壁间距离要比环刚体直径稍大。

这种模拟其实已经能达到不错的效果了,但是一个技能打破了这种思路的可能性。

产品那边想做一个放大技能,当用户使用此技能时环会放大,更容易套中。

但是在桶口直径不变的情况下,只是环贴图变大并不能降低游戏难度。

如果把环刚体变小,的确容易进了,但相近的环之间的贴图重叠范围会很大,这就显得很不合理了。

改进“进桶”的思路走不通是因为不兼容放大技能,而放大技能改变的是环的直径。

因此需要找到一种进针判断方法在环直径小时,进针难度大,直径大时,进针难度小。

下面两图分别为普通环和放大环,其中红色虚线表示水平方向的内环直径:在针顶设置一小段探测线(下图红色虚线),当内环的水平直径与探测线相交时,证明进针成功,然后走进针后的逻辑。

在环放大时,内环的水平直径变长,也就更容易与探测线相交。

伪代码:// Object Ring // 每一 Tick 都去判断每个运动中的环是否与探测线相交 update (waterful) { const texture = this.texture // 环当前中心点坐标 const x0 = texture.x const y0 = texture.y // 环的旋转弧度 const angle = texture.rotation // 内环半径 const r = waterful.enlarging ? 16 * 1.5 : 16 // 根据旋转角度算出内环水平直径的开始和结束坐标 // 注意 Matter.js 拿到的是 rotation 值是弧度,需要转成角度 const startPoint = { x: x0 – r * Math.cos(angle * (Math.PI / 180)), y: y0 – r * Math.sin(angle * (Math.PI / 180)) } const endPoint = { x: x0 + r * Math.cos(-angle * (Math.PI / 180)), y: y0 + r * Math.sin(angle * (Math.PI / 180)) } // mn 为左侧探测线段的两点,uv 为右侧探测线段的两点 const m = {x: 206, y: 216}, n = {x: 206, y: 400}, u = {x: 455, y: 216}, v = {x: 455, y: 400} if (segmentsIntr(startPoint, endPoint, m, n) || segmentsIntr(startPoint, endPoint, u, v)) { // 内环直径与 mn 或 uv 相交,证明进针成功 this.afterCollision(waterful) } … } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 // Object Ring // 每一 Tick 都去判断每个运动中的环是否与探测线相交 update ( waterful ) { const texture = this . texture // 环当前中心点坐标 const x0 = texture . x const y0 = texture . y // 环的旋转弧度 const angle = texture . rotation // 内环半径 const r = waterful . enlarging ? 16 * 1.5 : 16 // 根据旋转角度算出内环水平直径的开始和结束坐标 // 注意 Matter.js 拿到的是 rotation 值是弧度,需要转成角度 const startPoint = { x : x0 – r * Math . cos ( angle * ( Math . PI / 180 ) ) , y : y0 – r * Math . sin ( angle * ( Math . PI / 180 ) ) } const endPoint = { x : x0 + r * Math . cos ( – angle * ( Math . PI / 180 ) ) , y : y0 + r * Math . sin ( angle * ( Math . PI / 180 ) ) } // mn 为左侧探测线段的两点,uv 为右侧探测线段的两点 const m = { x : 206 , y : 216 } , n = { x : 206 , y : 400 } , u = { x : 455 , y : 216 } , v = { x : 455 , y : 400 } if ( segmentsIntr ( startPoint , endPoint , m , n ) || segmentsIntr ( startPoint , endPoint , u , v ) ) { // 内环直径与 mn 或 uv 相交,证明进针成功 this . afterCollision ( waterful ) } . . . }判断线段是否相交的算法可以参考这篇文章:谈谈”求线段交点”的几种算法这种思路有两个不合常理的点:1.当环在针顶平台直到静止时,内环水平直径都没有和探测线相交,或者相交了但是 rotation 值不符合进针要求,视觉上给人的感受就是环在针顶上静止了:解决思路一是通过重力感应,因为设置了重力感应,只要用户稍微动一下手机环就会动起来。

二是判断环刚体在针顶平台完全静止了,则给它施加一个力,让它往下掉。

2.有可能环的运动轨迹是在针顶划过,但与探测线相交了,此时会给玩家一种环被吸下来的感觉。

可以通过适当设置探测线的长度来减少这种情况发生的几率。

优化资源池资源回收复用,是游戏常用的优化手法,接下来通过讲解气泡动画的实现来简单介绍一下。

气泡动画是逐帧图,用户点击按钮时,即创建一个 createjs.Sprite。

在 animationend 时,把该 sprite 对象从 createjs.Stage 中 remove 掉。

可想而知,当用户不停点击时,会不断的创建 createjs.Sprite 对象,非常耗费资源。

如果能复用之前播放完被 remove 掉的 sprite 对象,就能解决此问题。

具体做法是每当用户按下按钮时,先去资源池数组找有没有 sprite 对象。

如果没有则创建,animationend 时把 sprite 对象从 stage 里 remove 掉,然后 push 进资源池。

如果有,则从资源池取出并直接使用该对象。

当然用户的点击操作事件需要节流处理,例如至少 300ms 后才能播放下一个气泡动画。

伪代码:// Object Waterful getBubble = throttle(function () { // 存在空闲泡泡即返回 if (this._idleBubbles.length) return this._idleBubbles.shift() // 不存在则创建 const bubble = new createjs.Sprite(…) bubble.on(‘animationend’, () => { this._stage.removeChild(bubble) this._idleBubbles.push(bubble) }) return bubble }, 300) 1 2 3 4 5 6 7 8 9 10 11 12 // Object Waterful getBubble = throttle ( function ( ) { // 存在空闲泡泡即返回 if ( this . _idleBubbles . length ) return this . _idleBubbles . shift ( ) // 不存在则创建 const bubble = new createjs . Sprite ( . . . ) bubble . on ( ‘animationend’ , ( ) = > { this . _stage . removeChild ( bubble ) this . _idleBubbles . push ( bubble ) } ) return bubble } , 300 )环速度过快导致飞出边界Matter.js 里由于没有实现持续碰撞检测算法(CCD),所以在物体速度过快的情况下,和其他物体的碰撞不会被检测出来。

当环速度很快时,也就会出现飞出墙壁的 bug。

正常情况下,每次按键给环施加的力都是很小的。

当用户快速连续点击时,y 方向累积的力也不至于过大。

但还是有玩家反应游戏过程中环不见了的问题。

最后发现当手机卡顿时,Matter.js 的 Tick 没有及时触发,导致卡顿完后把卡顿时累积起来的力一次性应用到环刚体上,环瞬间获得很大的速度,也就飞出了游戏场景。

解决方法有两个:给按钮节流,300ms才能施加一次力。

每次按下按钮,只是把一个标志位设为 true。

在每个 Matter.js 的 Tick 里判断该标志位是否为 true,是则施力。

保证每个 Matter.js 的 Tick 里只对环施加一次力。

伪代码:btn.addEventListener(‘touchstart’, e => { this.addForce = true }) Events.on(this._engine, ‘beforeUpdate’, e => { if (!this.addForce) return this.addForceLeft = false // 施力 this._rings.forEach(ring => { Matter.Body.applyForce(ring.body, {x: x, y: y}, {x: 0.02, y: -0.03}) Matter.Body.setAngularVelocity(ring.body, Math.PI/24) }) }) 1 2 3 4 5 6 7 8 9 10 11 12 btn . addEventListener ( ‘touchstart’ , e = > { this . addForce = true } ) Events . on ( this . _engine , ‘beforeUpdate’ , e = > { if ( ! this . addForce ) return this . addForceLeft = false // 施力 this . _rings . forEach ( ring = > { Matter . Body . applyForce ( ring . body , { x : x , y : y } , { x : 0.02 , y : – 0.03 } ) Matter . Body . setAngularVelocity ( ring . body , Math . PI / 24 ) } ) } )

前端开发者HTML5

https://www.rokub.com

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

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,738评论 0 38
  • 老周发来微信,说家里有位老人永远的画上了句号! 12月9号(周六)早上,老周给我发来了微信, 老周:丫头忙啥了 ...
    缄口不言阅读 283评论 0 0
  • ☞爱到深处才会吃醋,我若不在乎,绝对不会哭! 总是喜欢把最坏的自己,去展示给最爱的人,可能我就真的适合一个人的生活...
    贰十三先生阅读 190评论 2 3
  • 如何用口语表达影响他人 1.一条心法:当你写下每一个字时,做到四个字:对面有人 2.有没有对象感,对象感非常非常重...
    夜雨狂歌如梦阅读 1,160评论 1 3
  • 【在这个焦躁忙碌的年底,突然想起这样一段时光,这样一些人……】 大学毕业的第三年,我在一家工厂上班。 工厂在城市西...
    苏丽珍阅读 347评论 0 6