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 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 云端渲染框架的整合更新 -> 正文阅读

[游戏开发]云端渲染框架的整合更新

问题回顾

云端渲染模块是连接客户端与服务端、客户端与客户端的主要通信框架,其目的是协调各个客户端的操作,保证各个客户端之间的画面一致性。主要实现的功能如下:

  • 车流一致。每个客户端的列车都遵循运行时刻表在同一指定时间到达。
  • 客流一致。每个乘客的行走轨迹一致,在同一时间完成上下车。
  • 实体一致。场景实例化的所有物体在每一个客户端都是相同的,同时创建,同时销毁。
  • 数据一致。由于大部分计算过程都存在于客户端中,因此,客户端的场景和操作一致可以保证生成的数据也是一致的,避免了产生冲突数据。
  • 视角独立。查看各个地铁站场景是客户端最易发的操作,该过程需要控制相机的移动,但必须保证其独立性,防止视角干涉、加重通信负担。

在之前的工作中,借助GDNET框架和navmeshagent寻路模块,我们在第一版的灵境胡同地铁站场景中实现了基本的帧同步功能,但仍然存在以下问题未被解决:

  1. 寻路同步问题。借助unity编辑器中的navmeshagent寻路模块进行寻路时,由于随机数函数不一致的问题,生成的路径难以精确同步,在多次运行后误差会累计,影响场景的正常运行。
  2. 视角干涉问题。由于相机也是场景中的实体之一,在云端渲染框架中同样会被同步到其他客户端,这就导致客户端无法分辨应该用哪个相机进行场景显示,多个客户端会抢夺同一个相机的控制权,导致场景混乱。
  3. 场景更新问题。在第一版灵境胡同站场景的基础上,大部分实体、预制体、贴图等都进行了更新,形成了第二版灵境胡同站场景,为了保证各部分开发的一致性,需要将云端渲染框架进行迁移,在新场景下实现各项功能。

寻路同步问题

在解决寻路同步问题时,我们调研了三种解决方案,分别是使用navmeshagent获取路径后控制移动、使用recastnavigation获取路径后控制移动、使用Astar算法获取路径后控制移动,三种方案由简到难逐个进行尝试,以减少开发成本。

使用navmeshagent获取路径后控制移动

路径获取

获取路径主要借助navmeshagent的公共函数CalculatePath(),计算出的路径被保存为NavMeshPath形式的变量,NavMeshPath类的属性中,corners保存路径的各个角点,是一个Vector3变量数组,通过连接各个角点即可以形成一条完整的路径。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

using UnityEngine;
using UnityEngine.AI;
using System.Collections;

public class ExampleClass : MonoBehaviour
{
    public Transform target;
    private NavMeshAgent agent;
    void Start()
    {
        agent = GetComponent<NavMeshAgent>();
        //存储路径
        NavMeshPath path = new NavMeshPath();
        agent.CalculatePath(target.position, path);
        if (path.status == NavMeshPathStatus.PathPartial)
        {
        }
    }
}

控制移动

在获取到路径角点后,我们编写了控制人物沿路径移动的功能,并将其嵌入了云端渲染框架,保证乘客行走路径的一致。其核心代码如下所示:

 //帧同步逻辑帧更新
 public override void UpdateLogic()
 {
     base.UpdateLogic();
     if (isMove)
     {
         OnMove();
     }
 }

//控制人物沿路径移动
 private void OnMove()
 {
     if (index > points.Length - 1) return;
     //Translate是一个移动方式,可以直接调用,
     //从这个物体本身的位置移动到路标位置
     Vector3 offset = points[index] - transform.position;
     transform.Translate(offset.normalized * Time.deltaTime * speed, Space.World);
     offset.y = 0;
     transform.forward = offset;
     //这个路标位置到达之后,索引到下个路标的位置
     if (Vector3.Distance(points[index], transform.position) < 0.5f)
     {
         index++;
     }

     if (index > points.Length - 1)
     {
         isMove = false;
         index = 0;
         Debug.Log("到达目的地");
     }
 }

在完成上述过程后,我们编写了一个简单的示例场景进行了测试,测试中发现,该方案仍然难以实现寻路同步。经过debug排查,其原因是在计算路径时,其他客户端也会同步进行路径计算操作,这就导致两个路径发生了冲突,使寻路无法正常同步。我们随后尝试了将路径计算过程独立,但navmeshagent必须在每一个乘客上挂载,难以独立。但该方案中实现的控制移动功能是通用的,在其他方案的试验中得到了复用。

该部分共修改与新建C#脚本三个,包括Enemy.cs, PersonMove.cs, FindPath.cs

使用recastnavigation获取路径后控制移动

recastnavigation是业内先进的导航网格生成工具,可以完成场景寻路功能,主要使用C++编写,且代码在github完全开发,适合修改和使用。在使用该算法时,需要将其接入unity采用C#语言进行整合修改,实现路径计算功能。主要流程包括:

路径获取

1. recastDemo构建

该部分详细步骤在我的另一篇博客中进行了详细地记录(https://blog.csdn.net/qq_41281244/article/details/108686005),简要流程如下:

  • 解压文件目录
  • 配置SDL库与premake5
  • 通过命令行控制premake编译recastnavigation为sln工程
  • 运行并构建工程
    recastDemo效果如下,该界面可以实现navmesh.obj文件的显示与渲染,寻路参数的测试与配置,导航网格bin文件的生成以及可视化的寻路测试,用于检验模型的可用性。
    在这里插入图片描述

2. 桥接unity

该部分详细步骤在我的另一篇博客中进行了详细地记录(https://blog.csdn.net/qq_41281244/article/details/108686005),简要流程如下:

  • 新建用于生成dll的RecastNavDll工程
  • 编写C++文件打包各个函数目录
  • 运行工程生成dll文件
  • 编写桥接文件暴露方法给unity
    桥接部分的代码如下,该过程实现了recastnavigation与unity的桥接,使场景中编写的C#脚本可以调用以C++编写的各个寻路相关方法。
public class RecastInterface
    {
        private const string RecastDLL = "RecastNavDll";

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern bool recast_init();

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern void recast_fini();

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern bool recast_loadmap(int id, char[] path);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern bool recast_freemap(int id);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern int recast_findpath(int id, float[] spos, float[] epos);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern bool recast_smooth(int id, float step_size, float slop);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern int recast_raycast(int id, float[] spos, float[] epos);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern int recast_getcountpoly(int id);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern int recast_getcountsmooth(int id);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr recast_getpathpoly(int id);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr recast_getpathsmooth(int id);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr recast_getfixposition(int id, float[] pos);

        [DllImport(RecastDLL, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr recast_gethitposition(int id);
    }

之后我们进行了一些基本的测试,检验桥接后是否能正常寻路。

public static void BenchmarkSample()
{
    BenchmarkHelper.Profile("寻路100000次", BenchmarkRecast, 100000);
}

private static void BenchmarkRecast()
{
    if (RecastInterface.FindPath(100,
        new System.Numerics.Vector3(-RandomHelper.RandomNumber(2, 50) - RandomHelper.RandFloat(),
            RandomHelper.RandomNumber(-1, 5) + RandomHelper.RandFloat(), RandomHelper.RandomNumber(3, 20) + RandomHelper.RandFloat()),
        new System.Numerics.Vector3(-RandomHelper.RandomNumber(2, 50) - RandomHelper.RandFloat(),
            RandomHelper.RandomNumber(-1, 5) + RandomHelper.RandFloat(), RandomHelper.RandomNumber(3, 20) + RandomHelper.RandFloat())))
    {
        RecastInterface.Smooth(100, 2f, 0.5f);
        {
            int smoothCount = 0;
            float[] smooths = RecastInterface.GetPathSmooth(100, out smoothCount);
            List<Vector3> results = new List<Vector3>();
            for (int i = 0; i < smoothCount; ++i)
            {
                Vector3 node = new Vector3(smooths[i * 3], smooths[i * 3 + 1], smooths[i * 3 + 2]);
                results.Add(node);
            }
        }
    }
}

3. 导航网格生成

unity场景导出的navmesh文件格式为obj格式,而recastnavigation可以接收的是bin格式的导航网格文件,recastDemo提供了构建bin文件的功能,但由于该exe程序难以嵌入unity中,因此我们尝试修改了recastDemo工程,将其传参模式简化,通过unity唤起自动控制其构建bin文件的操作流程,并隐藏了界面,使其成为后台服务。核心代码如下:
obj网格生成

static void Export()
    {
        UnityEngine.Debug.Log("NavMesh Export Start");

        NavMeshTriangulation navMeshTriangulation = NavMesh.CalculateTriangulation();

        //文件路径 路径文件夹不存在会报错 
        //string path = Application.dataPath + "/RecastNav/obj/" + SceneManager.GetActiveScene().name + ".obj";
        string path = "E:/findPath/recastnavigation-master2/recastnavigation/RecastDemo/Bin/Meshes/" + SceneManager.GetActiveScene().name + ".obj";

        //新建文件
        StreamWriter streamWriter = new StreamWriter(path);

        //顶点  
        for (int i = 0; i < navMeshTriangulation.vertices.Length; i++)
        {
            streamWriter.WriteLine("v  " + navMeshTriangulation.vertices[i].x + " " + navMeshTriangulation.vertices[i].y + " " + navMeshTriangulation.vertices[i].z);
        }

        streamWriter.WriteLine("g pPlane1");

        //索引  
        for (int i = 0; i < navMeshTriangulation.indices.Length;)
        {
            streamWriter.WriteLine("f " + (navMeshTriangulation.indices[i] + 1) + " " + (navMeshTriangulation.indices[i + 1] + 1) + " " + (navMeshTriangulation.indices[i + 2] + 1));
            i = i + 3;
        }

        streamWriter.Flush();
        streamWriter.Close();


        AssetDatabase.Refresh();

        UnityEngine.Debug.Log("NavMesh Export Success");
    }

bin文件转化

if (showMenu)
		{
			if (imguiBeginScrollArea("Properties", width-250-10, 10, 250, height-20, &propScroll))
				mouseOverMenu = true;

			if (imguiCheck("Show Log", showLog))
				showLog = !showLog;
			if (imguiCheck("Show Tools", showTools))
				showTools = !showTools;

			imguiSeparator();
			imguiLabel("Sample");
			//sampleName = "Choose Sample...";
		    //if (imguiButton(sampleName.c_str()))//如果按钮为choose mesh
			//if(sampleName.c_str()=="Choose Sample...")
			if (InputMap)//直接打开菜单栏
			{
				if (showSample)
				{
					showSample = false;
				}
				else
				{
					showSample = true;
					showLevels = false;
					showTestCases = false;
				}
			}
			
			imguiSeparator();
			imguiLabel("Input Mesh");
			if (imguiButton(meshName.c_str()))
			{
				if (showLevels)
				{
					showLevels = false;
				}
				else
				{
					showSample = false;
					showTestCases = false;
					showLevels = true;
					scanDirectory(meshesFolder, ".obj", files);
					scanDirectoryAppend(meshesFolder, ".gset", files);
				}
			}
			if (geom)
			{
				char text[64];
				snprintf(text, 64, "Verts: %.1fk  Tris: %.1fk",
						 geom->getMesh()->getVertCount()/1000.0f,
						 geom->getMesh()->getTriCount()/1000.0f);
				imguiValue(text);
			}
			imguiSeparator();

			if (geom && sample)
			{
				imguiSeparatorLine();
				
				sample->handleSettings();

				//if (imguiButton("Build"))
				if (InputMap==false&&BuildComm==true)
				{
					//Sleep(10000);
					ctx.resetLog();
					if (!sample->handleBuild())
					{
						showLog = true;
						logScroll = 0;
					}
					ctx.dumpLog("Build log %s:", meshName.c_str());
					
					// Clear test.
					delete test;
					test = 0;
					BuildComm = false;//如果build成功,将不可再build
					SaveComm = 1;
				}

				imguiSeparator();
			}
			
			if (sample)
			{
				imguiSeparatorLine();
				sample->handleDebugMode();
			}

			imguiEndScrollArea();
		}

后台调用

void Start()
    {
        //ListAllAppliction();
        UnityEngine.Debug.Log("当前应用:" + Process.GetCurrentProcess().ProcessName + " 进程ID: " + Process.GetCurrentProcess().Id);
        //StartProcess(outputPath);
        //Debug.Log("当前应用:" + Process.GetCurrentProcess().ProcessName + " 进程ID: " + Process.GetCurrentProcess().Id);
    }
    void OnGUI()
    {
        if (GUI.Button(new Rect(10, 10, 200, 50), "寻路地图生成"))
        {
            Export();
            if (File.Exists(@"E:\findPath\recastnavigation-master2\recastnavigation\RecastDemo\Bin\Meshes\灵境胡同站.obj"))
            {
                if (CheckProcess("RecastDemo"))
                    return;
                else
                    StartProcess(outputPath);
                bool Flag = true;
                while (Flag == true)
                {
                    if (File.Exists(@"E:\findPath\recastnavigation-master2\recastnavigation\RecastDemo\Bin\solo_navmesh.bin"))
                    {
                        KillProcess("RecastDemo");
                        break;
                    }
                }
            }
        }
    }
    /// <summary>
    /// 开启应用
    /// </summary>
    /// <param name="ApplicationPath"></param>
    void StartProcess(string ApplicationPath)
    {
        UnityEngine.Debug.Log("打开本地应用");
        Process foo = new Process();
        foo.StartInfo.FileName = ApplicationPath;
        foo.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;//隐藏窗口
        foo.StartInfo.WorkingDirectory = @"E:\findPath\recastnavigation-master2\recastnavigation\RecastDemo\Bin";
        foo.Start();

        //foo.WaitForExit();
    }

    /// <summary>
    /// 检查应用是否正在运行
    /// </summary>
    bool CheckProcess(string processName)
    {
        bool isRunning = false;
        Process[] processes = Process.GetProcesses();
        int i = 0;
        foreach (Process process in processes)
        {
            try
            {
                i++;
                if (!process.HasExited)
                {
                    if (process.ProcessName.Contains(processName))
                    {
                        UnityEngine.Debug.Log(processName + "正在运行");
                        isRunning = true;
                        continue;
                    }
                    else if (!process.ProcessName.Contains(processName) && i > processes.Length)
                    {
                        UnityEngine.Debug.Log(processName + "没有运行");
                        isRunning = false;
                    }
                }
            }
            catch (Exception ep)
            {
            }
        }
        return isRunning;
    }
    /// <summary>
    /// 列出已开启的应用
    /// </summary>
    void ListAllAppliction()
    {
        Process[] processes = Process.GetProcesses();
        int i = 0;
        foreach (Process process in processes)
        {
            try
            {
                if (!process.HasExited)
                {
                    UnityEngine.Debug.Log("应用ID:" + process.Id + "应用名:" + process.ProcessName);
                }
            }
            catch (Exception ep)
            {
            }
        }
    }
    /// <summary>
    /// 杀死进程
    /// </summary>
    /// <param name="processName">应用程序名</param>
    void KillProcess(string processName)
    {
        Process[] processes = Process.GetProcesses();
        foreach (Process process in processes)
        {
            try
            {
                if (!process.HasExited)
                {
                    if (process.ProcessName == processName)
                    {
                        process.Kill();
                        UnityEngine.Debug.Log("已杀死进程");
                    }
                }
            }
            catch (System.InvalidOperationException)
            {
                //UnityEngine.Debug.Log("Holy batman we've got an exception!");
            }
        }
    }

4. 路径计算

使用recastnavigation计算路径主要有以下几个步骤:

  • 寻路初始化:RecastInterface.Init();
  • 加载bin格式地图:RecastInterface.LoadMap(10001, cc);
  • 寻路计算:RecastInterface.FindPath(10001, spos, epos)
  • 路径平滑化:float[] smooths = RecastInterface.GetPathSmooth(10001, out smoothCount);
  • 路径导出:Vector3 node = new Vector3(-smooths[i * 3], smooths[i * 3 + 1], smooths[i * 3 + 2]);
IEnumerator FindPathRecast()
{
    agent.enabled = true;
    yield return null; // 这里必须要等待一帧,否则 下一次寻路就会错位。


    Vector3 spos = new Vector3(5.3f, 11.7f, 8.87f);
    Vector3 epos = new Vector3(14.91f, 0f, 7f);
    //Vector3 epos = new Vector3(-endPoint.transform.position.x,endPoint.transform.position.y,endPoint.transform.position.z);
    //Debug.Log("jieshu"+endPoint.transform.position);
    RecastInterface.Init();
    string ss = @"E:\findPath\recastnavigation-master2\recastnavigation\RecastDemo\Bin\solo_navmesh.bin";
    char[] cc = ss.ToCharArray();
    RecastInterface.LoadMap(10001, cc);
    //Debug.Log("起始位置" + gameObject.transform.position.x.ToString() + "," + gameObject.transform.position.y.ToString() + "," + gameObject.transform.position.z.ToString());
    if (RecastInterface.FindPath(10001, spos, epos))
    {
        RecastInterface.GetPathPoly(10001, out int polyCount);
        RecastInterface.Smooth(10001, 0.1f, 0.5f);
        {
            // Debug.Log(RecastInterface.Raycast(10001, spos, epos));
            int smoothCount;
            float[] smooths = RecastInterface.GetPathSmooth(10001, out smoothCount);
            List<Vector3> results = new List<Vector3>();
            for (int i = 0; i < smoothCount; ++i)
            {
                Vector3 node = new Vector3(-smooths[i * 3], smooths[i * 3 + 1], smooths[i * 3 + 2]);
                results.Add(node);
            }
            points = results.ToArray();
            for (int i = 0; i < points.Length; i++)
            {
                Debug.Log("(" + points[i].x.ToString() + "," + points[i].y.ToString() + "," + points[i].z.ToString() + ")");
            }
        }
        Debug.Log("路径计算成功!");
    }
    else
    {
        Debug.Log("路径计算失败!");
    }

}

控制移动

该过程可直接复用上一部分的代码,完成控制移动过程。
第二种方案,使用recastnavigation进行寻路,在简单场景测试和灵境胡同站场景中均通过了测试,可以实现同步功能。因此,第三种方案我们仅进行了简单调研,其所能达到的效果与recastnavigation类似,但实现成本比较高,该方案被放弃。

相机干涉问题

为了解决该问题,我们修改了实现方法,从控制相机移动改为将相机挂载到物体上,控制物体移动,使相机跟随。同时,使用帧同步框架控制带有相机的物体生成,从而可以为每个客户端提供独立的视角。其实施流程分为两部分:

  1. 相机生成
switch (opt.cmd)
{
    case Command.Input:
        if (!players.ContainsKey(opt.name))
        {
            var p1 = Instantiate(player);
            p1.name = opt.name;
            if (p1.name == ClientBase.Instance.Identify)
            {
                var cam = FindObjectOfType<ARPGcamera>();
                if (cam == null)
                    cam = UnityEngine.Camera.main.gameObject.AddComponent<ARPGcamera>();
                cam.target = p1.transform;
            }

            players.Add(opt.name, p1);
        }
        var p = players[opt.name];
        p.UpdateLogic(opt);
        break;
}
  1. 移动控制
internal void UpdateLogic(Operation opt)
{
    if (Input.GetKeyDown(KeyCode.Q)) 
    {
        AttackID = 1;
    }
    if (opt.index == 1) 
    {
        var skill = Instantiate(skillObj, transform.position + new Vector3(0, 1f, 0), transform.rotation);
        skill.actor = this;
        GameScene.Instance.skills.Add(skill);
    }

    transform.Translate(opt.direction * 0.5f);
    //transform.position = p.transform.position.ToVector3(10);
}
  1. 相机跟随移动
using UnityEngine;

    public class ARPGcamera : MonoBehaviour
    {
        public Transform target;
        public float targetHeight = 1.2f;
        public float distance = 4.0f;
        public float maxDistance = 20;
        public float minDistance = 1.0f;
        public float xSpeed = 500.0f;
        public float ySpeed = 120.0f;
        public float yMinLimit = -10;
        public float yMaxLimit = 70;
        public float zoomRate = 80;
        public float rotationDampening = 3.0f;
        public float x = 20.0f;
        public float y = 0.0f;
        public float aimAngle = 8;
        public KeyCode key = KeyCode.Mouse1;
        protected Quaternion aim;
        protected Quaternion rotation;
        private Vector3 position;

        void LateUpdate()
        {
            if (!target)
                return;

            if (Input.GetKey(key)) {
                x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;
                y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;
            }

            distance -= (Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime) * zoomRate * Mathf.Abs(distance);
            distance = Mathf.Clamp(distance, minDistance, maxDistance);

            y = ClampAngle(y, yMinLimit, yMaxLimit);

            // Rotate Camera
            rotation = Quaternion.Euler(y, x, 0);
            transform.rotation = rotation;

            aim = Quaternion.Euler(y - aimAngle, x, 0);

            //Camera Position
            position = target.position - (rotation * Vector3.forward * distance + new Vector3(0, -targetHeight, 0));
            transform.position = position;
        }

        static float ClampAngle(float angle, float min, float max)
        {
            if (angle < -360)
                angle += 360;
            if (angle > 360)
                angle -= 360;
            return Mathf.Clamp(angle, min, max);
        }
    }

场景更新问题

迁移的实体、预制体、脚本

实体或预制体脚本
Canvas 1StartBattle.cs
GameObjectClientManager.cs, GameScene.cs, CallApplication.cs
CameraPlayer.cs
Man 1Enemy.cs
Main CameraNewGlobalSettings.cs

路径配置

名称位置变量
RecastDemo路径CallApplication.csprivate static string outputPath
obj地图模型路径CallApplication.cs :: void OnGUI()string objPath
bin地图模型路径CallApplication.cs :: void OnGUI()string binPath
工作目录路径CallApplication.cs :: void StartProcess(string ApplicationPath)foo.StartInfo.WorkingDirectory
Meshes路径CallApplication.cs :: static void Export()string path

最终效果

帧同步寻路

框架可以保证多个客户端寻路路径同步、时间同步
在这里插入图片描述

加速同步

新客户端接入后框架可以进行加速渲染,驱动场景迅速同步到与所有客户端一致的画面
在这里插入图片描述

  游戏开发 最新文章
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
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 13:05:24  更:2022-10-17 13:06:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/17 5:47:16-

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