mirror of
https://github.com/Alex-Rachel/TEngine.git
synced 2025-08-07 16:45:10 +00:00
[+] Books
[+] Books
This commit is contained in:
29
Books/0-介绍.md
Normal file
29
Books/0-介绍.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# 介绍- Introduce
|
||||
|
||||
### TEngine
|
||||
<a style="text-decoration:none">
|
||||
<img src="https://img.shields.io/badge/Unity%20Ver-2021.3.20++-blue.svg?style=flat-square" alt="status" />
|
||||
</a>
|
||||
<a style="text-decoration:none">
|
||||
<img src="https://img.shields.io/github/license/ALEXTANGXIAO/TEngine" alt="license" />
|
||||
</a>
|
||||
<a style="text-decoration:none">
|
||||
<img src="https://img.shields.io/github/last-commit/ALEXTANGXIAO/TEngine" alt="last" />
|
||||
</a>
|
||||
<a style="text-decoration:none">
|
||||
<img src="https://img.shields.io/github/issues/ALEXTANGXIAO/TEngine" alt="issue" />
|
||||
</a>
|
||||
<a style="text-decoration:none">
|
||||
<img src="https://img.shields.io/github/languages/top/ALEXTANGXIAO/TEngine" alt="topLanguage" />
|
||||
</a>
|
||||
|
||||
TEngine是一套用于Unity框架解决方案,用于帮助研发团队快速进行开发。
|
||||
|
||||
### 开箱即用
|
||||
开箱即用、用法简洁,即用即上手.高可读性和详细的文档说明帮助你更快更好的进行游戏开发。您不需要关心框架的底层,分离独自实现您的GamePlay。
|
||||
|
||||
### 性能强大
|
||||
TEngine 底层使用多线程管理与线程间的通信以及事件分发,可插件定制化,把复杂游戏简单化切以高性能、低耦合度实现。
|
||||
|
||||
### 高内聚低耦合
|
||||
内嵌了The Best 次时代热更新解决方案<a href="https://github.com/focus-creative-games/hybridclr"><strong>HybridCLR</strong></a>、百万DAU验证的资源解决方案<a href="https://github.com/tuyoogame/YooAsset"><strong>YooAsset</strong></a>
|
58
Books/1-快速开始.md
Normal file
58
Books/1-快速开始.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 快速开始- Quickly Start
|
||||
快速上手
|
||||
本教程引导从空项目开始体验TEngine。出于简化起见,只演示目标平台为Windows的情况。
|
||||
|
||||
请在Standalone平台上正确跑通热更新流程后再自行尝试Android、iOS平台的热更新,它们的流程非常相似。
|
||||
|
||||
### 1.使用Unity2021.3.20f1c1打开项目工程。
|
||||
|
||||
### 2.默认选择顶部栏目EditorMode编辑器下的模拟模式并点击Launcher开始运行
|
||||

|
||||
|
||||
### 3.Editor编辑器下运行成功!
|
||||

|
||||
|
||||
### 4.打包运行
|
||||
1.运行菜单 HybridCLR/Define Symbols/Enable HybridCLR 运行开启HybridCLR热更新
|
||||
2.运行菜单 HybridCLR/Generate/All 进行必要的生成操作。这一步不可遗漏!!!
|
||||
3.运行菜单 HybridCLR/Build/BuildAssets And CopyTo AssemblyPath,生成热更新dll并copy到热更程序集中。
|
||||
4.运行菜单 YooAsset/AssetBundle Builder 构建AB!!!
|
||||
5.打开Build Settings对话框,点击Build And Run,打包并且运行热更新示例工程。
|
||||
|
||||
### 遇到问题请查看HybridlCLR的<a href="https://hybridclr.doc.code-philosophy.com/docs/help/commonerrors"><strong>常见错误(commonerrors)</strong></a>
|
||||
|
||||
### 系统需求
|
||||
默认版本:Unity2021.3.20f1c1
|
||||
|
||||
支持版本: Unity2019.4 & Unity2020.3 & Unity2021.3 & Unity2022.3
|
||||
|
||||
支持平台: Windows、OSX、Android、iOS、WebGL
|
||||
|
||||
开发环境: .NET4.x
|
||||
|
||||
### 目录结构
|
||||
```
|
||||
Assets
|
||||
├── AssetRaw 资源目录
|
||||
├── Atlas 图集目录
|
||||
├── GameScripts 热更程序集目录
|
||||
├── Scenes 主场景目录
|
||||
└── TEngine
|
||||
├── AssetSetting YooAsset资源设置
|
||||
├── Editor TEngine-Editor程序集
|
||||
└── Runtime TEngine-Runtime程序集
|
||||
```
|
||||
|
||||
### 热更新程序集划分
|
||||
```
|
||||
Assets/GameScripts
|
||||
├── Editor 编辑器程序集
|
||||
├── HotFix 游戏热更程序集目录 [Folder]
|
||||
| ├── GameBase 游戏基础框架程序集 [Dll]
|
||||
| ├── GameProto 游戏配置协议程序集 [Dll]
|
||||
| ├── BattleCore 游戏核心战斗程序集 [Dll]
|
||||
| └── GameLogic 游戏业务逻辑程序集 [Dll]
|
||||
| ├── GameApp.cs 热更主入口
|
||||
| └── GameApp_RegisterSystem.cs 热更主入口注册系统
|
||||
└── Runtime Runtime程序集
|
||||
```
|
67
Books/2-框架概览.md
Normal file
67
Books/2-框架概览.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# TEngine
|
||||
|
||||
## TEngine-Runtime
|
||||
### AOT内核基于Gameframework,最简化以及商业化适配。
|
||||

|
||||
|
||||
## AOT游戏框架模块基类。
|
||||
#### 框架思路为面向接口编程,如Resource资源模块,开发白皮书为先定义IResourceManager的接口规范,然后编写ResourceManager继承框架具体实现(GameFrameworkModule)以及实现接口。最后实现调用层GameFrameworkModuleBase,调用层可以拓展编辑器供开发者自定义模块参数。
|
||||
``` csharp
|
||||
/// <summary>
|
||||
/// 游戏框架模块抽象类。GameFrameworkModule为具体框架模块实现。
|
||||
/// </summary>
|
||||
internal abstract class GameFrameworkModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取游戏框架模块优先级。
|
||||
/// </summary>
|
||||
/// <remarks>优先级较高的模块会优先轮询,并且关闭操作会后进行。</remarks>
|
||||
internal virtual int Priority => 0;
|
||||
|
||||
/// <summary>
|
||||
/// 游戏框架模块轮询。
|
||||
/// </summary>
|
||||
/// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
|
||||
/// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
|
||||
internal abstract void Update(float elapseSeconds, float realElapseSeconds);
|
||||
|
||||
/// <summary>
|
||||
/// 关闭并清理游戏框架模块。
|
||||
/// </summary>
|
||||
internal abstract void Shutdown();
|
||||
}
|
||||
|
||||
//=====================================================================//
|
||||
|
||||
/// <summary>
|
||||
/// 游戏框架模块抽象类。GameFrameworkModuleBase 为Mono调用层。
|
||||
/// </summary>
|
||||
public abstract class GameFrameworkModuleBase : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏框架模块初始化。
|
||||
/// </summary>
|
||||
protected virtual void Awake()
|
||||
{
|
||||
GameEntry.RegisterModule(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 常用模块接口
|
||||
|
||||
<strong>[3-1-资源模块](./3-1-资源模块.md)<strong>
|
||||
|
||||
<strong>[3-2-事件模块](./3-2-事件模块.md)<strong>
|
||||
|
||||
<strong>[3-3-内存池模块](./3-3-内存池模块.md)<strong>
|
||||
|
||||
<strong>[3-4-对象池模块](./3-4-对象池模块.md)<strong>
|
||||
|
||||
<strong>[3-5-UI模块](./3-5-UI模块.md)<strong>
|
||||
|
||||
<strong>[3-6-配置表模块](./3-6-配置表模块.md)<strong>
|
||||
|
||||
<strong>[3-7-流程模块](./3-7-流程模块.md)<strong>
|
||||
|
||||
<strong>[3-8-网络模块](./3-8-网络模块.md)<strong>
|
164
Books/3-1-资源模块.md
Normal file
164
Books/3-1-资源模块.md
Normal file
@@ -0,0 +1,164 @@
|
||||
## 3-1.资源模块 - ResourceModule
|
||||
资源模块运行模式有EditorSimulateMode、OfflinePlayMode以及HostPlayMode
|
||||
编辑器模式下以顶部导航栏的选项卡为优先选项,打包后以Scene场景中ResourceModule脚本的Enum选项卡为优先选项(打包后不会走EditorSimulateMode)
|
||||
``` csharp
|
||||
/// <summary>
|
||||
/// 获取当前资源包版本。
|
||||
/// </summary>
|
||||
/// <returns>资源包版本。</returns>
|
||||
public string GetPackageVersion();
|
||||
|
||||
/// <summary>
|
||||
/// 异步更新最新包的版本。
|
||||
/// </summary>
|
||||
/// <param name="appendTimeTicks">请求URL是否需要带时间戳。</param>
|
||||
/// <param name="timeout">超时时间。</param>
|
||||
/// <returns>请求远端包裹的最新版本操作句柄。</returns>
|
||||
public UpdatePackageVersionOperation UpdatePackageVersionAsync(bool appendTimeTicks = false, int timeout = 60);
|
||||
|
||||
/// <summary>
|
||||
/// 向网络端请求并更新清单
|
||||
/// </summary>
|
||||
/// <param name="packageVersion">更新的包裹版本</param>
|
||||
/// <param name="autoSaveVersion">更新成功后自动保存版本号,作为下次初始化的版本。</param>
|
||||
/// <param name="timeout">超时时间(默认值:60秒)</param>
|
||||
public UpdatePackageManifestOperation UpdatePackageManifestAsync(string packageVersion, bool autoSaveVersion = true, int timeout = 60);
|
||||
|
||||
/// <summary>
|
||||
/// 创建资源下载器,用于下载当前资源版本所有的资源包文件。
|
||||
/// </summary>
|
||||
public ResourceDownloaderOperation CreateResourceDownloader();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 清理包裹未使用的缓存文件。
|
||||
/// </summary>
|
||||
public ClearUnusedCacheFilesOperation ClearUnusedCacheFilesAsync();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 清理沙盒路径。
|
||||
/// </summary>
|
||||
public void ClearSandbox();
|
||||
|
||||
/// <summary>
|
||||
/// 强制执行释放未被使用的资源。
|
||||
/// </summary>
|
||||
/// <param name="performGCCollect">是否使用垃圾回收。</param>
|
||||
public void ForceUnloadUnusedAssets(bool performGCCollect);
|
||||
|
||||
/// <summary>
|
||||
/// 检查资源是否存在。
|
||||
/// </summary>
|
||||
/// <param name="assetName">要检查资源的名称。</param>
|
||||
/// <returns>检查资源是否存在的结果。</returns>
|
||||
public HasAssetResult HasAsset(string assetName);
|
||||
|
||||
/// 同步加载资源。
|
||||
/// </summary>
|
||||
/// <param name="assetName">要加载资源的名称。</param>
|
||||
/// <typeparam name="T">要加载资源的类型。</typeparam>
|
||||
/// <returns>资源实例。</returns>
|
||||
T LoadAsset<T>(string assetName) where T : Object;
|
||||
|
||||
/// <summary>
|
||||
/// 同步加载资源。
|
||||
/// </summary>
|
||||
/// <param name="assetName">要加载资源的名称。</param>
|
||||
/// <param name="parent">父节点位置。</param>
|
||||
/// <typeparam name="T">要加载资源的类型。</typeparam>
|
||||
/// <returns>资源实例。</returns>
|
||||
T LoadAsset<T>(string assetName, Transform parent) where T :Object;
|
||||
|
||||
/// <summary>
|
||||
/// 同步加载资源。
|
||||
/// </summary>
|
||||
/// <param name="handle">资源操作句柄。</param>
|
||||
/// <param name="assetName">要加载资源的名称。</param>
|
||||
/// <typeparam name="T">要加载资源的类型。</typeparam>
|
||||
/// <returns>资源实例。</returns>
|
||||
T LoadAsset<T>(string assetName,out AssetOperationHandle handle) where T : Object;
|
||||
|
||||
/// <summary>
|
||||
/// 同步加载资源。
|
||||
/// </summary>
|
||||
/// <param name="assetName">要加载资源的名称。</param>
|
||||
/// <param name="handle">资源操作句柄。</param>
|
||||
/// <param name="parent">父节点位置。</param>
|
||||
/// <typeparam name="T">要加载资源的类型。</typeparam>
|
||||
/// <returns>资源实例。</returns>
|
||||
T LoadAsset<T>(string assetName, Transform parent,out AssetOperationHandle handle) where T :Object;
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载资源。
|
||||
/// </summary>
|
||||
/// <param name="assetName">要加载资源的名称。</param>
|
||||
/// <param name="cancellationToken">取消操作Token。</param>
|
||||
/// <typeparam name="T">要加载资源的类型。</typeparam>
|
||||
/// <returns>异步资源实例。</returns>
|
||||
UniTask<T> LoadAssetAsync<T>(string assetName,CancellationToken cancellationToken) where T : Object;
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载游戏物体。
|
||||
/// </summary>
|
||||
/// <param name="assetName">要加载的游戏物体名称。</param>
|
||||
/// <param name="cancellationToken">取消操作Token。</param>
|
||||
/// <returns>异步游戏物体实例。</returns>
|
||||
UniTask<UnityEngine.GameObject> LoadGameObjectAsync(string assetName,CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// 同步加载资源并获取句柄。
|
||||
/// </summary>
|
||||
/// <param name="assetName">要加载资源的名称。</param>
|
||||
/// <typeparam name="T">要加载资源的类型。</typeparam>
|
||||
/// <returns>同步加载资源句柄。</returns>
|
||||
AssetOperationHandle LoadAssetGetOperation<T>(string assetName) where T : Object;
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载资源并获取句柄。
|
||||
/// </summary>
|
||||
/// <param name="assetName">要加载资源的名称。</param>
|
||||
/// <typeparam name="T">要加载资源的类型。</typeparam>
|
||||
/// <returns>异步加载资源句柄。</returns>
|
||||
AssetOperationHandle LoadAssetAsyncHandle<T>(string assetName) where T : Object;
|
||||
|
||||
/// <summary>
|
||||
/// 同步加载子资源对象
|
||||
/// </summary>
|
||||
/// <typeparam name="TObject">资源类型</typeparam>
|
||||
/// <param name="location">资源的定位地址</param>
|
||||
public SubAssetsOperationHandle LoadSubAssetsSync<TObject>(string location) where TObject : UnityEngine.Object;
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载子资源对象
|
||||
/// </summary>
|
||||
/// <typeparam name="TObject">资源类型</typeparam>
|
||||
/// <param name="location">资源的定位地址</param>
|
||||
public SubAssetsOperationHandle LoadSubAssetsAsync<TObject>(string location) where TObject : UnityEngine.Object;
|
||||
|
||||
/// <summary>
|
||||
/// 同步加载子资源对象
|
||||
/// </summary>
|
||||
/// <param name="assetInfo">资源信息。</param>
|
||||
public SubAssetsOperationHandle LoadSubAssetsSync(AssetInfo assetInfo);
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载场景。
|
||||
/// </summary>
|
||||
/// <param name="location">场景的定位地址</param>
|
||||
/// <param name="sceneMode">场景加载模式</param>
|
||||
/// <param name="activateOnLoad">加载完毕时是否主动激活</param>
|
||||
/// <param name="priority">优先级</param>
|
||||
/// <returns>异步加载场景句柄。</returns>
|
||||
SceneOperationHandle LoadSceneAsync(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100);
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载场景
|
||||
/// </summary>
|
||||
/// <param name="assetInfo">场景的资源信息</param>
|
||||
/// <param name="sceneMode">场景加载模式</param>
|
||||
/// <param name="activateOnLoad">加载完毕时是否主动激活</param>
|
||||
/// <param name="priority">优先级</param>
|
||||
/// <returns>异步加载场景句柄。</returns>
|
||||
SceneOperationHandle LoadSceneAsync(AssetInfo assetInfo, LoadSceneMode sceneMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100);
|
||||
```
|
67
Books/3-2-事件模块.md
Normal file
67
Books/3-2-事件模块.md
Normal file
@@ -0,0 +1,67 @@
|
||||
## 3-2.事件模块 - GameEvent
|
||||
高效的事件系统GameEventMgr,可以指定事件ID/事件String监听和分发事件。通过事件来驱动模块,如战斗的角色身上的事件流、UI和网络以及Model的数据流、开发中的绝大部分情况都可以通过事件来进行驱动。(配合UI模块或者拓展的战斗模块实现MVE[Model - View - Event]事件驱动架构)
|
||||
|
||||
<strong>注:UI模块的事件和UI生命周期存在绑定,销毁UI时可以自动移除UI所监听的事件。(AddUIEvent)</strong>
|
||||
``` csharp
|
||||
public static int Hellp = StringId.StringToHash("Hellp.Hellp");
|
||||
|
||||
class A
|
||||
{
|
||||
public A()
|
||||
{
|
||||
//添加事件监听string
|
||||
GameEvent.AddEventListener("TEngine很好用",TodoSomeThings);
|
||||
//添加事件监听int 事件ID
|
||||
GameEvent.AddEventListener(Hellp,TodoSomeThings2);
|
||||
}
|
||||
}
|
||||
|
||||
class B
|
||||
{
|
||||
private void SaySomeThings()
|
||||
{
|
||||
//发送事件流
|
||||
GameEvent.Send("TEngine很好用");
|
||||
GameEvent.Send(Hellp);
|
||||
}
|
||||
}
|
||||
|
||||
【举个例子:游戏中血量扣除的时候,服务器发来了一个减少Hp的消息包,
|
||||
我们可以在收到这个消息包的时候发送一个事件流,在玩家头顶的HP进度
|
||||
条组件/左上角Hp的UI血条组件添加一个监听事件,各个模块负责自己监听后的逻辑】
|
||||
Server -> SendMessage(ReduceHP)
|
||||
|
||||
class ClientHandle
|
||||
{
|
||||
private void HandleMessage(MainPack mainpack)
|
||||
{
|
||||
...
|
||||
HpPack hpPack = mainpack.hpPack;
|
||||
int playerId = mainpack.playerId;
|
||||
var player = PlayerMgr.Instance.GetPlayer(playerId);
|
||||
if(player != null){
|
||||
player.Event.Send("Hpchange",hpPack); //局部的事件管理器
|
||||
GameEvent.Send("Hpchange",hpPack); //全局事件中心
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlayerHp
|
||||
{
|
||||
public ECSEventCmpt Event { get; set; }
|
||||
PlayerHp(){
|
||||
Event.AddEventListener<HpPack>(Hellp,HandleUpChange);
|
||||
}
|
||||
}
|
||||
|
||||
[Window(UILayer.UI)]
|
||||
class BattleMainUI: UIWindow
|
||||
{
|
||||
public override void RegisterEvent()
|
||||
{
|
||||
AddUIEvent<HpPack>(Hellp,HandleUpChange);
|
||||
}
|
||||
|
||||
public void HandleUpChange(HpPack pack){}
|
||||
}
|
||||
```
|
18
Books/3-3-内存池模块.md
Normal file
18
Books/3-3-内存池模块.md
Normal file
@@ -0,0 +1,18 @@
|
||||
## 3-3.内存池模块 - MemoryPool
|
||||
使用案例
|
||||
``` csharp
|
||||
/// <summary>
|
||||
/// 资源组数据。
|
||||
/// <remarks>DisposeGroup。</remarks>
|
||||
/// </summary>
|
||||
public class AssetGroup : IMemory
|
||||
{
|
||||
public void Clear(){}
|
||||
}
|
||||
|
||||
//内存池中获取/生成内存对象。
|
||||
AssetGroup assetGroup = MemoryPool.Acquire<AssetGroup>();
|
||||
|
||||
//释放内存对象还给内存池。
|
||||
MemoryPool.Release(assetGroup);
|
||||
```
|
47
Books/3-4-对象池模块.md
Normal file
47
Books/3-4-对象池模块.md
Normal file
@@ -0,0 +1,47 @@
|
||||
## 3-4.对象池模块 - ObjectModule
|
||||
使用案例
|
||||
``` csharp
|
||||
/// <summary>
|
||||
/// Actor对象。
|
||||
/// </summary>
|
||||
public class Actor : ObjectBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 释放对象。
|
||||
/// </summary>
|
||||
/// <param name="isShutdown">是否是关闭对象池时触发。</param>
|
||||
protected override void Release(bool isShutdown){}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actor对象池。
|
||||
/// </summary>
|
||||
private IObjectPool<Actor> _actorPool;
|
||||
|
||||
void Start()
|
||||
{
|
||||
//创建允许单次获取的对象池。
|
||||
_actorPool = GameModule.ObjectPool.CreateSingleSpawnObjectPool<Actor>(Utility.Text.Format("Actor Instance Pool ({0})", name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建Actor对象。
|
||||
/// </summary>
|
||||
/// <returns>Actor对象实例。</returns>
|
||||
public Actor CreateActor()
|
||||
{
|
||||
Actor ret = null;
|
||||
if (_actorPool.CanSpawn())
|
||||
{
|
||||
ret = _actorPool.Spawn();
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = MemoryPool.Acquire<Actor>();
|
||||
_actorPool.Register(ret,true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
```
|
5
Books/3-5-UI模块.md
Normal file
5
Books/3-5-UI模块.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 3-5.UI模块 - UIModule
|
||||
使用案例
|
||||
``` csharp
|
||||
|
||||
```
|
4
Books/3-6-配置表模块.md
Normal file
4
Books/3-6-配置表模块.md
Normal file
@@ -0,0 +1,4 @@
|
||||
## 3-6.配置表模块 - ConfigLoader
|
||||
``` csharp
|
||||
|
||||
```
|
2
Books/3-8-网络模块.md
Normal file
2
Books/3-8-网络模块.md
Normal file
@@ -0,0 +1,2 @@
|
||||
## 8.网络模块 - Network
|
||||
待补充
|
15
Books/99-各平台运行RunAble.md
Normal file
15
Books/99-各平台运行RunAble.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# TEngine
|
||||
|
||||
### 日志记录编辑器下运行
|
||||

|
||||
|
||||
---
|
||||
### Win64位包运行
|
||||
|
||||

|
||||
|
||||
---
|
||||
### 安卓真机运行
|
||||

|
||||
|
||||
### TODO - IOS环境运行
|
BIN
Books/src/1-1.png
Normal file
BIN
Books/src/1-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 133 KiB |
BIN
Books/src/1-2.png
Normal file
BIN
Books/src/1-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 218 KiB |
BIN
Books/src/2-1.png
Normal file
BIN
Books/src/2-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 140 KiB |
Reference in New Issue
Block a user