IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity学习笔记 -> 正文阅读

[游戏开发]Unity学习笔记

目录

向左移动人物

上下左右控制物体

修改每秒渲染帧数

Tilemaps瓦片地图

物理系统

禁用重力:找到 Gravity Scale 属性并将其设置为 0

碰撞

去除旋转

去除抖动:修改物体移动的脚本

添加瓦片地图碰撞

图层与碰撞

触发器

?射线投射

给物体添加生命值统计功能

定义一个属性

精灵渲染器 (Sprite Renderer)

动画Animator

添加Animator 的组件

create an Animation Clip

构建 Controller

Instantiate(实例化)

摄像机

粒子

UI

EventSystem

Rect Transform(矩形变换)

Render Mode:

Canvas Scaler 组件:

Graphic Raycaste组件:

对话框

引用对象的新方法:使用静态成员


向左移动人物

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RubyController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        Vector2 position = transform.position;
        position.x = position.x + 0.1f;
        transform.position = position;
    }
}

Update()每帧调用

上下左右控制物体

使用Unity Input 系统

?此处用到Horizontal和Vertical

Input 页面列出所有玩家输入控件(例如,游戏手柄上的一个按键)的 Axes 值。值的范围从 -1 到 1(具体值取决于玩家执行的操作)。

例如,对于游戏手柄上的控制杆,可以将水平轴设置为:

  • -1(控制杆在左侧时)

  • 0(控制杆在中间时)

  • 1(控制杆在右侧时)

对于键盘按键,以 2 个键来定义轴:

  • 负值键,被按下时将轴设置为 -1

  • 正值键,被按下时将轴设置为 1

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RubyController : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        Debug.Log(vertical);//在 Console 窗口中写入一些内容
        Vector2 position = transform.position;
        position.x = position.x + 0.1f * horizontal;
        position.y = position.y + 0.1f * vertical;
        transform.position = position;
        /*
         * 如果你按向左键,则horizontal将为 -1(即移动距离为 position.x + -0.1f;)。Ruby 将向左移动 -0.1。
           如果你按向右键,则horizontal将为 1。Ruby 将向右移动 0.1。
           如果没有按下任何键,则horizontal将为 0 且 Ruby 不会移动(0.1 * 0 等于 0)。
        */
    }
}

Input系统的键盘输入,例:C按键

if (Input.GetKeyDown(KeyCode.C))

如果要确保在不同设备上有效,可以将 Input.GetButtonDown 与轴名称一起使用(就像前面针对移动执行的操作一样),并在输入设置 (Edit > Project Settings > Input) 中定义该轴对应的按钮。如需了解示例,请查看 Axes > Fire1

修改每秒渲染帧数

?也就是调用Update的频率,默认是60帧每秒。

    void Start()
    { 
        QualitySettings.vSyncCount = 0;
        Application.targetFrameRate = 10;//每秒渲染10帧
    }

修改后发现物体移动变慢,就有一个问题:

如果一个玩家的计算机非常陈旧,只能以每秒 30 帧的速度运行游戏,而另一个玩家的计算机能以每秒 120 帧的速度运行游戏,那么这两个玩家的主角的移动速度会有很大差异。这样就会使游戏的难易程度提高或降低,具体取决于运行游戏的计算机。

所以前面移动代码应改成

position.x = position.x + 0.1f * horizontal * Time.deltaTime;//乘上每帧渲染时间
position.y = position.y + 0.1f * vertical * Time.deltaTime;

这样不同帧速的电脑控制物体的速度才一样。

Tilemaps瓦片地图

不断重新渲染(Update())很大的游戏世界并不现实,瓦片地图可以用来解决此问题!瓦片地图将世界作为一个网格,你可以在其中为每个网格单元格设置不同的精灵。通过使用在视觉上连在一起的精灵,你可以创建便于在编辑器内直接更改的更大图像。

在 Hierarchy 窗口中创建两个游戏对象:

  • Grid(网格):顾名思义,场景中的网格可用于将游戏对象均匀地放置在网格单元格中。

  • Tilemap(瓦片地图):此瓦片地图是网格的子游戏对象。瓦片地图由瓦片 (Tiles) 组成;在本教程中,可将瓦片视为特殊精灵。

在 Project 窗口中,创建“Tiles”文件夹。选择 Create > Tile。

如果没有就选择 Create > 2D>Tile palette>Rectangular

然后在Hierarchy面板点Layer1,把Sprite拖进去就会生成一个Tile

?瓦片集:3x3,比单个瓦片方便

世界设计 - 瓦片地图 - Unity Learn自己看吧受不了了。

物理系统

添加Rigidbody 2D 组件

例1:在刚体上调用 AddForce,施加的力是方向与力的乘积。当力增大时,物理引擎将根据该力和方向逐帧移动飞弹。

rigidbody2d.AddForce(direction * force);

例2:rigidbody2D.simulated = false;//将刚体从物理系统模拟中删除

禁用重力:找到 Gravity Scale 属性并将其设置为 0

碰撞

告诉物理系统,该游戏对象的哪一部分是“实心的”。此操作是通过碰撞体完成的。

碰撞体是简单的形状(例如正方形或圆形),物理系统将这样的形状作为游戏对象的近似形状来进行碰撞计算。

添加Box Collider 2D组件

调整碰撞体大小:单击 Edit Collider 时,碰撞体在 Scene 视图中会变化为在侧面显示四个小方块。可以单击并拖动小方块来调整碰撞体的大小。

但是碰撞时会旋转和抖动

去除旋转

找到 Rigidbody 2D 组件。单击 Constraints 旁边的小箭头以展开该部分。 启用 Freeze Rotation

抖动原因:你不断在箱子内移动物体,而物理系统则将她移回。你要求代码执行的操作与物理系统执行的操作之间的这种冲突就会导致发生抖动。

去除抖动:修改物体移动的脚本

1.FixedUpdate 函数:定期进行更新(例如,每隔 16ms)

只要你想直接影响物理组件或对象(例如刚体),就需要使用该函数。但是,你不应该读取 Fixedupdate 函数中的输入。FixedUpdate 不会持续运行,因此有可能会错过用户输入。

2.需要将 Rigidbody 组件手动添加到游戏对象

Rigidbody2D rigidbody2d;

rigidbody2d = GetComponent<Rigidbody2D>();

3.位置transform.position改用rigidbody2d.position

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RubyController : MonoBehaviour
{
    Rigidbody2D rigidbody2d;
    float horizontal;
    float vertical;

    // 在第一次帧更新之前调用 Start
    void Start()
    {
        rigidbody2d = GetComponent<Rigidbody2D>();
    }

    // 每帧调用一次 Update
    void Update()
    {
        horizontal = Input.GetAxis("Horizontal");
        vertical = Input.GetAxis("Vertical");
    }

    void FixedUpdate()
    {
        Vector2 position = rigidbody2d.position;
        position.x = position.x + 3.0f * horizontal * Time.deltaTime;
        position.y = position.y + 3.0f * vertical * Time.deltaTime;

        rigidbody2d.MovePosition(position);
    }
}

添加瓦片地图碰撞

1. 在 Hierarchy 中,选择 Tilemap 游戏对象。

2. 在 Inspector 中,单击 Add Component 按钮。

3. 搜索“Tilemap Collider 2D”,然后选择此组件。你会看到在 Scene 视图中为所有瓦片添加绿色碰撞体方块,现在所有的瓦片都已设置为进行碰撞

4.修改不需要碰撞的瓦片(允许正常行走的地方)

在 Project 窗口中,找到 Tile 文件夹。选择所有不是水的瓦片。(你可以单击一个瓦片,然后按住 Shift 并单击列表中的最后一个瓦片来全部选中。)

在 Inspector 中,找到 Collider Type 属性,然后将该属性从 Sprite(目前值)更改为 None。

5.优化

每个瓦片都是一个单独的碰撞体。 这种方法效果良好,但会产生两个问题:

  • 物理系统的计算量更大;如果你的世界很大,可能会减慢你的游戏速度。

  • 在瓦片之间的边界上会产生小问题。由于瓦片是两个并排的碰撞体,并且两者之间存在微小间隙,因此有时计算上的微小误差也可能导致仍会发生碰撞的罕见情况。

选择 Tilemap 游戏对象添加Composite Collider 2D 的组件:可以获取对象(或对象的子对象)上的所有碰撞体,并由此创建一个大碰撞体。

在 Tilemap Collider 2D 组件中,启用 Used By Composite 复选框。

在 Rigidbody 2D 组件中,将 Rigidbody Body Type 属性设置为 Static(阻止你的世界移动。此外还有助于物理系统优化计算,因为它现在知道刚体不能移动)。

图层与碰撞

图层可将游戏对象分组在一起。

应用例子:创建一个角色图层来放入 Ruby 游戏对象,然后创建一个飞弹图层来放入所有飞弹。然后可以告诉物理系统,角色图层和飞弹图层不能碰撞,因此物理系统将忽略这些图层中的对象之间的所有碰撞。

右上角Layers>Edit layers>layers>创建新图层

设置物体的图层

Edit > Project Settings > Physics 2D ,查看底部的 Layer Collision Matrix,便可看到哪些图层彼此碰撞

取消选中不希望碰撞的图层行与列之间的交集,因此这两个图层不再发生碰撞。

触发器

触发器是一种特殊类型的碰撞体。触发器不会阻止移动,但是物理系统仍会检查角色是否会与触发器碰撞。当你的角色进入触发器时,你将收到一条消息,以便你可以处理该事件

Box Collider 2D 组件>启用 Is Trigger?

void OnTriggerEnter2D(Collider2D other)

当检测到新的刚体进入触发器时,Unity 将在第一帧调用此 OnTriggerEnter2D 函数。名为 other 的参数将包含刚进入触发器的碰撞体。注意只有进入时调用,若想用停留时调用,则

void OnTriggerStay2D?(Collider2D other)

刚体在触发器内的每一帧都会调用此函数,而不是在刚体刚进入时仅调用一次。

但刚体在里面不动的时候也不会调用该函数,需要在刚体的 Rigidbody 组件中将 Sleeping Mode 设置为 Never Sleep

Collider2D类型如何访问其组件:(RubyController组件仅为例子)

RubyController controller = other.GetComponent<RubyController >();

刚体与某个对象碰撞(会阻止移动)时调用的函数:

void OnCollisionEnter2D(Collision2D other)

Collision2D类型如何访问其组件:(RubyController组件仅为例子)

RubyController player = other.gameObject.GetComponent<RubyController>();

//EnemyController e = other.collider.GetComponent<EnemyController>();

?射线投射

射线投射是将射线投射到场景中并检查该射线是否与碰撞体相交的行为。射线具有起点、方向和长度。可以当成另一种形状的碰撞体?

从主角的位置朝着她目光的方向投射射线

RaycastHit2D hit = Physics2D.Raycast(rigidbody2d.position + Vector2.up * 0.2f, lookDirection, 1.5f, LayerMask.GetMask("NPC"));

//?Raycast(Vector2?origin起点,?Vector2?direction, float?distance?= Mathf.Infinity, int?layerMask?= DefaultRaycastLayers图层, float?minDepth?= -Mathf.Infinity, float?maxDepth?= Mathf.Infinity);

?if (hit.collider != null)
?{
? ? ? ??Debug.Log("Raycast has hit the object " + hit.collider.gameObject);
?}

给物体添加生命值统计功能

    public int maxHealth = 5;
    int currentHealth;

    void Start()
    {
        currentHealth = maxHealth;//初始满血
    }

    void ChangeHealth(int amount)
    {
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
        //钳制功能(Clamping) 可确保第一个参数(此处为 currentHealth +amount)大于等于第二个参数(此处为 0)且小于第三个参数(maxHealth)。因此生命值将始终保持在 0 与 maxHealth 之间。
        Debug.Log(currentHealth + "/" + maxHealth);
    }

定义一个属性

public int health { get { return currentHealth; } }//只读

在第一个代码块中,你使用了 get 关键字来获取第二个代码块中的任何内容。

第二个代码块就像普通函数一样,因此只需返回 currentHealth 值。

精灵渲染器 (Sprite Renderer)

矩形工具(T 键)调整大小

想要拉伸平铺:

  • 首先,确保游戏对象的缩放在 Transform 组件中设置为 1,1,1。

  • 然后在 Sprite Renderer 组件中将 Draw Mode 设置为 Tiled,并将 Tile Mode 更改为 Adaptive。

  • 在 Project 窗口中选择要设置的精灵,并将 Mesh Type 更改为 Full Rect。

  • 选中 Box Collider 2D 组件的 Auto Tiling 属性,以便碰撞体随精灵一起平铺。

世界交互 - 伤害区域和敌人 - Unity Learn

动画Animator

可以使用 Animator 对游戏对象任何组件中的任何属性进行随时间变化的动画处理。此动画可以是你希望随时间推移而变化的精灵颜色,也可以是大小变化。

添加Animator 的组件

Animator 组件上最重要的设置是 Controller 设置。

Controller 组件负责基于你定义的规则来选择要播放的动画(例如,当角色的速度大于 0 时,使角色从站立变为奔跑动画)。

Create > Animator Controller,然后拖到Animator 组件Controller处

得自己有动画文件供controller控制。

create an Animation Clip

选择 Window > Animation > Animation 来打开 Animation 窗口。

在 Hierarchy 中选择 物体,则 Animation 窗口点击” create an Animation Clip”

例子:通过更改 Sprite Renderer 随时间推移而使用的精灵来产生移动的视觉效果精灵动画 - Unity Learn

把不同动作图片拖进去,调整时长or间隔,播放键可以在Scene面板预览

构建 Controller

Controller 定义动画之间的关系,比如如何从一段动画切换到另一段动画

  • 打开 Animator 窗口(菜单:Windows > Animation > Animator)。 注意:确保选中物体

  • 第 1 部分:Layers 和 Parameters

    Layers 可用于 3D 动画,因为你可以将动画用于角色的不同部分。

    Parameters 由我们的脚本用来向 Controller 提供信息。

  • 第 2 部分:动画状态机

    动画状态机以图形方式显示动画的所有状态以及如何从一段动画过渡到另一段动画。

    第一段动画链接到 Entry,这表示此动画将在游戏开始时播放。

    你可以在所有动画之间创建链接

  • 一种更简单的方法是使用混合树 (Blend Tree),这种混合树允许你根据参数来混合多段动画。

    1. 首先通过选择动画并按 Delete 或者通过右键单击动画并选择 Delete来删除所有动画。

    2. 然后,在图中某处右键单击,然后选择 Create State > From New Blend Tree。

? ? ? ?3.双击 Blend Tree 以将其打开。

? ? ? ?4.单击“Blend Tree”节点,随即将在 Inspector 中显示相应设置

? ? ? ?5.Blend Type 设置可定义混合树将使用多少参数来选择要播放的动画。如果希望 Blend Tree 使用两个参数来控制水平和垂直方向的更改,请将 Blend Type 设置为 2D Simple Directional。

? ? ? 6.找到 Animator 窗口左侧的 Parameters 选项卡:单击搜索栏旁边的 + 图标,选择 数据类型,并将新参数命名。选择 Blend Tree 时,可以在 Inspector 顶部的下拉选单中选择参数

? ? ?7.单击 Motion 部分底部的 +,然后选择 Add Motion 字段,要混合几段动画就加多少次,然后设置每段动画对应的参数。

*将参数发送到 Animator Controller

Animator animator;

void Start() {animator = GetComponent<Animator>(); }

可以通过 Animator 上的 SetFloat 函数来完将参数值发送到 Animator(因为此例子使用的是浮点型参数)

animator.SetFloat("Move X", 0);

//例2trigger型参数,记得Parameters页创建该参数时不要默认选中

animator.SetTrigger("Fixed");

Instantiate(实例化)

第一个参数是一个对象,在第二个参数的位置处创建一个副本,第三个参数是旋转。

GameObject projectileObject = Instantiate(ProjectilePrefab, rigidbody2d.position + Vector2.up * 0.5f, Quaternion.identity);

Quaternion.identity 表示“无旋转”

注意:实例化对象不调用 Start,但调用Awake()

摄像机

使用一个名为 Cinemachine 的 Unity 包来自动控制摄像机,而无需编写代码。

希望摄像机跟随你的主角

要添加 Cinemachine 包,请执行以下操作:

  • 在 Unity 编辑器中打开 Package Manager(菜单:Window > Package Manager)。
  • 找到 Cinemachine 条目,然后单击右下角的 Install
  • 安装完在顶部菜单栏上选择 Cinemachine > Create 2D Camera 条目,从而将 Cinemachine 2D 摄像机添加到场景中。
  • Cinemachine 使用虚拟摄像机,你可以在每个虚拟摄像机上选择不同的设置,然后告诉实际摄像机(在 Hierarchy 中名为 Main Camera)当前哪个虚拟摄像机处于活动状态,以便可以复制设置。
  • 放大视野:Lens>FOV改小
  • 跟随:只需将你的主角从 Hierarchy 拖放到 vcam Inspector 的 Follow 属性中
  • 阻止摄像机显示地图之外的任何内容:用 Cinemachine Confiner 定义一些边界。
  1. vcam Inspector 的底部,单击 Add Extension 下拉选单,然后选择 CinemachineConfiner
  2. Hierarchy >Create >Create Empty 命名Confiner, 添加Polygon Collider 2D 组件。单击 Edit Collider 旁的按钮,然后移动各个点到地图角点

  3. 回到 vcam1 并将该游戏对象分配给 CinemachineConfiner 上的 Bounding Shape 2D 属性

  4. 添加Confiner图层,在 Confiner 游戏对象上,将 Layer 下拉选单设置为 Confiner。

  5. 选择 Edit > Project Settings > Physics 2D,然后取消勾选 Confiner 图层中的所有条目

粒子

使用 Hierarchy 窗口右上角的 Create 按钮(选择 Effects > Particle System)

粒子

例子:制作烟雾、炸弹效果

UI

?using UnityEngine.UI;

Unity 中的 UI 使用一种称为画布 (Canvas) 组件的游戏对象来渲染特定 UI 的组件,例如图像、滑动条和按钮。画布定义了每个 UI 元素应如何在屏幕上呈现,并负责渲染所有作为子对象的 UI 组件。

Hierarchy 窗口 Create>UI > Canvas

EventSystem

是一个带有特殊组件的游戏对象,可以处理事件以及与 UI 的交互,例如单击鼠标。

Rect Transform(矩形变换)

仍然是一种 Transform(变换),因此可以在脚本中用作变换,但矩形变换具有额外的 UI 数据

Render Mode:

  • Screen Space - Overlay:这是默认模式,可以让 Unity 始终在游戏的上层绘制 UI

  • Screen Space - Camera:这种模式在与摄像机对齐的平面上绘制 UI。平面的大小确定为始终填充整个屏幕,这样你就可以四处移动摄像机,并且平面将随摄像机一起移动,从而显示与 Overlay 图形相同的形状。但是,由于平面是在世界中绘制的,而不是在屏幕上层绘制的,因此世界中的对象可以绘制在 UI 的上层

  • World Space:这种模式可在世界中的任何位置绘制平面。例如,你可以将此平面用作游戏中的计算机屏幕,或者用作墙壁,或者放在角色的上层。这在 3D 游戏中更有用,因为 UI 会随着距离变小。

Canvas Scaler 组件:

用于定义 UI 在不同的屏幕大小下如何缩放。

有些玩家可能以 800 x 600 的分辨率运行你的游戏,而其他玩家可能以 1920 x 1080 的分辨率运行。或者对于移动端应用程序,可能会在横向和纵向模式下使用应用程序。所有这些选项都要求具有不同的屏幕大小和比例。?

UI Scale Mode:

  • Constant Size(Pixel 或 Physical):无论屏幕大小或形状如何,UI 均保持大小不变。这样可以使 UI 在任何屏幕上都能看清楚,但在较小的屏幕上可能会被 UI 占据很大的空间,而且如果屏幕太小,元素也不能重叠
  • Scale With Screen Size:让 UI 缩放取决于你设置为 Reference Resolution 的屏幕大小。

Graphic Raycaste组件:

可以检测玩家是否点击了画布中的对象(比如按钮)?

要添加图像,选择画布后在 Hierarchy 中单击 Create,然后选择 UI > Image。此时将创建一个 Image 游戏对象作为 Canvas 的子对象,因为需要在该画布上渲染这个图像。

设置Source Image

对话框

在 Hierarchy 中右键单击 Image 游戏对象,然后选择 UI > Text - TextMeshPro>单击 Import TMP Essentials。然后选中Text编辑文字。

显示对话:

先禁用画布将画布隐藏起来,创建新 C# 脚本,并将该脚本添加到 角色?上。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NonPlayerCharacter : MonoBehaviour
{
    public float displayTime = 4.0f;
    public GameObject dialogBox;
    float timerDisplay;//计时器
                       
    void Start()
    {
        dialogBox.SetActive(false);//       确保禁用对话框
        timerDisplay = -1.0f;
    }

    void Update()
    {
        if (timerDisplay >= 0)
        {
            timerDisplay -= Time.deltaTime;
            if (timerDisplay < 0)
            {
                dialogBox.SetActive(false);//显示时间到了就禁用
            }
        }
    }
    public void DisplayDialog()
    {
        timerDisplay = displayTime;
        dialogBox.SetActive(true);
    }

}

调用这个DisplayDialog函数就ok了

注:要在 Inspector 中 脚本的“Dialog Box”设置中分配 角色 的 Canvas 子对象(对话框)。?

世界交互 - 对话射线投射 - Unity Learn

引用对象的新方法:使用静态成员

静态成员由该类型的所有对象共享。因此,如果在敌人脚本中将速度设为静态成员(通过在前面添加关键字 static),然后对一个敌人更改速度,则会更改所有敌人的速度。这是因为静态成员访问内存中的相同空间,而不是各自有自己的空间。静态成员还允许我们使用类名代替引用来访问该变量(省略GetComponent)

将脚本设为静态:

public static UIHealthBar instance { get; private set; }

void Awake() { instance = this; }

使用方法:

UIHealthBar.instance.SetValue(currentHealth / (float)maxHealth);

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-08-21 15:47:34  更:2021-08-21 15:48:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 9:16:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码