【Unreal】Unreal蓝图,命名规范(资源+代码)学习-笔记三

目录

蓝图功能的简单总结

增强输入系统

推荐的资产命名规范

代码规范

类组织(Class Organization)

版权声明

命名规范

标准库的使用

范例格式

现代C++语言语法

强类型化枚举

命名空间

物理依赖

一般格式问题

API设计指导方针

平台特定代码 

目录结构 介绍

根目录

通用目录

引擎专有目录

游戏项目目录

解决方案目录

总结


1-4 蓝图基础,组件机制,创建一个发光的发射物_哔哩哔哩_bilibili

看了下蓝图。。。感觉没什么可记录的,就简单做下总结后直接进入下一部分了

蓝图功能的简单总结

  • 蓝图类都可以添加组件
  • 发光原理: 小于1为只改颜色,超过1会发光
  • 可再触发延迟节点:计时器重新计时 
  • UI:右键->用户界面->控件蓝图->用户控件
  • 打包基本设置
    • 环境安装尤其是目标平台的SDK+面向Unreal Engine的IDE支持,最好放在系统盘(一般电脑的系统盘是C盘,但是本人的系统盘是J盘,由于重做过系统又懒得改)

增强输入系统

2-5 让角色动起来,增强输入系统_哔哩哔哩_bilibili

  • 创建输入映射情境:特定输入映射的一个集合
  • 创建输入操作 
  • 在输入映射情境中添加映射事件
  •  x轴代表前后,y轴代表左右,z轴代表上下
  • 创建游戏模式基础与玩家控制器蓝图类
  • 在场景中添加角色蓝图后
  • 修改世界场景设置 
  •  在玩家控制器蓝图中进行修改 
  •  设置结束后运行,按下“W”键屏幕输出“Hello”

至此Unreal Engine 5 的入门知识就结束了。直接开始看各种规范。

推荐的资产命名规范

https://docs.unrealengine.com/5.3/zh-CN/recommended-asset-naming-conventions-in-unreal-engine-projects/

  • [AssetTypePrefix]_[AssetName]_[Descriptor]_[OptionalVariantLetterOrNumber]
    • AssetTypePrefix 将表明资产的类型
    • AssetName 是资产的名称
    • Descriptor 用法。例如,纹理是正常贴图还是不透明贴图
    • OptionalVariantLetterOrNumber 是可选的,用于区分资产的多个版本或变体
资产英文前缀简单解释
通用General
HDRIHDRIHDR_一种背景的类型
材质MaterialM_
材质实例Material InstanceMI_
物理资产Physics AssetPHYS_骨架网格体使用的物理和碰撞
物理材质Physics MaterialPM_
后期处理材质Post Process MaterialPPM_
骨骼网格体Skeletal MeshSK_
静态网格体Static MeshSM_
纹理TextureT_
OCIO配置文件OCIO ProfileOCIO_颜色管理
蓝图Blueprints
Actor组件Actor ComponentAC_
动画蓝图Animation BlueprintABP_
蓝图接口Blueprint InterfaceBI_
蓝图BlueprintBP_
曲线表Curve TableCT_
数据表Data TableDT_
枚举EnumE_
结构StrcutureF_
控件蓝图Widget BlueprintWBP_
粒子效果Particle Effects
Niagara发射器Niagara EmitterFXE_
Niagara系统Niagara SystemFXS_
Niagara函数Niagara FunctionFXF_
骨骼网格体动画Skeletal Mesh Animations
绑定RigRig_
骨架SkeletonSKEL_
蒙太奇MontagesAM_

将动画合并至一个资产并通过蓝图控制播放

动画序列Animation SequenceAS_
混合空间Blend SpaceBS_
ICVFXICVFX摄像机内视效
NDisplay配置NDisplay ConfigurationNDC_
动画Animation
关卡序列帧Level SequenceLS_
Sequencer编辑Sequencer EditsEDIT_
媒体Media
媒体源Media SourceMS_
媒体输出Media OutputMO_
媒体播放器Media PlayerMP_
媒体配置文件Media ProfileMPR_
其他Other
关卡快照Level SnapshotsSNAP_

保存和恢复关卡Actor的特定布局

远程控制预设Remote Control PresetRCP_

在网页客户端远程操作虚幻引擎项目

代码规范

类组织(Class Organization)

通常类均是由读取者进行排列,而非写入者。多数读取者会使用类的公共接口,因此首先需要对此进行声明,之后再声明类的私有实现。

    UCLASS()

    class EXAMPLEPROJECT_API AExampleActor : public AActor
    {
        GENERATED_BODY()

    public: 
        // Sets default values for this actor's properties
        AExampleActor();

    protected:

              // Called when the game starts or when spawned
        virtual void BeginPlay() override;

    };

版权声明

由Epic提供用于分配的源文件(.h、.cpp、.xaml、etc.)必须在文件的首行包含版权声明。版权声明的格式必须严格按照以下形式编写:

// Copyright Epic Games, Inc. All Rights Reserved.

若此行缺失或格式错误,CIS将生成错误或失败提示。 

命名规范

  • 命名(如类型或变量)中的每个单词需大写首字母,单词间通常无下划线。例如:Health 和 UPrimitiveComponent,而非 lastMouseCoordinates 或 delta_coordinates
  • 类型名前缀需使用额外的大写字母,用于区分其和变量命名。例如:FSkin 为类型名,而 Skin 则是 FSkin 的实例
    • 模板类的前缀为T
    • 继承自 UObject 的类前缀为U
    • 继承自 AActor 的类前缀为A
    • 继承自 SWidget 的类前缀为S
    • 抽象界面类的前缀为I
    • Epic提供的概念类型的类(用作 TModels 的第一个参数),其前缀为C
    • 枚举的前缀为E
    • 布尔变量必须以b为前缀(例如 bPendingDestruction 或 bHasFadedIn
    • 其他多数类均以F为前缀,而部分子系统则以其他字母为前缀
    • Typedefs应以任何与其类型相符的字母为前缀:若为结构体的Typedefs,则使用F;若为 Uobject 的Typedefs,则使用U,以此类推
      • 特别模板实例化的Typedef不再是模板,并应加上相应前缀,例如:
         typedef TArray<FMytype> FArrayOfMyTypes;
      • C#中省略前缀
      • 多数情况下,UnrealHeaderTool(虚幻头文件分析工具)需要正确的前缀,因此添加前缀至关重要
  • 类型模板参数和基于这些模板参数的嵌套类型别名不受上述前缀规则的约束,因为类型的类别是未知的
    • 一般在描述性术语后添加一个Type后缀
    • 通过使用In前缀,将模板参数与别名区分开来
      template <typename InElementType>
          class TContainer
          {
          public:
              using ElementType = InElementType;
          };
    • 宏应该全部大写,用下划线隔开,并以 UE_ 作为前缀
  • 所有返回布尔的函数应发起true/false的询问,如`IsVisible()或`ShouldClearBuffer()
  • 建议在下列情况使用"Out"做为函数参数名的前缀
    • 函数参数通过引用传递
    • 函数应该写入该值(The function is expected to write to that value)
    • 此操作将明确表明传入该参数的值将被函数替换
  • 如果 In 或 Out 的参数也是布尔,以b作为In/Out的前缀,如 bOutResult
  • 返回值的函数应描述返回的值.命名应说明函数将返回的值。此规则对布尔函数极为重要
        // True的意义是什么?
        bool CheckTea(FTea Tea);
    
        // 命名明确说明茶是新鲜的
        bool IsTeaFresh(FTea Tea);
  • 示例
        float TeaWeight;
        int32 TeaCount;
        bool bDoesTeaStink;
        FName TeaName;
        FString TeaFriendlyName;
        UClass* TeaClass;
        USoundCue* TeaSound;
        UTexture* TeaTexture;

标准库的使用

Unreal 官方文档

https://docs.unrealengine.com/5.3/zh-CN/epic-cplusplus-coding-standard-for-unreal-engine/

微软C++标准库

C++ 标准库头文件 | Microsoft Learn

<atomic> :

  • 是一种多线程编程中常用的同步机制,它能够确保对共享变量的操作在执行时不会被其他线程的操作干扰,从而避免竞态条件(race condition)和死锁(deadlock)等问题。

<type_traits> :

  • 定义了编译时常量的模板,这些常量可提供有关其类型参数的属性的信息或生成转换类型。
  • 提供一种用来处理type属性的方法。它是个template,可在编译期根据一个或多个template实参产出一个type或value

https://www.baidu.com/link?url=cuDGz32w5mHWMMkDnL5mIulZepbsYwVxlPuFXXOz-SB4UpK-NQstn7d_77O7VeeV5ZmubPM9VXYjLqjkNgMHd_&wd=&eqid=c9284c480002219d000000036513f9c8

<initializer_list> :

  • 提供访问元素数组的权限,其中数组的每个成员均具有指定的类型

<regex> :

  • 这是一种标准化的方式来表达要与字符序列匹配的模式 
  • 定义一个类模板来分析正则表达式 (C++),以及定义多个类模板和函数以在文本中搜索正则表达式对象的匹配项

C++ 标准库 - <regex>

<limits> : 

  • 它是一种数值限制类型,它提供有关库编译的特定平台中算术类型(整数或浮点)属性的信息

<cmath> :

  • cmath是c++语言中的标准库头文件
  • 包含标准 C 库标头 <math.h> 并将关联名称添加到 std 命名空间

<cstring> :

  • 包含标准 C 库标头 <string.h> 并将关联名称添加到 std 命名空间

范例格式

记住:注释应扩展代码。代码记录实现,而注释说明意图。修改代码的部分目的后应及时更新注释。

类注释应包括

  • 此类解决的问题的描述
  • 创建此类的原因

现代C++语言语法

  • 所用情况下均使用 nullptr,而非C-style NULL 宏
  • 此情况有一例外:C++/CX版本(如Xbox One)中的 nullptr 实际上为已管理的空引用类型。其主要与原声C++中的 nullptr 兼容(与其类型相同的和部分模板实例化情景除外),因此应使用 TYPE_OF_NULLPTR 宏进行兼容,而非更为常用的 decltype(nullptr)
  • Auto
    • 不应在C++代码中使用 auto 模式,但以下范例除外
      • 当需要将 lambda 绑定到变量时,以为 lambda类型无法用代码表示
      • 对于迭代器变量,但仅适用于迭代器类型冗长且会损害可读性的情况
      • 无法清楚识别表达式的模板代码中适用。此为高阶情况
  • 匿名函数
    • 注意:常用的状态性匿名函数无法指定至函数指针
    • 非浅显匿名函数应使用正则函数相同的方式进行记载。建议将其分为数行,以便添加注释

强类型化枚举

  • 枚举类是旧式命名空间枚举的替代品,无论是普通枚举还是 UENUMs。
        // 旧枚举
        UENUM()
        namespace EThing
        {
            enum Type
            {
                Thing1,
                Thing2
            };
        }
    
        // 新枚举
        UENUM()
        enum class EThing : uint8
        {
            Thing1,
            Thing2
        }

命名空间

  • 大多数UE代码目前尚未包裹在整体命名空间中
    • 在整体作用域中避免冲突,尤其是在使用或包含第三方代码时
  • UnrealHeaderTool不支持命名空间
    • 在定义uclass、ustruct等时不应该使用命名空间
  • 不是uclass、ustruct等的新api应该放到UE::命名空间中,最好是嵌套命名空间,例如UE::Audio::。
  • using 声明
    • 不要将 using声明放在全局作用域中,即使是在.cpp文件中(会导致我们的“unity”构建系统出现问题)
    • 可将using放入其他命名空间或函数体中 
    • 若将using声明放入命名空间,其会带入同一翻译单元中的其他命名空间。只需保持一致即可
  • 注意:前置声明需要在各自命名空间中进行声明,否则将造成连接问题
  • 若在命名空间中声明过多类/类型,将导致在其他整体作用中使用这些类时出现困难
  • 可以使用using声明将命名空间中的特定变量仅别名到作用域中,例如:Foo::FBar。但在虚幻代码中并不常用
  • 宏无法存在于命名空间中,但应使用 UE_ 作为前缀,例如 UE_LOG

物理依赖

  • 文件名应尽量不添加前缀。例如使用 Scene.cpp ,而非 UnScene.cpp 。此操作可通过减少用于标识文件的字母数,实现在解决方案中使用工作区Whiz或Visual Assist的打开文件等工具
  • 设置指令后所有头文件应防止使用含有 #pragma once 等多种格式。注意:需要使用的编译器现在均支持使用 #pragma once。
  • 通常需要最小化物理耦合。特别是,避免包含来自其他头文件的标准库头文件
  • 若可使用前置声明,而非头文件,请使用前置声明
  •  including a header 时尽量细粒化。例如,勿包含Core.h,而在核心中包含需要定义的特定头文件
  • 直接包含全部所需头文件,以便进行细粒化包含
  • 请勿依赖被包含的其他头文件间接包含的头文件
  • 不要依赖通过另一个标题包含的任何内容。包括你需要的一切
  • 其他模块需要的任何定义都必须在公共目录的头中。其他的都应该在私有目录里。在较旧的Unreal模块中,这些目录可能仍被称为“Src”和“Inc ”,但这些目录旨在以相同的方式将私有代码和公共代码分开,而不是将头文件与源文件分开
  • 不要担心为预编译头文件生成设置头文件(precompiled header generation),UnrealBuildTool在这方面比你做得更好
  • 将大功能拆分成逻辑子功能
    • 编译器优化的一个方面是消除公共子表达式。你的函数越大,编译器识别它们的工作就越多。这导致构建时间大大增加
  • 不要使用大量的内联函数
    • 内联函数会强制在不使用其的文件中强行编译,因此勿使用过多内联函数。内联函数仅可在浅显访问器中和分析显示有益时使用。

  • 使用FORCEINLINE时要谨慎

    • 所有代码和局部变量都将扩展到调用函数中。这将导致与大型函数相同的构建时问题。

一般格式问题

  • 最小化依赖距离
    • 当代码依赖于一个具有特定值的变量时,在使用它之前,设置该变量的值。在执行块的顶部初始化一个变量,并且不在一百行代码中使用它,会给某些人提供大量的空间来意外地更改值,而没有意识到依赖关系。将它放在下一行可以清楚地说明为什么要以这种方式初始化变量,以及在哪里使用它。
  • 尽可能将方法分成子方法
    • 纵观全局,再深入查看感兴趣的细节,而不会以细节入手,最后重构全局。此类操作更加容易。相较于包含子方法全部代码的同等方法,若以此方法,理解调用多个命名优良的子方法序列的简易方法会更为容易。
  • 在函数声明或函数调用点中,不要在函数名和参数列表前面的括号之间添加空格。
  • 修复编译器警告
    • 出现编译器警告消息意味着某些项目出错。应修复编译器警告的内容。如无法修复,使用 #pragma 压制警告,此为最后补救办法
  • 在文件末尾留下空白行。所有.cpp和.h文件应包含空白行,以便和gcc兼容
  • 调试代码需为有用并经过优化,或为已迁入。与其他代码相互混杂的调试代码将导致读取其他代码时出现困难。

  • 在字符串文字周围固定使用 TEXT() 宏。若未使用,在文字中构建 FStrings 的代码将导致不理想的字符转换过程

  • 避免循环相同的多余运算

    • 将公共子表达式移出循环,以避免冗余计算。在某些情况下使用静态,以避免跨函数调用的全局冗余操作,例如从字符串文字构造FName。

  • 注意热重载

    • 最小化依赖性来减少迭代时间。无使用可能会在重载时发生改变的函数内联或模板。仅对在重载时保持不变的对象使用静态

  • 使用中间变量简化复杂的表达式

    • 如果您有一个复杂的表达式,将其拆分为指定至中间变量的子表达式将更易理解(该子表达式的的命名描述了其在父表达式中的意义)

    • 例如:

      if ((Blah->BlahP->WindowExists->Etc && Stuff) &&
              !(bPlayerExists && bGameStarted && bPlayerStillHasPawn &&
              IsTuesday())))
          {
              DoSomething();
          }

                替换成 

    const bool bIsLegalWindow = Blah->BlahP->WindowExists->Etc && Stuff;
    const bool bIsPlayerDead = bPlayerExists && bGameStarted && bPlayerStillHasPawn && IsTuesday();
    if (bIsLegalWindow && !bIsPlayerDead)
    {
        DoSomething();
    }
  • 指针与引用应仅含一个空格,该空格位于指针/引用右侧
    FShaderType* Ptr
  •  不允许隐藏变量
    • C++可在外部作用域隐藏变量,但此操作会导致语意不清。例如,在这个成员函数中有三个可用的Count变量
      class FSomeClass
              {
              public:
                  void Func(const int32 Count)
                  {
                      for (int32 Count = 0; Count != 10; ++Count)
                      {
                          // Use Count
                      }
                  }
      
              private:
                  int32 Count;
              }
  •  避免在函数调用中使用匿名文字。建议使用描述其含义的命名常量
        // 旧样式
        Trigger(TEXT("Soldier"), 5, true);.
    
        // 新样式
        const FName ObjectName                = TEXT("Soldier");
        const float CooldownInSeconds         = 5;
        const bool bVulnerableDuringCooldown  = true;
        Trigger(ObjectName, CooldownInSeconds, bVulnerableDuringCooldown);

    由于无需查找函数声明即可理解目的,因此此操作可协助普通读者快速理解 

  •  请避免在头文件中定义无关重要的静态变量,因为这会导致在每一个包含该头文件的转译单元中编译出一个实例

API设计指导方针

  • 应该避免布尔函数参数,特别是,对于传递给函数的标志,应该避免使用布尔参数。其也拥有与前文提到匿名文字问题,但API利用更多行为扩展时,此类问题将成倍增加。相反,应优先使用枚举:
        // 旧样式
        FCup* MakeCupOfTea(FTea* Tea, bool bAddSugar = false, bool bAddMilk = false, bool bAddHoney = false, bool bAddLemon = false);
        FCup* Cup = MakeCupOfTea(Tea, false, true, true);
    
        // 新样式
        enum class ETeaFlags
        {
            None,
            Milk  = 0x01,
            Sugar = 0x02,
            Honey = 0x04,
            Lemon = 0x08
        };
        ENUM_CLASS_FLAGS(ETeaFlags)
    
        FCup* MakeCupOfTea(FTea* Tea, ETeaFlags Flags = ETeaFlags::None);
        FCup* Cup = MakeCupOfTea(Tea, ETeaFlags::Milk | ETeaFlags::Honey);
    • 此类形式可防止意外置换标签,避免意外在指针和整数参数中转换,而无需重复多余默认值,从而变得更加高效。 
  • 避免过长的函数参数列表。如函数使用多个参数,则选择传递专属结构体
  • 避免使用 bool 和 FString 重载函数,此操作可能导致意外行为
  • 接口类(前缀为"I")固定为抽象,切不可拥有成员变量。只要接口为内联实现,其可包含非纯虚拟方法,甚至非虚拟或静态方法
  • 在声明重写方法时,使用 virtual 和 override 关键字
    • 当在派生类中声明重写父类中的虚函数时,必须同时使用 virtual override 关键字
          class A
          {
          public:
              virtual void F() {}
          };
      
          class B : public A
          {
          public:
              virtual void F() override;
          }

由于最近添加了 override 关键字,许多现有代码还没有遵循这一点。应该在方便时将override 关键字添加到代码中。 

平台特定代码 

  • 应固定抽取平台特定代码,并在正确命名的子目录中平台特定源文件内实现,例如:
    Engine/Platforms/[PLATFORM]/Source/Runtime/Core/Private/[PLATFORM]PlatformMemory.cpp
  •  通常应避免在名为 [PLATFORM] 的目录外,将 PLATFORM_[PLATFORM] 的使用(例如 PLATFORM_XBOXONE)添加到代码。相反,应扩展硬件抽象层,以添加静态函数,例如在FPlatformMisc中
        FORCEINLINE static int32 GetMaxPathLength()
        {
            return 128;
        }
  • 之后,平台可覆盖此函数,返回平台特定常量值,或使用平台API决定结果 
  • 如强制内联该函数,其将具有和使用定义时同样的性能特征
  • 在绝对需要定义的情况下,应新建描述可应用到函数的特定属性的#define,例如 PLATFORM_USE_PTHREADS。在Platform.h中设置默认值,并覆盖平台(平台特定Platform.h文件中需要)的默认值,例如,在Platform.h中有:
        #ifndef PLATFORM_USE_PTHREADS
            #define PLATFORM_USE_PTHREADS 1
        #endif

        Windows/WindowsPlatform.h有: 

    #define PLATFORM_USE_PTHREADS 0
  • 跨平台代码可直接使用该定义,无需知晓平台。
    #if PLATFORM_USE_PTHREADS
        #include "HAL/PThreadRunnableThread.h"
    #endif
  •  理由:将引擎的平台特定细节集中化,平台特定源文件中可完全包含此类细节。此操作可便于维护多个平台的引擎,还可将代码移植到新平台,而无需在基本代码中查找平台特定定义。
  • PS4、XboxOne和Nintendo Switch等NDA平台要求将平台代码保存在平台特定文件夹中
  • 无论 [PLATFORM] 子目录是否显示,应确保编译并运行代码。换言之,跨平台代码不可依赖平台特定代码

目录结构 介绍

文档中还是UE4的目录结构,但应该都是大差不差的目录结构

根目录

  • Engine - 包含构成引擎的所有源代码、内容等。
  • Templates - 创建新项目时可用的项目模板集合。
  • GenerateProjectFiles.bat - 用于创建在Visual Studio中使用引擎和游戏所需的UE4解决方案和项目文件。请参阅IDE的项目文件以了解详细信息。
  • UE4Games.uprojectdirs - 辅助文件,帮助引擎找到子目录中的项目。

通用目录

  • Binaries - 包含可执行文件或编译期间创建的其他文件
  • Build - 包含编译引擎或游戏所需的文件,包括为某些特定平台创建项目版本时所需的文件
  • Config - 配置文件,包含的参数可用于控制引擎的行为。你在游戏项目Config文件中设置的值会覆盖 Engine\Config 目录中设置的值
  • Content - 保存引擎或游戏中的内容,例如资产包、贴图
  • DerivedDataCache - 包含派生数据文件。这类数据专为被引用内容生成,并且在加载时生成。假如被引用内容未生成过缓存文件,则加载时间会显著增加。

  • Intermediate - 包含编译引擎或游戏时生成的临时文件。在游戏目录中,着色器也保存在Intermediate目录中

  • Saved - 包含自动保存文件、配置(.ini)文件和日志文件。此外,Engine > Saved 目录还包含崩溃日志、硬件信息和Swarm选项与数据

  • Source - 包含引擎或游戏的所有源文件,包括引擎源代码、工具和游戏类等

    • Engine - Engine目录中的源文件组织结构如下

      • Developer - 编辑器和引擎共同使用的文件

      • Editor - 仅供编辑器使用的文件

      • Programs - 引擎或编辑器使用的外部工具

      • Runtime - 仅供引擎使用的文件

    • Game - 游戏项目目录中的源文件按模块分组,一个模块一个目录。每个模块包含以下内容

      • Classes - 包含所有的头文件(.h

      • Private - 包含所有 .cpp 文件,包括游戏逻辑类以及各种模块的实现文件

      • Public - 包含模块的头文件

引擎专有目录

  • Documentation - 包含引擎文档,包括源文件和发布的文件
    • HTML - 发布的HTML文档文件
    • Source - 源markdown文档文件
  • Extras - 其他辅助和工具文件
  • Plugins - 包含引擎中使用的插件
  • Programs - 包含UE根目录中各个项目及其他虚幻程序(如UnrealFrontend和UnrealHeaderTool)的配置文件和日志文件
  • Shaders - 保存引擎的着色器源文件(.usf

游戏项目目录

目录(Directory)说明(Description)
Binaries包含可执行文件或编译期间创建的其他文件
Config游戏的默认项目设置
Content包含引擎或游戏的内容,包括资产包和贴图
External dependencies显示公有的引擎头文件(仅在Visual Studio中可见)
Intermediate包含UnrealBuildTool生成的文件,如Visual Studio项目文件。这些文件可以删除并重新构建
Saved包含引擎生成的文件,如配置文件和日志。这些文件可以删除并重新构建
Source包含游戏模块对象类文件

解决方案目录

目录(Directory)说明(Description)
Classes包含游戏对象的类定义(.h 文件)
Config游戏的默认项目设置
External dependencies显示公有引擎头文件(仅在Visual Studio中可见)
Private包含私有游戏对象类的实现文件(.cpp 文件)
Public包含公有游戏对象类的实现文件(.cpp 文件)

总结

以上也不是说一下子就能全部都记住的,还是需要多做东西,每次做的时候就按照规范来,孰能生巧,谁也不能一上来就吃个胖子。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值