《Unity網(wǎng)絡(luò)游戲?qū)崙?zhàn)》Chapter3: 亂斗小游戲

1第献、介紹

《Unity網(wǎng)絡(luò)游戲?qū)崙?zhàn)》的第三章節(jié)是做一個(gè)亂斗小游戲。實(shí)現(xiàn)的功能是玩家進(jìn)入到一個(gè)場(chǎng)景兔港,右鍵點(diǎn)擊地面移動(dòng)庸毫,左鍵點(diǎn)擊為攻擊,擊中其他玩家就扣血衫樊,血量為0就死亡飒赃。


image.png

2蜗帜、客戶端

本地玩家的控制腳本CtrlHuman和同步其他玩家的SyncHuman都繼承于BaseHuman涣雕,玩家的控制邏輯都寫在這三個(gè)腳本里面。網(wǎng)絡(luò)消息的發(fā)送和接收處理难礼,則是用了一個(gè)靜態(tài)類NetManager和NetWorkManager臀栈。NetWorkManager可以掛在在游戲的任何一個(gè)物體中蔫慧。


image.png

貼上代碼,和書上的源碼有些許不同权薯。
BaseHuman.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 作為CtrlHuman和SyncHuman的基類姑躲,實(shí)現(xiàn)共同的功能
public class BaseHuman : MonoBehaviour
{
    // 是否正在移動(dòng)
    internal bool isMoving = false;
    // 移動(dòng)目標(biāo)點(diǎn)
    private Vector3 targetPosition;
    // 移動(dòng)速度
    public float speed = 1.2f;
    // 動(dòng)畫組件
    private Animator animator;
    // 是否正在攻擊
    internal bool isAttacking = false;
    internal float attackTime = float.MinValue;
    // 描述
    public string desc = "";

    // 移動(dòng) -- 動(dòng)作
    public void MoveTo(Vector3 pos)
    {
        targetPosition = pos;
        isMoving = true;
        animator.SetBool("isMoving", true);
    }

    // 移動(dòng)Update,每一幀的移動(dòng)
    public void MoveUpdate()
    {
        if (!isMoving)
            return;

        if (isAttacking)
        {
            isAttacking = false;
            animator.SetBool("isAttacking", false);
        }
            
        // 角色當(dāng)前的位置
        Vector3 pos = transform.position;
        // 移動(dòng)到targetPosition
        transform.position = Vector3.MoveTowards(pos, targetPosition, speed * Time.deltaTime);
        // 用transform.Translate也可以實(shí)現(xiàn)運(yùn)動(dòng)的效果 -- 但是要在space.world中
        // transform.Translate(transform.forward * Time.deltaTime, Space.World);
        // 看向目標(biāo)位置
        transform.LookAt(targetPosition);
        // 當(dāng)距離目標(biāo)小于0.1時(shí)盟蚣,停下
        if (Vector3.Distance(transform.position, targetPosition) < 0.1f)
        {
            isMoving = false;
            animator.SetBool("isMoving", false);
        }
    }

    // 攻擊Attack -- 動(dòng)作
    public void Attack()
    {
        isAttacking = true;
        attackTime = Time.time;
        animator.SetBool("isAttacking", true);
    }

    // 攻擊Attack Update --- 每一幀更新(判斷一次attack是否結(jié)束)
    public void AttackUpdate()
    {
        if (!isAttacking)
            return;

        if (Time.time - attackTime < 1.2f)
            return;

        isAttacking = false;
        animator.SetBool("isAttacking", false);
    }

    // Start is called before the first frame update
    internal void Start()
    {
        animator = GetComponent<Animator>();
    }

    // Update is called once per frame
    internal void Update()
    {
        MoveUpdate();
        AttackUpdate();
    }
}

CtrlHuman.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CtrlHuman : BaseHuman
{
    // Start is called before the first frame update
    new void Start()
    {
        base.Start();
    }

    // Update is called once per frame
    new void Update()
    {
        base.Update();

        if(Input.GetMouseButtonDown(1))
        {
            // 點(diǎn)擊鼠標(biāo)右鍵
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            // Debug.DrawRay(ray.origin, ray.direction * 10, Color.yellow);
            RaycastHit hit;
            Physics.Raycast(ray, out hit);
            // 點(diǎn)擊地板 移動(dòng)
            if (hit.collider.tag == "Enviroment")
            {
                MoveTo(hit.point);
                transform.LookAt(hit.point);
            }

            // 組裝Move協(xié)議
            string sendStr = "Move|";
            sendStr += desc + ",";
            sendStr += hit.point.x + ",";
            sendStr += hit.point.y + ",";
            sendStr += hit.point.z + ",";
            sendStr += transform.eulerAngles.y;
            NetManager.Send(sendStr);
        }

        if(Input.GetMouseButtonDown(0))
        {
            if (isMoving || isAttacking)
                return;

            // 點(diǎn)擊鼠標(biāo)左鍵黍析,攻擊
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            Physics.Raycast(ray, out hit);

            transform.LookAt(hit.point);
            Attack();

            // 組裝協(xié)議
            string sendStr = "Attack|";
            sendStr += desc + ",";
            sendStr += transform.eulerAngles.y;
            NetManager.Send(sendStr);

            // 攻擊判定 -- Hit協(xié)議(客戶端不需要處理)
            // 線段起點(diǎn)
            Vector3 startPoint = transform.position + 0.5f * Vector3.up;
            // 線段終點(diǎn)
            Vector3 endPoint = startPoint + transform.forward * 20.0f;
            // 檢測(cè)是否擊中敵人
            if(Physics.Linecast(startPoint, endPoint, out hit))
            {
                GameObject gobj = hit.collider.gameObject;
                if (gobj == gameObject)
                    return;

                SyncHuman human = gobj.GetComponent<SyncHuman>();
                if (human == null)
                    return;

                // 組裝協(xié)議 -- 服務(wù)器判斷誰打中了誰
                sendStr = "Hit|";
                sendStr += desc + ",";
                sendStr += human.desc;
                NetManager.Send(sendStr);
            }
        }
    }
}

SyncHuman.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SyncHuman : BaseHuman
{
    // Start is called before the first frame update
    new void Start()
    {
        base.Start(); 
    }

    // Update is called once per frame
    new void Update()
    {
        base.Update();
    }

    public void SyncAttack(float euly)
    {
        transform.eulerAngles = new Vector3(0, euly, 0);
        Attack();
    }
}

NetManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System;

public static class NetManager
{
    static Socket socket;
    static int buffsize = 1024;
    static byte[] recvBuff = new byte[buffsize];

    // 監(jiān)聽協(xié)議的委托類型
    public delegate void MsgListener(string str);

    private static Dictionary<string, MsgListener> listeners = new Dictionary<string, MsgListener>();

    static List<string> msgList = new List<string>();

    // 添加監(jiān)聽
    public static void AddListener(string msgName, MsgListener listener)
    {
        listeners.Add(msgName, listener);
    }

    // 獲取描述 --- 本地玩家的ip - 端口
    public static string GetDesc()
    {
        if (socket == null)
            return "";
        if (!socket.Connected)
            return "";

        return socket.LocalEndPoint.ToString();
    }

    // 連接服務(wù)器
    public static void Connect(string ip, int port)
    {
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.Connect(ip, port);
        socket.BeginReceive(recvBuff, 0, buffsize, SocketFlags.None, ReceiveCallBack, socket);
    }

    // Receive Call Back
    private static void ReceiveCallBack(IAsyncResult _ar)
    {
        try
        {
            Socket socket = (Socket)_ar.AsyncState;
            int count = socket.EndReceive(_ar);
            string recvStr = System.Text.Encoding.UTF8.GetString(recvBuff, 0, count);
            msgList.Add(recvStr);
            socket.BeginReceive(recvBuff, 0, buffsize, SocketFlags.None, ReceiveCallBack, socket);
        }
        catch(SocketException ex)
        {
            Debug.Log("Socket Receive fail: " + ex.ToString());
        }
    }

    public static void Send(string sendStr)
    {
        if (socket == null)
            return;
        if (!socket.Connected)
            return;

        byte[] sendBytes = System.Text.Encoding.UTF8.GetBytes(sendStr);
        socket.BeginSend(sendBytes, 0, sendBytes.Length, SocketFlags.None, SendCallBack, socket);
    }

    // Send Call Back
    private static void SendCallBack(IAsyncResult _ar)
    {
        try
        {
            Socket socket = (Socket)_ar.AsyncState;            
        }
        catch(SocketException ex)
        {
            Debug.Log("Send failed, " + ex.ToString());
        }
    }

    public static void ProcessMsg()
    {
        if (msgList.Count <= 0)
            return;

        string msgStr = msgList[0];
        msgList.RemoveAt(0);
        string[] splitmsg = msgStr.Split('|');
        string msgName = splitmsg[0];
        string msgbody = splitmsg[1];

        if (listeners.ContainsKey(msgName))
            listeners[msgName](msgbody);
    }

}

NetWorkManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NetWorkManager : MonoBehaviour
{
    public GameObject humanPrefab;
    // 人物列表 -- 本地玩家的控制組件:myHuman;其他玩家列表:otherHumans
    public BaseHuman myHuman;
    public Dictionary<string, BaseHuman> otherHuman = new Dictionary<string, BaseHuman>();

    void Start()
    {
        NetManager.AddListener("Enter", OnEnter);
        NetManager.AddListener("List", OnList);
        NetManager.AddListener("Move", OnMove);
        NetManager.AddListener("Leave", OnLeave);
        NetManager.AddListener("Attack", OnAttack);
        NetManager.AddListener("Die", OnDie);
        NetManager.Connect("127.0.0.1", 8888);

        // 生成角色
        GameObject gobj = Instantiate(humanPrefab);
        float x = Random.Range(-10, 10);
        float z = Random.Range(-10, 10);
        gobj.transform.position = new Vector3(x, 0, z);
        gobj.name = NetManager.GetDesc();

        // 添加組件
        myHuman = gobj.AddComponent<CtrlHuman>();
        myHuman.desc = NetManager.GetDesc();

        Debug.Log(myHuman.desc);

        // 發(fā)送協(xié)議
        Vector3 pos = myHuman.transform.position;
        float euly = myHuman.transform.eulerAngles.y;
        // 組裝消息
        string sendStr = "Enter|";
        // PS: 如果寫成 pos.x + ','; 則逗號(hào)不會(huì)加進(jìn)去
        sendStr += myHuman.desc + ",";
        sendStr += pos.x + ",";
        sendStr += pos.y + ",";
        sendStr += pos.z + ",";
        sendStr += euly;
        NetManager.Send(sendStr);
        NetManager.Send("List|GetAllPlayerStates");
    }

    void OnEnter(string msgbody)
    {
        Debug.Log("OnEnter: " + msgbody);

        // 解析參數(shù)
        string[] splitmsg = msgbody.Split(',');
        string desc = splitmsg[0];
        float x = float.Parse(splitmsg[1]);
        float y = float.Parse(splitmsg[2]);
        float z = float.Parse(splitmsg[3]);
        float euly = float.Parse(splitmsg[4]);
        
        // 如果是自己進(jìn)入則不處理 
        if (desc == myHuman.desc)
            return;

        // 生成角色
        GameObject gobj = Instantiate(humanPrefab);
        gobj.name = desc;
        gobj.transform.position = new Vector3(x, y, z);
        gobj.transform.eulerAngles = new Vector3(0, euly, 0);
        // 添加同步角色組件
        BaseHuman human = gobj.AddComponent<SyncHuman>();
        human.desc = desc;
        // 用endpoint作為主鍵屎开,但是正常應(yīng)該是用username
        otherHuman.Add(desc, human);
    }

    void OnList(string msgbody)
    {
        Debug.Log(msgbody);
        // 解析參數(shù)
        string[] splitmsg = msgbody.Split(',');
        // count: 玩家個(gè)數(shù)
        int count = splitmsg.Length / 6;
        // 生成每一個(gè)玩家
        for (int i = 0; i < count; i++)
        {
            // 解析每一個(gè)參數(shù)
            string desc = splitmsg[i * 6 + 0];
            float x = float.Parse(splitmsg[i * 6 + 1]);
            float y = float.Parse(splitmsg[i * 6 + 2]);
            float z = float.Parse(splitmsg[i * 6 + 3]);
            float euly = float.Parse(splitmsg[i * 6 + 4]);
            int hp = int.Parse(splitmsg[i * 6 + 5]);
            // check is other player
            if (desc == myHuman.desc)
                continue;
            // 創(chuàng)建其他玩家
            GameObject gobj = Instantiate(humanPrefab);
            gobj.name = desc;
            gobj.transform.position = new Vector3(x, y, z);
            gobj.transform.eulerAngles = new Vector3(0, euly, 0);
            BaseHuman human = gobj.AddComponent<SyncHuman>();
            human.desc = desc;
            otherHuman.Add(human.desc, human);
        }
    }

    void OnMove(string msgbody)
    {
        // 解析參數(shù)
        string[] splitmsg = msgbody.Split(',');
        string desc = splitmsg[0];
        float x = float.Parse(splitmsg[1]);
        float y = float.Parse(splitmsg[2]);
        float z = float.Parse(splitmsg[3]);
        // 同步移動(dòng)其他玩家
        if (!otherHuman.ContainsKey(desc))
            return;
        otherHuman[desc].MoveTo(new Vector3(x, y, z)); 
    }

    void OnLeave(string msgbody)
    {
        // 解析參數(shù)
        string desc = msgbody;
        // 刪除離線玩家
        if (!otherHuman.ContainsKey(desc))
            return;

        Destroy(otherHuman[desc].gameObject);
        otherHuman.Remove(desc);
    }

    void OnAttack(string msgbody)
    {
        // 解析參數(shù)
        string[] splitmsg = msgbody.Split(',');
        string desc = splitmsg[0];
        float euly = float.Parse(splitmsg[1]);

        // 同步攻擊
        if (!otherHuman.ContainsKey(desc))
            return;

        SyncHuman human = (SyncHuman)otherHuman[desc];
        human.SyncAttack(euly);
    }

    void OnDie(string msgbody)
    {
        // 解析參數(shù)
        string[] splitmsg = msgbody.Split(',');
        string playerDead_desc = splitmsg[0];
        string killer_desc = splitmsg[1];

        if (playerDead_desc == myHuman.desc)
        {
            Debug.Log("You Dead! Game Over! Killer: " + killer_desc);
            Destroy(myHuman.gameObject);
            return;
        }
        // check
        if(!otherHuman.ContainsKey(playerDead_desc))
            return;

        Debug.Log(killer_desc + " kill " + playerDead_desc);
        Destroy(otherHuman[playerDead_desc].gameObject);
        otherHuman.Remove(playerDead_desc);
    }

    void Update()
    {
        NetManager.ProcessMsg();
    }
}

客戶端的實(shí)現(xiàn)就這樣橄仍,代碼也比較簡(jiǎn)單。

3、服務(wù)器

服務(wù)器用python編寫侮繁,MessageHandler為處理網(wǎng)絡(luò)消息的類;GameServer為服務(wù)器類如孝,用于接收消息宪哩;ClientStaes為客戶端狀態(tài)類。
ClientStates.py

class ClientStates(object):
    def __init__(self, sock, address):
        self.socket = sock
        self.addr = address
        self.recv_buff = []
        self.hp = -100
        self.pos_x = 0
        self.pos_y = 0
        self.pos_z = 0
        self.euly = 0

MessageHandler.py

class MessageHandler(object):
    def __init__(self, server):
        self.protocols = {'Enter': self.enter_response,
                          'List': self.list_response,
                          'Move': self.move_response,
                          'Leave': self.leave_response,
                          'Attack': self.attack_response,
                          'Hit': self.hit_response}
        self.game_server = server

    def handle(self, msg, client_socket):
        split_msg = msg.split('|')
        msg_name = split_msg[0]
        msg_body = split_msg[1]
        self.protocols[msg_name](msg_body, client_socket)

    def broadcast(self, send_msg):
        for client in self.game_server.client_states.values():
            client.socket.send(send_msg)

    def enter_response(self, msg_body, client_socket):
        # parse param
        spilt_msg = msg_body.split(',')
        x = spilt_msg[1]
        y = spilt_msg[2]
        z = spilt_msg[3]
        euly = spilt_msg[4]
        # update client states
        self.game_server.client_states[client_socket].hp = 100
        self.game_server.client_states[client_socket].pos_x = x
        self.game_server.client_states[client_socket].pos_y = y
        self.game_server.client_states[client_socket].pos_z = z
        self.game_server.client_states[client_socket].euly = euly
        # broadcast to all client
        self.broadcast('Enter|' + msg_body)

    def list_response(self, msg_body, client_socket):
        # check the param
        if msg_body != "GetAllPlayerStates":
            print 'List param Error'
        # send the player states to new Enter player
        send_msg = "List|"
        for client in self.game_server.client_states.values():
            desc = client.addr[0] + ':' + str(client.addr[1])
            send_msg += desc + ','
            send_msg += client.pos_x + ','
            send_msg += client.pos_y + ','
            send_msg += client.pos_z + ','
            send_msg += client.euly + ','
            send_msg += str(client.hp) + ','
        client_socket.send(send_msg.rstrip(','))

    def move_response(self, msg_body, client_socket):
        # parse the param
        splitmsg = msg_body.split(',')
        x = splitmsg[1]
        y = splitmsg[2]
        z = splitmsg[3]
        euly = splitmsg[4]
        # update player pos
        self.game_server.client_states[client_socket].pos_x = x
        self.game_server.client_states[client_socket].pos_y = y
        self.game_server.client_states[client_socket].pos_z = z
        self.game_server.client_states[client_socket].euly = euly
        # broadcast
        self.broadcast("Move|" + msg_body)

    def leave_response(self, msg_body, client_socket):
        # check param
        if msg_body != "PlayerLeave":
            print "in leave response, the param != Leave"
        end_point = self.game_server.client_states[client_socket].addr[0] + ':' + \
                    str(self.game_server.client_states[client_socket].addr[1])
        self.broadcast("Leave|" + end_point)

    def attack_response(self, msg_body, client_socket):
        if client_socket not in self.game_server.client_states:
            print 'a client not in client_states, but it send a msg'
        send_msg = "Attack|" + msg_body
        self.broadcast(send_msg)

    def hit_response(self, msg_body, client_socket):
        # parse the param
        split_msg = msg_body.split(',')
        attack_addr = split_msg[0]
        hit_addr = split_msg[1]
        print 'hit_desc',hit_addr
        # find the hit player
        for client in self.game_server.client_states.values():
            end_point = client.addr[0] + ':' + str(client.addr[1])
            if end_point == hit_addr:
                client.hp -= 25
                if client.hp <= 0:
                    # player die, broadcast the die
                    self.broadcast("Die|" + hit_addr + ',' + attack_addr)
                break

demo.py

import Chapter2.ClientStates as cs
import socket
import select
import MessageHandler as mh

class GameServer(object):
    def __init__(self):
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.bind(("127.0.0.1", 8888))
        self.server.listen(5)
        self.buffer_size = 1024
        self.connect_socket = [self.server]
        self.client_states = {}
        self.handler = mh.MessageHandler(self)

    def close_fd(self, fd):
        self.handler.handle("Leave|PlayerLeave", fd)
        self.connect_socket.remove(fd)
        self.client_states.pop(fd)
        fd.close()

    def read_server_fd(self, fd):
        # client connect
        client_socket, client_address = fd.accept()
        print client_address, 'connected'
        self.connect_socket.append(client_socket)
        self.client_states[client_socket] = cs.ClientStates(client_socket, client_address)

    def read_client_fd(self, fd):
        try:
            data = fd.recv(self.buffer_size)
            if data:
                print 'receive data from: ', self.client_states[fd].addr, data
                self.handler.handle(data, fd)
            else:
                self.close_fd(fd)
        except socket.error:
            self.close_fd(fd)

    def run(self):
        print "Server Start."
        # Main Loop
        while True:
            # select mode
            read_fds, write_fds, error_fds = select.select(self.connect_socket, [], [], 1)
            for fd in read_fds:
                if fd is self.server:
                    self.read_server_fd(fd)
                else:
                    self.read_client_fd(fd)


server = GameServer()
server.run()

4第晰、結(jié)語

客戶端的收發(fā)消息用的異步socket锁孟,服務(wù)端的復(fù)用模型為select模型,性能較差茁瘦,并且客戶端和服務(wù)端都沒有處理粘包半包的問題品抽,在之后的章節(jié)中會(huì)處理這個(gè)問題。上述的客戶端代碼還存在一個(gè)問題甜熔,就是客戶端是用本機(jī)的ip-port作為自身的標(biāo)識(shí)圆恤,但是這個(gè)方法在局域網(wǎng)中是不行的,因?yàn)槌隽寺酚善髦蟮膇p會(huì)變腔稀,這樣會(huì)導(dǎo)致進(jìn)游戲的時(shí)候會(huì)創(chuàng)建兩個(gè)自己盆昙。修改方法只需將其改用username作為唯一標(biāo)識(shí)即可。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末焊虏,一起剝皮案震驚了整個(gè)濱河市淡喜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诵闭,老刑警劉巖炼团,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異疏尿,居然都是意外死亡瘟芝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門润歉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來模狭,“玉大人,你說我怎么就攤上這事踩衩〗鲤模” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵驱富,是天一觀的道長锚赤。 經(jīng)常有香客問我,道長褐鸥,這世上最難降的妖魔是什么线脚? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上浑侥,老公的妹妹穿的比我還像新娘姊舵。我一直安慰自己,他們只是感情好寓落,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布括丁。 她就那樣靜靜地躺著,像睡著了一般伶选。 火紅的嫁衣襯著肌膚如雪史飞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天仰税,我揣著相機(jī)與錄音构资,去河邊找鬼。 笑死陨簇,一個(gè)胖子當(dāng)著我的面吹牛吐绵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播塞帐,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼拦赠,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了葵姥?” 一聲冷哼從身側(cè)響起荷鼠,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎榔幸,沒想到半個(gè)月后允乐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡削咆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年牍疏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拨齐。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鳞陨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞻惋,到底是詐尸還是另有隱情厦滤,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布歼狼,位于F島的核電站掏导,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏羽峰。R本人自食惡果不足惜趟咆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一添瓷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧值纱,春花似錦鳞贷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凿滤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庶近,已是汗流浹背翁脆。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鼻种,地道東北人反番。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像叉钥,于是被迫代替她去往敵國和親罢缸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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