C#与DirectX结合制作小游戏实战教程

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:C#与DirectX结合是游戏开发的重要技能。通过【C# DirectX小游戏】项目,初学者可以从***获得资源,学习如何使用C#和DirectX API创建简单游戏,深入理解游戏编程。本项目详细介绍Direct3D基础、C#与DirectX交互、3D模型加载、图形渲染、输入处理、音频处理、游戏循环、碰撞检测、性能优化以及状态管理等关键知识点,旨在提升实践能力和游戏开发流程理解。

1. C#与DirectX结合游戏开发简介

随着游戏行业的蓬勃发展,C#与DirectX的结合为开发者提供了一种强大的工具集,用于创建高效、高性能的2D和3D游戏。C#语言以其简洁、易学的特性,在微软的.NET平台上扮演着重要角色,而DirectX作为Windows系统的核心游戏开发API,提供了直接访问硬件加速图形、音频等多媒体功能的能力。

C#与DirectX的结合,特别是借助Windows Presentation Foundation (WPF) 或 Universal Windows Platform (UWP) 进行游戏开发,不仅降低了游戏开发的门槛,而且简化了复杂的多媒体处理。本章将概述C#与DirectX结合的基础知识,为之后深入讨论Direct3D初始化、3D模型处理、用户输入、音频处理、游戏循环、碰撞检测、性能优化和游戏状态管理等奠定基础。通过本章学习,读者将对C#结合DirectX进行游戏开发有一个全面的了解,并准备好深入探究这些高级主题。

2. ```

第二章:Direct3D基本概念与初始化

Direct3D是DirectX中负责3D图形渲染的部分,它是构建复杂3D游戏不可或缺的技术之一。在本章中,我们将深入了解Direct3D的核心概念,并探讨如何进行有效的初始化。这包括3D图形管线的概述、设备和资源的管理,以及初始化Direct3D环境和创建交换链和渲染目标的详细步骤。

2.1 Direct3D核心概念解析

Direct3D中存在多种概念,为确保3D图形的正确渲染提供了基础框架。理解这些概念对于开发高效的3D应用程序至关重要。

2.1.1 3D图形管线概述

3D图形管线是一个处理3D场景到2D图像的复杂过程,包括多个步骤,每个步骤都是对场景数据的转换和处理。这些步骤可以分为以下几个主要部分:

  • 顶点处理阶段:包括顶点着色器、曲面细分着色器和几何着色器,这个阶段主要处理3D模型的顶点数据。
  • 光栅化阶段:将顶点数据转换成像素,并进行像素着色器处理。
  • 输出合并阶段:将像素着色器处理后的像素数据写入到帧缓冲区。

2.1.2 设备和资源的管理

在Direct3D中,设备(Device)是进行渲染操作的主要对象,负责创建和管理资源。资源则包括纹理、缓冲区、着色器等,它们是图形管线中处理数据的载体。

  • 设备(Device):分为硬件设备和参考设备。硬件设备使用GPU进行图形处理,而参考设备则在CPU上模拟渲染过程。
  • 资源的管理:资源被创建后需要适当管理以优化内存和性能。资源可以是静态的、动态的或者默认缓冲类型,它们的生命周期需要通过引用计数和显式释放来管理。

2.2 Direct3D初始化流程

初始化Direct3D环境是创建3D应用的第一步,它涉及到设备的创建和资源的分配。

2.2.1 初始化Direct3D环境

初始化Direct3D环境首先需要创建一个Direct3D实例,然后在该实例基础上创建一个Direct3D设备。以下是初始化Direct3D环境的示例代码:

using SharpDX.Direct3D;
using SharpDX.DXGI;

Device device;
SwapChain swapChain;

// 创建Direct3D设备和交换链
using (var dxgiFactory = new Factory1())
{
    var featureLevels = new[] { FeatureLevel.Level_11_0 };
    Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, featureLevels, dxgiFactory, out device, out swapChain);
}

在这段代码中,我们首先引用了 SharpDX.Direct3D SharpDX.DXGI 命名空间,并创建了 Device SwapChain 对象。我们使用 DriverType.Hardware 指定了设备类型为硬件设备,并指定 FeatureLevel.Level_11_0 作为支持的最低特性级别。

2.2.2 创建交换链和渲染目标

创建交换链(SwapChain)和渲染目标是完成初始化的下一步。交换链负责管理前后缓存,渲染目标则用来存储实际的像素数据。以下是创建交换链和渲染目标的示例代码:

using (var backBuffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0))
{
    using (var renderView = new RenderTargetView(device, backBuffer))
    {
        // 初始化渲染管线、设置视口等操作
    }
}

在这段代码中,我们从交换链中获取后缓冲区(backBuffer),并基于此创建一个渲染目标视图(renderView)。创建完这些后,我们将继续初始化渲染管线,并设置视口(Viewport)来准备渲染场景。

表格展示

为了更好地理解Direct3D的设备类型和特性级别,可以参考以下表格:

| 设备类型 | 说明 | | --------------- | ------------------------------------------------------------ | | DriverType.Software | 软件模拟的设备,运行在CPU上,不利用硬件加速 | | DriverType.Hardware | 硬件加速设备,利用显卡进行图形处理,速度更快 | | DriverType.Warp | 高速硬件抽象层设备,即使没有实际的GPU也能模拟硬件加速的效果 | | DriverType.Reference | 参考设备,用于调试和开发,运行在CPU上,可以模拟图形硬件行为 |

特性级别则指定了Direct3D所支持的最低渲染特性,包括不同的渲染技术和顶点处理能力。

mermaid流程图

接下来是一个简化的流程图,描述了初始化Direct3D环境的基本步骤:

graph TD
    A[开始] --> B[创建Direct3D实例]
    B --> C[创建Direct3D设备]
    C --> D[创建交换链]
    D --> E[创建渲染目标]
    E --> F[初始化渲染管线]
    F --> G[设置视口]
    G --> H[初始化完成]
    H --> I[渲染循环]

以上内容概述了Direct3D初始化的主要概念和步骤。在接下来的章节中,我们将进一步探讨如何在C#环境中与DirectX API进行交互,并逐步深入了解如何加载3D模型、编写着色器、处理用户输入,以及优化游戏性能等关键开发话题。


# 3. C#与DirectX API的交互方法
在现代游戏开发中,C#与DirectX的交互方法对于开发者而言是一个重要的技能,这不仅能够将C#的高级特性与DirectX底层能力结合,还能实现高效的跨平台游戏开发。本章将详细介绍C#中调用DirectX接口的策略以及如何在C#环境中管理DirectX资源。

## 3.1 C#中调用DirectX接口
### 3.1.1 通过P/Invoke调用本地API
C#支持通过平台调用(P/Invoke)机制,调用DLL中的本地函数。DirectX API大多数是用C++编写的,它们暴露为DLL中的函数。P/Invoke允许我们直接从C#代码中调用这些本地函数。

```csharp
using System;
using System.Runtime.InteropServices;

class DirectXInterop {
    // 导入DirectX的DLL中的函数
    [DllImport("d3d11.dll", EntryPoint = "D3D11CreateDevice", CallingConvention = CallingConvention.StdCall)]
    public static extern int D3D11CreateDevice(
        IntPtr pAdapter,
        D3D_DRIVER_TYPE DriverType,
        IntPtr Software,
        uint Flags,
        ref D3D_FEATURE_LEVEL pFeatureLevels,
        uint FeatureLevels,
        uint SDKVersion,
        ref ID3D11Device pDevice,
        out D3D_FEATURE_LEVEL pFeatureLevel,
        ref ID3D11DeviceContext pImmediateContext);
    // 其他相关的枚举、结构体定义,因为篇幅原因这里省略...
}

通过上述代码,我们可以在C#中直接调用 D3D11CreateDevice 函数创建Direct3D 11设备。P/Invoke需要对参数的正确类型、调用约定(Calling Convention)、入口点(EntryPoint)等进行明确声明,这样才能正确地从C#代码调用本地DirectX API。

3.1.2 使用C++/CLI封装DirectX组件

另一种方法是使用C++/CLI(C++的托管扩展)来封装DirectX API。C++/CLI允许混合托管代码(如C#)与非托管代码(如C++),使得可以在C++/CLI中创建封装类,然后在C#中使用这些封装类来调用DirectX。

// C++/CLI代码片段
public ref class Direct3DHelper {
public:
    static ID3D11Device^ CreateDevice() {
        ID3D11Device* pDevice;
        D3D_FEATURE_LEVEL featureLevel;
        HRESULT hr = D3D11CreateDevice(
            nullptr,                            // pAdapter
            D3D_DRIVER_TYPE::D3D_DRIVER_TYPE_HARDWARE,
            nullptr,                            // Software
            0,                                  // Flags
            nullptr,                            // pFeatureLevels
            0,                                  // FeatureLevels
            D3D11_SDK_VERSION,                  // SDKVersion
            &pDevice,                           // pDevice
            &featureLevel,                      // pFeatureLevel
            nullptr);                           // pImmediateContext

        // 检查是否成功创建设备,并返回ID3D11Device接口
        if (SUCCEEDED(hr)) {
            return gcnew ID3D11Device(pDevice);
        }
        return nullptr;
    }
};

上述C++/CLI代码创建了一个封装类 Direct3DHelper ,通过这个类,C#代码可以更加自然地使用C++封装的DirectX功能。

3.2 C#中管理DirectX资源

3.2.1 资源加载与释放策略

正确地加载和释放DirectX资源是确保游戏性能与稳定性的重要部分。在C#中,我们需要确保及时释放不再使用的资源,如纹理、缓冲区等。

ID3D11Texture2D* pTexture; // 假设这是一个Direct3D的纹理资源指针
// 在C#中,我们需要确保释放资源
public void ReleaseTexture(ID3D11Texture2D texture) {
    Marshal.ReleaseComObject(texture); // 释放COM资源
    texture = null; // 防止C# GC过早回收资源
}

3.2.2 错误处理与异常管理

错误处理是任何编程实践中的关键部分。在C#中,我们通常使用try-catch块来捕获和处理异常。在DirectX开发中,我们可以使用 HR 宏(假设的宏,用于简化错误处理)来检查函数调用的结果,并在出现错误时抛出异常。

try {
    int hr = DirectXAPI.SomeFunction(); // 调用DirectX API
    if (FAILED(hr)) { // 如果函数失败,则抛出异常
        throw new DirectXException(hr, "Function call failed.");
    }
}
catch (DirectXException ex) {
    // 在这里处理异常,例如记录日志、显示错误信息等
}

在上述代码中,我们使用了一个假设的 FAILED 宏来检查返回的 hr 值,并通过 DirectXException 自定义异常类抛出异常,从而使C#代码的异常处理更加符合.NET开发的标准。

通过本章节的介绍,我们已经详细探讨了如何在C#中调用DirectX接口,并管理DirectX资源。这些交互方法是构建高性能、可维护的游戏程序的基础。在下一章节中,我们将进一步深入了解3D模型的加载与解析技术,这是创建游戏世界和对象的重要步骤。

4. 3D模型加载与解析技术

4.1 3D模型数据结构

4.1.1 顶点缓冲和索引缓冲

在3D图形编程中,顶点缓冲(Vertex Buffer)和索引缓冲(Index Buffer)是核心概念,它们使得对3D模型数据的处理变得高效。

顶点缓冲用于存储模型的顶点数据,如顶点坐标、法线、纹理坐标和颜色等。通过顶点缓冲,可以一次性将顶点信息发送到显卡,减少CPU到GPU的数据传输,从而提升渲染效率。顶点缓冲对象(VBO)是在OpenGL中的术语,而在Direct3D中被称作顶点缓冲区(vertex buffers)。

索引缓冲用于存储顶点数据的索引,允许在绘制时复用顶点。这可以显著减少内存使用,特别是在处理具有大量三角形的复杂模型时。例如,一个正方形由4个顶点组成,但它需要由两个三角形构成,如果我们为这两个三角形分别存储4个顶点数据,则总共需要存储8个顶点。然而,通过索引缓冲,我们只需存储4个顶点,并在索引缓冲中引用这些顶点两次,大大提高了内存效率。

4.1.2 3D模型的格式和转换

3D模型可以通过多种文件格式进行存储,如OBJ, FBX, COLLADA等。每种格式具有不同的特点和用途,比如OBJ格式简单且广泛支持,而FBX格式提供了更加复杂的数据结构,如动画和材质信息。在游戏开发过程中,经常需要将这些不同格式的模型转换为适合游戏引擎使用的格式。

转换过程中,可能会用到如Blender、Maya等3D建模软件,或专门的3D模型转换工具。在C#中,可以使用如AssimpNet这样的库来读取多种3D模型格式,并导出为DirectX或OpenGL可接受的格式。使用这样的库可以将不同格式的3D模型数据转换为统一的数据结构,使得游戏引擎能够一致地处理不同的模型数据。

4.2 加载3D模型到Direct3D

4.2.1 使用XNA Content Pipeline加载模型

XNA Framework Content Pipeline是一个用于将3D模型数据和其他资源编译进游戏执行文件的强大工具。XNA使得开发者能够方便地加载和处理模型、纹理、声音等资源。

要使用XNA Content Pipeline加载模型,首先需要安装XNA Game Studio,并在项目中引用XNA相关组件。然后,将模型文件添加到Content项目中,并设置适当的构建动作,这使得内容被XNA处理并可在运行时被加载。加载模型可以通过ContentManager类实现,如下示例代码:

using (Stream stream = TitleContainer.OpenStream("ModelName.fbx"))
{
    Model model = Content.Load<Model>("ModelName");
    // 接下来可以将模型的各个部分添加到场景中
}

这段代码首先通过 TitleContainer.OpenStream 打开模型文件的流,然后使用 Content.Load 方法将模型加载到内存中。加载成功后,模型的各个部分就可以被添加到Direct3D渲染循环中。

4.2.2 动态加载和处理模型数据

动态加载3D模型是指在游戏运行时从外部资源文件中加载模型数据。动态加载通常用于需要按需加载资源的场景,比如一个地图编辑器游戏,或者大型开放世界游戏,其中的模型数据可能因为地图大小而无法全部加载到内存中。

在C#中,可以通过读取文件、解析模型数据,并手动创建模型的顶点和索引缓冲区,实现动态加载。对于支持的3D模型格式,通常需要实现一个模型解析器来提取顶点和索引数据,然后使用这些数据创建Direct3D的顶点缓冲和索引缓冲。以下是使用.NET的文件操作和流处理来实现动态加载模型的一个非常简化的例子:

using System.IO;
using SharpDX.Direct3D11;
using Buffer = SharpDX.Direct3D11.Buffer;

// 假设我们已经解析了模型数据,并得到以下格式的顶点和索引数组
VertexPositionNormalTexture[] vertices = ...;
int[] indices = ...;

// 创建顶点缓冲区
var vertexBufferDescription = new BufferDescription()
{
    Usage = ResourceUsage.Dynamic,
    SizeInBytes = Utilities.SizeOf<VertexPositionNormalTexture>() * vertices.Length,
    BindFlags = BindFlags.VertexBuffer,
    CpuAccessFlags = CpuAccessFlags.Write,
    OptionFlags = ResourceOptionFlags.None
};
using (var vertexBuffer = new Buffer(device, vertexBufferDescription))
{
    DataStream vertexStream = null;
    device.ImmediateContext.MapSubresource(vertexBuffer, MapMode.WriteDiscard, SharpDX.Direct3D11.MapFlags.None, out vertexStream);
    vertexStream.Write(vertices);
    device.ImmediateContext.UnmapSubresource(vertexBuffer, 0);

    // 创建索引缓冲区类似...
}

// 接下来就可以使用vertexBuffer和indices来渲染模型

请注意,上面的代码仅提供了加载模型数据到顶点缓冲区的概念,实际使用时需要完整的解析和加载逻辑。

在进行3D模型加载和解析时,需要特别注意数据结构的设计,以确保加载的模型与游戏引擎的渲染管线兼容。此外,性能优化往往也是加载模型时需要考虑的重点,比如尽量减少内存占用,以及减少模型加载时对游戏运行的影响。通过合理的加载和处理模型数据,可以显著提高游戏性能,并改善玩家的游戏体验。

5. 图形渲染技巧,包括着色器编写

在现代游戏开发中,图形渲染技术是实现高质量视觉效果的关键。通过Direct3D,开发者能够利用各种渲染技巧来增强游戏的图形表现力。本章节将深入探讨Direct3D渲染管线的高级应用,并介绍着色器的编写基础。

5.1 Direct3D渲染管线高级应用

5.1.1 着色器的类型与作用

Direct3D中的着色器是一种小程序,运行在GPU上,用于执行图形渲染过程中的各种计算。它们在渲染管线的不同阶段发挥作用,例如顶点着色器(Vertex Shader)、像素着色器(Pixel Shader)、几何着色器(Geometry Shader)和域着色器(Domain Shader)等。每种着色器类型都有其特定的用途,比如:

  • 顶点着色器 :处理每个顶点的坐标变换和光照计算。
  • 像素着色器 :决定每个像素的颜色值。
  • 几何着色器 :用于生成新的几何图形或改变顶点数据。
  • 域着色器 :与细分着色器配合使用,进行细分曲面的顶点位置和纹理坐标的计算。

5.1.2 光照、纹理映射和阴影效果

光照效果是实现真实感渲染的重要因素之一。Direct3D支持多种光照模型,包括漫反射、镜面反射和环境光等。开发者可以编写自定义的光照算法,或者使用高级光照技术如全局光照(Global Illumination)来提高场景的真实感。

纹理映射是将二维图像映射到三维模型表面的过程。通过顶点着色器和像素着色器的配合,可以在渲染过程中实现复杂的纹理效果,例如法线映射、位移映射和镜面反射贴图等。

阴影效果对于增强场景深度和复杂性也非常关键。阴影贴图(Shadow Mapping)是一种常用的实现方式。开发者可以通过顶点着色器计算阴影贴图,再通过像素着色器进行阴影测试来确定哪些区域是处于阴影中的。

5.2 着色器编程基础

5.2.1 HLSL语法和结构体

HLSL(High-Level Shader Language)是用于编写Direct3D着色器的高级语言,与C语言有很高的相似性。以下是一个简单的HLSL结构体示例:

struct VSInput
{
    float4 Position : POSITION;
    float3 Normal   : NORMAL;
};

struct PSInput
{
    float4 Position : POSITION;
    float3 Normal   : TEXCOORD0;
    float2 TexCoord : TEXCOORD1;
};

在这个例子中,定义了两个结构体: VSInput PSInput VSInput 描述了顶点着色器的输入数据,包括位置和法线; PSInput 描述了像素着色器的输入数据,包括位置、法线和纹理坐标。

5.2.2 着色器的编写和调试

编写一个基本的顶点着色器,可以按照以下步骤:

// 定义一个顶点着色器
VSInput VSMain(VSInput input)
{
    VSInput output;
    output.Position = mul(input.Position, WorldViewProjection);
    output.Normal = mul(input.Normal, WorldInverseTranspose);
    return output;
}

在这个顶点着色器中, WorldViewProjection 是一个矩阵,用于将顶点坐标从模型空间变换到裁剪空间。 WorldInverseTranspose 用于将法线向量从模型空间变换到世界空间,同时保持正交性。

编写完着色器代码后,使用Direct3D的设备对象(ID3D11Device)创建一个着色器实例,并编译该代码。如果编译失败,通常会收到编译器的错误信息,这有助于定位和解决问题。

调试着色器通常需要利用图形调试工具,如RenderDoc或Visual Studio的图形调试功能。这些工具允许开发者单步执行着色器代码,观察每个变量的状态和渲染管线的输出结果。

表格:HLSL基本语法元素

| 语法元素 | 描述 | |---------|------| | float4 | 四分量浮点向量 | | float3 | 三分量浮点向量 | | float | 单精度浮点数 | | int | 整数 | | bool | 布尔值 | | texture | 纹理资源 | | sampler | 纹理采样器 | | struct | 自定义结构体 |

着色器编程对于提升游戏图形质量至关重要,同时需要对图形管线有深入的理解。本章介绍了Direct3D渲染管线的高级应用和着色器编写的基础知识。下一章节将详细讨论如何管理3D模型的加载和解析技术,为游戏带来丰富多变的视觉体验。

6. 用户输入处理,实现交互

用户输入是游戏交互的核心,它允许玩家控制角色和游戏世界中的元素。在C#中结合DirectX处理用户输入,能够提供流畅和直观的游戏体验。本章将详细介绍如何管理输入设备,并实现玩家与游戏的互动逻辑。

6.1 输入设备管理

游戏的输入设备通常包括键盘、鼠标和游戏手柄等。了解如何管理这些设备并捕获玩家的输入至关重要。

6.1.1 键盘、鼠标事件处理

在C#中,可以使用.NET框架提供的 System.Windows.Forms System.Windows.Input 命名空间中的类来处理键盘和鼠标事件。例如,创建一个简单的键盘事件监听器可以通过继承 Form 类并重写 OnKeyDown 方法来实现。

using System;
using System.Windows.Forms;

public class MyForm : Form
{
    protected override void OnKeyDown(KeyEventArgs e)
    {
        base.OnKeyDown(e);
        Console.WriteLine("Key Pressed: " + e.KeyCode);
        // 根据按键执行游戏中的相应逻辑
    }
}

在DirectX中,通常会使用DirectInput来处理更复杂的输入情况,如游戏手柄的多按钮操作。

6.1.2 游戏手柄支持与配置

游戏手柄的管理较为复杂,因为手柄上有多个按钮、摇杆和触发器。DirectX提供了DirectInput接口,用于处理这些输入设备。首先需要初始化DirectInput,并创建设备对象来代表手柄。

using SharpDX.DirectInput;

public class GamePadManager
{
    private Device gamePad;
    private DirectInput directInput;

    public GamePadManager()
    {
        directInput = new DirectInput();
        // 初始化游戏手柄设备
        gamePad = new Device(directInput, DeviceType.Gamepad, DeviceInstance);
        // 设置协作级别和同步设备
        gamePad.SetCooperativeLevel(handle, CooperativeLevel.Nonexclusive | CooperativeLevel.Background);
        // 设置数据格式
        gamePad.SetDataFormat(new JoystickState());
        // 加载设备
        gamePad.Acquire();
    }
    public void Update()
    {
        // 读取设备状态
        gamePad.Poll();
        var state = gamePad.GetCurrentState<JoystickState>();
        // 处理按钮和摇杆输入
    }
}

在上述代码中, Update 方法应定期调用以同步手柄的状态,并根据输入更新游戏逻辑。

6.2 实现玩家交互逻辑

玩家交互逻辑是将输入事件转化为游戏内的响应行为。例如,玩家按下跳跃键时,角色应该跳起来;移动鼠标时,相机跟随移动等。

6.2.1 响应玩家输入

当检测到玩家的输入时,通常需要将这些事件映射到游戏世界中的行为。这可能涉及角色的移动、旋转相机视角、激活道具等。

public class PlayerController
{
    public void OnJump()
    {
        // 执行跳跃逻辑
    }

    public void OnMove(Vector3 direction)
    {
        // 根据方向移动角色
    }

    public void OnLook(Vector2 delta)
    {
        // 根据偏移旋转相机
    }
}

6.2.2 控制角色和环境互动

角色和环境的互动是通过玩家输入来控制角色与游戏世界中元素的交互,如推开箱子、打开门等。

public class GameWorld
{
    public void OnObjectInteraction(Player player)
    {
        foreach(var obj in worldObjects)
        {
            if(obj.Intersects(player)) 
            {
                obj.InteractWith(player);
                break;
            }
        }
    }
}

在这个简化的例子中,当玩家与世界中的某个对象发生交互时,我们将调用对象的 InteractWith 方法来处理这种交互逻辑。

通过上述章节的讨论,我们了解到在C#和DirectX环境下,管理输入设备和实现玩家交互逻辑的必要步骤和方法。理解这些基本原理和编码模式将帮助开发者创建更具吸引力和可玩性的游戏。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:C#与DirectX结合是游戏开发的重要技能。通过【C# DirectX小游戏】项目,初学者可以从***获得资源,学习如何使用C#和DirectX API创建简单游戏,深入理解游戏编程。本项目详细介绍Direct3D基础、C#与DirectX交互、3D模型加载、图形渲染、输入处理、音频处理、游戏循环、碰撞检测、性能优化以及状态管理等关键知识点,旨在提升实践能力和游戏开发流程理解。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值