Flutter Flame實(shí)戰(zhàn) - 復(fù)刻經(jīng)典游戲”割繩子“

Flame是一款基于Flutter的2D游戲引擎,今天我將使用它制作一款經(jīng)典小游戲割繩子

ctr_preview.gif

基本物品準(zhǔn)備

添加游戲背景

游戲的背景圖片資源包含多個(gè)圖片,這里通過Sprite的截取功能裁出來(lái)我們需要的部分坏怪,然后添加到游戲中作為背景

final bgSprite = await Sprite.load("bgr_01_hd.jpeg",
    images: images,
    srcSize: Vector2(770, 1036),
    srcPosition: Vector2(0, 0));
final bgComponent = SpriteComponent(sprite: bgSprite)
  ..anchor = Anchor.center
  ..position = Vector2(size.x * 0.5, size.y * 0.5);
add(bgComponent);

小怪獸

小怪獸的Sprite Sheet圖如下简烘,想要使用它構(gòu)建動(dòng)畫笑陈,需要知道每一個(gè)Sprite的大小以及偏移的位置放刨,這些數(shù)據(jù)需要一些手段獲取,我硬編碼在了項(xiàng)目中云稚。

char_animations.png

新建CTRPlayer類表示小怪獸隧饼,他繼承自PositionComponent

class CTRPlayer extends PositionComponent {
    ...
}

通過Sprite大小和偏移數(shù)據(jù)構(gòu)建小怪獸的Sprite列表,使用ImageComposition整合出符合統(tǒng)一規(guī)范的Sprite

Future<List<Sprite>> _genSpriteSlices() async {
    List<Sprite> sprites = [];
    final List<Map<String, double>> rects = [
      ...
    ];
    final List<Offset> offsets = spriteOffsets();
    for (int i = 0; i < rects.length; ++i) {
      final rect = rects[i];
      final pos = Vector2(rect["x"]!, rect["y"]!);
      final size = Vector2(rect["M"]!, rect["U"]!);
      final sprite = await Sprite.load("char_animations.png",
          srcPosition: pos, srcSize: size, images: images);
      final offset = offsets[i];
      final composition = ImageComposition()
        ..add(await sprite.toImage(), Vector2(offset.dx, offset.dy));
      final composeImage = await composition.compose();
      sprites.add(Sprite(composeImage));
    }
    return sprites;
    }
    
    List<Offset> spriteOffsets() {
    final List<Map<String, double>> raw = [
        ...
    ];
    List<Offset> offsets = [];
    for (final pos in raw) {
      offsets.add(Offset(pos["x"]! - 76, pos["y"]! - 83));
    }
    return offsets;
}

通過SpriteAnimationGroupComponent管理小怪獸的多個(gè)動(dòng)畫静陈,SpriteAnimationGroupComponent可以將多組幀動(dòng)畫合并燕雁,并通過將current設(shè)置為對(duì)應(yīng)狀態(tài)值快速切換動(dòng)畫

final animMap = {
  CTRPlayerAnimState.reset: SpriteAnimation.spriteList(
      _sprites.sublist(0, 1),
      stepTime: 0.06,
      loop: true),
  CTRPlayerAnimState.eat: SpriteAnimation.spriteList(
      _sprites.sublist(27, 40),
      stepTime: 0.06,
      loop: false),
  CTRPlayerAnimState.idle1: SpriteAnimation.spriteList(
      _sprites.sublist(64, 83),
      stepTime: 0.06,
      loop: false),
  CTRPlayerAnimState.idle2: SpriteAnimation.spriteList(
      _sprites.sublist(53, 61),
      stepTime: 0.06,
      loop: false),
};
_animationComponent = SpriteAnimationGroupComponent(
    animations: animMap, current: CTRPlayerAnimState.reset);

接著我們讓小怪獸在沒吃到糖果前隨機(jī)進(jìn)行idle1idle2兩種動(dòng)畫,通過_lastIdleAnimElapsedTime控制每8s嘗試播放一次idle1或者idle2動(dòng)畫

@override
update(double dt) {
  super.update(dt);
  _lastIdleAnimElapsedTime += dt;
  if (_lastIdleAnimElapsedTime > 8 &&
      _animationComponent.current == CTRPlayerAnimState.reset &&
      _animationComponent.current != CTRPlayerAnimState.eat) {
    _lastIdleAnimElapsedTime = 0;
    final states = [CTRPlayerAnimState.idle1, CTRPlayerAnimState.idle2];
    final state = states[Random().nextInt(states.length)];
    _animationComponent.current = state;
  }
}
ctr_player_anim.gif

最后給小怪獸增加一個(gè) duan~ duan~ duan~ 的效果窿给,通過ScaleEffect反復(fù)縮放實(shí)現(xiàn)

final charEffect = ScaleEffect.to(
    Vector2(1.1, 0.9),
    EffectController(
        duration: 0.6,
        reverseDuration: 0.3,
        infinite: true,
        curve: Curves.easeOutCubic));
_animationComponent.add(charEffect);
ctr_player_anim_duan.gif

糖果

新建CTRCandy類表示糖果贵白,糖果只是簡(jiǎn)單的合并了前景圖和背景圖,原始素材上有閃光幀動(dòng)畫崩泡,想要添加的話禁荒,再增加一層SpriteAnimationComponent展示即可

final candyBg = await Sprite.load("obj_candy_01.png",
    images: images, srcPosition: Vector2(2, 2), srcSize: Vector2(87, 90));
_candyBgComponent = SpriteComponent(sprite: candyBg)
  ..anchor = Anchor.center
  ..position = Vector2(6, 13);
add(_candyBgComponent);

final candyFg = await Sprite.load("obj_candy_01.png",
    images: images, srcPosition: Vector2(2, 95), srcSize: Vector2(60, 60));
_candyFgComponent = SpriteComponent(sprite: candyFg)
  ..anchor = Anchor.center
  ..position = Vector2(0, 0);
add(_candyFgComponent);

ctr_candy.png

固定點(diǎn)

固定點(diǎn)和糖果類似,也只是簡(jiǎn)單的合并了前景圖和背景圖角撞,新建了CTRHook類表示

final hookBg = await Sprite.load("obj_hook_01.png",
    images: images, srcPosition: Vector2(2, 2), srcSize: Vector2(50, 50));
final hookBgComponent = SpriteComponent(sprite: hookBg)
  ..anchor = Anchor.topLeft
  ..position = Vector2(0, 0);
add(hookBgComponent);

final hookFg = await Sprite.load("obj_hook_01.png",
    images: images, srcPosition: Vector2(2, 55), srcSize: Vector2(15, 14));
final hookFgComponent = SpriteComponent(sprite: hookFg)
  ..anchor = Anchor.center
  ..position = Vector2(25, 25);
add(hookFgComponent);
ctr_hook.png

星星

星星包含兩組動(dòng)畫呛伴,旋轉(zhuǎn)和消失勃痴,新建CTRStar類表示并通過SpriteAnimationGroupComponent管理動(dòng)畫

final idleSprites = await _idleSprites();
final disappearSprites = await _disappearSprites();

final animMap = {
  CTRStarState.idle: SpriteAnimation.spriteList(idleSprites.sublist(1, 18),
      stepTime: 0.05, loop: true),
  CTRStarState.disappear: SpriteAnimation.spriteList(
      disappearSprites.sublist(0, 12),
      stepTime: 0.05,
      loop: false),
};
_animationComponent = SpriteAnimationGroupComponent(
    animations: animMap, current: CTRStarState.idle);
_animationComponent.position = Vector2(0, 0);
_animationComponent.anchor = Anchor.topLeft;
add(_animationComponent);
ctr_star.gif

繩子模擬

創(chuàng)建CTRRope類表示繩子

物理模擬

繩子的模擬采用了多個(gè)BodyComponent使用RopeJoint鏈接的方式實(shí)現(xiàn),首先創(chuàng)建CTRRopeSegment表示繩子的一段热康,它繼承自BodyComponent沛申,主要支持物理模擬,不參與渲染

class CTRRopeSegment extends BodyComponent {
  final Offset pos;
  late Vector2 _size;
  bool isBreak = false;

  CTRRopeSegment({this.pos = const Offset(0, 0)}) {
    _size = Vector2(10, 10);
    renderBody = false;
  }

  @override
  Body createBody() {
    final bodyDef = BodyDef(
        type: BodyType.dynamic,
        userData: this,
        position: Vector2(pos.dx, pos.dy));
    return world.createBody(bodyDef)
      ..createFixtureFromShape(CircleShape()..radius = _size.x * 0.5,
          density: 1, friction: 0, restitution: 0);
  }
}

接著還需要?jiǎng)?chuàng)建另一個(gè)類CTRRopePin姐军,它和CTRRopeSegment類似铁材,但是他不能動(dòng),用與將繩子的一頭固定

class CTRRopePin extends BodyComponent {
  double size;
  Offset pos;

  CTRRopePin({this.size = 20, this.pos = const Offset(0, 20)}) {
    renderBody = false;
  }

  @override
  Body createBody() {
    final bodyDef = BodyDef(
        type: BodyType.static,
        userData: this,
        position: Vector2(this.pos.dx, this.pos.dy));
    return world.createBody(bodyDef)
      ..createFixtureFromShape(CircleShape()..radius = size * 0.5,
          friction: 0.2, restitution: 0.5);
  }
}

萬(wàn)事俱備奕锌,將它們湊成一根繩子

final pin = CTRRopePin(pos: Offset(startPosition.dx, startPosition.dy));
add(pin);
await Future.wait([pin.loaded]);
const ropeSegLen = 8.0;
final ropeSegCount = length ~/ ropeSegLen;
final deltaOffset = (endPosition - startPosition);
final space = deltaOffset.distance / ropeSegCount;
final dirVec = deltaOffset / deltaOffset.distance;
CTRRopeSegment? lastRopeSeg;
for (int i = 0; i < ropeSegCount; ++i) {
  final seg =
      CTRRopeSegment(pos: dirVec * i.toDouble() * space + startPosition);
  add(seg);
  await Future.wait([seg.loaded]);
  final jointDef = RopeJointDef()
    ..bodyA = lastRopeSeg?.body ?? pin.body
    ..bodyB = seg.body
    ..maxLength = ropeSegLen;
  game.world.createJoint(RopeJoint(jointDef));
  lastRopeSeg = seg;
  _ropSegs.add(seg);
}

首先創(chuàng)建CTRRopePin作為繩子的開端著觉,然后通過各種參數(shù)計(jì)算出需要多少個(gè)CTRRopeSegment,最后通過RopeJoint逐個(gè)相連惊暴。打開CTRRopeSegmentrenderBody饼丘,可以大致看出繩子的模擬效果

ctr_rope_raw.gif

繩子渲染

游戲中繩子是兩種顏色相間的,我們可以在CTRRopevoid render(Canvas canvas)中進(jìn)行自定義繪制辽话,首先準(zhǔn)備好要繪制的點(diǎn)和兩種顏色的Paint

List<Offset> points = [];
points.add(startPosition);
final paint1 = Paint()
  ..color = const Color(0xff815c3c)
  ..style = PaintingStyle.stroke
  ..strokeWidth = 5
  ..strokeJoin = StrokeJoin.round
  ..strokeCap = StrokeCap.round;
final paint2 = Paint()
  ..color = const Color.fromARGB(255, 65, 44, 27)
  ..style = PaintingStyle.stroke
  ..strokeWidth = 5
  ..strokeJoin = StrokeJoin.round
  ..strokeCap = StrokeCap.round;
for (int i = 0; i < _ropSegs.length; i++) {
  final currenPt = _ropSegs[i].position;
  points.add(Offset(currenPt.x, currenPt.y));
}

接著每隔4段更換一次顏色肄鸽,并通過drawLine繪制繩子

final newPoints = points;
bool togglePaint = false;
for (int i = 0; i < newPoints.length - 1; i++) {
  if (i % 4 == 0) {
    togglePaint = !togglePaint;
  }
  final paint = togglePaint ? paint1 : paint2;
  canvas.drawLine(Offset(newPoints[i].dx, newPoints[i].dy),
      Offset(newPoints[i + 1].dx, newPoints[i + 1].dy), paint);
}
ctr_rope_render.gif

如何切斷繩子

切斷繩子需要解決2個(gè)問題,一個(gè)是如何判斷哪一段被切到油啤,還有就是切完之后渲染不正確的問題典徘。

如何判斷哪一段被切到

我采用了一個(gè)簡(jiǎn)單的方案,創(chuàng)建一個(gè)繼承自BodyComponent的類CTRScissors村砂,手指移動(dòng)時(shí)控制它的位置烂斋,如果它和CTRRopeSegment發(fā)生的碰撞,則從被碰撞的CTRRopeSegment處切斷繩子

class CTRScissors extends BodyComponent with ContactCallbacks {
  bool valid = true;

  CTRScissors() {
    renderBody = false;
  }

  updatePosition(Vector2 newPos) {
    body.setTransform(newPos, 0);
  }

  @override
  Body createBody() {
    const bodySize = 20.0;
    final bodyDef = BodyDef(
        type: BodyType.dynamic,
        gravityOverride: Vector2(0, 0),
        userData: this,
        bullet: true,
        position: Vector2(0, 0));
    return world.createBody(bodyDef)
      ..createFixtureFromShape(CircleShape()..radius = bodySize * 0.5,
          density: 1, friction: 0.2, restitution: 0.5);
  }

  @override
  void beginContact(Object other, Contact contact) {
    super.beginContact(other, contact);
    if (other is CTRRopeSegment) {
      if (valid && !other.isBreak) {
        other.removeFromParent();
        other.isBreak = true;
      }
    }
  }
}

other.removeFromParent();會(huì)直接讓繩子變成2段础废,other.isBreak = true;則是用于防止多次移除和解決切斷后渲染問題

切完之后如何渲染

只需要做一些小改動(dòng),首先如果CTRRopeSegmentisBreaktrue罕模,添加一個(gè)-1,-1點(diǎn)到points

for (int i = 0; i < _ropSegs.length; i++) {
  final currenPt = _ropSegs[i].position;
  if (_ropSegs[i].isBreak) {
    points.add(const Offset(-1, -1));
  } else {
    points.add(Offset(currenPt.x, currenPt.y));
  }
}

接著繪制時(shí)發(fā)現(xiàn)當(dāng)前點(diǎn)或者接下來(lái)一個(gè)點(diǎn)坐標(biāo)都小于0评腺,直接不繪制

for (int i = 0; i < newPoints.length - 1; i++) {
  if (i % 4 == 0) {
    togglePaint = !togglePaint;
  }
  final paint = togglePaint ? paint1 : paint2;
  if ((newPoints[i + 1].dx < 0 && newPoints[i + 1].dy < 0) ||
      (newPoints[i].dx < 0 && newPoints[i].dy < 0)) {
    continue;
  }
  canvas.drawLine(Offset(newPoints[i].dx, newPoints[i].dy),
      Offset(newPoints[i + 1].dx, newPoints[i + 1].dy), paint);
}
ctr_rope_cut.gif

將糖果掛到繩子上

糖果想要掛載到繩子上,首先自己需要繼承BodyComponent淑掌,然后將自身傳遞給CTRRope蒿讥,CTRRope增加attachComponent用于接收掛載物

final Offset startPosition;
final BodyComponent? attachComponent;
final double length;

final List<CTRRopeSegment> _ropSegs = [];
CTRRope(
  {this.startPosition = const Offset(0, 0),
  this.length = 100,
  this.attachComponent});

CTRRope發(fā)現(xiàn)掛載物不為空時(shí),創(chuàng)建RopeJoint將繩子最后一段和掛載物連接起來(lái)

if (attachComponent != null) {
  final jointDef = RopeJointDef()
    ..bodyA = lastRopeSeg?.body ?? pin.body
    ..bodyB = attachComponent!.body
    ..maxLength = ropeSegLen;
  game.world.createJoint(RopeJoint(jointDef));
}
ctr_rope_with_candy.gif

采集星星

糖果和星星的碰撞檢測(cè)抛腕,我使用了flame的CollisionCallbacks芋绸,但是我發(fā)現(xiàn)無(wú)法直接給繼承自BodyComponentCTRCandy開啟CollisionCallbacks,只能新建一個(gè)專門用于碰撞檢測(cè)的組件

class CTRCandyCollisionComponent extends PositionComponent
    with CollisionCallbacks {
  final WeakReference<CTRCandy>? candy;
  CTRCandyCollisionComponent({this.candy});
  
  @override
  FutureOr<void> onLoad() {
    size = Vector2(40, 40);

    add(CircleHitbox(radius: 30)
      ..anchor = Anchor.center
      ..position = Vector2(0, 0));
    return super.onLoad();
  }

  @override
  void onCollisionStart(
      Set<Vector2> intersectionPoints, PositionComponent other) {
    super.onCollisionStart(intersectionPoints, other);
    if (other is CTRStar) {
      other.disappear();
    }
  }
}

但是這個(gè)組件如果直接addCTRCandy上担敌,CollisionCallbacks依然無(wú)法生效摔敛,經(jīng)過嘗試,我將它掛載到了根節(jié)點(diǎn)上全封,并通過update同步CTRCandyCTRCandyCollisionComponent的位置

class CTRCandy extends BodyComponent {
...
  @override
  Future<void> onLoad() async {
    ...
    collisionComponent = CTRCandyCollisionComponent(candy: WeakReference(this));
    parent?.add(collisionComponent);
    ...
  }
  
  @override
  void update(double dt) {
    super.update(dt);
    collisionComponent.position = body.position;
  }
}

CTRStar繼承自PositionComponent马昙,所以直接加一個(gè)CircleHitbox即可

add(CircleHitbox(radius: 10)
  ..anchor = Anchor.center
  ..position = size * 0.5);

碰撞時(shí)桃犬,調(diào)用CTRStardisappear()觸發(fā)消失動(dòng)畫,CTRStar通過對(duì)animationTickers的監(jiān)控行楞,在動(dòng)畫結(jié)束時(shí)銷毀自己

disappear() {
  _animationComponent.current = CTRStarState.disappear;
  _animationComponent.animationTickers?[CTRStarState.disappear]?.onComplete =
        () {
    removeFromParent();
  };
}
ctr_star_disappear.gif

怪獸吃到糖果

怪獸和糖果之間也是采用的CollisionCallbacks進(jìn)行檢測(cè)

@override
void onCollisionStart(
    Set<Vector2> intersectionPoints, PositionComponent other) {
  super.onCollisionStart(intersectionPoints, other);
  if (other is CTRCandyCollisionComponent) {
    other.candy?.target?.beEaten();
    eat();
  }
}

發(fā)現(xiàn)怪獸碰撞到了糖果攒暇,調(diào)用糖果的beEaten觸發(fā)fadeOut效果,通過OpacityEffect實(shí)現(xiàn)

beEaten() {
  _candyBgComponent.add(OpacityEffect.fadeOut(EffectController(duration: 0.3))
    ..removeOnFinish = false);
  _candyFgComponent.add(OpacityEffect.fadeOut(EffectController(duration: 0.3))
    ..removeOnFinish = false);
}

eat();則是觸發(fā)了小怪獸的干飯動(dòng)畫

eat() {
    _animationComponent.animationTickers?[CTRPlayerAnimState.eat]?.reset();
    _animationComponent.current = CTRPlayerAnimState.eat;
}
ctr_player_eat.gif

合在一起子房,布置一個(gè)關(guān)卡

首先添加小怪獸

_player = CTRPlayer(images: images)
  ..anchor = Anchor.center
  ..position = Vector2(size.x * 0.8, size.y * 0.8);
add(_player);

然后布置小星星

add(CTRStar(images: images)
      ..anchor = Anchor.center
      ..position = Vector2(100, 400));

add(CTRStar(images: images)
  ..anchor = Anchor.center
  ..position = Vector2(220, 430));

add(CTRStar(images: images)
  ..anchor = Anchor.center
  ..position = Vector2(320, 530));

接下來(lái)創(chuàng)建糖果但是不添加

final candy = CTRCandy(images: images, pos: Offset(100, 200));

最后布置繩子并添加糖果

{
      final hook = CTRHook(images: images)
        ..anchor = Anchor.center
        ..position = Vector2(100, 100);
      final rope = CTRRope(
          startPosition: hook.position.toOffset(),
          attachComponent: candy,
          length: 200);
      add(rope);
      add(hook);
}

{
  final hook = CTRHook(images: images)
    ..anchor = Anchor.center
    ..position = Vector2(250, 100);
  final rope = CTRRope(
      startPosition: hook.position.toOffset(),
      attachComponent: candy,
      length: 300);
  add(rope);
  add(hook);
}

add(candy);

就可以得到開頭的游戲場(chǎng)景啦~

接下來(lái)...

簡(jiǎn)單總結(jié)一下形用,這個(gè)小游戲主要涉及以下技術(shù)點(diǎn)

  • SpriteAnimationComponentSpriteAnimationGroupComponent的使用
  • flame_forge2d的基礎(chǔ)用法和RopeJoint的使用
  • flame碰撞檢測(cè)的使用

訪問 https://github.com/BuildMyGame/FlutterFlameGames 可以獲取完整代碼,更多細(xì)節(jié)閱讀代碼就可以知道了哦~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末证杭,一起剝皮案震驚了整個(gè)濱河市尾序,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌躯砰,老刑警劉巖每币,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異琢歇,居然都是意外死亡兰怠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門李茫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)揭保,“玉大人,你說(shuō)我怎么就攤上這事魄宏〗章拢” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵宠互,是天一觀的道長(zhǎng)味榛。 經(jīng)常有香客問我,道長(zhǎng)予跌,這世上最難降的妖魔是什么搏色? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮券册,結(jié)果婚禮上频轿,老公的妹妹穿的比我還像新娘。我一直安慰自己烁焙,他們只是感情好航邢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骄蝇,像睡著了一般膳殷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乞榨,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天秽之,我揣著相機(jī)與錄音当娱,去河邊找鬼。 笑死考榨,一個(gè)胖子當(dāng)著我的面吹牛跨细,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播河质,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冀惭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了掀鹅?” 一聲冷哼從身側(cè)響起散休,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乐尊,沒想到半個(gè)月后戚丸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扔嵌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年限府,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痢缎。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胁勺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出独旷,到底是詐尸還是另有隱情署穗,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布嵌洼,位于F島的核電站案疲,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏咱台。R本人自食惡果不足惜络拌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望回溺。 院中可真熱鬧,春花似錦混萝、人聲如沸遗遵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)车要。三九已至,卻和暖如春崭倘,著一層夾襖步出監(jiān)牢的瞬間翼岁,已是汗流浹背类垫。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琅坡,地道東北人悉患。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像榆俺,于是被迫代替她去往敵國(guó)和親售躁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容