2019-12-27 14:59:18 qq_31788759 阅读数 634

整理自B站视频[UE4]5个必备的蓝图编辑器工具 插件,改善工作流程,减少时间(大部分免费都在库里)

Node Graph Assistant——虚幻4蓝图助手

功能超多超强大的蓝图助手,可参考制作者的Github使用说明,本文中唯一官方商城收费的插件,但也可在作者Github上免费下载。

现将本人认为好用的功能记录如下,制作者的Github内附动图更容易理解:

  • Alt+R:自动排列小数量节点布局
  • Alt+C:连接当前选中节点所有可连的引脚
  • Alt+X:溶解节点,若按住Shift会保留被溶解的节点
  • Alt+MM:断开滑动经过的线
  • Alt+T:交换连线
  • Shift+LM:点击或拖动引脚来复制连线
  • 快速甩动节点:断开
  • 在线创建节点:右键线插入到连接线左右节点之间
  • 选择相关节点:中键双击选择连接着的节点,如果双击位置在节点左边则选择输入连接的节点,反之选择输出连接的节点
  • 连线样式:点击工具栏图标切换连线样式

Auto Size Comments Plugin——注释框大小自动调节

Property Transfer Tool——变量批量转移到其他蓝图

Magic Node——蓝图中编写C++节点

Graph Formatter——节点自动对齐排列不交叉

2016-11-04 10:45:07 qq_20309931 阅读数 1264

Unreal Engine 4 C++ AI 粗略探究

好记性不如烂笔头啊,还是记录一下!


在Unreal Engine 4的AI在C++中有两种实现方式

  • 用AI控制器(AIController)执行已经创建的行为树(BehaviorTree)
  • 用AI控制器(AIController)执行自己写的行为逻辑

这两种方式都可是实现强大的逻辑,第一种方式是用蓝图创建行为树(BehaviorTree)编译完成后用C++来执行,第二种方式就是纯C++写行为模式了。


1.行为树(BehaviorTree)

行为树主要包含以下三个方面:

  1. AIController    AI的行动控制者
  2. BehaviorTree  AI的行动逻辑
  3. Blackboard    AI的记忆数据

运行行为树需要添加AIModule的依赖

PublicDependencyModuleNames.AddRange(
    new string[] {
        "Core",
        "CoreUObject",
        "Engine",
        "InputCore",
        "AIModule"
    }
);

运行行为树的代码为


MyAIController->RunBehaviorTree(BehaviorTree);

即可运行行为树
关于行为树的构建


2.AI控制器(AIController)

用AIController来写AI,可以实现最强大的功能,因为全部可以用C++实现

一样要在工程里添加AIModule的依赖

PublicDependencyModuleNames.AddRange(
    new string[] {
        "Core",
        "CoreUObject",
        "Engine",
        "InputCore",
        "AIModule"
    }
);

简单的实现个走动的AI

  • MyAIController.h

#include "AIController.h"
#include "Navigation/PathFollowingComponent.h"
#include "Actions/PawnAction_Move.h"
#include "MyAIController.generated.h"

/**
 * 
 */
UCLASS()
class TESTMOBILE_API AMyAIController : public AAIController
{
    GENERATED_BODY()

    // 设置基本属性的构造函数
    AMyAIController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

public:
    // 开始控制角色的函数
    virtual void Possess(APawn* inPawn) override;
    virtual void UnPossess() override;
    // 结束控制角色的函数

    /** Action Event */
    void OnActionEvent(UPawnAction&, EPawnActionEventType::Type);
};
  • MyAIController.cpp

#include "MyCharacter.h"
#include "MyAIController.h"


// 初始化设置
AMyAIController::AMyAIController(const FObjectInitializer & ObjectInitializer)
    : Super(ObjectInitializer)
{
    BrainComponent = CreateDefaultSubobject<UBrainComponent>(TEXT("BrainComponent"));

}


// 开始控制
void AMyAIController::Possess(APawn * inPawn)
{
    Super::Possess(inPawn);

    GetActionsComp()->SetControlledPawn(inPawn);
    BrainComponent->RegisterComponent();

    FVector NewLocation = FVector(0.0f);
    FVector OffsetLocation = FVector(900.0f, 0.0f, 0.0f);

    AMyCharacter* const MyCharacter = Cast<AMyCharacter>(GetPawn());
    if (MyCharacter != NULL && MyCharacter->GetCharacterMovement() != NULL)
    {
        MyCharacter->GetCharacterMovement()->SetMovementMode(MOVE_Walking);
        MyCharacter->SetCharacterState(CharacterState::Walk);
        FVector OldLocation = MyCharacter->GetActorLocation();
        NewLocation = OldLocation + OffsetLocation;
    }

    UPawnAction* Action = UPawnAction_Move::CreateAction(*GetWorld(), NewLocation, EPawnActionMoveMode::Type::StraightLine);
    Action->SetActionObserver(FPawnActionEventDelegate::CreateUObject(this, &AMyAIController::OnActionEvent));
    this->PerformAction(*Action, EAIRequestPriority::Logic);
}


// 结束控制
void AMyAIController::UnPossess()
{    
    Super::UnPossess();
}


// 动作事件
void AMyAIController::OnActionEvent(UPawnAction& Action, EPawnActionEventType::Type EventType)
{
    if ( EPawnActionEventType::Type::FinishedExecution == EventType )
    {
        AMyCharacter* const MyCharacter = Cast<AMyCharacter>(GetPawn());
        if (MyCharacter != NULL)
        {
            MyCharacter->SetCharacterState(CharacterState::Idle);
        }
    }
}

上面的代码主要需要注意的是,
一定要设置ActionComponent的控制的角色,PawnAction的处理都在ActionComponent中。
以及一定要注册BrainComponent,AI的消息处理都在BrainComponent中。

    GetActionsComp()->SetControlledPawn(inPawn);
    BrainComponent->RegisterComponent();

上面的代码完成后会达成如下效果:

移动成功

2017-03-22 23:59:19 shidya 阅读数 1046

PDF文件:

Unreal+Engine+4+AI+Programming+Essential....pdf

Kindle 电子书文件:

Unreal+Engine+4+AI+Programming+Essential....rar


VR热门内容:

实时演算:

根据已知设备 头盔,两个手柄。 外加玩家的几个测量参数 实时演算 玩家手,手臂,手肘,动作。

如可以增加一些外设,如鞋子,腰带。可对玩家的四肢,腰部进行准确的实时演算。

由此可以对游戏角色进行准确控制。

真实的材质模型 + AI:

不用说,模拟的就是人类。

VR网络多人:

与人沟通很重要。

2017-05-02 17:34:00 weixin_34122548 阅读数 201


q1

前 言

Unreal Engine 4蓝图可视化编程
游戏引擎(例如虚幻引擎4)作为强大的商业游戏的制作工具,越来越受传统游戏工作室以外的新老游戏开发者所欢迎。虚幻引擎为过去10年中发布的许多最受欢迎的控制台和PC游戏提供了动力,最新版本的虚幻引擎尽可能地包含了开发者所需的工具。这些工具中最具变革性的是蓝图可视化编程系统,其允许非专业程序人员创建和实现游戏机制、用户界面(User Interface,UI)和交互。

本书采用分步方法,指导读者使用可视化的蓝图节点构成蓝图行为,并将它们链接在一起以创建游戏机制、UI等。在这个过程中,读者将学习所有使用蓝图在虚幻引擎4中开发游戏的必要技能。

我们从基础的第一人称射击模板开始,每个章节将扩展原型,以创建一个越来越复杂和稳定的游戏体验。从创造基本的射击机制逐渐过渡到更复杂的系统,将生成用户界面和智能敌人行为。学完这本书时,你将完成一个功能齐全的第一人称射击游戏,在游戏开发过程中学会一些必要的技能。

目 录

第1章 使用蓝图进行对象交互
1.1 创建项目和关卡
1.2 为关卡添加对象
1.3 材质
1.4 创建第一个蓝图
1.5 制作移动标靶
1.6 改变目标方向
1.7 小结
第2章 升级玩家的技能
2.1 通过扩展蓝图添加加速技能
2.2 制作瞄准镜效果
2.3 添加音效和粒子效果
2.4 小结
第3章 创建屏幕UI元素
第4章 创建约束和游戏性对象
第5章 使用AI制作移动的敌人
第6章 升级AI敌人
第7章 跟踪游戏状态完成游戏体验
第8章 打包与发行

2018-06-14 16:10:44 kmyhy 阅读数 34315

原文:Unreal Engine 4 Tutorial for Beginners: Getting Started
作者:Tommy Tran
译者:kmyhy

Unreal Engine 4 是一个游戏开发工具集,能够开发从 2D 手机游戏到 3A 级主机游戏的一切。“方舟:生存进化”、“泰克肯7”和“王国之心 III”这些游戏背后使用的引擎就是它。

对于初学者来首,用 Unreal Engine 4 开发是很简单的。通过蓝图可视化脚本系统,你可以不写一行代码就创建出整个游戏。再加上一个易于使用的界面,你就可以获得一个可以运行的游戏原型。

本教程主要是让初学者入门。它涉及以下几个知识点:

  • 安装引擎
  • 导入资源
  • 创建材质
  • 用蓝图(Blueprint)创建具有基本功能的对象

为了学习这些知识,你会创建一个用于展示一只香蕉的旋转转盘。

注:本教程属于 Unreal Engine 教程系列,这个系列共 10 个部分:

安装 Unreal Engin 4

Unreal Engine 4 使用 Epic Games Launcher 进行安装。进入 Unreal Engine 网站 并点击 Get Unreal 按钮(右上角)。

下载这个安装器需要创建一个账号。创建账号之后,选择下载和你的操作系统对应的版本。

下载并安装完安装器之后,运行安装器。会显示一个窗口:

输入 email 和密码,点击 sign in。登录之后会显示这个窗口:

在左上角,点击 Install Engine,安装器会让你选择要安装的组件:

注:Epic Games 经常升级 Unreal Engine,因此你的引擎版本会与此不同。例如我写第一稿时,版本已经升级到 4.14.3! 只要你的版本不低于 4.14, 你就可以使用本教程。

默认选中的是 Starter Content、Templates and Feature Packs 和 Engine Source。就保持这样的选择不变。它们分别是:

  • Starter Content: 这是你可以在自己项目中免费使用的资源集。它包含了一些模型和材质。你可以在你的游戏中临时使用它们。
  • Templates and Feature Packs: 模板可以根据你的选择来创建基本功能。例如,Side Scroller 模板会创建一个项目,项目中包含一个角色、基本动作和一个固定的水平相机。
  • Engine Source: Epic 提供了源码,这样任何人都可以修改这个引擎。例如,如果你想为编辑器添加自定义功能,你可以通过修改源码来实现。

拖动这个列表,还有几种不同的平台。如果你不打算支持某个平台,请让它反选。

选好组件之后,点击 Install。安装完成后,引擎会在 library 中显示。接下来我们创建项目。

创建项目

点击 Launch 按钮打开项目浏览器。在项目浏览器中,点击 New Project 标签页。

点击 Blueprint 标签页。这里,你可以选择一个模板。但是,因为我们想从空白开始,所以选择了 Blank template。

在下面,你会发现有更多的设置。

分别有这几个选项:

  • Target Hardware: 选择 Mobile/Tablet 会关闭一些后期处理效果。还会用鼠标模拟触摸。设置为 Desktop/Console。
  • Graphical Target: 选择 Scalable 3D or 2D 会关闭一些后期处理特效。请设置为 Maximum Quality。
  • Starter Content: 使用这个选项可以包含一些开始内容。为了简单起见,这里将它设为 No Starter Content。

最后一部分设置是指定项目文件夹地址和项目名称。

点击 Folder 栏右边的 3 个小点,可以改变项目文件夹地址。

项目名称不代表游戏名称,因此不用担心后期可以修改这个名称。选择 Name 栏的文本框,输入 BananaTurntable。

最后,点击 Create Project.

认识界面

创建完项目,编辑器会打开。编辑器分成了几个区域:

  1. 内容浏览器: 这里显示所有项目文件。用它创建文件夹并组织文件。可以在搜索栏中查找文件或者过滤文件。
  2. Modes: 这里可以选用各种工具比如 Landscape 工具和 Foliage 工具。默认工具是 Place 工具。它允许将各种对象比如灯光和相机放到游戏关卡中。
  3. 世界大纲编辑器: 显示当前关卡中的所有对象。你可以通过将相关的对象放入文件夹中来组织整个列表。它也可以搜索或者通过类型来过滤。
  4. 详情: 你选中的对象会在这里显示出属性。这里可以编辑对象的设置。这里进行的修改只会影响这个对象的实例。例如,如果你有两个球体,如果你改变了其中一个的大小,那么只会对你选中的对象有效。
  5. 工具栏: 包含各种功能。你用得最多的一个就是 Play。
  6. 视口: 关卡视图。按下右键拖动鼠标可以查看四周。按下右键,用 WASD 键进行移动。

导入 Assets

光有一个圆桌却没有要陈列的东西有什么用?请下载香蕉模型。里面有两个文件 Banana_Model.fbx 和 Banana_Texture.jpg。当然,你可以用自己的模型。但你怎么来使用这个该死的香蕉呢?

在 Unreal 能够使用一个文件之前,你必须导入它。在内容浏览器,点击 Import。

通过文件浏览器,找到 Banana_Model.fbx 和 Banana_Texture.jpg。框选住两个文件,然后点击 Open。

对于 .fbx 文件,Unreal 会提供几个导入选项。确保 Import Materials 未选中,因为你会创建自己的材质。其它设置保持不变。

点击 Import。这两个文件就显示到内容浏览器中了。

在导入文件时,实际上它是不会保存到项目中的,除非你明确地这样做。你可以用右键点击这个文件,选择 Save 就可以保存它们了。还可以选择 File\Save All 来一次性保存所有文件。确保经常使用保存功能。

注意在 Unreal 中模型被称为网格。选择你已经有一个香蕉的网格了,可以将它放到关卡中了。

添加网格到关卡

选择关卡看起来空空的,让我们放点东西。
要添加网格,左键将 Banana_Model 从内容浏览器中拖到视口。放开鼠标左键,就可以将网格放下。

关卡中的对象是可以移动、旋转和缩放的。它们的快捷键分别是 W、E 和 R。然后使用操纵杆:

材质

如果你近距离查看香蕉,你会发现它根本不是黄的。事实上,它是灰色的。要给香蕉一些颜色和细节,你必须创建材质。

材质是什么?

材质决定了某件东西的表面外观。在一个基本的关卡中,材质定义了 4 个方面:

  • 基本色: 一个表面的颜色或纹理。常用于添加细节和色彩的变化。
  • 金属化:表面和金属的相似度。通常,纯粹的金属拥有的金属化值是最大的,而织物的金属化值为 0。
  • 高光:控制非金属表面的发光。例如,陶瓷拥有较高的高光值,而黏土则相反。
  • 粗糙度:粗糙度最大时,没有任何高光。用于岩石或木头的表面。

下面是 3 种材质的例子。它们颜色相同但属性不同。每种材质都有在对应的属性上设置为较高值。另外的属性则设为 0。

创建材质

要创建材质,请进入内容浏览器,点击绿色的 Add New 按钮。会弹出一个菜单显示你可以创建的 assets。点击 Material。

将材质取名为 Banana_Material 然后双击文件打开材质编辑器。

材质编辑器

材质编辑器由 5 部分组成:

  1. 图(graph): 这个区域包含所有节点和结果节点。按下鼠标右键并移动鼠标可以平移。滚动鼠标滚轮可以进行缩放。
  2. 详情: 这里显示选中节点的属性。如果没有选择任何节点,这里会显示材质的属性。
  3. 视口: 包含一个预览的网格,显示你的材质。要转动相机可以按住鼠标左键并移动鼠标。要缩放可以用鼠标滚轮。
  4. 调色板:一个列表,列出材质所有节点变量。

什么是节点?

在开始编辑材质前,你必须知道节点是什么。

节点构成了材质的主体。有许多不同的节点,提供了不同的功能。

节点可以有输入和输出,用一个圆圈+一个箭头来表示。输入位于左边,输出位于右边。

材质有一个特殊的节点,就是结果节点,在 Banana_Material 中它是现成的。它是所有节点的终点。一旦你接通这个节点,就可以决定材质的最终显示。

添加材质

要为一个模型添加颜色和细节,你需要一个纹理。一个纹理就是一张 2D 图片。通常,它们会投射到 3D 模型上,让它具有颜色和细节。

为了给香蕉贴图,你需要使用到 Banana_Texture.jpg。TextureSample 节点允许你在材质上使用贴图。

找到调色板,找到 TextureSample。按下鼠标左键,将它拖到“graph”窗口。

要选择纹理,首先需要选中这个 TextureSample 节点。找到详情面板,点击 Texture 右边的下拉按钮。

列表中列出项目中所有纹理。选择 Banana_Texture.

要在预览网格上查看贴图,需要将它插进结果节点中。左键点击 TextureSample 节点的白色输出按钮上,将它拖到结果节点的 Base Color 输入按钮上。

回到视口,在预览网格上查看纹理。旋转它(按下鼠标左键并拖动)查看其它细节。

点击工具栏上的 Apply 按钮,应用你的材质。然后关闭材质编辑器——这部分的工作结束了。

使用材质

要将材质应用到香蕉上,你需要指定它。回到内容浏览器,双击 Banana_Model 打开它。这会显示一个编辑器:

找到详情面板 Materials 一节。点击 Element 0 右边的下拉按钮,选择Banana_Material.

关闭网格编辑器,回到主编辑器,看一下视口。你将看到香蕉现在已经贴图了。恭喜,你已经成为了一个关卡设计师了!

注:如果光线太暗,你可以在世界大纲编辑器中点击 Light Source 来改变它。在详情面板中,找到 Intensity,设置为更高的值。

蓝图

香蕉看起来很好,但如果将它放到圆桌上旋转就更好了。用蓝图来创建一个圆桌很轻松。

简单讲,一个蓝图(Blueprint)表示一件”东西“。蓝图允许你为对象创建自定义行为。对象可以是某种物体(比如圆桌),也可以是某些抽象的东西比如生命系统。

想制作一辆会动的车吗?构建一个蓝图。一只会飞的猪呢?使用蓝图。一只在碰撞中爆炸的小猫?还是蓝图。

就像材质一样,蓝图使用了节点系统。这意味着你只需要创建节点然后将它们连接起来,不需要编写代码!

注:如果你更喜欢写代码,你可以用 C++。蓝图易于使用,但它们无法做到和 c++ 代码一样快。如果你不得不用到大量计算的东西,比如复杂算法,你应当使用 c++。
当然虽然你喜欢 c++,但很多时候用蓝图其实是个好主意。蓝图有这些优点:

  • 通常,蓝图比 c++ 的开发速度更快。
  • 易于组织。你可以将节点分成不同的模块,比如功能和图形。
  • 如果你和非编程人员一起工作,修改蓝图更容易,因为它的可视化和直观性。

将对象用蓝图来创建是一种好办法。当需要提升性能时,将它们转换成 c++。

创建蓝图

在内容浏览器,点击 Add New。在列表中选择 Blueprint Class。

弹出一个窗口,让你选择父类。你的蓝图会从父类继承所有的变量、函数和组件。花点时间了解每个类能做什么。

注:Pawn 和 Character 也是 Actor 类,因为你可以放置和生成它们。

因为圆桌只会待在一个地方不动,Actor 类最适合。选择 Actor 然后给文件命名为 Banana_Blueprint。

最后,双击 Banana_Blueprint 打开它。当窗口弹出时,点击 Open Full Blueprint Editor :

蓝图编辑器

首先,在蓝图编辑器中,选择 Event Graph 标签页。

蓝图编辑器有 5 个主要的窗口:

  1. 组件窗口:当前组件的列表。
  2. 我的蓝图:用于管理你的图形、功能和变量。
  3. 详情窗口:显示当前选中对象的属性。
  4. 图形窗口:这是关键。所有节点和逻辑都在这里。按下右键并拖动鼠标进行平移。滚动鼠标滚轮进行缩放。
  5. 视口:任何拥有可视化元素的组件都在这里显示。你可以用主编辑器一样的方式移动和旋转。

创建圆桌

要创建圆桌,需要两个东西:一个基座和一个陈列品。你可以通过组件来创建这两个东西。

什么是组件?

如果一个蓝图是一辆车,那么组件就是组成车辆的构件。例如,车门、轮胎、引擎都是组件。

但是,组件不仅仅限于物理对象。

例如,要让车动起来,你需要添加一个移动组件,你甚至可以为它添加一个飞行组件让它飞起来。

添加组件

在你看到任何组件之前,你需要切换到视口视图。点击 Viewport 标签页即可切换到它。它看起来像这个样子:

注:DefaultSceneRoot 组件在玩游戏时不会显示。它只在编辑器中显示。

这个圆桌用到了两个组件:

  • Cylinder: 一个简单的白色圆柱体。这是放置香蕉的底座。
  • Static Mesh: 这个组件用于显示香蕉网格。

要添加底座,找到组件面板。点击 Add Component 并选择 Cylindar。

最好让底座变得更短一点。按下 R 键,显示缩放操作杆,然后将它缩短(不要管真实大小,你可以在后面修改它)。

然后来添加网格。回到组件面板,在空白地方点击左键,清除对圆柱体的选中状态。这能保证接下来的组件不会附加在圆柱体组件上。

注:如果不这样做,接下来的组件会附加在圆柱体上。这意味着它会继承圆柱体的 scale 属性。因为你缩短了圆柱体,那么接下来的组件也会被缩短。

然后。点击 Add Component,选择 Static Mesh。

要显示香蕉,选择这个静态网格组件,然后点击 Details 标签页。点击 Static Mesh 右边的下拉按钮,选择 Banana_Model。

如果它的位置不对,请移动香蕉。按 W 激活操作杆,移动它的位置。

蓝图节点

接下来让圆桌旋转。这就要用到蓝图节点了。

和材质节点不同,蓝图节点拥有特殊的 pin 叫做 Execution pin。位于左边的 pin 是输入,位于右边的则是输出。所有节点至少有一个输入/输出。

如果节点有一个输入 pin,它前边必定有一个连接才能执行。如果有一个节点没连接,那么后续的任何节点都不会执行。

举一个例子:

节点 A 和节点 B 会执行,因为他们的输入 pin 已经有连接。节点 C 和节点 D 不会执行,因为节点 C 的输入 pin 是断开的。

转动圆桌

在开始之前,看一眼组件面板。你会看到 Cylindar 和 Static Mesh 有缩进而 DefaultSceneRoot 没有缩进。这是因为前者是附加到 DefaultSceneRoot 上的。

如果你移动、旋转或缩放 root 组件,那么它包含的组件也会有同样动作。通过这种方式,你可以让 Cylinder 和 Static Mesh 一起旋转,而不是分别旋转。

创建节点

要开始编写脚本,需要进入 Event Graph 标签页。

让某个对象旋转非常简单,只需要创建一个节点。在 graph 视图空白地方右键,打开有效的节点菜单。搜索 AddLocalRotation。因为你需要旋转底座和香蕉,你只需要旋转 root 组件即可。选择 AddLocalRotation(DefaultSceneRoot)。

注:如果没有列出这个节点,请反选菜单右上角的 Context Sensitive 选项。

在 graph 窗口中会多出一个 AddLocalRotation 节点。Target 输入会自动连接到你选择的组件。

要设置旋转角度,找到 Delta Rotation 输入,然后修改 Z 值为 1.0。这将导致蓝图沿 Z 轴旋转。值越大,转速越快。

要不断地旋转,你必须逐帧调用 AddLocalRotation。要逐帧调用,需要使用 Event Tick 节点。它已经在你的 graph 视图中了。如果没有,用前面一样的方法创建一个。

将 Event Tick 节点的输出 pin 拖到 AddLocalRotation 节点的输入 pin。

注:用这种方法,旋转速度取决于帧率。这意味着机器越慢圆桌的转速越慢,反之亦然。这对于本教程来说是可以的,因为我让事情简单化了,但在后面的教程中我会介绍如何解决这个问题。

最后,点击工具栏上的 Compile 按钮,更新你的蓝图,然后关闭蓝图编辑器。

将蓝图添加到关卡

在添加蓝图之前,在主编辑器的视口中,删除香蕉的模型。选择该模型,点击 Edit\Delete 菜单或者按 Delete 键。

添加蓝图和添加网格一样。在文件上按住左键,将它拖到视口中。

点击工具栏上 的 Play 按钮,看看效果!

注:如果你没有删除原有的香蕉模型,你可能会被警告”灯光需要重建“。如果删掉模型,则错误不再显示。

接下来去哪里?

在这里下载完成后的项目。

你已经学完这篇教程,但那只是 Unreal 中极小的一部分。如果你想继续学习,请阅读本系列的下一篇教程,我将介绍 Unreal 引擎更多关于蓝图的内容。

unreal4特性介绍

阅读数 182