Unity简单商城系统,用SQLite数据库保存/加载数据

Unity简单商城系统案例

  • 流程
  • 最后效果展示
  • 1. 创建项目并导入SQLite需要的dll文件
  • 2. 创建数据库表(玩家表和商店表)
  • 3. Singleton 单例脚本
  • 4. 封装SQLite数据库的操作方法(SQLiteManager 脚本)
  • 5. 基于 SQLiteManager脚本进行二次封装(ShopManager 脚本)
    • 5-1. 买装备的方法实现
    • 5-2. 卖装备方法的实现
    • 5-3. ShopManager 脚本内容
  • 6. 测试买/卖装备
    • 6-1.买装备
    • 6-2. 卖装备
  • 7. UI的搭建
  • 8. 生成商城图片
  • 9. 装备图片脚本
  • 10. 玩家信息的初始化和更新
  • 11. 购买装备的点击事件
  • 12. 出售装备的点击事件
  • 13. 最后效果

不会在Unity使用SQLite数据库可以看一下这篇文章Unity2021中使用SQLite数据库

流程

  1. 封装数据库方法脚本(打开数据库,关闭数据库,增删改,查单个数据,查多个数据)
  2. 根据数据库脚本再封装一个买装备卖装备的脚本(获取英雄名称,金钱,属性,背包中的装备、获取装备的价格,属性、属性的相加/相减,更新玩家金钱,属性,身上的装备到数据库中)
  3. 商店系统脚本实现(商店初始化)
  4. 玩家数据脚本实现(更新玩家的名称,金钱,属性到UI界面中)
  5. 商店格子的脚本和背包格子的脚本实现(点击买装备或卖装备)

最后效果展示

在这里插入图片描述

1. 创建项目并导入SQLite需要的dll文件

将需要3个dll文件导入到 Plugins 文件夹中

mono.data.sqlite.dll,System.data.dll,sqlite3.dll

在这里插入图片描述

2. 创建数据库表(玩家表和商店表)

  1. 打开可视化工具创建数据库并添加两个表(玩家表和商城表)

数据库保存的位置选择在项目里的 StreamingAssets 文件夹中在这里插入图片描述

在这里插入图片描述

  1. 玩家表(玩家拥有的装备保存格式:在每个装备之间用 # 分割 –> 例: 幽梦#电刀#)

字段在这里插入图片描述

数据在这里插入图片描述

  1. 商城表

字段在这里插入图片描述

数据在这里插入图片描述

3. Singleton 单例脚本

使用: 别的脚本只需要继承 Singleton脚本 并创造一个私有的构造函数即可
例:

  public class Test : Singleton<Test> // 继承{private Test(){} // 私有构造}```
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Singleton<T> where T : class
{private static T instance;public static T Instance{get{if(instance == null){instance = (T)Activator.CreateInstance(typeof(T), true);}return instance;}}
}

4. 封装SQLite数据库的操作方法(SQLiteManager 脚本)

继承与 Singleton脚本, 别的脚本可以通过 SQLiteManager.Instance 来访问当前脚本的方法

封装的方法:

  1. 打开数据库
  2. 关闭数据库
  3. 非查询语句的执行
    3-1. 插入语句
    3-2. 更新语句
    3-3. 删除语句
  4. 查询单个数据的执行方法
  5. 查询多个数据的执行方法
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mono.Data.Sqlite;public class SQLiteManager : Singleton<SQLiteManager>
{protected SQLiteManager() { }private SqliteConnection con;private SqliteCommand command;private SqliteDataReader reader;// 打开数据库public void OpenDatabase(string fileName){if(!fileName.EndsWith(".db")){fileName += ".db";}string path = "Data Source=" + Application.streamingAssetsPath + "/" + fileName;con = new SqliteConnection(path);con.Open();}// 关闭数据库public void CloseDatabase(){if(reader != null){reader.Close();reader = null;}if(command != null){command.Dispose();command = null;}if(con != null){con.Close();con = null;}}// 非查询语句执行private int NonQuery(string query){if (command != null){command.Dispose();command = null;}command = con.CreateCommand();command.CommandText = query;return command.ExecuteNonQuery();}// 添加public int Insert(string query){return NonQuery(query);}// 更新public int Update(string query){return NonQuery(query);}// 删除public int Delete(string query){return NonQuery(query);}// 查询单个数据public object SelectSingleData(string query){if (command != null){command.Dispose();command = null;}command = con.CreateCommand();command.CommandText = query;return command.ExecuteScalar();}// 查询多个数据public List<ArrayList> SelectMultipleData(string query){if (command != null){command.Dispose();command = null;}if (reader != null){reader.Close();reader = null;}command = con.CreateCommand();command.CommandText = query;reader = command.ExecuteReader();List<ArrayList> result = new List<ArrayList>();while(reader.Read()){ArrayList list = new ArrayList();for(int i = 0; i < reader.FieldCount; i++){list.Add(reader.GetValue(i));}result.Add(list);}return result;}}

5. 基于 SQLiteManager脚本进行二次封装(ShopManager 脚本)

封装的方法:

  1. 更新玩家身上的装备
  2. 获取玩家身上的装备
  3. 更新玩家属性到数据库中
  4. 玩家添加或移除装备属性
  5. 获取玩家的属性
  6. 获取装备属性
  7. 更新玩家的钱到数据库中
  8. 获取玩家身上的钱
  9. 获取装备价格

封装的方法:

// 更新玩家身上的装备
public void SetPlayerEquips(string playerName, string equips)
{query = "update playertable set equips='" + equips + "' where name = '" + playerName + "'";Update(query);
}// 获取玩家身上的装备
public string GetPlayerEquips(string playerName)
{query = "select equips from playertable where name = '" + playerName + "'";object obj = SelectSingleData(query);if(obj == null){return null;}return obj.ToString();
}// 更新玩家属性到数据库中
public void SetPlayerProperties(string playerName, int[] properties)
{query = "update playertable set ad = " + properties[0] + ", ap = " + properties[1] + ", ar = " + properties[2] + ", sr = " + properties[3] + " where name = '" + playerName + "'";Update(query);
}// 玩家添加或移除装备属性, 参数3,传入1:Add,2:Remove
public int[] PlayerAddOrRemoveProperties(int[] playerProperties, int[] equipProperties, int operation)
{int[] result = new int[playerProperties.Length];if(operation == 1){for(int i = 0; i < result.Length; i++){result[i] = playerProperties[i] + equipProperties[i];}}else if(operation == 2){for(int i = 0; i < result.Length; i++){result[i] = playerProperties[i] - equipProperties[i];}}return result;
}// 获取玩家的属性
public int[] GetPlayerProperties(string playerName)
{query = "select * from playertable where name = '" + playerName + "'";List<ArrayList> arrayLists = SelectMultipleData(query);int[] properties = new int[4];for(int i = 0; i < properties.Length; i++){properties[i] = Convert.ToInt32(arrayLists[0][i + 3]);}return properties;
}// 获取装备属性
public int[] GetEquipProperties(string equipName)
{query = "select * from shoptable where name = '" + equipName + "'";List<ArrayList> arrayLists = SelectMultipleData(query);int[] properties = new int[4];for (int i = 0; i < properties.Length; i++){properties[i] = Convert.ToInt32(arrayLists[0][i + 3]);}return properties;
}// 更新玩家的钱到数据库中
public void SetPlayerMoney(string playerName, int money)
{query = "Update playertable set money = " + money + "  where name = '" + playerName + "'";Update(query);
}// 获取玩家身上的钱
public int GetPlayerMoney(string playerName)
{query = "select money from playertable where name = '" + playerName + "'";object obj = SelectSingleData(query);return Convert.ToInt32(obj);
}// 获取装备价格
public int GetEquipPrice(string equipName)
{query = "select price from shoptable where name = '" + equipName + "'";object obj = SelectSingleData(query);return Convert.ToInt32(obj);}

5-1. 买装备的方法实现

  1. 查看装备价格,玩家身上的钱
  2. 判断玩家身上的钱是否大于装备的价格
  3. 不够前就不能买(return),够钱了就给玩家购买
  4. 获取装备属性,获取玩家的属性, 属性相加形成新的值, 更新到玩家表中,
  5. 将装备名称拼接到玩家装备字符串上,将玩家装备保存到数据库中
  6. 扣除玩家的钱
// 买装备
public void BuyEquip(string playerName, string equipName)
{// 查看装备价格,玩家身上的钱int playerMoney = GetPlayerMoney(playerName);int equipPrice = GetEquipPrice(equipName);// 判断玩家身上的钱是否大于装备的价格,不够前就不能买(return)if (playerMoney < equipPrice){Debug.Log("钱不够");return;}// 够钱了就给玩家购买//  获取装备属性,获取玩家的属性, 属性相加形成新的值, 更新到玩家表中,int[] playerProperties = GetPlayerProperties(playerName);int[] equipProperties = GetEquipProperties(equipName);int[] newProperties = PlayerAddOrRemoveProperties(playerProperties, equipProperties, 1);SetPlayerProperties(playerName, newProperties);//  将装备名称拼接到玩家装备字符串上,将玩家装备保存到数据库中string playerEquips = GetPlayerEquips(playerName);playerEquips += equipName + "#";SetPlayerEquips(playerName, playerEquips);//  扣除玩家的钱SetPlayerMoney(playerName, playerMoney - equipPrice);
}  

5-2. 卖装备方法的实现

  1. 获取玩家身上的钱和装备价格
  2. 获取玩家和装备的属性
  3. 移除玩家身上该装备的属性
  4. 更新玩家属性
  5. 获取玩家装备字符串
  6. 找到装备字符串中的下标,移除该装备
  7. 更新玩家装备
  8. 卖掉的装备是原价的一半,将玩家身上的钱加上该装备价格的一半的价钱
  9. 更新玩家的金钱
// 卖装备public void SellEquip(string playerName, string equipName){// 获取玩家身上的钱和装备价格int playerMoney = GetPlayerMoney(playerName);int equipPrice = GetEquipPrice(equipName);// 获取玩家和装备的属性int[] playerProperties = GetPlayerProperties(playerName);int[] equipProperties = GetEquipProperties(equipName);// 移除玩家身上该装备的属性int[] newPlayerProperties = PlayerAddOrRemoveProperties(playerProperties, equipProperties, 2);// 更新玩家属性SetPlayerProperties(playerName, newPlayerProperties);// 获取玩家装备字符串string playerEquips = GetPlayerEquips(playerName);// 找到装备字符串中的下标,移除该装备playerEquips = playerEquips.Remove(playerEquips.LastIndexOf(equipName), equipName.Length + 1); // 这里最后的加一是为将装备名字后面的 # 删除// 更新玩家装备SetPlayerEquips(playerName, playerEquips);// 卖掉的装备是原价的一半,将玩家身上的钱加上该装备价格的一半的价钱playerMoney += equipPrice / 2;// 更新玩家的金钱SetPlayerMoney(playerName, playerMoney);}

5-3. ShopManager 脚本内容

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class ShopManager : SQLiteManager
{// 单例private static ShopManager instance;public static new ShopManager Instance{get{if(instance == null){instance = new ShopManager();}return instance;}}private ShopManager() { }private string query;// 买装备public void BuyEquip(string playerName, string equipName){// 查看装备价格,玩家身上的钱int playerMoney = GetPlayerMoney(playerName);int equipPrice = GetEquipPrice(equipName);// 判断玩家身上的钱是否大于装备的价格,不够前就不能买(return)if (playerMoney < equipPrice){Debug.Log("钱不够");return;}// 够钱了就给玩家购买//  获取装备属性,获取玩家的属性, 属性相加形成新的值, 更新到玩家表中,int[] playerProperties = GetPlayerProperties(playerName);int[] equipProperties = GetEquipProperties(equipName);int[] newProperties = PlayerAddOrRemoveProperties(playerProperties, equipProperties, 1);SetPlayerProperties(playerName, newProperties);//  将装备名称拼接到玩家装备字符串上,将玩家装备保存到数据库中string playerEquips = GetPlayerEquips(playerName);playerEquips += equipName + "#";SetPlayerEquips(playerName, playerEquips);//  扣除玩家的钱SetPlayerMoney(playerName, playerMoney - equipPrice);}   // 卖装备public void SellEquip(string playerName, string equipName){// 获取玩家身上的钱和装备价格int playerMoney = GetPlayerMoney(playerName);int equipPrice = GetEquipPrice(equipName);// 获取玩家和装备的属性int[] playerProperties = GetPlayerProperties(playerName);int[] equipProperties = GetEquipProperties(equipName);// 移除玩家身上该装备的属性int[] newPlayerProperties = PlayerAddOrRemoveProperties(playerProperties, equipProperties, 2);// 更新玩家属性SetPlayerProperties(playerName, newPlayerProperties);// 获取玩家装备字符串string playerEquips = GetPlayerEquips(playerName);// 找到装备字符串中的下标,移除该装备playerEquips = playerEquips.Remove(playerEquips.LastIndexOf(equipName), equipName.Length + 1); // 这里最后的加一是为将装备名字后面的 # 删除// 更新玩家装备SetPlayerEquips(playerName, playerEquips);// 卖掉的装备是原价的一半,将玩家身上的钱加上该装备价格的一半的价钱playerMoney += equipPrice / 2;// 更新玩家的金钱SetPlayerMoney(playerName, playerMoney);}// 更新玩家身上的装备public void SetPlayerEquips(string playerName, string equips){query = "update playertable set equips='" + equips + "' where name = '" + playerName + "'";Update(query);}// 获取玩家身上的装备public string GetPlayerEquips(string playerName){query = "select equips from playertable where name = '" + playerName + "'";object obj = SelectSingleData(query);if(obj == null){return null;}return obj.ToString();}// 更新玩家属性到数据库中public void SetPlayerProperties(string playerName, int[] properties){query = "update playertable set ad = " + properties[0] + ", ap = " + properties[1] + ", ar = " + properties[2] + ", sr = " + properties[3] + " where name = '" + playerName + "'";Update(query);}// 玩家添加或移除装备属性, 参数3,传入1:Add,2:Removepublic int[] PlayerAddOrRemoveProperties(int[] playerProperties, int[] equipProperties, int operation){int[] result = new int[playerProperties.Length];if(operation == 1){for(int i = 0; i < result.Length; i++){result[i] = playerProperties[i] + equipProperties[i];}}else if(operation == 2){for(int i = 0; i < result.Length; i++){result[i] = playerProperties[i] - equipProperties[i];}}return result;}// 获取玩家的属性public int[] GetPlayerProperties(string playerName){query = "select * from playertable where name = '" + playerName + "'";List<ArrayList> arrayLists = SelectMultipleData(query);int[] properties = new int[4];for(int i = 0; i < properties.Length; i++){properties[i] = Convert.ToInt32(arrayLists[0][i + 3]);}return properties;}// 获取装备属性public int[] GetEquipProperties(string equipName){query = "select * from shoptable where name = '" + equipName + "'";List<ArrayList> arrayLists = SelectMultipleData(query);int[] properties = new int[4];for (int i = 0; i < properties.Length; i++){properties[i] = Convert.ToInt32(arrayLists[0][i + 3]);}return properties;}// 更新玩家的钱到数据库中public void SetPlayerMoney(string playerName, int money){query = "Update playertable set money = " + money + "  where name = '" + playerName + "'";Update(query);}// 获取玩家身上的钱public int GetPlayerMoney(string playerName){query = "select money from playertable where name = '" + playerName + "'";object obj = SelectSingleData(query);return Convert.ToInt32(obj);}// 获取装备价格public int GetEquipPrice(string equipName){query = "select price from shoptable where name = '" + equipName + "'";object obj = SelectSingleData(query);return Convert.ToInt32(obj);}}

6. 测试买/卖装备

6-1.买装备

创建Test脚本(挂载在空对象)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Test : MonoBehaviour
{private void Start(){// 打开数据库ShopManager.Instance.OpenDatabase("ShopSystem");// 买装备ShopManager.Instance.BuyEquip("Akali", "电刀");}private void OnDestroy(){// 关闭数据库ShopManager.Instance.CloseDatabase();}
}

未执行脚本前的数据
在这里插入图片描述

执行完脚本后的数据(买装备)
在这里插入图片描述

6-2. 卖装备

将Test脚本进行修改

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Test : MonoBehaviour
{private void Start(){// 打开数据库ShopManager.Instance.OpenDatabase("ShopSystem");// 买装备// ShopManager.Instance.BuyEquip("Akali", "电刀");// 卖装备ShopManager.Instance.SellEquip("Akali", "电刀");}private void OnDestroy(){ShopManager.Instance.CloseDatabase();}}

未执行脚本前的数据
在这里插入图片描述

执行完脚本后的数据(卖装备)
在这里插入图片描述

7. UI的搭建

背包和商城两个界面

在这里插入图片描述

8. 生成商城图片

  1. 创建商城装备图片的预制体

  2. 创建ShopWindow脚本, 挂载在所有装备格子的父对象

  3. 回到 ShopManager脚本 中添加获取所有装备名称的方法和获取玩家名称的方法

// 获取商店里面所有的装备名称
public string[] GetShopEquipName()
{query = "select name from shoptable";List<ArrayList> obj = SelectMultipleData(query);string[] names = new string[obj.Count];for(int i = 0; i < names.Length; i++){// 每一行里面的名字保存在namesnames[i] = obj[i][0].ToString();}return names;
}
// 获取玩家名称
public string GetPlayerName()
{query = "select name from playertable";object obj = SelectSingleData(query);return obj.ToString();
}

4 初始化商店

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class ShopWindow : MonoBehaviour
{private GameObject shopEquipPrefab;private void Awake(){// 加载预制体shopEquipPrefab = Resources.Load<GameObject>("Prefabs/ShopEquip");}private void Start(){// 打开数据库ShopManager.Instance.OpenDatabase("ShopSystem");ShopInit();}// 初始化商店private void ShopInit(){// 获取商店所有装备的名称string[] equipNames = ShopManager.Instance.GetShopEquipName();for(int i = 0; i < equipNames.Length; i++){// 实例化预制体,并修改其父对象GameObject go = Instantiate(shopEquipPrefab, transform.GetChild(i));go.transform.localPosition = Vector3.zero;go.transform.localScale = Vector3.one;// 获取装备图片Sprite sprite = Resources.Load<Sprite>("LOLicon/" + equipNames[i]);// 修改装备图片go.GetComponent<Image>().sprite = sprite;}}private void OnDestroy(){ShopManager.Instance.CloseDatabase();}}

在这里插入图片描述

9. 装备图片脚本

创建Equip脚本,挂载在装备图片预制体,让其生成出来的时候绑定点击事件,由于是在自身的脚本,获取到的button组件也是自身的。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class Equip : MonoBehaviour
{public void Init(){GetComponent<Button>().onClick.AddListener(OnClickEvent);}// 按钮点击事件private void OnClickEvent(){}
}

调用自身的初始化方法是在 ShopWindow脚本生成装备的时候。

// 初始化商店private void ShopInit(){// 获取商店所有装备的名称string[] equipNames = ShopManager.Instance.GetShopEquipName();for(int i = 0; i < equipNames.Length; i++){// 实例化预制体,并修改其父对象GameObject go = Instantiate(prefab, transform.GetChild(i));go.transform.localPosition = Vector3.zero;go.transform.localScale = Vector3.one;// 获取装备图片Sprite sprite = Resources.Load<Sprite>("LOLicon/" + equipNames[i]);// 修改装备图片go.GetComponent<Image>().sprite = sprite;// 初始化装备图片go.GetComponent<Equip>().Init();}}

10. 玩家信息的初始化和更新

  1. 需要更新玩家名称,金钱,属性,装备
  2. 每一个玩家信息都封装成方法,然后通过一个方法来调用全部方法
  3. 需要给玩家背包加上BagEquip,也是获取按钮初始(跟上面一样)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class PlayerData : MonoBehaviour
{public static PlayerData instance;private GameObject bagEquipPrefab; // 背包装备private Transform bagEquipPrefabParent; // 背包装备的父对象private Text playerNameText; // 玩家名称文本private Text[] propertiesText; // 4个属性文本private Text moneyText; // 金钱文本[HideInInspector]public string playerName; // 玩家名称private void Awake(){instance = this;playerNameText = transform.Find("Name").GetComponent<Text>();moneyText = transform.Find("Money/Text").GetComponent<Text>();bagEquipPrefabParent = transform.Find("Bag/Item/Scroll View/Viewport/Content");propertiesText = new Text[4];for(int i = 0; i < propertiesText.Length; i++){propertiesText[i] = transform.Find("Values").GetChild(i).GetComponent<Text>();}// 加载预制体bagEquipPrefab = Resources.Load<GameObject>("Prefabs/BagEquip");}private void Start(){ShopManager.Instance.OpenDatabase("ShopSystem");UpdatePlayerData();}// 设置玩家名称文本private void SetPlayerName(){// 获取玩家名称playerName = ShopManager.Instance.GetPlayerName();// 修改文本playerNameText.text = playerName;}// 设置玩家属性文本private void SetPlayerProperties(){// 获取玩家属性int[] playerProperties = ShopManager.Instance.GetPlayerProperties(playerName);for(int i = 0; i < playerProperties.Length; i++){propertiesText[i].text = playerProperties[i].ToString();}}// 设置玩家金钱文本private void SetPlayerMoney(){int playerMoney = ShopManager.Instance.GetPlayerMoney(playerName);moneyText.text = playerMoney.ToString();}// 清空玩家装备private void ClearPlayerEquip(){for(int i = 0; i < bagEquipPrefabParent.childCount; i++){if(bagEquipPrefabParent.GetChild(i).childCount > 0){Destroy(bagEquipPrefabParent.GetChild(i).GetChild(0).gameObject);}}}// 设置玩家装备private void SetPlayerEquip(){string playerEquip = ShopManager.Instance.GetPlayerEquips(playerName);string[] playerEquips = playerEquip.Split(new char[] { '#' }); // 获取所有装备for(int i = 0; i < playerEquips.Length; i++){if (playerEquips[i] == "") continue;GameObject go = Instantiate(bagEquipPrefab, bagEquipPrefabParent.GetChild(i));go.transform.localPosition = Vector3.zero;go.transform.localScale = Vector3.one;// 获取图片Sprite sprite = Resources.Load<Sprite>("LOLicon/" + playerEquips[i]);// 设置图片go.GetComponent<Image>().sprite = sprite;go.GetComponent<BagEquip>().Init();}}// 更新玩家信息public void UpdatePlayerData(){SetPlayerName();SetPlayerProperties();SetPlayerMoney();ClearPlayerEquip();SetPlayerEquip();}private void OnDestroy(){ShopManager.Instance.CloseDatabase();}}

BagEquip

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class BagEquip : MonoBehaviour
{public void Init(){GetComponent<Button>().onClick.AddListener(OnClickEvent);}// 按钮点击事件private void OnClickEvent(){// 卖装备}}

在这里插入图片描述

11. 购买装备的点击事件

shopEquip身上的脚本

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;public class Equip : MonoBehaviour
{private string equip; // 要购买的装备private string playerName;public void Init(){GetComponent<Button>().onClick.AddListener(OnClickEvent);equip = GetComponent<Image>().sprite.name;}// 按钮点击事件private void OnClickEvent(){// 买装备先获取玩家名字playerName = PlayerData.instance.playerName;// 买装备ShopManager.Instance.BuyEquip(playerName, equip);// 买完装备更新玩家信息PlayerData.instance.UpdatePlayerData();}}

在这里插入图片描述

12. 出售装备的点击事件

BagEquip身上的脚本

using UnityEngine;
using UnityEngine.UI;public class BagEquip : MonoBehaviour
{private string playerName;private string equip; // 要卖掉的装备public void Init(){GetComponent<Button>().onClick.AddListener(OnClickEvent);equip = GetComponent<Image>().sprite.name;}// 按钮点击事件private void OnClickEvent(){playerName = PlayerData.instance.playerName;// 卖装备ShopManager.Instance.SellEquip(playerName, equip);PlayerData.instance.UpdatePlayerData();}}

在这里插入图片描述

13. 最后效果

在这里插入图片描述

Published by

风君子

独自遨游何稽首 揭天掀地慰生平