Hunger乱写日记

主要类

MapManger(面板填写参数)

启动时初始化地图生成器

// 地图尺寸
public int mapSize;        // 一行或者一列有多少个地图块
public int mapChunkSize;   // 一个地图块有多少个格子
public float cellSize;     // 一个格子多少米

// 地图的随机参数
public float noiseLacunarity;  // 噪音间隙
public int mapSeed;            // 地图种子
public int spawnSeed;          // 随时地图对象的种子
public float marshLimit;       // 沼泽的边界

// 地图的美术资源
public Material mapMaterial;
public Texture2D forestTexutre;
public Texture2D[] marshTextures;
public MapConfig mapConfig;   //地图配置

private MapGenerator mapGenerator;
public int viewDinstance;       // 玩家可视距离,单位是Chunk
public Transform viewer;        // 观察者
private Vector3 lastViewerPos = Vector3.one * -1;
public Dictionary<Vector2Int, MapChunkController> mapChunkDic;  // 全部已有的地图块

public float updateChunkTime = 1f;
private bool canUpdateChunk = true;
private float chunkSizeOnWord;  // 在世界中实际的地图块尺寸 单位米
private List<MapChunkController> lastVisibleChunkList = new List<MapChunkController>();

// 某个类型可以生成那些配置的ID
private Dictionary<MapVertexType, List<int>> spawnConfigDic;

void Start()
{
    // 确定配置
    Dictionary<int, ConfigBase> tempDic = ConfigManager.Instance.GetConfigs(ConfigName.MapObject);
    spawnConfigDic = new Dictionary<MapVertexType, List<int>>();
    spawnConfigDic.Add(MapVertexType.Forest, new List<int>());
    spawnConfigDic.Add(MapVertexType.Marsh, new List<int>());
    foreach (var item in tempDic)
    {
        MapVertexType mapVertexType = (item.Value as MapObjectConfig).MapVertexType;
        spawnConfigDic[mapVertexType].Add(item.Key);
    }

    // 初始化地图生成器
    mapGenerator = new MapGenerator(mapSize, mapChunkSize, cellSize,noiseLacunarity,mapSeed,spawnSeed,marshLimit,mapMaterial,forestTexutre,marshTextures, spawnConfigDic) ;
    mapGenerator.GenerateMapData();
    mapChunkDic = new Dictionary<Vector2Int, MapChunkController>();
    chunkSizeOnWord = mapChunkSize * cellSize;
}

MapGenerator

/// <summary>
/// 生成地图数据,主要是所有地图块都通用的数据
/// </summary>
public MapGenerator(int mapSize, int mapChunkSize, float cellSize, float noiseLacunarity, int mapSeed, int spawnSeed, float marshLimit, Material mapMaterial, Texture2D forestTexutre, Texture2D[] marshTextures, Dictionary<MapVertexType, List<int>> spawnConfigDic)
{
      this.mapSize = mapSize;
      this.mapChunkSize = mapChunkSize;
      this.cellSize = cellSize;
      this.noiseLacunarity = noiseLacunarity;
      this.mapSeed = mapSeed;
      this.spawnSeed = spawnSeed;
      this.marshLimit = marshLimit;
      this.mapMaterial = mapMaterial;
      this.forestTexutre = forestTexutre;
      this.marshTextures = marshTextures;
      this.spawnConfigDic = spawnConfigDic;
}

public void GenerateMapData()
{
    Random.InitState(mapSeed);
    // 生成噪声图
    float[,] noiseMap = GenerateNoiseMap(mapSize * mapChunkSize, mapSize * mapChunkSize, noiseLacunarity);
    // 生成网格数据
    mapGrid = new MapGrid(mapSize * mapChunkSize, mapSize * mapChunkSize, cellSize);
    // 确定网格 格子的贴图索引
    mapGrid.CalculateMapVertexType(noiseMap, marshLimit);
    // 初始化默认材质的尺寸
    mapMaterial.mainTexture = forestTexutre;
    mapMaterial.SetTextureScale("_MainTex", new Vector2(cellSize * mapChunkSize, cellSize * mapChunkSize));
    // 实例化一个沼泽材质
    marshMaterial = new Material(mapMaterial);
    marshMaterial.SetTextureScale("_MainTex", Vector2.one);

    chunkMesh = GenerateMapMesh(mapChunkSize, mapChunkSize, cellSize);

    Random.InitState(spawnSeed);
    List<int> temps = spawnConfigDic[MapVertexType.Forest];
	for (int i = 0; i < temps.Count; i++) forestSpawanWeightTotal += ConfigManager.Instance.GetConfig<MapObjectConfig>(ConfigName.MapObject, temps[i]).Probability;
	temps = spawnConfigDic[MapVertexType.Marsh];
	for (int i = 0; i < temps.Count; i++) marshSpawanWeightTotal += ConfigManager.Instance.GetConfig<MapObjectConfig>(ConfigName.MapObject, temps[i]).Probability;
}
生成噪声图(MapGenerator中)

float[,] noiseMap = GenerateNoiseMap(mapSize * mapChunkSize, mapSize * mapChunkSize, noiseLacunarity);

/// <summary>
/// 生成噪声图
/// </summary>
private float[,] GenerateNoiseMap(int width, int height, float lacunarity)
{ 
   
    lacunarity += 0.1f;
    // 这里的噪声图是为了顶点服务的
    float[,] noiseMap = new float[width-1,height-1];
    float offsetX = Random.Range(-10000f, 10000f);
    float offsetY = Random.Range(-10000f, 10000f);

    for (int x = 0; x < width - 1; x++)
    {
        for (int y = 0; y < height - 1; y++)
        {
            noiseMap[x,y] = Mathf.PerlinNoise(x * lacunarity + offsetX,y * lacunarity + offsetY);
        }
    }
    return noiseMap;
}

生成网格(MapGrid中)

mapGrid = new MapGrid(mapSize * mapChunkSize, mapSize * mapChunkSize, cellSize);

// 顶点数据
public Dictionary<Vector2Int, MapVertex> vertexDic = new Dictionary<Vector2Int, MapVertex>();
// 格子数据
public Dictionary<Vector2Int, MapCell> cellDic = new Dictionary<Vector2Int, MapCell>();

public MapGrid(int mapHeight, int mapWdith, float cellSize)
{
    MapHeight = mapHeight;
    MapWidth = mapWdith;
    CellSize = cellSize;

    // 生成顶点数据
    for (int x = 1; x < mapWdith; x++)
    {
        for (int z = 1; z < mapHeight; z++)
        {
            AddVertex(x, z);
            AddCell(x, z);
        }
    }
    // 增加一行一列
    for (int x = 1; x <= mapWdith; x++)
    {
        AddCell(x, mapHeight);
    }
    for (int z = 1; z < mapWdith; z++)
    {
        AddCell(mapWdith, z);
    }

	private void AddVertex(int x, int y)
	{
    vertexDic.Add(new Vector2Int(x, y)
        , new MapVertex()
        {
            Position = new Vector3(x * CellSize, 0, y * CellSize)
    	});
	}
	private void AddCell(int x, int y)
	{
    float offset = CellSize / 2;
    cellDic.Add(new Vector2Int(x, y), 
        new MapCell()
        {
            Position = new Vector3(x * CellSize - offset, 0, y * CellSize - offset)
        });
	}

}
随后计算贴图索引(MapGrid中)

mapGrid.CalculateMapVertexType(noiseMap, marshLimit);

/// <summary>
/// 计算格子贴图的索引数字
/// </summary>
public void CalculateMapVertexType(float[,] noiseMap,float limit)
{ 
    int width = noiseMap.GetLength(0);
    int height = noiseMap.GetLength(1);

    for (int x = 1; x < width; x++)
    {
        for (int z = 1; z < height; z++)
        {
            // 基于噪声中的值确定这个顶点的类型
            // 大于边界是沼泽,否则是森林
            if (noiseMap[x,z] >=limit)
            {
                SetVertexType(x, z, MapVertexType.Marsh);
            }
            else
            {
                SetVertexType(x, z, MapVertexType.Forest);
            }
        }
    }
/// <summary>
/// 设置顶点类型,通过计算顶点接触的四个面
/// </summary>
private void SetVertexType(Vector2Int vertexIndex, MapVertexType mapVertexType)
{
    MapVertex vertex = GetVertex(vertexIndex);
    if (vertex.VertexType != mapVertexType)
    {
        vertex.VertexType = mapVertexType;
        // 只有沼泽需要计算
        if (vertex.VertexType == MapVertexType.Marsh)
        {
            // 计算附近的贴图权重

            MapCell tempCell = GetLeftBottomMapCell(vertexIndex);
            if (tempCell != null) tempCell.TextureIndex += 1;

            tempCell = GetRightBottomMapCell(vertexIndex);
            if (tempCell != null) tempCell.TextureIndex += 2;

             tempCell = GetLeftTopMapCell(vertexIndex);
            if (tempCell != null) tempCell.TextureIndex += 4;

            tempCell = GetRightTopMapCell(vertexIndex);
            if (tempCell != null) tempCell.TextureIndex += 8;
        }
    }
}
/// <summary>
/// 获取格子,如果没有找到会返回Null
/// </summary>
public MapCell GetCell(Vector2Int index)
{
    MapCell cell = null;
    cellDic.TryGetValue(index, out cell);
    return cell;
}

public MapCell GetCell(int x,int y)
{
    return GetCell(new Vector2Int(x,y));
}

/// <summary>
/// 获取左下角格子
/// </summary>
public MapCell GetLeftBottomMapCell(Vector2Int vertexIndex)
{
    return GetCell(vertexIndex);
}

/// <summary>
/// 获取右下角格子
/// </summary>
public MapCell GetRightBottomMapCell(Vector2Int vertexIndex)
{
    return GetCell(vertexIndex.x+1,vertexIndex.y);
}

/// <summary>
/// 获取左上角格子
/// </summary>
public MapCell GetLeftTopMapCell(Vector2Int vertexIndex)
{
    return GetCell(vertexIndex.x, vertexIndex.y+1);
}

/// <summary>
/// 获取右上角格子
/// </summary>
public MapCell GetRightTopMapCell(Vector2Int vertexIndex)
{
    return GetCell(vertexIndex.x+1, vertexIndex.y + 1);
}
/// <summary>
/// 设置顶点类型
/// </summary>
private void SetVertexType(int x,int y,MapVertexType mapVertexType)
{
    SetVertexType(new Vector2Int(x, y), mapVertexType);
}
}
随后生成材质球
mapMaterial.mainTexture = forestTexutre;
mapMaterial.SetTextureScale("_MainTex", new Vector2(cellSize * mapChunkSize, cellSize * mapChunkSize));
// 实例化一个沼泽材质
marshMaterial = new Material(mapMaterial);
marshMaterial.SetTextureScale("_MainTex", Vector2.one);

chunkMesh = GenerateMapMesh(mapChunkSize, mapChunkSize, cellSize);

/// <summary>
/// 生成地形Mesh
/// </summary>
private Mesh GenerateMapMesh(int height,int wdith, float cellSize)
{
    Mesh mesh = new Mesh();
    // 确定顶点在哪里
    mesh.vertices = new Vector3[]
    {
        new Vector3(0,0,0),
        new Vector3(0,0,height*cellSize),
        new Vector3(wdith*cellSize,0,height*cellSize),
        new Vector3(wdith*cellSize,0,0),
    };
    // 确定哪些点形成三角形
    mesh.triangles = new int[]
    {
        0,1,2,
        0,2,3
    };
    mesh.uv = new Vector2[]
    {
        new Vector3(0,0),
        new Vector3(0,1),
        new Vector3(1,1),
        new Vector3(1,0),
    };
    // 计算法线
    mesh.RecalculateNormals();
    return mesh;
}

MapObjectConfig (这个类是用来存储地图物品预制体的)

[CreateAssetMenu(fileName = "地图物体配置", menuName = "Config/地图物体配置")]
public class MapObjectConfig : ConfigBase
{
    [LabelText("空的 不生成物品")]
    public bool IsEmpty = false;
    [LabelText("所在的地图顶点类型")]
    public MapVertexType MapVertexType;
    [LabelText("生成的预制体")]
    public GameObject Prefab;
    [LabelText("生成概率 权重类型")]
    public int Probability;
}

玩家移动位置(MapManger)

void Update()
{
    UpdateVisibleChunk();
}

// 根据观察者的位置来刷新那些地图块可以看到
private void UpdateVisibleChunk()
{
    // 如果观察者没有移动过,不需要刷新
    if (viewer.position == lastViewerPos) return;
    // 如果时间没到 不允许更新
    if (canUpdateChunk == false) return;

    // 当前观察者所在的地图快,
    Vector2Int currChunkIndex = GetMapChunkIndexByWorldPosition(viewer.position);

    // 关闭全部不需要显示的地图块
    for (int i = lastVisibleChunkList.Count-1; i >= 0; i--)
    {
        Vector2Int chunkIndex = lastVisibleChunkList[i].ChunkIndex;
        if (Mathf.Abs(chunkIndex.x - currChunkIndex.x)>viewDinstance
            || Mathf.Abs(chunkIndex.y - currChunkIndex.y)>viewDinstance)
        {
            lastVisibleChunkList[i].SetActive(false);
            lastVisibleChunkList.RemoveAt(i);
        }
    }

    int startX = currChunkIndex.x - viewDinstance;
    int startY = currChunkIndex.y - viewDinstance;
    // 开启需要显示的地图块
    for (int x = 0; x < 2*viewDinstance+1; x++)
    {
        for (int y = 0; y < 2 * viewDinstance + 1; y++)
        {
            canUpdateChunk = false;
            Invoke("RestCanUpdateChunkFlag", updateChunkTime);
            Vector2Int chunkIndex = new Vector2Int(startX + x, startY + y);
            // 之前加载过
            if (mapChunkDic.TryGetValue(chunkIndex,out MapChunkController chunk))
            {
                // 这个地图是不是已经在显示列表
                if (lastVisibleChunkList.Contains(chunk) == false)
                {
                    lastVisibleChunkList.Add(chunk);
                    chunk.SetActive(true);
                }
            }
            // 之前没有加载
            else
            {
                chunk = GenerateMapChunk(chunkIndex);
                if (chunk!=null)
                {
                    chunk.SetActive(true);
                    lastVisibleChunkList.Add(chunk);
                }
            }
        }
    }
}

// 之前没有加载
else{
chunk = GenerateMapChunk(chunkIndex);
if (chunk!=null)
{
chunk.SetActive(true);
lastVisibleChunkList.Add(chunk);
}
}

private MapChunkController GenerateMapChunk(Vector2Int index)
{
    // 检查坐标的合法性
    if (index.x > mapSize-1 || index.y > mapSize-1) return null;
    if (index.x < 0 || index.y < 0) return null;
    MapChunkController chunk = mapGenerator.GenerateMapChunk(index, transform);
    mapChunkDic.Add(index, chunk);
    return chunk;
}
 /// <summary>
 /// 生成地图块
 /// </summary>
 public MapChunkController GenerateMapChunk(Vector2Int chunkIndex,Transform parent)
 {
     // 生成地图块物体
     GameObject mapChunkObj = new GameObject("Chunk_" + chunkIndex.ToString());
     MapChunkController mapChunk=mapChunkObj.AddComponent<MapChunkController>();

     // 生成Mesh
     mapChunkObj.AddComponent<MeshFilter>().mesh = chunkMesh;
     // 添加碰撞体
     mapChunkObj.AddComponent<MeshCollider>();

     // 生成地图块的贴图
     Texture2D mapTexture;
      this.StartCoroutine
      (
          GenerateMapTexture(chunkIndex, (tex, isAllForest) => {
          // 如果完全是森林,没必要在实例化一个材质球
          if (isAllForest)
          {
              mapChunkObj.AddComponent<MeshRenderer>().sharedMaterial = mapMaterial;
          }
          else
          {
              mapTexture = tex;
              Material material = new Material(marshMaterial);
              material.mainTexture = tex;
              mapChunkObj.AddComponent<MeshRenderer>().material = material;
          }
      }));
    
     // 确定坐标
     Vector3 position = new Vector3(chunkIndex.x * mapChunkSize * cellSize, 0, chunkIndex.y * mapChunkSize * cellSize);
     mapChunk.transform.position = position;
     mapChunkObj.transform.SetParent(parent);

     // 生成场景物体数据
     List<MapChunkMapObjectModel> mapObjectModelList = SpawnMapObject(chunkIndex);
     mapChunk.Init(chunkIndex,position + new Vector3((mapChunkSize * cellSize) / 2, 0, (mapChunkSize * cellSize) / 2), mapObjectModelList);

     return mapChunk;
 }

// 生成地图块的贴图
Texture2D mapTexture;
this.StartCoroutine
(
GenerateMapTexture(chunkIndex, (tex, isAllForest) => {
// 如果完全是森林,没必要在实例化一个材质球
if (isAllForest)
{
mapChunkObj.AddComponent().sharedMaterial = mapMaterial;
}
else
{
mapTexture = tex;
Material material = new Material(marshMaterial);
material.mainTexture = tex;
mapChunkObj.AddComponent().material = material;
}
}));

/// <summary>
/// 分帧 生成地图贴图
/// 如果这个地图块完全是森林,直接返回森林贴图
/// </summary>
private IEnumerator GenerateMapTexture(Vector2Int chunkIndex,System.Action<Texture2D,bool> callBack)
{
    // 当前地块的偏移量 找到这个地图块具体的每一个格子
    int cellOffsetX = chunkIndex.x * mapChunkSize + 1;
    int cellOffsetY = chunkIndex.y * mapChunkSize + 1;

    // 是不是一张完整的森林地图块
    bool isAllForest = true;
    // 检查是否只有森林类型的格子
    for (int y = 0; y < mapChunkSize; y++)
    {
        if (isAllForest == false) break;
        for (int x = 0; x < mapChunkSize; x++)
        {
            MapCell cell = mapGrid.GetCell(x + cellOffsetX, y + cellOffsetY);
            if (cell != null && cell.TextureIndex !=0)
            {
                isAllForest = false;
                break;
            }
        }
    }

    Texture2D mapTexture = null;
    // 有沼泽的情况
    if (!isAllForest)
    {
        // 贴图都是矩形
        int textureCellSize = forestTexutre.width;
        // 整个地图块的宽高,正方形
        int textureSize = mapChunkSize * textureCellSize;
        mapTexture = new Texture2D(textureSize, textureSize, TextureFormat.RGB24, false);

        // 遍历每一个格子
        for (int y = 0; y < mapChunkSize; y++)
        {
            // 一帧只执行一列 只绘制一列的像素
            yield return null;
            // 像素偏移量
            int pixelOffsetY = y * textureCellSize;
            for (int x = 0; x < mapChunkSize; x++)
            {

                int pixelOffsetX = x * textureCellSize;
                int textureIndex = mapGrid.GetCell(x + cellOffsetX, y + cellOffsetY).TextureIndex - 1;
                // 绘制每一个格子内的像素
                // 访问每一个像素点
                for (int y1 = 0; y1 < textureCellSize; y1++)
                {
                    for (int x1 = 0; x1 < textureCellSize; x1++)
                    {

                        // 设置某个像素点的颜色
                        // 确定是森林还是沼泽
                        // 这个地方是森林 ||
                        // 这个地方是沼泽但是是透明的,这种情况需要绘制groundTexture同位置的像素颜色
                        if (textureIndex < 0)
                        {
                            Color color = forestTexutre.GetPixel(x1, y1);
                            mapTexture.SetPixel(x1 + pixelOffsetX, y1 + pixelOffsetY, color);
                        }
                        else
                        {
                            // 是沼泽贴图的颜色
                            Color color = marshTextures[textureIndex].GetPixel(x1, y1);
                            if (color.a < 1f)
                            {
                                mapTexture.SetPixel(x1 + pixelOffsetX, y1 + pixelOffsetY, forestTexutre.GetPixel(x1, y1));
                            }
                            else
                            {
                                mapTexture.SetPixel(x1 + pixelOffsetX, y1 + pixelOffsetY, color);
                            }
                        }

                    }
                }
            }
        }
        mapTexture.filterMode = FilterMode.Point;
        mapTexture.wrapMode = TextureWrapMode.Clamp;
        mapTexture.Apply();
    }
    callBack?.Invoke(mapTexture,isAllForest);
}

// 生成场景物体数据
List mapObjectModelList = SpawnMapObject(chunkIndex);

/// <summary>
/// 生成各种地图对象
/// </summary>
private List<MapChunkMapObjectModel> SpawnMapObject(Vector2Int chunkIndex)
{

    List<MapChunkMapObjectModel> mapChunkObjectList = new List<MapChunkMapObjectModel>();

    int offsetX = chunkIndex.x * mapChunkSize;
    int offsetY = chunkIndex.y * mapChunkSize;

    // 遍历地图顶点
    for (int x = 1; x < mapChunkSize; x++)
    {
        for (int y = 1; y < mapChunkSize; y++)
        {
            MapVertex mapVertex = mapGrid.GetVertex(x + offsetX, y + offsetY);
            // 根据概率配置随机
            List<int> configIDs = spawnConfigDic[mapVertex.VertexType];

            // 确定权重的总和
            int weightTotal = mapVertex.VertexType == MapVertexType.Forest?forestSpawanWeightTotal:marshSpawanWeightTotal;

            int randValue = Random.Range(1, weightTotal+1); // 实际命中数字是从1~weightTotal
            float temp = 0;
            int spawnConfigIndex = 0;   // 最终要生成的物品

            
            for (int i = 0; i < configIDs.Count; i++)
            {
                temp +=ConfigManager.Instance.GetConfig<MapObjectConfig>(ConfigName.MapObject, configIDs[i]).Probability;
                if (randValue < temp)
                {
                    // 命中
                    spawnConfigIndex = i;
                    break;
                }
            }

            int configID = configIDs[spawnConfigIndex];
            // 确定到底生成什么物品
            MapObjectConfig spawnModel = ConfigManager.Instance.GetConfig<MapObjectConfig>(ConfigName.MapObject, configID);
            if (spawnModel.IsEmpty == false)
            {
                Vector3 position = mapVertex.Position + new Vector3(Random.Range(-cellSize / 2, cellSize / 2), 0, Random.Range(-cellSize / 2, cellSize / 2));
                mapChunkObjectList.Add(
                    new MapChunkMapObjectModel() { Prefab = spawnModel.Prefab, Position = position }
               );
            }
        }
    }

    return mapChunkObjectList;
}
mapChunk.Init(chunkIndex,position + new Vector3((mapChunkSize * cellSize) / 2, 0, (mapChunkSize * cellSize) / 2), mapObjectModelList);
public class MapChunkData
{
    public List<MapChunkMapObjectModel> MapObjectList = new List<MapChunkMapObjectModel>();
}
public class MapChunkMapObjectModel
{
    public GameObject Prefab;
    public Vector3 Position;
}



public class MapChunkController : MonoBehaviour
{
    public Vector2Int ChunkIndex { get; private set; }
    public Vector3 CentrePosition { get; private set; }

    private MapChunkData mapChunkData;
    private List<GameObject> mapObjectList;

    private bool isActive = false;
    public void Init(Vector2Int chunkIndex, Vector3 centrePosition, List<MapChunkMapObjectModel> MapObjectList)
    {
        ChunkIndex = chunkIndex;
        CentrePosition = centrePosition;
        mapChunkData = new MapChunkData();
        mapChunkData.MapObjectList = MapObjectList;
        mapObjectList = new List<GameObject>(MapObjectList.Count);
    }
}

回到MapManger

if (chunk!=null)
{
chunk.SetActive(true);
lastVisibleChunkList.Add(chunk);
}

//其中lastVisibleChunkList
private List<MapChunkController> lastVisibleChunkList = new List<MapChunkController>();
public void SetActive(bool active)
{
    if (isActive!=active)
    {
        isActive = active;
        gameObject.SetActive(isActive);

        List<MapChunkMapObjectModel> ObjectList = mapChunkData.MapObjectList;
        // 从对象池中获取所有物体
        if (isActive)
        {
            for (int i = 0; i < ObjectList.Count; i++)
            {
                GameObject go = PoolManager.Instance.GetGameObject(ObjectList[i].Prefab, transform);
                go.transform.position = ObjectList[i].Position;
                mapObjectList.Add(go);
            }
        }
        // 把所有物体放回对象池
        else
        {
            for (int i = 0; i < ObjectList.Count; i++)
            {
                PoolManager.Instance.PushGameObject(mapObjectList[i]);
            }
            mapObjectList.Clear();
        }
    }
}

MapChunkMapObjectModel 存放了 地图物品预制体,三维位置

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值