Photon多人游戏开发教程

http://gad.qq.com/article/detail/26112

PUN介绍

入门

Photon Unity Networking(首字母缩写PUN)是一个Unity多人游戏插件包。它提供了身份验证选项、匹配,以及快速、可靠的通过我们的Photon后端实现的游戏内通信。

PUN输出几乎所有Unity支持的平台,且有两种选项:

注意:对于Unity 5,两个PUN插件包都含相同的文件。你可以买PUN+ 来获得60个月的100 CCU,但客户端上仍使用PUN Free。

PUN、PUN+和UNet的对比


 


PUN


PUN+


UN


 备注


价格


免费


$ 95


免费


免费CCU


20


100



全球托管




×


UNet需要主服务器。


Unity Networking兼容





老版本的Unity Networking (不是UNet).


Unity免费: 网页,PC





Unity 4 免费: iOS, Android


×



!


UNet在手机上有穿透问题


Unity 5 免费: iOS, Android




!


UNet需要移动电话的中继服务。


主机迁移




×


房间、大厅支持




×

详见文章Photon Unity Networking (PUN) Compared To Unity Networking (UN)来查看两者比较的细节。

一些必须的代码

要充分使用PUN,你将需要写一些脚本。本页向你展示入门的最重要部分。

你也应该花一些时间来通过Marco Polo Tutorial

连接

C#代码示例:

 

1

PhotonNetwork.ConnectUsingSettings("v4.2");

上面的代码是你需要连接并开始使用Photon功能的所有代码。

ConnectUsingSettings 设置你的客户端的游戏版本并使用一个由PUN设置向导写入的配置文件,该配置文件保存在PhotonServerSettings里面。

匹配

接下来,你想加入现有的房间或创建自己的。下面的代码显示了启动或加入游戏的可能方法调用。

 

1

2

3

4

5

6

7

8

9

10

11

//加入名为"someRoom"的房间

PhotonNetwork.JoinRoom("someRoom");

//如果没有开放的游戏就会失败。错误回调: OnPhotonJoinRoomFailed

 

//尝试加入任何随机游戏:

PhotonNetwork.JoinRandomRoom();

//如果没有开放的游戏就会失败。错误回调: OnPhotonRandomJoinFailed

 

//创建名为"MyMatch"的房间。

PhotonNetwork.CreateRoom("MyMatch");

//如果名为"MyMatch"的房间已存在就会失败并调用:OnPhotonCreateRoomFailed

好朋友常常想要一起玩游戏。如果他们可以交流(例如 使用Photon Chat, Facebook), 他们可以瞎编一个房间名并使用JoinOrCreateRoom方法。因为他们知道房间的名字,他们可以创建为他人不可见,像这样:

C#代码示例:


1

2

3

4

    RoomOptions roomOptions = new RoomOptions() { isVisible = false, maxPlayers = 4 };

    PhotonNetwork.JoinOrCreateRoom(nameEveryFriendKnows, roomOptions,

TypedLobby.Default);

使用 JoinOrCreateRoom方法,如果房间不存在就会创建该房间。如果房间满了, OnPhotonJoinRoomFailed会被调用 (如果你在某个地方实现了这个回调函数)。

游戏

GameObjects可以被实例化为"networked GameObjects"。它们会有一个可以被识别的PhotonView组件和一个所有者(或控制者)。所有者会更新其他人。持续更新可以通过拖拽一个脚本到一个PhotonView的 Observed字段被发送。需要更新的脚本必须实现OnPhotonSerializeView像这样:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

// 在一个"observed"[ "observed",被观察的。] 脚本里:

public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)

{

    if (stream.isWriting)

    {

        Vector3 pos = transform.localPosition;

        stream.Serialize(ref pos);

    }

    else

    {

        Vector3 pos = Vector3.zero;

        stream.Serialize(ref pos);  // pos被填充。必须在某个地方使用

    }

}

客户端可以为不见用的操作执行Remote Procedure Calls:


1

2

3

4

5

6

7

8

9

10

// 定义一个可以被其他客户端调用的方法:

[PunRPC]

public void OnAwakeRPC(byte myParameter)

{

    //Debug.Log("RPC: ‘OnAwakeRPC‘ Parameter: " + myParameter + " PhotonView: " + this.photonView);

}

 

// [...]

// 在别的某个地方调用该RPC

photonView.RPC("OnAwakeRPC", PhotonTargets.All, (byte)1);

独立于GameObjects, 你也可以发送你自己的事件:

 

1

PhotonNetwork.RaiseEvent((byte)eventCode, (object)eventContent,(bool)sendReliable, (RaiseEventOptions)options)

初始设置

Photon Unity Networking (PUN)真的很容易设置。把PUN导入到一个新的项目中,然后PUN设置向导就会弹出来,如图0-1所示。通过输入一个邮箱地址来注册一个新的(免费) Photon Cloud帐号,或者复制粘贴一个已有的AppId到该字段里。打完收工。

如果你想要自己托管一个Photon服务器,点击"skip",然后像如下描述的那样编辑 PhotonServerSettings。

图 0-1 PUN设置向导

要连接,你只需在你的代码中调用PhotonNetwork.ConnectUsingSettings()。如果你需要更多的控制,详见下面的 Connect Manually。

Photon服务器设置

设置向导会添加一个PhotonServerSettings文件到你的项目,用来保存配置。如图0-2所示,这也是去编辑服务器设置的地方。

图 0-2 PhotonServerSetting文件属性

你可以设置AppId、Photon Cloud Region和更多的。你的客户端的Game Version是在代码里被设置的。

要选择的最重要的选项是托管类型。

托管类型

通过Hosting Type你选择处理你游戏的服务器和其他配置。

Photon Cloud和Best Region都涉及到我们管理的云服务。您可以选择特定区域,也可以让客户选择最佳ping区域。

如果你想在别的地方运行Photon服务器,选择Self Hosted。安装程序如下。

或者,你的客户可以在脱机模式。

最佳托管区域

最佳区域模式将在应用首次启动的时候ping所有已知区域。由于这需要一点时间,结果被存储在PlayerPrefs。这会加快连接时间。

你可以设置哪些区域可以忽略。在更少的区域分发客户端会导致剩余区域的玩家更多。这在游戏流行之前是有益的。

使用PhotonNetwork.OverrideBestCloudServer()来定义要使用的另一个区域。

自托管

如果你为iOS开发游戏可以考虑阅读 PUN and IPv6how to setup Photon Server for IPv6

如果你要自己托管一个Photon服务器,你应在PhotonServerSettings里面设置好它的地址和端口。当这些都被正确设置了,你可以在你的代码里调用PhotonNetwork.ConnectUsingSettings()。

确保您的客户端可以到达输入的地址。它可以是一个公共的、静态的IP地址、主机名或在你的客户端也使用的网络中的任何地址。

端口取决于所选协议,所以请确保这两个字段匹配。清除该字段会将其重置为默认端口。

协议

这里默认是(可靠的)UDP,但Photon还支持使用TCP以及将允许一个可靠的HTTP协议。

我们建议你坚持UDP。PUN+不支持TCP。WebGL导出只能使用WebSockets。

客户端设置

客户端设置部分包含了每个项目应设置的几个选项。

当你勾选Auto-Join Lobby时,PUN将在连接(或离开房间)时自动加入默认大厅。Photon的大厅提供当前房间的列表,这样玩家可以选择一个加入。这个默认是关闭的,因为更好的选择是使用随机匹配,就像所有的演示案例中使用的那样。

启用Enable Lobby Stats来从服务器获取大厅统计信息。如果游戏使用多个大厅,并且你想要向玩家展示每一个活动,则这个统计信息会很有用。每个大厅,你都可以获取这些属性: name、type、room和playercount。详见PhotonNetworking.LobbyStatistics!

这些设置在PUN v1.60版本引入。

远程过程调用列表

Remote Procedure Calls使你可以在一个房间里调用所有客户端上的方法。PUN 将这些方法的列表保存在PhotonServerSettings。对于最初的设置,这是不相关的。详见Remote Procedure Calls。

手动连接

作为替代自动连接的PhotonNetwork.ConnectUsingSettings()方法你可以通过PhotonNetwork.ConnectToMaster()方法来手动连接你自己的Photon服务器。当你托管付费Photon服务器时这是有用的。

对于ConnectToMaster(),你需要提供一个masterServerAddress和一个port参数。地址可以是你的On-Premises DNS名称或一个IP。它可以包括冒号后的端口(然后传递0作为端口)或您可以单独通过端口。

ConnectToMaster()方法有更多的另外两个参数 : "appID"和"gameVersion"。两者都只与Photon Cloud有关,并且当你自己托管Photon服务器时,可以设置为任何值。

对于Photon Cloud, 使用ConnectUsingSettings()方法。它涉及到我们的Name Server自动找到一个区域的主服务器。

功能概述

内容提要

· PUN插件

· 连接和主服务器

· 版本控制

· 创建和加入游戏

· MonoBehaviour回调函数

· 在游戏房间里发送消息

· Photon视图组件

· 观察Transform

· 观察MonoBehaviour

· 远程过程调用

· RPCs和加载关卡的时机

PUN

当你导入PUN时,设置向导窗口会弹出来。如何设置请看导入PUN与设置小节。

PUN由相当多的文件组成, 然而只有一个是真正重要的: PhotonNetwork。这个类包含所有需要的函数和变量.。如果您有自定义要求,可以随时修改源文件。

要从UnityScript中使用PUN,你需要把 "PhotonNetwork"和"UtilityScripts" 文件夹移动到AssetsPlugins文件夹。

为了告诉你这个API如何工作,这里有几个例子。

回到内容提要

连接

PhotonNetwork始终使用主服务器和一个或多个游戏服务器。主服务器管理当前可用的游戏并进行匹配。一旦房间被发现或创建,实际的游戏是在游戏服务器上完成的。

所有的服务器都运行在专用的机器上,没有所谓的玩家托管的服务器。你不必费心记住该服务器组织,PUN会为你处理它。

C#代码示例:


1

PhotonNetwork.ConnectUsingSettings("v1.0");

上面的代码是你需要连接并开始使用Photon功能的所有代码。ConnectUsingSettings 设置你的客户端的游戏版本并使用一个由PUN设置向导写入的配置文件,该配置文件保存在PhotonServerSettings里面。你也可以修改文件PhotonServerSettings 属性来连接到你自己的服务器。或者,使用Connect()方法来忽略该PhotonServerSettings 文件。

版本控制

Photon的负载均衡逻辑使用你的AppId来区分你的和他人的游戏。玩家也会被游戏版本分开,ConnectUsingSettings的参数(见上文)。通过这种方式,您可以发布新功能的客户端,而不破坏旧版本的游戏。

由于我们不能保证不同PUN的版本之间相互兼容,PUN把它自己的版本号添加到你的游戏里。更新PUN可能会从旧的版本中分离出新的客户端,但不会打破老客户端。

创建和加入游戏

接下来,你想加入或创建一个房间。下面的代码展示了一些必要的函数:


1

2

3

4

5

6

7

8

9

10

//加入一个房间

PhotonNetwork.JoinRoom(roomName); 

 

//创建这个房间。

PhotonNetwork.CreateRoom(roomName); 

// 如果该房间已存在则会失败并调用: OnPhotonCreateGameFailed

 

//尝试加入任何随机游戏:

PhotonNetwork.JoinRandomRoom(); 

//如果没有匹配的游戏则会失败并调用: OnPhotonRandomJoinFailed

在最好的情况下,您的游戏使用随机配对。JoinRandomRoom()将尝试加入任何房间。如果该方法失败了(没有房间接受另一个玩家),只需创建一个新的房间,并等到其他玩家随机加入它为止。

或者,您的客户端可以获得当前可用的房间列表。这是通过加入一个大厅来获得的。

大厅自动发送他们的房间列表到客户端,并在时间间隔内更新(从而减少流量)。玩家不会看到对方,且无法沟通(以防止当您的游戏繁忙时出问题)。

PhotonNetwork插件可以在其连接时自动加入默认大厅。把PhotonServerSettings文件里的"Auto-Join Lobby"属性开启即可。

当你的客户端在一个大厅里时,房间列表会得到更新, 这些更新会缓存。如果需要的话,你可以通过GetRoomList方法来每一帧访问房间列表。

C#代码示例:


1

2

3

4

foreach (RoomInfo room in PhotonNetwork.GetRoomList())

{

    GUILayout.Label(room.name + " " + room.playerCount + "/" + room.maxPlayers);

}

关于匹配的更多信息请参考Matchmaking And Room Properties。

回调函数

PhotonNetwork使用多个回调函数来让你的游戏知道状态的变化,如“已连接”或“已加入一个游戏”。像往常对Unity一样,回调可在任何脚本里实现。

如果你的脚本扩展Photon.PunBehaviour, 你可以单独重写每个回调。在这种情况下,您不必调用基类实现。

C#代码示例:


1

2

3

4

public override void OnJoinedRoom()

{

    Debug.Log("OnJoinedRoom() called by PUN: " + PhotonNetwork.room.name);

}

你不需要扩展PunBehaviour。如果你在其本身身上实现它所有的回调函数也会起作用。它们也在枚举PhotonNetworkingMessage中被列出和描述。

这包括建立游戏房间的基础知识。接下来是游戏中的实际交流。

发消息

在一个房间里,你可以发送网络信息给其他连接的玩家。此外,您还可以发送缓冲消息,也将被发送到未来连接的玩家(以玩家生成为例)。

发送消息可以使用两种方法。无论是RPCs,还是通过在一个由PhotonView观察的脚本里实现OnSerializePhotonView。

然而有更多的网络互动。你可以监听一些网络事件的回调函数,如OnPhotonInstantiate或OnPhotonPlayerConnected,并且你可以触发其中一些事件,如 PhotonNetwork.Instantiate。如果你被最后一段弄糊涂了,不要担心,下一步我们会为这些主题逐个做解释。

Photon视觉同步组件

PhotonView是一个用于发送消息(RPCs和OnSerializePhotonView)的脚本组件。你需要将PhotonView依附到游戏对象或预设上。请注意,PhotonView和Unity的NetworkView非常相似。

整个过程,你的游戏中需要至少一个PhotonView,才能发送消息和可选的实例化/分配其他的PhotonViews。

如图下图所示,添加一个PhotonView到一个游戏对象,只需选择一个游戏对象并使用: "Components/Miscellaneous/Photon View"。

图 0-1 Photon Cloud:Photon View

观察Transform

如果你将一个Transform绑定到PhotonView的观察属性上,你可以选择同步位置、旋转和尺度或玩家的这些属性组合。这可以极大的帮助制作原型或小游戏。注意:任何观察到的值变化将发送所有观察到的值-而不只是发生变化的那个单一值。此外,更新的值是不平滑的或插值。

观察MonoBehaviour

PhotonView可以被设置来观察MonoBehaviour。在这种情况下,脚本的OnPhotonSerializeView方法会被调用。此方法被调用来写入对象的状态并读取它,这取决于脚本是否由本地玩家控制。

下面简单的代码展示了如何用几行代码来增加角色状态同步:

C#代码示例:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)

{

   if (stream.isWriting)

   {

       //我们拥有这个玩家:把我们的数据发送给别的玩家

       stream.SendNext((int)controllerScript._characterState);

       stream.SendNext(transform.position);

       stream.SendNext(transform.rotation);

   }

   else

   {

       //网络玩家,接收数据

       controllerScript._characterState = (CharacterState)(int)stream.ReceiveNext();

       correctPlayerPos = (Vector3)stream.ReceiveNext();

       correctPlayerRot = (Quaternion)stream.ReceiveNext();

   }

}

观察选项

Observe Option字段让你选择更新如何发送以及何时被发送。该字段还会影响到OnPhotonSerializeView被调用的频率。

Off 顾名思义,关掉。如果该PhotonView被保留为RPCs限定时可以很有用。

Unreliable 更新如是被发送,但可能会丢失。这个想法是,下一次更新很快到来,并提供所需的正确的/绝对的值。这对于位置和其他绝对数据来说是有利的,但对于像切换武器这样触发器来说是不好的。当用于同步的游戏对象的位置,它会总是发送更新,即使该游戏对象停止运动(这是不好的)。

Unreliable on Change 将检查每一个更新的更改。如果所有值与之前发送的一样,该更新将作为可靠的被发送,然后所有者停止发送更新直到事情再次发生变化。这对于那些可能会停止运动的以及暂时不会创建进一步更新的游戏对象来说是有利的。例如那些在找到自己的位置后就不再移动的箱子。

Reliable Delta Compressed 将更新的每个值与它之前的值进行比较。未更改的值将跳过以保持低流量。接收端只需填入先前更新的值。任何你通过OnPhotonSerializeView写入的都会自动进行检查并以这种方式被压缩。如果没有改变, OnPhotonSerializeView不会在接收客户端调用。该“可靠的”部分需要一些开销,所以对于小的更新,应该考虑这些开销。

现在开始,以另一种方式交流:RPCs。

远程过程调用

Remote Procedure Calls (RPC)使你可以调用"networked GameObjects"上的方法,对由用户输入等触发的不常用动作很有用。

一个RPC会被在同房间里的每个玩家在相同的游戏对象上被执行,所以你可以容易地触发整个场景效果就像你可以修改某些GameObject。

作为RPC被调用的方法必须在一个带PhotonView组件的游戏对象上。该方法自身必须要被[PunRPC]属性标记。


1

2

3

4

5

[PunRPC]

void ChatMessage(string a, string b)

{

    Debug.Log("ChatMessage " + a + " " + b);

}

要调用该方法,先访问到目标对象的PhotonView组件。而不是直接调用目标方法,调用PhotonView.RPC()并提供想要调用的方法名称:


1

2

PhotonView photonView = PhotonView.Get(this);

photonView.RPC("ChatMessage", PhotonTargets.All, "jup", "and jup!");

你可以发送一系列的参数,但它必须匹配该RPC方法的定义。

这些是最基本的。详情请阅读Remote Procedure Calls.

时机

RPCs在指定的PhotonViews上被调用,并总是以接收客户端上的匹配者为目标。如果一个远程客户端还没有加载或创建匹配的PhotonView,这个RPC就会丢失!

因此,丢失RPCs一个典型的原因就是当客户端加载新场景的时候。它只需要一个已经加载有新游戏对象的场景的客户端,并且其他客户端不能理解这个RPC(直到这些客户端也加载了相同的场景)。

PUN可以帮你解决此问题。只需在你连接之前设置PhotonNetwork.automaticallySyncScene = true并在房间的主客户端上使用 PhotonNetwork.LoadLevel()。这样,一个客户端定义了所有客户端必须在房间/游戏中加载的关卡。

客户端可以停止执行接收到的消息来防止RPCs丢失(这正是LoadLevel方法帮你做的)。当你得到一个RPC来加载一些场景,立即设置isMessageQueueRunning = false直到该内容被初始化。

例子:


1

2

3

4

5

6

private IEnumerator MoveToGameScene()

{

    // 加载关卡前临时禁用进一步的网络信息处理

    PhotonNetwork.isMessageQueueRunning = false;

    Application.LoadLevel(levelName);

}

禁用消息队列将延迟传入和传出消息,直到队列被解锁。显然,当你准备好要继续的时候,打开队列是非常重要的

时间: 2024-08-30 07:28:21

Photon多人游戏开发教程的相关文章

Unity多玩家网络游戏开发教程1章Unity带有网络功能

Unity网络多玩家游戏开发教程第1章Unity自带网络功能 Unity拥有大量的第三方插件.专门提供了对网络功能的支持. 可是.大部分开发人员第一次接触到的还是Unity自带的网络功能.也就是大家常常说到的Unity Networking API.这些API是借助于组件NetworkView发挥作用的,而它能够简化开发人员大量的网络功能编码任务.本文选自<Unity网络多玩家游戏开发教程(大学霸内部资料)> NetworkView组件 在Unity中,NetworkView组件用于处理游戏在

Unity网络多玩家游戏开发教程第1章Unity自带网络功能

Unity网络多玩家游戏开发教程第1章Unity自带网络功能 Unity拥有大量的第三方插件,专门提供了对网络功能的支持.但是,大部分开发者第一次接触到的还是Unity自带的网络功能,也就是大家经常说到的Unity Networking API.这些API是借助于组件NetworkView发挥作用的,而它可以简化开发者大量的网络功能编码任务.本文选自<Unity网络多玩家游戏开发教程(大学霸内部资料)> NetworkView组件 在Unity中,NetworkView组件用于处理游戏在网络上

微信公众账号开发教程

微信公众账号开发教程 一.第1篇-引言 本文转载来自柳峰老师的博客,在这里非常感谢柳峰老师的分享和贡献! 内容方面,大概会涉及到: 1)前沿知识:微信公众帐号的分类.两种模式各自的特点和区别.开发模式的配置使用等: 2)API中各类消息的使用(我已经对api进行封装并打成了jar包,到时候会考虑分享出来): 3)微信公众帐号开发中的小技巧(如换行.通过代码发送表情.屏幕飘雪花.表情的接收识别.在Android和iOS上表现不一致等等): 4)与业务系统对接的方法(链接.短信等,除了技术讲解还会做

[051] 微信公众平台开发教程第22篇-如何保证access_token长期有效

为了使第三方开发者能够为用户提供更多更有价值的个性化服务,微信公众平台开放了许多接口,包括自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等,开发者在调用这些接口时,都需要传入一个相同的参数access_token,它是公众账号的全局唯一票据,它是接口访问凭证. access_token的有效期是7200秒(两小时),在有效期内,可以一直使用,只有当access_token过期时,才需要再次调用接口获取access_token.在理想情况下,一个7x24小时运行的系统,每天只需

微信开放平台 公众号第三方平台开发 教程三 一键登录授权给第三方平台

原文:微信开放平台 公众号第三方平台开发 教程三 一键登录授权给第三方平台 教程导航: 微信开放平台 公众号第三方平台开发 教程一 平台介绍 微信开放平台 公众号第三方平台开发 教程二 创建公众号第三方平台 微信开放平台 公众号第三方平台开发 教程三 一键登录授权给第三方平台 微信开放平台 公众号第三方平台开发 教程四 代公众号调用接口的SDK和demo 公众号第三方平台的开放,是为了让公众号运营者,在面向垂直行业需求时,可以一键登录授权给第三方的公众号运营平台,通过第三方开发者提供的公众号第三

微信开放平台 公众号第三方平台开发 教程五 代公众号发起网页授权源码

教程导航: 微信开放平台 公众号第三方平台开发 教程一 平台介绍 微信开放平台 公众号第三方平台开发 教程二 创建公众号第三方平台 微信开放平台 公众号第三方平台开发 教程三 一键登录授权给第三方平台 微信开放平台 公众号第三方平台开发 教程四 代公众号调用接口的SDK和demo 微信开放平台 公众号第三方平台开发 教程五 代公众号发起网页授权源码 由于最近比较忙,这个教程没有连续上,我会尽量连载.书节上回,上次我介绍了代公众号调用接口的方法和SDK的使用.不少朋友也都加了QQ群(2489462

android入门开发教程之网络性能的优化

我在麦子学院上android开发的时候,麦子学院android开发老师讲到Android开发过程中经常会涉及到性能优化的问题,应该从基础.网络.测试等各个层面进行整合优化.现在咱们聊聊Android开发之网络性能的优化. 1)避免频繁网络请求 访问server端时,建立连接本身比传输需要跟多的时间,如非必要,不要将一交互可以做的事情分成多次交互(这需要与Server端协调好).有效管理Service 后台服务就相当于一个持续运行的Acitivity,如果开发的程序后台都会一个service不停的

Android WiFi开发教程(一)——WiFi热点的创建与关闭

相对于BlueTooth,WiFi是当今使用最广的一种无线网络传输技术, 几乎所有智能手机.平板电脑和笔记本电脑都支持Wi-Fi上网.因此,掌握基本的WiFI开发技术是非常必要的.本教程将围绕一个小Demo初步与大家一同探讨WiFi开发. 先上效果图   Demo功能比较简单,四个按钮.两个文本和一个列表.功能主要有创建WiFi热点,关闭WiFi热点,搜索WiFi,连接WiFi,数据通讯.源码会在教程结尾提供. 本章节主要介绍WiFi热点的创建和关闭 需要用到的权限 <uses-permissi

微信应用号开发教程

——第一篇 微信应用号(小程序,「应用号」的新称呼)终于来了! 目前还处于内测阶段,微信只邀请了部分企业参与封测.想必大家都关心应用号的最终形态到底是什么样子?怎样将一个「服务号」改造成为「小程序」? 我们暂时以一款简单的第三方工具的实例,来演示一下开发过程吧.(公司的项目保密还不能分享代码和截图.小白是边加班边偷偷给大家写教程.感谢「名片盒」团队提供他们的服务号来动这个手术,所以小白的教程就用「名片盒」的公众号滚动更新发布吧??) OK,为了让大家尽快看到这份教程,小白注定要熬夜了!今晚开始更