【Godot】1.0 入门基础

,握基本语法、逻辑、面向对象、数组、字典、委托、接口等知识即可。

API中文文档:https://www.bookstack.cn/read/godot-4.2-zh/

脚本编辑软件:https://www.bookstack.cn/read/godot-4.2-zh/678acee2979bfdbf.md

常见的生命周期方法

using Godot;
using System;

public partial class Sprite2d : Sprite2D
{
    //节点添加到节点树中的时候调用
    public override void _EnterTree()
    {
        GD.Print("enterTree");
    }

    //节点加载完成,这里初始化
    public override void _Ready()
    {
        GD.Print("Ready");
    }
    //帧
    public override void _Process(double delta) { }
    //游戏逻辑
    //每次物理系统计算,会调用一次1 个引用
    public override void _PhysicsProcess(double delta)
    {
    }

    //节点离开节点树,销毁

    public override void _ExitTree()
    {
        GD.Print("ExitTree");
    }
}

鼠标和按键

using Godot;
using System;

public partial class InputTest : Node
{
    public override void _Ready()
    {
        // 鼠标设置:显示、隐藏鼠标、限制在游戏窗口
        // 限制并且隐藏
        Input.MouseMode = Input.MouseModeEnum.ConfinedHidden;
        // Input.MouseMode = Input.MouseModeEnum.Confined;
    }

    public override void _Process(double delta)
    {
        // 按键事件
        //是否按下键盘的B键
        if (Input.IsKeyPressed(Key.B))
        {
            GD.Print("按下了B键");
        }
    }
}

鼠标和键盘事件处理

using Godot;
using System;

public partial class InputTest : Node
{
    public override void _Ready()
    {
        // 鼠标设置:显示、隐藏鼠标、限制在游戏窗口
        // 限制并且隐藏
        // Input.MouseMode = Input.MouseModeEnum.ConfinedHidden;
        // Input.MouseMode = Input.MouseModeEnum.Confined;
    }

    public override void _Process(double delta)
    {
        // 按键事件
        //是否按下键盘的B键
        if (Input.IsKeyPressed(Key.B))
        {
            GD.Print("按下了B键");
        }
    }

    public override void _Input(InputEvent @event)
    {
        base._Input(@event);
        // 如果是键盘事件,转成键盘事件去操作
        if (@event is InputEventKey)
        {
            //转成键盘事件
            var key = @event as InputEventKey;
            // 判断当前是否按下的是V键
            if (key.Keycode == Key.V)
            {
                GD.Print("按下了V键");
                // 判断当前是否是持续按压
                if (key.IsEcho())
                {
                    GD.Print("持续按压V键");
                }
                // 判断当前是否是刚按下
                else if (key.IsPressed())
                {
                    GD.Print("刚按下V键");
                }
                // 判断当前是否是抬起瞬间
                else if (key.IsReleased())
                {
                    GD.Print("抬起V键");
                }
            }
        }
        // 如果是鼠标事件,转成鼠标事件去操作
        if (@event is InputEventMouse)
        {
            //转成鼠标事件
            var mouse = @event as InputEventMouse;
            // 判断当前是否按下
            if (mouse.IsPressed())
            {
                GD.Print("按下鼠标:", mouse.Position, mouse.ButtonMask);
            }
        }
    }
}

按键映射

image.png
    public override void _Process(double delta)
    {
        // 按键事件
        //是否按下键盘的B键
        if (Input.IsKeyPressed(Key.B))
        {
            GD.Print("按下了B键");
        }


        // 按下虚拟按键
        if (Input.IsActionJustPressed("跳跃"))
        {
            GD.Print("按下跳跃");
        }
        if (Input.IsActionPressed("跳跃"))
        {
            GD.Print("跳跃中");
        }
        if (Input.IsActionJustReleased("跳跃"))
        {
            GD.Print("结束跳跃");
        }
        // 获取按键力度
        float s = Input.GetActionStrength("跳跃");
        GD.Print("跳跃力度:", s);
    }

获取虚拟的左和右。


image.png
    public override void _Process(double delta)
    {
        // 按键事件
        //是否按下键盘的B键
        if (Input.IsKeyPressed(Key.B))
        {
            GD.Print("按下了B键");
        }


        // 按下虚拟按键
        if (Input.IsActionJustPressed("跳跃"))
        {
            GD.Print("按下跳跃");
        }
        if (Input.IsActionPressed("跳跃"))
        {
            GD.Print("跳跃中");
        }
        if (Input.IsActionJustReleased("跳跃"))
        {
            GD.Print("结束跳跃");
        }
        // 获取按键力度
        float s = Input.GetActionStrength("跳跃");
        GD.Print("跳跃力度:", s);
        // 获取一个x轴,水平轴
        float horizontal = Input.GetAxis("左", "右");
        GD.Print("方向:", horizontal);
    }

上下左右

        Vector2 dir = Input.GetVector("左", "右", "上", "下");
        GD.Print("方向:", dir);

代码操作节点

using Godot;
using System;

public partial class NodeOp : Node
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        if (Input.IsActionJustPressed("左"))
        {
            //获取当前场景的根节点
            Node root = this.GetTree().CurrentScene;
            // 去寻找Sprite2D节点
            Node test = root.FindChild("Sprite2D");
            // 删除Sprite2D节点
            test.QueueFree();
        }

    }
}

移动节点

using Godot;
using System;

public partial class NodeOp : Node
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        if (Input.IsActionJustPressed("左"))
        {
            //获取当前场景的根节点
            Node root = this.GetTree().CurrentScene;
            // 去寻找InputTest节点
            Node test = root.FindChild("Sprite2D");
            // 删除InputTest节点
            // test.QueueFree();
            // 从节点树拿出来
            root.RemoveChild(test);
            // 将节点添加到当前节点下
            this.AddChild(test);
        }
    }
}

添加节点


        if (Input.IsActionJustPressed("右"))
        {
            //获取当前场景的根节点
            Node root = this.GetTree().CurrentScene;
            Node2D node2D = new Node2D();
            node2D.Name = "New";
            this.AddChild(node2D);
        }
image.png

找父节点

 if (Input.IsActionJustPressed("右"))
        {
            Node2D node2D = new Node2D();
            node2D.Name = "New";
            Node3D node =  this.GetParent<Node3D>();
            node.AddChild(node2D);
        }

找任意节点


image.png
        if (Input.IsActionJustPressed("右"))
        {
            Node2D node2D = new Node2D();
            node2D.Name = "New";

            // 绝对路径
            Node2D node = GetNode<Node2D>("/root/Home/p2/test");
            node.AddChild(node2D);
        }
image.png
   // 相对路径
            Node2D node = GetNode<Node2D>("../test1");
            node.AddChild(node2D);
image.png

切换场景

        if (Input.IsActionJustPressed("右"))
        {
            SceneTree st = this.GetTree();
            st.ChangeSceneToFile("res://home/home.tscn");
        }
using Godot;
using System;

public partial class NodeOp : Node
{
    // 新场景
    [Export] public PackedScene NewScene;

    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
     
        if (Input.IsActionJustPressed("右"))
        {
            // 跳转场景方法1
            // SceneTree st = this.GetTree();
            // st.ChangeSceneToFile("res://game1/game1.tscn");
            // 跳转场景方法2
            SceneTree st = this.GetTree();
            st.ChangeSceneToPacked(NewScene);
        }
    }
}
image.png

拖动场景放入,效果一致。

只需要另一个场景中的子节点,加入当前节点

比如子弹场景,加入当前场景


        if (Input.IsActionJustPressed("左"))
        {
            // 实例化新场景,并返回一个根节点
            Node node = NewScene.Instantiate();
            //添加到根节点
            this.GetTree().CurrentScene.AddChild(node);
        }

自动加载脚本

创建一个主目录下的node节点下的脚本。


image.png

假设脚本是对游戏做一些管理设定,只要游戏运行就一直存活。
因此,一般情况下,需要将该脚本设置成单例。


image.png

运行后会发现脚本已经运行在场景中,且切换场景一直存在。


image.png

常用属性代码设置

比如一个精灵

       //CanvasItem常用属性//是否显示
        this.Visible = true;
        //渲染顺序
        this.ZIndex = 10;
        // 是否相对本身所在顺序加权
        this.ZAsRelative = false; 
        //Node2D常用属性
        ////位置
        this.Position = new Vector2(500, 300); 
        //旋转
        // this.Rotation =0.1f; // 幅度
        //this.RotationDegrees = 30; // 角度 30度
        // 缩放
        this.Scale = new Vector2(2, 2);
        //倾斜
        this.Skew = 30;
    //帧
    public override void _Process(double delta)
    {
        // 获取鼠标位置
        var pos = GetGlobalMousePosition();
        // 看向某个点
        LookAt(pos);
    }

动态加载精灵图片

        this.Texture=GD.Load<Texture2D>("res://icon.svg");

或者

    [Export]
    public Texture2D newTexture;

    //节点加载完成,这里初始化
    public override void _Ready()
    {
        this.Texture=newTexture;
    }

出现的图标只有四分之一,可以设置中心点偏移模式:

    public override void _Ready()
    {
        this.Texture=newTexture;
        // 中心点还是左上角
        this.Centered = false;
    }

物品分组是个好习惯

image.png

image.png
    //帧
    public override void _Process(double delta)
    {
  
        if (Input.IsActionJustPressed("右"))
        {
            // 获得分组中的节点
            var enemys = this.GetTree().GetNodesInGroup("小兵");
            // 遍历敌人 销毁
            foreach (Node enemy in enemys)
            {
                // 销毁
                enemy.QueueFree();
            }
        }
    }

给每个敌人挂一个脚本

using Godot;
using System;

public partial class Dogface : Node
{

    public void test()
    {
        GD.Print("我是小兵哈哈哈哈");
    }
}

找个随便其他存活脚本执行如下代码:

    //帧
    public override void _Process(double delta)
    {
  
        if (Input.IsActionJustPressed("左"))
        {
            // 给分组节点发消息
            this.GetTree().CallGroup("小兵", "test");
        }
    }
image.png

脚本动态加入分组


        if (Input.IsActionJustPressed("上"))
        {
            // 将当前节点加到敌人分组
            this.AddToGroup("小兵");
        }

信号的接发

所谓信号,其实就是事件监听和响应。


image.png

左侧按钮点击,会导致右侧图标消失。
不同的控件有不同的信号方法:


image.png

创建脚本MyButton.cs并绑定button按钮:

using Godot;
using System;

public partial class MyButton : Node
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
    }

    public void buttonClick()
    {
        // 找到节点并删除
        Node2D node = GetNode<Node2D>("/root/Home/Node2D/Sprite2D");
        node.QueueFree();
    }
}

image.png

image.png

image.png

脚本动态连接信号

using Godot;
using System;

public partial class MyButton : Node
{
    public override void _Ready()
    {
        // 动态连接信号
        this.Connect("pressed", new Callable(this,"buttonClick"));
    }

    public override void _Process(double delta)
    {
    }

    public void buttonClick()
    {
        // 找到节点并删除
        Node2D node = GetNode<Node2D>("/root/Home/Node2D/Sprite2D");
        node.QueueFree();
    }
}

自定义信号(事件)

如果有vue知识,就会发现等同于emit和@事件。
A.cs

using Godot;
using System;

public partial class A : Sprite2D
{
    // 信号:声明一个委托
    [Signal]
    public delegate void MySignalEventHandler();

    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        if (Input.IsActionJustPressed("左"))
        {
            // 发射信号
            EmitSignal("MySignal");
        }
    }
}

B.cs

using Godot;
using System;

public partial class B : Sprite2D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
    }
    
    
    public void buttonClick()
    {
        Godot.GD.Print("接收到信号了!");
    }
}
image.png

动画精灵

image.png
image.png

脚本控制动画播放

using Godot;
using System;

public partial class MyAnimatedSprite2d : AnimatedSprite2D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        if(Input.IsActionPressed("左"))
        {
            this.Play("idle");
        }
        if(Input.IsActionPressed("右"))
        {
            this.Play("attack");
        }
    }
}

动画播放

创建一个角色的动画


image.png

image.png

image.png

image.png

选择纹理。


image.png

右击,插入关键帧
image.png

点一下关键帧
image.png

因为有2个图像,所以只需要2个图像关键帧:
image.png

右侧勾选,循环:


image.png

一秒长度太长,改成0.4秒
image.png

调整参数,一号关键帧在0.0秒,2号在0.2秒:
image.png

动画播放就做完了。
增加代码脚本:
using Godot;
using System;

public partial class MyAnimationPlayer : AnimationPlayer
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        if(Input.IsActionPressed("左"))
        {
            this.Play("idle");
        }
        
        if(Input.IsActionPressed("右"))
        {
            this.Play("run");
        }
    }
}

效果一致。
其他常用函数

Pause 暂停播放
Stop 播放停止
IsPlaying 是否在播放

动画播放和上面的精灵动画相比,好处是 动画播放还能做其他更多的事情。
比方run的同,能改变位置和缩放。


image.png

光照

2D主要有2种灯光,平行光DirectionalLight2D和点光源PointLight2D 。

2D种的平行光,可以理解为全局性的灯光。


image.png
image.png

点光源是需要纹理的:


image.png

灯光遮罩,只有匹配上的组件,才会有灯光。


image.png

image.png

投影(阴影)

希望灯光照射到精灵上有阴影区域
首先系统不清楚阴影范围要多大。


image.png

设置障碍物范围。


image.png

设置灯光支持阴影,启用:
image.png

要有背景才能看出阴影,增加一个背景精灵即可:
image.png

可以设置阴影的颜色:


image.png

声音

image.png

代码控制:

using Godot;
using System;

public partial class MyAudioStreamPlayer : AudioStreamPlayer
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
        if (Input.IsActionPressed("左"))
        {
            if (this.Playing==false)
            {
                this.Play();
            }
            
        }
        if (Input.IsActionPressed("右"))
        {
            this.StreamPaused = true;
        }
    }
}
image.png

代码实现音频播放

image.png
image.png
using Godot;
using System;

public partial class MyNode2d : Node2D
{
    
    AudioStreamPlayer audioStreamPlayer;
    [Export] private AudioStream bgm;
    public override void _Ready()
    {
        
        // 实例化节点
        audioStreamPlayer = new AudioStreamPlayer();
        // 加到当前子节点
        this.AddChild(audioStreamPlayer);
        // 加载音频
        audioStreamPlayer.Stream = bgm;
        audioStreamPlayer.Play();
    }

    public override void _Process(double delta)
    {
    }
}

碰撞

如下,敌人和玩家,需要检测碰撞,但精灵类是只负责展示,没办法检测碰撞的。

image.png

增加碰撞区域类,同时会报警告,因为没有设置碰撞区域,设置区域需要增加一个子类,2D有多边形和规则图形。


image.png

image.png

碰撞触发和碰撞离开:


image.png

射线碰撞

image.png

一个射线一个精灵。


image.png

默认勾选,排除父节点碰撞。
增加精灵碰撞体:


image.png

射线支持和2种产生碰撞,一种是物理碰撞,一种是区域碰撞。


image.png

增加脚本
···
using Godot;
using System;

public partial class MyRayCast2d : RayCast2D
{
public override void _Ready()
{
}

public override void _Process(double delta)
{
    // 无论什么状态,立即更新光线的碰撞信息,而不等待下一个_physics_process调用。
    // this.ForceRaycastUpdate();
}

/**
 * 物理更新函数
 */
public override void _PhysicsProcess(double delta)
{
    // 每次按下左键检测一次 是否 碰撞
    if(Input.IsActionPressed("左"))
    {
        if (this.IsColliding())
        {
            // 拿到碰撞的区域
            Area2D area2D = this.GetCollider() as Area2D;
            GD.Print("检测到了:" + area2D.Name);
        }
        else
        {
            GD.Print("没检测到");
        }
    }
    
}

}
···

类似效果还有组件ShapeCast2D,区别是射线区域,有一个宽度范围。

image.png

物理碰撞

上面做了区域碰撞,godot还有静态物理区域组件:

image.png

假设做一个2d的横版游戏的地面:


image.png

这个组件也需要增加碰撞区域:


image.png

增加一个玩家,游戏会动的碰撞区域——刚体:
image.png

运行,玩家会落下来,倾斜墙体,人物会有物理效果。
image.png
image.png

一些属性:

image.png
image.png

控制玩家

RigidBody2D增加脚本MyPlayer.cs

using Godot;
using System;

public partial class MyPlayer : RigidBody2D
{
    public override void _Ready()
    {
    }

    public override void _Process(double delta)
    {
    }

    /**
     * 物理·相关的写到这个里面
     */
    public override void _PhysicsProcess(double delta)
    {
        // 通过获取水平轴控制左右移动
        float horizontal = Input.GetAxis("左", "右");
        // 速度
        this.LinearVelocity = new Vector2(horizontal * 100, this.LinearVelocity.Y);
    }
}

把人物的碰撞区域换成胶囊形状


image.png

会发现是滚着走的


image.png

锁定旋转

using Godot;
using System;

public partial class MyPlayer : RigidBody2D
{
    public override void _Ready()
    {
        this.LockRotation = true;
    }

    public override void _Process(double delta)
    {
    }

    /**
     * 物理·相关的写到这个里面
     */
    public override void _PhysicsProcess(double delta)
    {
        // 通过获取水平轴控制左右移动
        float horizontal = Input.GetAxis("左", "右");
        // 速度
        this.LinearVelocity = new Vector2(horizontal * 300, this.LinearVelocity.Y);
    }
}

增加跳跃:

...
    /**
     * 物理·相关的写到这个里面
     */
    public override void _PhysicsProcess(double delta)
    {
        // 通过获取水平轴控制左右移动
        float horizontal = Input.GetAxis("左", "右");
        // 速度
        this.LinearVelocity = new Vector2(horizontal * 300, this.LinearVelocity.Y);
        
        // 如果按了空格,就跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            this.LinearVelocity = new Vector2(this.LinearVelocity.X,-600);
        }
    }

这里发现玩家可以无限跳跃,限制只能跳跃最多2次:

using Godot;
using System;

public partial class MyPlayer : RigidBody2D
{
    public override void _Ready()
    {
        this.LockRotation = true;
    }

    public override void _Process(double delta)
    {
    }

    /**
     * 物理·相关的写到这个里面
     */
    public override void _PhysicsProcess(double delta)
    {
        // 通过获取水平轴控制左右移动
        float horizontal = Input.GetAxis("左", "右");
        // 速度
        this.LinearVelocity = new Vector2(horizontal * 300, this.LinearVelocity.Y);
        
        // 如果按了空格,就跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            this.LinearVelocity = new Vector2(this.LinearVelocity.X,-600);
        }
    }

    public void bodyEntered(Node body)
    {
        GD.Print("碰撞");
    }
}
image.png

发现无法触发:


image.png

增加代码:

using Godot;
using System;

public partial class MyPlayer : RigidBody2D
{
    public override void _Ready()
    {
        // 锁定旋转
        this.LockRotation = true;
         // 如果要做碰撞检测,需要设置:
         this.ContactMonitor = true;
         this.MaxContactsReported = 1;
    }

    public override void _Process(double delta)
    {
    }

    /**
     * 物理·相关的写到这个里面
     */
    public override void _PhysicsProcess(double delta)
    {
        // 通过获取水平轴控制左右移动
        float horizontal = Input.GetAxis("左", "右");
        // 速度
        this.LinearVelocity = new Vector2(horizontal * 300, this.LinearVelocity.Y);
        
        // 如果按了空格,就跳跃
        if (Input.IsActionPressed("跳跃"))
        {
            this.LinearVelocity = new Vector2(this.LinearVelocity.X,-600);
        }
    }

    public void bodyEntered(Node body)
    {
        GD.Print("碰撞");
    }
}
image.png

如果不通过代码,也可以通过勾选配置:


image.png
image.png

image.png
using Godot;
using System;

public partial class MyPlayer : RigidBody2D
{
    private bool isGround;
    private int maxDump = 0;

    public override void _Ready()
    {
        // 锁定旋转
        this.LockRotation = true;
        // 如果要做碰撞检测,需要设置:
        this.ContactMonitor = true;
        this.MaxContactsReported = 1;
    }

    public override void _Process(double delta)
    {
    }

    /**
     * 物理·相关的写到这个里面
     */
    public override void _PhysicsProcess(double delta)
    {
        // 通过获取水平轴控制左右移动
        float horizontal = Input.GetAxis("左", "右");
        // 速度
        this.LinearVelocity = new Vector2(horizontal * 300, this.LinearVelocity.Y);

        // 如果按了空格,就跳跃(限制只跳跃一次)
        // if (Input.IsActionPressed("跳跃")&&isGround)
        // {
        //     this.LinearVelocity = new Vector2(this.LinearVelocity.X, -600);
        // }
        // 如果按了空格,就跳跃(限制最多跳跃2次)
        if (Input.IsActionJustPressed("跳跃")&&maxDump< 2)
        {
            maxDump++;
            GD.Print(maxDump);
            this.LinearVelocity = new Vector2(this.LinearVelocity.X, -600);
        }
    }

    public void bodyEntered(Node body)
    {
        // isGround = true;
        maxDump = 0;
    }

    public void bodyExited(Node body)
    {
        // isGround = false;
    }
}

角色控制

godot提供一种专门用于角色控制的组件CharacterBody2D。

该控件本身既没有重力,也不会滚动,一切都需要用代码来实现。


image.png

创建脚本的时候,可以用基础2d移动模板,实现角色控制。


image.png

自动生成如下代码:

using Godot;
using System;

public partial class CharacterBody2d : CharacterBody2D
{
    public const float Speed = 300.0f;
    public const float JumpVelocity = -400.0f;

    public override void _PhysicsProcess(double delta)
    {
        Vector2 velocity = Velocity;

        // Add the gravity.
        if (!IsOnFloor())
        {
            velocity += GetGravity() * (float)delta;
        }

        // Handle Jump.
        if (Input.IsActionJustPressed("ui_accept") && IsOnFloor())
        {
            velocity.Y = JumpVelocity;
        }

        // Get the input direction and handle the movement/deceleration.
        // As good practice, you should replace UI actions with custom gameplay actions.
        Vector2 direction = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
        if (direction != Vector2.Zero)
        {
            velocity.X = direction.X * Speed;
        }
        else
        {
            velocity.X = Mathf.MoveToward(Velocity.X, 0, Speed);
        }

        Velocity = velocity;
        MoveAndSlide();
    }
}

生成的代码省略很多this,为了可 阅读性,可以加上:

using Godot;
using System;

public partial class CharacterBody2d : CharacterBody2D
{
    public const float Speed = 300.0f;
    public const float JumpVelocity = -400.0f;

    public override void _PhysicsProcess(double delta)
    {
        Vector2 velocity = this.Velocity;

        
        // Add the gravity.
        if (!this.IsOnFloor())
        {
            velocity += this.GetGravity() * (float)delta;
        }

        // Handle Jump.
        if (Input.IsActionJustPressed("ui_accept") && this.IsOnFloor())
        {
            velocity.Y = JumpVelocity;
        }

        // Get the input direction and handle the movement/deceleration.
        // As good practice, you should replace UI actions with custom gameplay actions.
        Vector2 direction = Input.GetVector("ui_left", "ui_right", "ui_up", "ui_down");
        if (direction != Vector2.Zero)
        {
            velocity.X = direction.X * Speed;
        }
        else
        {
            velocity.X = Mathf.MoveToward(Velocity.X, 0, Speed);
        }

        this.Velocity = velocity;
        MoveAndSlide();
    }
}

想要一切修改生效需要最后执行MoveAndSlide();方法。

角色控制具体用刚体还是角色刚体,取决于开发者实际需求,两者通过调整,效果都可以达到一致。

UI界面

本质上就是用到Control下的组件实现UI界面效果。


image.png

比方Home页面增加UI界面,一般做法并不是在Home场景下,增加各种Contorl下组件,而是创建一个专门的UI场景。

而dodot还真有创建新的“UI场景”。

image.png

其实,用户界面场景,就是根节点为Control的场景。


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容