[Unity3D] BMFont 简易生成工具

对网上大佬的代码进行了易用性修改,仅WINDOWS可用。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;


public class BmfInfo
{
    public string filePath;
    public string fileName;
    public int outWidth = 256;
    public int outHeight = 256;
}

public class BMFontTools : EditorWindow
{
    private string configDirPath;

    private string configFlag = "# imported icon images";

    //字体文件生成路径
    private string fontDirPath;
    private Vector2 scrollPos;
    private string currentSelect = string.Empty;

    private struct FontInfo
    {
        //图片路径
        public string ImgPath;

        //字符
        public char charInfo;
    }

    private List<FontInfo> _fontInfoList = new List<FontInfo>();

    private readonly List<BmfInfo> bmfInfoList = new List<BmfInfo>();

    private int xAdvanceOffset;

    private void Awake()
    {
        configDirPath = Path.Combine(Application.dataPath, "../Tools/BMFont");
        fontDirPath = Path.Combine(Application.dataPath, "AllRes/Digit");
        InitConfigInfo();
    }

    private void InitConfigInfo()
    {
        bmfInfoList.Clear();
        List<string> pathList = new List<string>(Directory.GetFiles(configDirPath, "*.bmfc"));
        for (int i = 0; i < pathList.Count; i++)
        {
            BmfInfo info = new BmfInfo();
            info.fileName = Path.GetFileNameWithoutExtension(pathList[i]);
            info.filePath = pathList[i];
            string[] tempLines = File.ReadAllLines(info.filePath);
            foreach (string tempLine in tempLines)
            {
                if (tempLine.StartsWith("outWidth="))
                {
                    string infoTemp = tempLine.Split('#')[0];
                    int width = int.Parse(infoTemp.Replace("outWidth=", string.Empty));
                    info.outWidth = width;
                }
                else if (tempLine.StartsWith("outHeight="))
                {
                    string infoTemp = tempLine.Split('#')[0];
                    int height = int.Parse(infoTemp.Replace("outHeight=", string.Empty));
                    info.outHeight = height;
                }
            }

            bmfInfoList.Add(info);
        }

        if (!IsNullOrEmpty(bmfInfoList))
        {
            currentSelect = bmfInfoList[0].filePath;
            _fontInfoList = AnalysisConfig(currentSelect);
        }
    }

#if UNITY_EDITOR_WIN
    [MenuItem("AssetsTools/BMFont/BMFontTextureTools", false)]
    private static void MyBMFontTools()
    {
        string path = Path.Combine(Application.dataPath, "../Tools/BMFont/bmfont.exe");
        if (!File.Exists(path))
        {
            Debug.LogWarning($"{path}目录下不存在[bmfont.exe]");
            if (EditorUtility.DisplayDialog("BMFont工具不存在", $"{path}目录下不存在[bmfont.exe]", "这就去下载"))
            {
                Application.OpenURL("http://www.angelcode.com/products/bmfont/");
            };
            return;
        }
        path = Path.Combine(Application.dataPath, "../Tools/BMFont/BMFontGenerate.bat");
        if (!File.Exists(path))
        {
            File.WriteAllText(path, "bmfont.exe -c %1  -o %2  ");
        }
        BMFontTools bmFont = GetWindow<BMFontTools>();
        bmFont.Show();
    }
#endif

    private void OnGUI()
    {
        GUILayout.Label("使用说明:");
        GUILayout.Label("1、点击NewFont创建新的字体配置。");
        GUILayout.Label("2、修改FontName后,点击ReName修改字体名字。");
        GUILayout.Label("3、outWidth和outHeight为输出图集宽高,如果输出多张图集,请增大图集宽高。");
        GUILayout.Label("4、点击Select选中字体配置,选中后为绿色。");
        GUILayout.Label("5、点击下方Add按钮,增加字符。");
        GUILayout.Label("6、点击SelectImg选择图片,要求路径是全英文路径。");
        GUILayout.Label("7、[可选]修改字符的X轴间隔。");
        GUILayout.Label("8、点击下方SaveAndExportFont输出字体文件。");
        GUILayout.Label("9、如果生成失败,请检查图片格式是否为PNG-24,即位深度为32。");
        

        GUILayout.BeginHorizontal();
        GUILayout.FlexibleSpace();
        if (GUILayout.Button("Refresh", EditorStyles.toolbarButton))
        {
            InitConfigInfo();
        }

        if (GUILayout.Button("NewFont", EditorStyles.toolbarButton))
        {
            string fileName = $"{DateTime.Now:yyyyMMddhhmmss}.bmfc";
            File.WriteAllText(Path.Combine(configDirPath, fileName), configFlag, Encoding.UTF8);
            InitConfigInfo();
            currentSelect = ToFind(bmfInfoList, x => x.filePath.Contains(fileName)).filePath;
            _fontInfoList = AnalysisConfig(currentSelect);
        }

        if (GUILayout.Button("Delete", EditorStyles.toolbarButton))
        {
            if (File.Exists(currentSelect))
            {
                File.Delete(currentSelect);
            }
            _fontInfoList = new List<FontInfo>();
            InitConfigInfo();
            return;
        }

        GUILayout.EndHorizontal();

        scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
        EditorGUILayout.BeginVertical();
        EditorGUILayout.BeginHorizontal();
        GUILayout.Label("Font Save Path:", GUILayout.MaxWidth(100));
        fontDirPath = GUILayout.TextField(fontDirPath);
        if (GUILayout.Button("SelectFold"))
        {
            fontDirPath = EditorUtility.OpenFolderPanel("SelectFontOutPutDir", "", "");
        }
        EditorGUILayout.EndHorizontal();
        SetBMFontConfigs();
        SetConfigInfo();

        EditorGUILayout.EndVertical();
        EditorGUILayout.EndScrollView();
    }

    private void SetBMFontConfigs()
    {
        for (int i = 0; i < bmfInfoList.Count; i++)
        {
            if (bmfInfoList[i].filePath.Equals(currentSelect))
            {
                GUI.color = Color.green;
            }

            string filePath = bmfInfoList[i].filePath;

            GUILayout.BeginHorizontal();
            GUI.enabled = false;
            EditorGUILayout.TextField(filePath, GUILayout.MaxWidth(500));
            GUI.enabled = true;
            EditorGUILayout.LabelField("FontName:", GUILayout.MaxWidth(80));
            bmfInfoList[i].fileName = EditorGUILayout.TextField(bmfInfoList[i].fileName, GUILayout.MaxWidth(100));
            EditorGUILayout.LabelField("outWidth:", GUILayout.MaxWidth(70));
            bmfInfoList[i].outWidth =
                int.Parse(EditorGUILayout.TextField(bmfInfoList[i].outWidth.ToString(), GUILayout.MaxWidth(50)));
            EditorGUILayout.LabelField("outHeight:", GUILayout.MaxWidth(70));
            bmfInfoList[i].outHeight =
                int.Parse(EditorGUILayout.TextField(bmfInfoList[i].outHeight.ToString(), GUILayout.MaxWidth(50)));
            if (GUILayout.Button("ReName"))
            {
                Regex regex = new Regex(@"/|\\|<|>|\*|\?");
                if (!string.IsNullOrEmpty(bmfInfoList[i].fileName) && !regex.IsMatch(bmfInfoList[i].fileName))
                {
                    string fileNameTemp = filePath.Replace(Path.GetFileNameWithoutExtension(filePath),
                        bmfInfoList[i].fileName);
                    if (File.Exists(fileNameTemp))
                    {
                        Debug.LogError("文件冲突,命名失败");
                    }
                    else
                    {
                        File.Move(filePath, fileNameTemp);
                    }

                    InitConfigInfo();
                }
                else
                {
                    Debug.LogError("文件名非法或为空,命名失败");
                }
            }

            if (GUILayout.Button("Select"))
            {
                currentSelect = filePath;
                _fontInfoList = AnalysisConfig(currentSelect);
            }
            GUILayout.EndHorizontal();
            if (filePath.Equals(currentSelect))
            {
                GUI.color = Color.white;
            }
        }
    }

    private void SetConfigInfo()
    {
        if (!string.IsNullOrEmpty(currentSelect))
        {
            for (int i = 0; i < _fontInfoList.Count; i++)
            {
                EditorGUILayout.BeginHorizontal();
                if (GUILayout.Button("Select Img", GUILayout.MaxWidth(100)))
                {
                    string pathTemp = EditorUtility.OpenFilePanelWithFilters("选择图片", "", new[] { "Image", "png" });
                    string fileName = Path.GetFileName(pathTemp).Replace(".png", "");
                    if (!string.IsNullOrEmpty(pathTemp))
                    {
                        FontInfo fontInfo = new FontInfo();
                        fontInfo.charInfo = _fontInfoList[i].charInfo;
                        if (fontInfo.charInfo == '\0')
                        {
                            if (int.TryParse(fileName, out int rs))
                            {
                                fontInfo.charInfo = rs.ToString()[0];
                            }
                            else
                            {
                                fontInfo.charInfo = fileName[fileName.Length - 1];
                            }
                        }
                        fontInfo.ImgPath = FormatPath(pathTemp);
                        _fontInfoList[i] = fontInfo;
                        int result = 0;
                        if (int.TryParse(fileName, out result) || int.TryParse(fileName[fileName.Length - 1].ToString(), out result))
                        {
                            int i2 = result;
                            int index = i + 1;
                            while (i2 < 10)
                            {
                                i2++;
                                string path2 = pathTemp.Replace(result + ".png", i2 + ".png");
                                if (!string.IsNullOrEmpty(path2) && File.Exists(path2))
                                {
                                    FontInfo fontInfo2 = new FontInfo();
                                    fontInfo2.charInfo = i2.ToString()[0];
                                    fontInfo2.ImgPath = FormatPath(path2);

                                    while (true)
                                    {
                                        if (_fontInfoList.Count <= index)
                                        {
                                            _fontInfoList.Add(fontInfo2);
                                            index++;
                                            break;
                                        }
                                        if (string.IsNullOrEmpty(_fontInfoList[index].ImgPath))
                                        {
                                            _fontInfoList[index] = fontInfo2;
                                            index++;
                                            break;
                                        }
                                        else
                                        {
                                            index++;
                                        }
                                    }
                                }
                                else
                                {
                                    break;
                                }
                            }
                        }
                    }
                }

                EditorGUILayout.LabelField("Char:", GUILayout.MaxWidth(55));

                if (!string.IsNullOrEmpty(_fontInfoList[i].charInfo.ToString()))
                {
                    FontInfo info = new FontInfo();
                    string temp =
                        EditorGUILayout.TextField(_fontInfoList[i].charInfo.ToString(), GUILayout.MaxWidth(30));
                    if (temp.Length == 1 && Regex.IsMatch(temp, "[\x20-\x7e]"))
                    {
                        info.charInfo = temp[0];
                        info.ImgPath = _fontInfoList[i].ImgPath;
                        _fontInfoList[i] = info;
                    }
                }

                EditorGUILayout.LabelField("ImgPath:", GUILayout.MaxWidth(55));
                GUI.enabled = false;
                EditorGUILayout.TextField(_fontInfoList[i].ImgPath);
                GUI.enabled = true;
                if (GUILayout.Button("Delete"))
                {
                    _fontInfoList.RemoveAt(i);
                    i--;
                }
                EditorGUILayout.EndHorizontal();
            }
            EditorGUILayout.LabelField($"当前字符数量:{_fontInfoList.Count}");
            GUILayout.Space(10);
            EditorGUILayout.BeginHorizontal();
            int.TryParse(EditorGUILayout.TextField("额外的文字间隔", xAdvanceOffset.ToString()), out xAdvanceOffset);
            EditorGUILayout.EndHorizontal();
            if (GUILayout.Button("Add"))
            {
                _fontInfoList.Add(new FontInfo());
            }

            GUI.enabled = !IsNullOrEmpty(_fontInfoList);
            GUILayout.Space(50);
            if (GUILayout.Button("Save And Export Font"))
            {
                SaveFontAndExport();
            }

            GUI.enabled = true;
        }
    }

    private void SaveFontAndExport()
    {
        BmfInfo bmfInfo = ToFind(bmfInfoList, x => x.filePath.Equals(currentSelect));
        string baseFontInfo = File.ReadAllText(configDirPath + "/BaseConfig.bmf");
        baseFontInfo = baseFontInfo
            .Replace("outWidth=", $"outWidth={bmfInfo.outWidth}#")
            .Replace("outHeight=", $"outHeight={bmfInfo.outHeight}#");
        baseFontInfo += "\n";
        for (int i = 0; i < _fontInfoList.Count; i++)
        {
            if (string.IsNullOrEmpty(_fontInfoList[i].ImgPath) ||
                string.IsNullOrEmpty(_fontInfoList[i].charInfo.ToString()))
            {
                continue;
            }

            string info = $"icon=\"{_fontInfoList[i].ImgPath}\",{(int)_fontInfoList[i].charInfo},0,0,0\n";
            baseFontInfo += info;
        }

        File.WriteAllText(currentSelect, baseFontInfo);

        ExportFontInfo();

        AssetDatabase.Refresh();
    }

    private void ExportFontInfo()
    {
        string fileName = Path.GetFileNameWithoutExtension(currentSelect);
        string targetDir = Path.Combine(fontDirPath, fileName);
        if (!Directory.Exists(targetDir))
        {
            Directory.CreateDirectory(targetDir);
        }

        Process process = new Process();
        string batPath = Path.Combine(configDirPath, "BMFontGenerate.bat");
        process.StartInfo.FileName = batPath;
        process.StartInfo.WorkingDirectory = configDirPath;
        process.StartInfo.Arguments =
            string.Format("{0} {1}", currentSelect, Path.Combine(fontDirPath, targetDir + "/" + fileName));
        process.Start();
        process.WaitForExit();
        AssetDatabase.Refresh();
        GenFontInfo(targetDir, fileName);
    }

    private string GetAssetPath(string path)
    {
        string pathTemp = path.Replace("\\", "/");
        pathTemp = pathTemp.Replace(Application.dataPath, "Assets");
        return pathTemp;
    }

    private void GenFontInfo(string fontDirPath, string fileName)
    {
        string matPath = Path.Combine(fontDirPath, fileName + "_0.mat");
        Material mat = AssetDatabase.LoadAssetAtPath<Material>(GetAssetPath(matPath));
        if (mat == null)
        {
            mat = new Material(Shader.Find("UI/Default Font"));
            AssetDatabase.CreateAsset(mat, GetAssetPath(matPath));
        }

        string texturePath = Path.Combine(fontDirPath, fileName + "_0.png");
        Texture _fontTexture = AssetDatabase.LoadAssetAtPath<Texture>(GetAssetPath(texturePath));
        mat = AssetDatabase.LoadAssetAtPath<Material>(GetAssetPath(matPath));
        mat.SetTexture("_MainTex", _fontTexture);

        string fontPath = Path.Combine(fontDirPath, fileName + ".fontsettings");
        Font font = AssetDatabase.LoadAssetAtPath<Font>(GetAssetPath(fontPath));
        if (font == null)
        {
            font = new Font();
            AssetDatabase.CreateAsset(font, GetAssetPath(fontPath));
        }

        string fontConfigPath = Path.Combine(fontDirPath, fileName + ".fnt");
        List<CharacterInfo> chars = GetFontInfo(fontConfigPath, _fontTexture);
        font.material = mat;
        SerializeFont(font, chars, 1);
        //File.Delete(fontConfigPath);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();
    }

    private List<CharacterInfo> GetFontInfo(string fontConfig, Texture texture)
    {
        XmlDocument xml = new XmlDocument();
        xml.Load(fontConfig);

        XmlNode info = xml.GetElementsByTagName("info")[0];
        XmlNodeList chars = xml.GetElementsByTagName("chars")[0].ChildNodes;

        CharacterInfo[] charInfos = new CharacterInfo[chars.Count];

        for (int cnt = 0; cnt < chars.Count; cnt++)
        {
            XmlNode node = chars[cnt];
            CharacterInfo charInfo = new CharacterInfo();
            charInfo.index = ToInt(node, "id");
            charInfo.advance = (int)ToFloat(node, "xadvance") + xAdvanceOffset;
            Rect r = GetUV(node, (Texture2D)texture);
            //这里注意下UV坐标系和从BMFont里得到的信息的坐标系是不一样的哦,前者左下角为(0,0),
            //右上角为(1,1)。而后者则是左上角上角为(0,0),右下角为(图宽,图高)

            charInfo.uvBottomLeft = new Vector2(r.xMin, r.yMin);                        //字符uv左下角坐标
            charInfo.uvBottomRight = new Vector2(r.xMax, r.yMin);                       //字符uv右下角坐标
            charInfo.uvTopLeft = new Vector2(r.xMin, r.yMax);                           //字符uv左上角坐标
            charInfo.uvTopRight = new Vector2(r.xMax, r.yMax);                          //字符uv右上角坐标

            //Debug.LogError($"charInfo.advance = {charInfo.advance}");                 // uv1.x    字符的横轴占用空间
            //Debug.LogError($"r.xMin = {r.xMin}");                                     // uv1.x    uv起始点x坐标
            //Debug.LogError($"r.yMin = {r.yMin}");                                     // uv1.y    uv起始点y坐标
            //Debug.LogError($"r.xMax = {r.xMax}");                                     // uv2.x    uv终止点y坐标
            //Debug.LogError($"r.yMax = {r.yMax}");                                     // uv2.y    uv终止点y坐标

            //Debug.Log("charInfo.minY = " + charInfo.minY);                            // 同上uv
            //Debug.Log("charInfo.maxY = " + charInfo.maxY);
            //Debug.Log("charInfo.minX = " + charInfo.minX);
            //Debug.Log("charInfo.maxX = " + charInfo.maxX);
            //Debug.Log("------------------------");


            r = GetVert(node);
            charInfo.minX = (int)r.xMin;
            charInfo.maxX = (int)r.xMax;

            //不居中
            //charInfo.minY = (int)r.yMax; 
            //charInfo.maxY = (int)r.yMin;
            //居中显示
            charInfo.minY = (int)((r.yMax - r.yMin) / 2);
            charInfo.maxY = -(int)((r.yMax - r.yMin) / 2);

            charInfos[cnt] = charInfo;
        }
        return new List<CharacterInfo>(charInfos);
    }

    private static void SetLineHeight(SerializedObject font, float height)
    {
        font.FindProperty("m_LineSpacing").floatValue = height;
    }

    private static SerializedObject SerializeFont(Font font, List<CharacterInfo> chars, float lineHeight)
    {
        SerializedObject serializedFont = new SerializedObject(font);
        SetLineHeight(serializedFont, lineHeight);
        SerializeFontCharInfos(serializedFont, chars);
        serializedFont.ApplyModifiedProperties();
        return serializedFont;
    }

    private static void SerializeFontCharInfos(SerializedObject font, List<CharacterInfo> chars)
    {
        SerializedProperty charRects = font.FindProperty("m_CharacterRects");
        charRects.arraySize = chars.Count;
        for (int i = 0; i < chars.Count; ++i)
        {
            CharacterInfo info = chars[i];
            SerializedProperty prop = charRects.GetArrayElementAtIndex(i);
            SerializeCharInfo(prop, info);
        }
    }

    private static void SerializeCharInfo(SerializedProperty prop, CharacterInfo charInfo)
    {
        prop.FindPropertyRelative("index").intValue = charInfo.index;
        prop.FindPropertyRelative("uv").rectValue = charInfo.uv;
        prop.FindPropertyRelative("vert").rectValue = charInfo.vert;
        prop.FindPropertyRelative("advance").floatValue = charInfo.advance;
        prop.FindPropertyRelative("flipped").boolValue = false;
    }

    private List<FontInfo> AnalysisConfig(string configPath)
    {
        List<FontInfo> infoList = new List<FontInfo>();
        string[] fileInfo = File.ReadAllLines(configPath);
        bool isGetInfoFlag = false;
        for (int i = 0; i < fileInfo.Length; i++)
        {
            if (fileInfo[i].Contains(configFlag) || isGetInfoFlag)
            {
                if (!isGetInfoFlag)
                {
                    i++;
                    isGetInfoFlag = true;
                }

                if (i < fileInfo.Length && !string.IsNullOrEmpty(fileInfo[i]))
                {
                    infoList.Add(GetFontInfoByStr(fileInfo[i]));
                }
            }
        }
        return infoList;
    }

    private FontInfo GetFontInfoByStr(string str)
    {
        string[] strTemp = str.Split(',');
        FontInfo fontInfo = new FontInfo();
        string strPathTemp = string.Empty;
        for (int i = 0; i < strTemp.Length; i++)
        {
            if (IsOddDoubleQuota(strTemp[i]))
            {
                strPathTemp += strTemp[i] + ",";
                if (!IsOddDoubleQuota(strPathTemp))
                {
                    strPathTemp = strPathTemp.Substring(0, strPathTemp.Length - 1);
                    break;
                }
            }
            else
            {
                strPathTemp = strTemp[i];
                break;
            }
        }

        fontInfo.ImgPath = strPathTemp.Replace("icon=\"", string.Empty).Replace("\"", string.Empty);
        fontInfo.charInfo = (char)int.Parse(strTemp[strTemp.Length - 4]);
        return fontInfo;
    }

    private bool IsOddDoubleQuota(string str)
    {
        return GetDoubleQuotaCount(str) % 2 == 1;
    }

    private int GetDoubleQuotaCount(string str)
    {
        string[] strArray = str.Split('"');
        int doubleQuotaCount = strArray.Length - 1;
        doubleQuotaCount = doubleQuotaCount < 0 ? 0 : doubleQuotaCount;
        return doubleQuotaCount;
    }

    private string FormatPath(string path)
    {
        path = path.Replace("\\", "/");
        return path;
    }

    private Rect GetUV(XmlNode node, Texture2D textureFile)
    {
        Rect uv = new Rect
        {
            x = ToFloat(node, "x") / textureFile.width,
            y = ToFloat(node, "y") / textureFile.height,
            width = ToFloat(node, "width") / textureFile.width,
            height = ToFloat(node, "height") / textureFile.height
        };
        uv.y = 1f - uv.y - uv.height;
        return uv;
    }

    private Rect GetVert(XmlNode node)
    {
        Rect uv = new Rect
        {
            x = ToFloat(node, "xoffset"),
            y = ToFloat(node, "yoffset"),
            width = ToFloat(node, "width"),
            height = ToFloat(node, "height")
        };
        uv.y = -uv.y;
        uv.height = -uv.height;
        return uv;
    }


    private int ToInt(XmlNode node, string name)
    {
        return Convert.ToInt32(node.Attributes.GetNamedItem(name).InnerText);
    }

    private float ToFloat(XmlNode node, string name)
    {
        return (float)ToInt(node, name);
    }


    static bool IsNullOrEmpty<T>(List<T> lst)
    {
        if (lst == null)
        {
            return true;
        }

        if (lst.Count == 0)
        {
            return true;
        }

        return false;
    }

    static T ToFind<T>(List<T> obj, Predicate<T> predicate)
    {
        for (int i = 0; i < obj.Count; i++)
        {
            if (predicate(obj[i]))
            {
                return obj[i];
            }
        }

        return default;
    }
}

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

推荐阅读更多精彩内容