草庐IT

微信小程序源码及H5小游戏源码内核构建方法

hsifbab 2023-04-05 原文

  在创建微信小程序源码及h5小游戏源码过程中,我们将创建我们的游戏应用程序(使用 Unity 3d),我们将连接到我们的WebSocket服务器。议使用 Unity Hub 来管理您的 Unity 下载,因为它允许您拥有多个版本的 Unity,如果您愿意的话。在撰写本文时,我使用的是 Unity 版本2020.3.23f1。在大多数情况下,我们今天将介绍的内容应该适用于所有 2018、2019、2020 版本的 Unity。我建议使用 Unity 的 LTS(长期支持版本)版本之一,您可以从LTS Releases Unity Page下载它们。在我们构建游戏应用程序时,使用其中一个版本可确保更高的稳定性和支持。

       仓库源码:y.wxlbyx.icu
  所以,现在我们已经在我们的机器上安装了 Unity3D 引擎,让我们创建我们的 Unity 项目。
  创建我们的 Unity 项目
  首先,让我们打开 Unity Hub 并确保我们在应用程序的项目部分如下图所示:

  进入 Unity Hub 后,选择“新建”按钮旁边的向下插入符号,然后选择您想要创建应用程序的 Unity 版本。
  您现在应该看到一个标题为“创建新项目...”的对话框,并确保您在“模板”部分中选择了“3D”。在“设置”部分,选择项目名称(我将使用“有史以来最好的游戏 - 多人游戏”)并选择您希望创建项目的位置。准备就绪后,选择“创建”按钮,如下图所示:


  选择“创建”后,您将看到 Unity 开始工作,因为它创建了我们将基于“3D”项目模板使用的基础项目。
  创建我们的玩家移动脚本
  创建玩家角色 player 后,让我们通过创建移动脚本来为其添加在场景中移动的能力。为了让事情井井有条,让我们快速创建一个“Scripts”文件夹来保存我们所有的脚本,并在该文件夹中创建一个名为“Player”的文件夹来保存播放器特定的文件夹。
  因此,在“Project”窗口的 Assets 文件夹中,让我们创建一个名为“Scripts”的文件夹。创建后,让我们进入该文件夹并创建一个名为“Player”的文件夹。创建文件夹后,让我们创建一个名为“Movement”的新 C# 脚本。
  注意:当我们创建脚本时,请确保在取消选择文件之前更改文件的名称。否则你将不得不做额外的工作来确保你的脚本被正确命名。我不会详细说明这意味着什么,因为需要一些时间来解释,但请注意这一点。
  在我们的 Player 文件夹中,右键单击空白区域并选择“创建 > C# 脚本”。一旦出现脚本图标,我们将命名我们的脚本运动,如下所示:
  现在我们的“运动”脚本已经创建,让我们打开它和我们的代码。所以继续双击该文件并在您首选的编辑器中打开我们的脚本(默认情况下,Unity 使用 Visual Studio)并删除所有默认代码,直到您的文件如下所示:

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

public class Movement : MonoBehaviour
{

}

  现在我们的文件已经很清楚了,让我们在文件中的 Movement 公共类中添加这行代码:

  float movementSpeed = 5.0f;

        这个浮点变量将用于确定我们播放器的速度。从理论上讲,您可以将此值设置为您想要的任何值,但在这种情况下,我们将其设置为 5.0f。
  添加第一个变量后,让我们添加实际移动玩家角色的功能。在我们的新变量下面,让我们添加以下代码:

void Update()
    {
        Vector3 moveDir = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        Vector3 newMoveDir = moveDir * movementSpeed;
        transform.position += newMoveDir * Time.deltaTime;

    }

  此代码利用 Unity 引擎每帧调用的 Update 函数,并根据播放器的移动输入(WASD 和箭头键)更改播放器位置。moveDir变量创建一个 Vector3 对象,该对象使用 Input.GetAxis 方法存储玩家的输入。GetAxis根据按下的方向输出一个从 -1 到 1 的值,传入Input.GetAxis方法的 Horizo ​​ntal和Vertical参数对应于键输入:
  水平:A/D/左箭头/右箭头
  垂直:W/S/向上箭头/向下箭头
  一旦这个 Vector3 对象被创建,它就会被添加到玩家的对象变换位置,它决定了玩家在世界中的位置。
  现在我们已经创建了脚本,我们需要将它添加到我们的玩家对象中,以便我们的玩家角色移动。让我们导航回 Unity 编辑器,然后选择我们的 Cube。
  在 Inspector 窗口中,导航到底部并单击“添加组件”按钮,如下所示:
  创建多人游戏套接字连接
  在花了一些时间构建我们非常简单的游戏之后,是时候将我们的游戏连接到我们之前制作的多人游戏服务器了。为此,我们需要在项目中添加两个新脚本:PlayerData和SocketManager。
  这两个脚本对于使我们的游戏与我们的 WebSocket 服务器有效通信至关重要。
  因此,让我们从 PlayerData 脚本开始。
  在我们项目的 Scripts 文件夹中,让我们创建一个名为“Multiplayer”的新文件夹。这个文件夹将包含我们游戏的多人游戏方面的所有代码。打开我们新建的“Multiplayer”文件夹并创建一个新脚本(右键单击> Create > C# Script)并命名为“PlayerData”。在我们的 PlayerData 脚本文件中,将其全部内容替换为以下代码:

using System;

[Serializable]
public struct PlayerData
{
    public string id;
    public float xPos;
    public float yPos;
    public float zPos;
    public double timestamp;
}

  这个脚本本质上是为我们将传递给我们的服务器的数据设置结构,其中将包含有关我们玩家角色当前位置的信息。我们创建这个脚本的原因是因为它可以让我们轻松地将我们的玩家数据转换为我们可以轻松发送到我们的服务器的 JSON 对象/字符串。
  如果您查看代码,您可能会注意到[Serializable]行。这实质上使我们创建的这个数据结构成为可序列化的,这意味着我们可以使用一些基本的 Unity 实用程序轻松地将这些数据转换为其他形式。
  您可能还注意到这个 PlayerData 对象是一个struct。这允许我们将此 PlayerData 对象用作“结构”,作为我们的数据对象将遵循的形式,并与说一个类相比保持该数据非常轻量级。所以这可能是描述这一点的最糟糕的方式,但如果您有兴趣进一步了解这一点,我鼓励您查看“结构和类之间的差异”。我保证你会学到很多东西。
  当我们继续我们的 SocketManager 脚本时,您将看到我们使用 PlayerData 结构来快速存储我们的数据并将其转换为 JSON,然后再将其发送到服务器。
  现在我们已经创建了 PlayerData 脚本(或者更好地描述为 PlayerData 结构),让我们创建 SocketManager 脚本,我们将使用它来管理我们的连接、向游戏服务器发送数据和从游戏服务器接收数据。
  在 Unity 编辑器的 Project 区域的 Script 文件夹中,让我们在“Multiplayer”文件夹中添加一个名为“SocketManager”的新脚本。我们现在应该在 Multiplayer 文件夹中看到两个脚本 PlayerData 和 SocketManager,如下所示:
  16-Multiplayer-scripts-in-folder
  创建该文件后,让我们打开该文件。进入文件后,删除为您预先制作的所有代码并添加以下代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;
using Newtonsoft.Json.Linq;

public class SocketManager : MonoBehaviour
{
    WebSocket socket;
    public GameObject player;
    public PlayerData playerData;

    // Start is called before the first frame update
    void Start()
    {

        socket = new WebSocket("ws://localhost:8080");
        //socket = new WebSocket("ws://websocket-starter-code-multiplayer-websocket-app.bsh-serverconnect-b3c-4x1-162e406f043e20da9b0ef0731954a894-0000.us-south.containers.appdomain.cloud/");
        socket.Connect();

        //WebSocket onMessage function
        socket.OnMessage += (sender, e) =>
        {

            //If received data is type text...
            if (e.IsText)
            {
                //Debug.Log("IsText");
                //Debug.Log(e.Data);
                JObject jsonObj = JObject.Parse(e.Data);

                //Get Initial Data server ID data (From intial serverhandshake
                if (jsonObj["id"] != null)
                {
                    //Convert Intial player data Json (from server) to Player data object
                    PlayerData tempPlayerData = JsonUtility.FromJson<PlayerData>(e.Data);
                    playerData = tempPlayerData;
                    Debug.Log("player ID is " + playerData.id);
                    return;
                }

            }

        };

        //If server connection closes (not client originated)
        socket.OnClose += (sender, e) =>
        {
            Debug.Log(e.Code);
            Debug.Log(e.Reason);
            Debug.Log("Connection Closed!");
        };
    }

    // Update is called once per frame
    void Update()
    {
        //Debug.Log(player.transform.position);
        if (socket == null)
        {
            return;
        }

        //If player is correctly configured, begin sending player data to server
        if (player != null && playerData.id != "")
        {
            //Grab player current position and rotation data
            playerData.xPos = player.transform.position.x;
            playerData.yPos = player.transform.position.y;
            playerData.zPos = player.transform.position.z;

            System.DateTime epochStart =  new System.DateTime(1970, 1, 1, 8, 0, 0, System.DateTimeKind.Utc);
            double timestamp = (System.DateTime.UtcNow - epochStart).TotalSeconds;
            //Debug.Log(timestamp);
            playerData.timestamp = timestamp;

            string playerDataJSON = JsonUtility.ToJson(playerData);
            socket.Send(playerDataJSON);
        }

        if (Input.GetKeyDown(KeyCode.M))
        {
            string messageJSON = "{\"message\": \"Some Message From Client\"}";
            socket.Send(messageJSON);
        }
    }

    private void OnDestroy()
    {
        //Close socket when exiting application
        socket.Close();
    }

}

  现在这是一大段代码。所以我要做的就是把它分成 6 个部分并总结每个部分发生的事情。一旦我们理解了这段代码,我们就可以设置我们的 Unity 场景来使用它并连接到我们之前创建的服务器。但在我们继续之前,您需要做一些非常重要的事情来使 SocketManager 脚本工作
  现在所有必要的文件都准备好了,让我们继续。正如我所提到的,我现在将套接字管理器分解为单独的部分。

1. 进口

  using System.Collections;
  using System.Collections.Generic;
  using UnityEngine;
  using WebSocketSharp;
  using Newtonsoft.Json.Linq;

  本节非常简单,因为我们只是导入了运行代码所需的所有必要框架。需要注意的最重要的事情是我们正在导入WebSocketSharp(从我们的 .dll 文件)和Newtonsoft.Json.Linq。注意:在 Unity 2020 之前,您需要手动将 Newtonsoft 添加到您的项目中。因此,如果您使用的是 2020 年之前的版本,您可以访问 [此页面] 以了解有关将 Newtonsoft 添加到您的项目的更多信息。
  2.变量

  WebSocket socket;
  public GameObject player;
  public PlayerData playerData;

  这部分代码声明了我们将在整个脚本中使用的变量,以使我们的 SocketManager 工作。socket变量是我们将用来连接、发送和接收来自服务器的消息的变量。player游戏对象只是代表我们游戏中的玩家,而playerData对象是我们将发送到套接字服务器的数据。这些将是我们的经理工作所需的唯一变量。
  3. 创建我们的 WebSocket 连接
  因此,在我们的 Start 函数(启动此脚本时触发的函数)中,您将首先看到以下代码行:

  socket = new WebSocket("ws://localhost:8080");
  socket.Connect();

  第一行代码指定了我们的 websocket 游戏服务器的位置以及连接的位置。这可以在我们的本地机器上或 Red Hat OpenShift 上的远程服务器上。唯一需要注意的是,由于我们使用的是 WebSocket 协议,所以我们的网址需要以ws://开头
  第二行代码实际上会将我们连接到前行指定的游戏服务器。这应该会启动客户端(Unity 游戏)和服务器之间的“握手”。
  4. 接收消息

  //WebSocket onMessage function
  socket.OnMessage += (sender, e) =>
  {
  //If received data is type text...
  if (e.IsText)
  {
  //Debug.Log("IsText");
  //Debug.Log(e.Data);
  JObject jsonObj = JObject.Parse(e.Data);
  //Get Initial Data server ID data (From intial serverhandshake
  if (jsonObj["id"] != null)
  {
  //Convert Intial player data Json (from server) to Player data object
  PlayerData tempPlayerData = JsonUtility.FromJson<PlayerData>(e.Data);
  playerData = tempPlayerData;
  Debug.Log("player ID is " + playerData.id);
  return;
  }
  }
  };
  //If server connection closes (not client originated)
  socket.OnClose += (sender, e) =>
  {
  Debug.Log(e.Code);
  Debug.Log(e.Reason);
  Debug.Log("Connection Closed!");
  };

  所以这部分代码应该看起来与我们在服务器上创建的代码非常相似。本质上,这段代码中有两个监听器在运行;一个 OnMessage 侦听器和一个 OnClose 侦听器。与我们的服务器类似,这两个侦听器正在侦听传入消息以及何时从服务器触发关闭方法(也就是服务器出于某种原因关闭它与我们的游戏的连接)。
  在我们的 OnMessage 代码部分中,您将看到:
  if (e.IsText)...
  这只是一个健全性检查,因为我们的服务器理论上可以向我们发送二进制或文本数据。在我们的例子中,我们只想处理文本数据。

  JObject jsonObj = JObject.Parse(e.Data);
  //Get Initial Data server ID data (From initial server handshake
  if (jsonObj["id"] != null)
  {
  //Convert Initial player data Json (from server) to Player data object
  PlayerData tempPlayerData = JsonUtility.FromJson<PlayerData>(e.Data);
  playerData = tempPlayerData;
  Debug.Log("player ID is " + playerData.id);
  return;
  }

  此代码解析从服务器发送的 JSON 字符串数据,然后检查 JSON 字符串是否采用我们希望使用的格式。在这种情况下,它专门查看 JSON 数据是否具有id字段。这样做是因为我们将服务器设计为向客户端发送一组特定的数据,其中包含玩家 ID 以供以后存储。您可以使用这样的逻辑来发送具有特定字段的数据,并根据数据可能具有的字段对数据执行不同的操作。
  一旦我们确认了该字段,我们就会将我们收到的 JSON 数据转换为一个 PlayerData 对象,然后我们可以在我们的代码中使用该对象。这非常方便,因为它通过自动将 JSON 字段链接到 PlayerData 对象中的数据字段来为我们节省大量时间,然后我们可以在 Unity 代码中使用这些字段。它不会包含 PlayerData 对象所期望的所有数据,但 Unity 能够实现这一点,并且只是根据属性类型将不存在的字段设置为默认值。
  5. 向游戏服务器发送数据

  //Debug.Log(player.transform.position);
  if (socket == null)
  {
  return;
  }
  //If player is correctly configured, begin sending player data to server
  if (player != null && playerData.id != "")
  {
  //Grab player current position and rotation data
  playerData.xPos = player.transform.position.x;
  playerData.yPos = player.transform.position.y;
  playerData.zPos = player.transform.position.z;
  System.DateTime epochStart = new System.DateTime(1970, 1, 1, 8, 0, 0, System.DateTimeKind.Utc);
  double timestamp = (System.DateTime.UtcNow - epochStart).TotalSeconds;
  //Debug.Log(timestamp);
  playerData.timestamp = timestamp;
  string playerDataJSON = JsonUtility.ToJson(playerData);
  socket.Send(playerDataJSON);
  }

  在我们的更新函数中,我们做了很多事情。我们要做的第一件事就是检查我们的套接字变量是否有效。如果不是,我们只是短路/停止我们的代码,因为它下面的所有代码都需要套接字对象有效。
  如果套接字对象有效,我们有一个“IF 语句”来检查我们的玩家游戏对象是否有效,以及我们的 playerData id 字段是否有一个非空的字符串值。这个检查做了两件重要的事情:
  它确保我们有一个与该脚本关联的有效播放器。这很重要,因为我们的 IF 语句中的所有代码都需要有效的玩家数据(例如位置)才能发送到服务器。
  它确保玩家有一个ID。这很重要,因为在我们向服务器发送数据之前,我们需要一个标识符让服务器知道哪个客户端发送了数据。这只是确保我们的游戏在我们正确配置与服务器的连接之前不会发送数据。
  在我们确保我们的玩家和玩家 ID 是有效的之后,剩下的代码就是获取关于玩家当前位置的信息,并设置一个时间戳,在这个时间戳上我们捕获了关于玩家的这些信息。这些都存储在我们的 PlayerData 对象中,然后由 Unity 的 JSONUtility 转换为 JSON 字符串并发送到我们的服务器以供其检索。
  就像我们的游戏正在向我们的服务器发送关于我们的玩家以及玩家在任何特定时刻的位置的数据一样。
  5.关闭游戏服务器连接


  private void OnDestroy()
  {
  //Close socket when exiting application
  socket.Close();
  }


  最后,我们有一小部分代码在脚本本身被销毁(也就是游戏应用程序因任何原因关闭时)被触发。此代码只是向我们的服务器发送一条“关闭”消息,这将触发服务器上的 OnClose 侦听器。这是一组重要的代码,因为服务器必须知道玩家何时离开游戏,这样它才能将该玩家从服务器中移除并通知所有其他玩家。
  服务器启动并运行后,返回 Unity 编辑器并按下播放按钮运行我们的游戏。游戏开始后,返回终端窗口,您现在应该会看到终端窗口打印有关我们的播放器的信息,如下所示:

  Client 50c7f039-1d1a-45d5-a695-a8656c6a4ba8 Connected!
  Player Message
  {
  id: '50c7f039-1d1a-45d5-a695-a8656c6a4ba8',
  xPos: -0.7148541808128357,
  yPos: 0.5,
  zPos: 0.6946321129798889,
  timestamp: 1638454171.455932
  }
  Player Message
  {
  id: '50c7f039-1d1a-45d5-a695-a8656c6a4ba8',
  xPos: -0.7148541808128357,
  yPos: 0.5,
  zPos: 0.6946321129798889,
  timestamp: 1638454171.488816
  }

有关微信小程序源码及H5小游戏源码内核构建方法的更多相关文章

  1. ruby - 在 Ruby 中构建长字符串的简洁方法 - 2

    在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo

  2. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  3. 微信小程序通过字典表匹配对应数据 - 2

    前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立

  4. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  5. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  6. ruby - 使用 rbenv 和 ruby​​-build 构建 ruby​​ 失败,出现 undefined symbol : SSLv2_method - 2

    我正在尝试在配备ARMv7处理器的SynologyDS215j上安装ruby​​2.2.4或2.3.0。我用了optware-ng安装gcc、make、openssl、openssl-dev和zlib。我根据README中的说明安装了rbenv(版本1.0.0-19-g29b4da7)和ruby​​-build插件。.这些是随optware-ng安装的软件包及其版本binutils-2.25.1-1gcc-5.3.0-6gconv-modules-2.21-3glibc-opt-2.21-4libc-dev-2.21-1libgmp-6.0.0a-1libmpc-1.0.2-1libm

  7. ruby - 我需要从 facebook 游戏中抓取数据——使用 ruby - 2

    修改(澄清问题)我已经花了几天时间试图弄清楚如何从Facebook游戏中抓取特定信息;但是,我遇到了一堵又一堵砖墙。据我所知,主要问题如下。我可以使用Chrome的检查元素工具手动查找我需要的html-它似乎位于iframe中。但是,当我尝试抓取该iframe时,它​​是空的(属性除外):如果我使用浏览器的“查看页面源代码”工具,这与我看到的输出相同。我不明白为什么我看不到iframe中的数据。答案不是它是由AJAX之后添加的。(我知道这既是因为“查看页面源代码”可以读取Ajax添加的数据,也是因为我有b/c我一直等到我可以看到数据页面之后才抓取它,但它仍然不存在)。发生这种情况是因为

  8. ruby-on-rails - 如何构建复杂的 Rails 系统 - 2

    关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭8年前。Improvethisquestion我们有以下(以及更多)系统,我们将数据从一个应用推送/拉取到另一个:托管CRM(InsideSales.com)Asterisk电话系统(内部)横幅广告系统(openx,我们托管)潜在客户生成系统(自行开发)电子商务商店(spree,我们托管)工作板(本土)一些工作网站抓取+入站工作提要电子邮件传送系统(如Mailchimp,自主开发)事件管理系统(如eventbrite,自主开发)仪表板系统(大量图表和

  9. python - Ruby 或 Python 的 3d 游戏引擎? - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?

  10. ruby-on-rails -/usr/local/lib/libz.1.dylib,文件是为 i386 构建的,它不是被链接的体系结构 (x86_64) - 2

    在我的mac上安装几个东西时遇到这个问题,我认为这个问题来自将我的豹子升级到雪豹。我认为这个问题也与macports有关。/usr/local/lib/libz.1.dylib,filewasbuiltfori386whichisnotthearchitecturebeinglinked(x86_64)有什么想法吗?更新更具体地说,这发生在安装nokogirigem时日志看起来像:xslt_stylesheet.c:127:warning:passingargument1of‘Nokogiri_wrap_xml_document’withdifferentwidthduetoproto

随机推荐