适配

  1. 项目引用了navMesh库,2022版本已经内置到引擎,因此升级后要删掉库引用
  2. auth库的2.4.0有bug,编辑器环境下profile检查会抛异常,导致编辑器环境不能使用lobby,仿照官方,降到2.3.1版本
  3. 不清楚为什么打出来的包不能跟编辑器联机,貌似编出来的是release版,没找到改参数的地方,多人调试只能用parrelSync

参考文档

知乎其他人经验 这个人的代码老旧,部分已经对不上,如角色网络对象和显示分离做法已经变了

官方项目代码介绍,更复杂,看起来累,但避免了货不对板

同步方式很重要,先看官方这个才能看懂代码

初始化

在StartUp做游戏初始化,然后再到开始场景
切换场景不销毁的对象,都用了DontDestroyOnLoad
NetworkManager是NetCode的控制器,挂在全局对象上
ConnectionManager是BossRoom自己写的组件,声明并且注入了游戏状态机,游戏状态改变时调ChangeState
ClientConnectingState 进入这个状态会起一个异步任务,连接服务器ConnectClientAsync
玩家连接,断开服务器,都通过这个管理
GameDataSource 里存行为列表

using Action = Unity.BossRoom.Gameplay.Actions.Action;
[Tooltip("All Action prototype scriptable objects should be slotted in here")]
[SerializeField]
// 这就是BossRoom的所有技能,都做成asset配置,存到这里了
private Action[] m_ActionPrototypes;

SceneLoader里SceneLoaderWrapper是BossRoom实现的类
NetCode自带了一个场景管理NetworkSceneManager,并且官方希望刚开始用NetCode的人都要用这个场景管理
OnSceneEvent附带挺多事件的,加载过程根据这几个状态做进度可视化,直到SynchronizeComplete才做逻辑初始化,可避免掉虚空

玩家

对服务器而言玩家只是个网络连接,对客户端而言玩游戏玩的是输入控制
玩家不等于游戏角色,只不过玩家的输入控制,影响了游戏角色
每个网络连接会绑定一个永久的玩家Prefab,BossRoom里叫PersistentPlayer,它拥有NetworkObject组件,定义在NetworkManager里面,这个东西不销毁

Startup场景初始化游戏,没有角色也不需要输入控制
MainMenu场景已经初始化了游戏,需要指定服务器,输入控制实际上控制的是配置和网络连接,没角色
CharSelect场景连上了服务器,但是也不需要角色,输入控制就是组队的UI交互
BossRoom场景才真正有了游戏角色,根据网络连接创建角色通知客户端,客户端再判断是不是玩家,是不是自己,如果是自己,就绑定输入控制

void OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List<ulong> clientsCompleted, List<ulong> clientsTimedOut)
{if (!InitialSpawnDone && loadSceneMode == LoadSceneMode.Single){InitialSpawnDone = true;// 为每个网络连接创建游戏角色并初始化位置foreach (var kvp in NetworkManager.Singleton.ConnectedClients){//ServerBossRoomState定义了PlayerPrefab,创建玩家角色的时候就创建这个prefabSpawnPlayer(kvp.Key, false);}}
}// 上面的SpawnPlayer常规执行创建,关键是最后
// NetworkObject创建好,要调用SpawnWithOwnership,把自己注册进NetworkSpawnManager,注册完成会回调各个组件的OnNetworkSpawn做初始化,游戏就可以开始了
// spawn players characters with destroyWithScene = true
newPlayer.SpawnWithOwnership(clientId, true);

角色信息的传递

guid不是guide,不是引导!!!!是职业ID
NetworkAvatarGuidState的作用根据guid初始化角色信息,因此它专门建了个asset配置AvatarRegistry,管理所有的角色配置

/// <summary>
/// NetworkBehaviour component to send/receive GUIDs from server to clients.
/// </summary>
public class NetworkAvatarGuidState : NetworkBehaviour
{//这个GUID会随机初始化,然后选角色的后更新[FormerlySerializedAs("AvatarGuidArray")][HideInInspector]public NetworkVariable<NetworkGuid> AvatarGuid = new NetworkVariable<NetworkGuid>();[SerializeField]AvatarRegistry m_AvatarRegistry;
}//它被声明在PersistentPlayer里面,先是初始化随机一个值
[RequireComponent(typeof(NetworkObject))]
public class PersistentPlayer : NetworkBehaviour
{[SerializeField]PersistentPlayerRuntimeCollection m_PersistentPlayerRuntimeCollection;[SerializeField]NetworkNameState m_NetworkNameState;[SerializeField]NetworkAvatarGuidState m_NetworkAvatarGuidState;public NetworkNameState NetworkNameState => m_NetworkNameState;public NetworkAvatarGuidState NetworkAvatarGuidState => m_NetworkAvatarGuidState;
}
// 选角色完成的时候会把角色信息存进去,由此可见,这个persistentPlayer,就是用来玩家数据并传递的
void SaveLobbyResults()
{foreach (NetworkCharSelection.LobbyPlayerState playerInfo in networkCharSelectioLobbyPlayers){var playerNetworkObject = NetworkManager.Singleton.SpawnManageGetPlayerNetworkObject(playerInfo.ClientId)if (playerNetworkObject && playerNetworkObject.TryGetComponent(ouPersistentPlayer persistentPlayer)){// pass avatar GUID to PersistentPlayer// it'd be great to simplify this with something like NetworkScriptableObjects :(persistentPlayer.NetworkAvatarGuidState.AvatarGuid.Value =networkCharSelection.AvatarConfiguration[playerInfo.SeatIdx].GuiToNetworkGuid();}}
}
// BossRoom场景加载完成,SpawnPlayer的时候,将这个值传给PlayerAvatar里的同名组件
// 这个PlayerAvatar只是战斗用的对象,可以被销毁,虽然也有个NetworkAvatarGuidState,但这个state跟对象一样是一次性的,并且因为是新组件,所以m_Avatar为空,
// pass character type from persistent player to avatar
var networkAvatarGuidStateExists =newPlayer.TryGetComponent(out NetworkAvatarGuidState networkAvatarGuidState);Assert.IsTrue(networkAvatarGuidStateExists,$"NetworkCharacterGuidState not found on player avatar!");// if reconnecting, set the player's position and rotation to its previous state
if (lateJoin)
{SessionPlayerData? sessionPlayerData = SessionManager<SessionPlayerData>.Instance.GetPlayerData(clientId);if (sessionPlayerData is { HasCharacterSpawned: true }){physicsTransform.SetPositionAndRotation(sessionPlayerData.Value.PlayerPosition, sessionPlayerData.Value.PlayerRotation);}
}networkAvatarGuidState.AvatarGuid.Value =persistentPlayer.NetworkAvatarGuidState.AvatarGuid.Value;// ServerCharacter组件引用了这个state,职业信息CharacterClass的数据从这个state拿
// 因为ServerCharacter引用的GuidState是新创建的,所以m_Avatar是空,所以引用RegisteredAvatar的时候会以实际选择的guid初始化,职业就对上了
public CharacterClass CharacterClass
{get{if (m_CharacterClass == null){m_CharacterClass = m_State.RegisteredAvatar.CharacterClass;return m_CharacterClass;set => m_CharacterClass = value;
}NetworkAvatarGuidState m_State;
//这个state存了个角色的avatar
[SerializeField]
AvatarRegistry m_AvatarRegistry
Avatar m_Avatar
public Avatar RegisteredAvatar
{get{if (m_Avatar == null){//这时候的guid已经被设置为玩家选择的职业ID了RegisterAvatar(AvatarGuid.Value.ToGuid());return m_Avatar;}
}
//根据guid注册avatar,serverCharacter的职业在这里设置
void RegisterAvatar(Guid guid)
{if (guid.Equals(Guid.Empty)){// not a valid Guidreturn;// based on the Guid received, Avatar is fetched from AvatarRegistryif (!m_AvatarRegistry.TryGetAvatar(guid, out var avatar)){Debug.LogError("Avatar not found!");return;if (m_Avatar != null){// already set, this is an idempotent call, we don't want to Instantiate twicereturn;m_Avatar = avatarif (TryGetComponent<ServerCharacter>(out var serverCharacter)){//职业信息写回ServerCharacterserverCharacter.CharacterClass = avatar.CharacterClass;}
}
// 最终在组件ClientAvatarGuidHandler组件里创建了客户端能看到的模型
// 这个函数服务器不执行,毕竟服务器不需要跑渲染
// spawn avatar graphics GameObject
Instantiate(m_NetworkAvatarGuidState.RegisteredAvatar.Graphics, m_GraphicsAnimattransform);

血量同步

血量HP,全称是HitPoints,即打击次数,HP100就是能挨100刀的意思
游戏里血量也是有一个状态管理,角色的预制体是PlayerAvatar,组件叫NetworkHelthState
![血量组件](https://img-blog.csdnimg.cn/47a60b08ea6e428785ac4af7a2893b6d.png

需要单独同步的数据类型是NetworkVariable,这个类型的字段在修改时会自动同步,并且能监听事件OnValueChanged
NetworkHealthState.cs代码一共没几行,也就是定义HP,和HP变化的事件

        [HideInInspector]public NetworkVariable<int> HitPoints = new NetworkVariable<int>();

玩家受伤同样有个组件,DamageReceiver监听,血量变化时触发DamageReceived事件

ServerCharacter即服务器角色组件,监听受伤事件,做血量更新计算
这里也定义了HitPoints,实际上只能算是引用,值还是NetHealthState那边的

        /// <summary>/// Current HP. This value is populated at startup time from CharacterClass data./// </summary>public int HitPoints{get => NetHealthState.HitPoints.Value;private set => NetHealthState.HitPoints.Value = value;}

BossRoom的代码命名跟我的习惯不一样,他这里HP是血量变化的增量,HitPoints才是玩家的当前血量
我的习惯是addHp和curHp

        void ReceiveHP(ServerCharacter inflicter, int HP){//加血和掉血,还处理了buff加成//to our own effects, and modify the damage or healing as appropriate. But in this game, we just take it straight.if (HP > 0){m_ServerActionPlayer.OnGameplayActivity(Action.GameplayActivity.Healed);float healingMod = m_ServerActionPlayer.GetBuffedValue(Action.BuffableValue.PercentHealingReceived);HP = (int)(HP * healingMod);}else{#if UNITY_EDITOR || DEVELOPMENT_BUILD// Don't apply damage if god mode is onif (NetLifeState.IsGodMode.Value){return;}
#endifm_ServerActionPlayer.OnGameplayActivity(Action.GameplayActivity.AttackedByEnemy);float damageMod = m_ServerActionPlayer.GetBuffedValue(Action.BuffableValue.PercentDamageReceived);HP = (int)(HP * damageMod);serverAnimationHandler.NetworkAnimator.SetTrigger("HitReact1");}//HP更新,因为set函数被重写到NetHealthState那里赋值了,会触发同步HitPoints = Mathf.Clamp(HitPoints + HP, 0, CharacterClass.BaseHP.Value);if (m_AIBrain != null){//let the brain know about the modified amount of damage we received.m_AIBrain.ReceiveHP(inflicter, HP);}//判断死亡,LifeState跟HP是类似的组件管理,忽略

客户端监听Prefab的HitPoints变化,触发UI的更新

// 主角的血量更新
m_OwnedServerCharacter.NetHealthState.HitPoints.OnValueChanged += SetHeroHealth;// 队友的血量更新
serverCharacter.NetHealthState.HitPoints.OnValueChanged += (int previousValue, int newValue) =>
{SetAllyHealth(id, newValue);
};

选角色

选人界面的networkCharSelection.LobbyPlayers存放着所有玩家的选角信息,用来做选人的校验和同步
private NetworkList m_LobbyPlayers;
OnListChanged是NetworkList定义的一个委托,当玩家信息变化时触发事件,游戏接入处理了选角色时的UI响应
具体的网络传输疑似被封装进INetworkUpdateSystem里面,VS不能调试进入
跟踪ChangeSeatServerRpc发现,非主机不会走进来,说明这个函数是走了网络,到主机上执行的(实际就是典型的ServerRpc用法)
NetworkListEvent针对NetworkList定义了增删改查操作,业务通过泛型传具体的结构体数据,然后注册响应事件做处理,定制性还可以
实际代码客户端处理是先对玩家遍历更新,然后再对主角单独处理ready界面

// 遍历列表,找到客户端的id
// now let's find our local player in the list and update the character/info box appropriately
int localPlayerIdx = -1;
for (int i = 0; i < m_NetworkCharSelection.LobbyPlayers.Count; ++i)
{if (m_NetworkCharSelection.LobbyPlayers[i].ClientId == NetworkManager.Singleton.LocalClientId){localPlayerIdx = i;break;}
}

客户端的ready按钮变化,是监听了服务器消息,下断点可以看到 NetworkList`1:ReadDelta 发生了Value修改,通过OnLobbyPlayerStateChanged回调到UI

Void Unity.BossRoom.Gameplay.UI.UICharSelectClassInfoBox:SetLockedIn (Boolean)+0x1 atC:\sandbox\unity\TestBossRoom\Assets\Scripts\Gameplay\UI\UICharSelectClassInfoBox.cs[62:13-62:77]  C#
Void Unity.BossRoom.Gameplay.GameState.ClientCharSelectState:UpdateCharacterSelection(SeatState, Int32)+0x15f atC:\sandbox\unity\TestBossRoom\Assets\Scripts\Gameplay\GameState\ClientCharSelectState.cs[288:25-288:59]    C#
>Void Unity.BossRoom.Gameplay.GameState.ClientCharSelectState:OnLobbyPlayerStateChanged (NetworkListEvent`1)+0xeb at C:\sandbox\unity\TestBossRoom\Assets\Scripts\Gameplay\GameState\ClientCharSelectState.cs:[228:17-228:166] C#
Void Unity.Netcode.NetworkList`1:ReadDelta (FastBufferReader, Boolean)+0x47b at \Library\PackageCache\com.unity.netcode.gameobjects@1.20\Runtime\NetworkVariable\Collections\NetworkList.cs:[312:33-318:36]  C#
Void Unity.Netcode.NetworkVariableDeltaMessage:Handle (NetworkContext)+0x2c3 at \Library\PackageCache\com.unity.netcode.gameobjects@1.20\Runtime\Messaging\Messages\NetworkVariableDeltaMessage.cs:[197:25-197:107]   C#
Void Unity.Netcode.MessagingSystem:ReceiveMessage (FastBufferReader, NetworkContext,MessagingSystem)+0xe2 at .\Library\PackageCache\com.unity.netcode.gameobjects@1.20\Runtime\Messaging\MessagingSystem.cs:[511:17-511:45]   C#
Void Unity.Netcode.MessagingSystem:HandleMessage (MessageHeader, FastBufferReader, UInt64,Single, Int32)+0x13c at .\Library\PackageCache\com.unity.netcode.gameobjects@1.20\Runtime\Messaging\MessagingSystem.cs:[384:25-384:67]  C#
Void Unity.Netcode.MessagingSystem:ProcessIncomingMessageQueue ()+0x32 at \Library\PackageCache\com.unity.netcode.gameobjects@1.2.0\Runtime\Messaging\MessagingSystemcs:[404:17-404:122]  C#
Void Unity.Netcode.NetworkManager:OnNetworkEarlyUpdate ()+0x66 at .\Library\PackageCache\comunity.netcode.gameobjects@1.2.0\Runtime\Core\NetworkManager.cs:[1600:13-1600:59]  C#
Void Unity.Netcode.NetworkManager:NetworkUpdate (NetworkUpdateStage)+0x18 at \Library\PackageCache\com.unity.netcode.gameobjects@1.2.0\Runtime\Core\NetworkManager.cs[1532:21-1532:44]    C#
Void Unity.Netcode.NetworkUpdateLoop:RunNetworkUpdateStage (NetworkUpdateStage)+0x2f at \Library\PackageCache\com.unity.netcode.gameobjects@1.2.0\Runtime\Core\NetworkUpdateLoop.cs[185:17-185:51]    C#
Void <>c:<CreateLoopSystem>b__0_0 ()+0x1 at .\Library\PackageCache\com.unity.netcodegameobjects@1.2.0\Runtime\Core\NetworkUpdateLoop.cs:[208:44-208:97]   C#

战斗

NetworkBehaviour官方文档 必看代码简单
ClientCharacter.cs继承自NetworkBehaviour,复写方法OnNetworkSpawn
动态创建先OnNetworkSpawn再Start,静态场景的对象先Start再OnNetworkSpawn,是个坑

// 如果不是NPC,那就是玩家
if (!m_ServerCharacter.IsNpc)
// 如果是主角,就给gameObject加上摄像机,再注册输入的监听
if (m_ServerCharacter.IsOwner)
{ActionRequestData data = new ActionRequestData { ActionID = GameDataSource.Instance.GeneralTargetActionPrototActionID };m_ClientActionViz.PlayAction(ref data);gameObject.AddComponent<CameraController>if (m_ServerCharacter.TryGetComponent(out ClientInputSender inputSender)){// TODO: revisit; anticipated actions would play twice on the hostif (!IsServer){inputSender.ActionInputEvent += OnActionInput;}inputSender.ClientMoveEvent += OnMoveInput;}
}

Action

BossRoom的协议包基类是ActionRequestData,继承自INetworkSerializable,定义了很多非必填字段,又根据flag做流量优化,如果字段是默认值,就算缺省
游戏定义了一种资源类型Action,列举了玩家行为,基本可以认为是同步包协议的定义

//发包函数
void SendInput(ActionRequestData action)
{ActionInputEvent?.Invoke(action);m_ServerCharacter.RecvDoActionServerRPC(action);
}
//组包发送
var data = new ActionRequestData();
PopulateSkillRequest(k_CachedHit[0].point, actionID, ref data);
SendInput(data);//施放需要指定目标或范围指示器的技能,也一样被包装进Action,字段是ActionInput
//ArcherVolley里面引用的ClientAoeInpuf,就是个指示器
//实际上会生成一个GameObject在场景,挂脚本AoeActionInput控制
var actionPrototype = GameDataSource.Instance.GetActionPrototypeByID(m_ActionRequests[i].RequestedActionID);
if (actionPrototype.Config.ActionInput != null)
{//如果ActionInpuf不为空,就生成指示器var skillPlayer = Instantiate(actionPrototype.Config.ActionInput);//指示器的参数传了委托Action,sendInput,实际就是发包skillPlayer.Initiate(m_ServerCharacter, m_PhysicsWrapper.Transform.position, actionPrototype.ActionID, SendInput, FinishSkill);m_CurrentSkillInput = skillPlayer;
}
else
{PerformSkill(actionPrototype.ActionID, m_ActionRequests[i].TriggerStyle, m_ActionRequests[i].TargetId);
}
//鼠标操作
//IsPointerOverGameObject是unity很常用的方法,判断点击的是UI还是场景
if (!EventSystem.current.IsPointerOverGameObject() && m_CurrentSkillInput == null)
{//IsPointerOverGameObject() is a simple way to determine if the mouse is overUI element. If it is, we don't perform mouse input logic,//to model the button "blocking" mouse clicks from falling through ainteracting with the worl//鼠标输入右键按下是攻击if (Input.GetMouseButtonDown(1)){RequestAction(CharacterClass.Skill1.ActionID, SkillTriggerStyle.MouseClick);}//左键按下是选中目标,朝目标移动if (Input.GetMouseButtonDown(0)){RequestAction(GameDataSource.Instance.GeneralTargetActionPrototype.ActionISkillTriggerStyle.MouseClick);}//如果当前帧不是按下左键,但左键还没松开,就认为是控制移动else if (Input.GetMouseButton(0)){m_MoveRequest = true;--todo:fixUpdate里面通过射线检测更新目标点}
}//用法
var actionPrototype = GameDataSource.Instance.GetActionPrototypeByID(m_ActionRequests[i].RequestedActionID);
if (actionPrototype.Config.ActionInput != null)
{var skillPlayer = Instantiate(actionPrototype.Config.ActionInput);skillPlayer.Initiate(m_ServerCharacter, m_PhysicsWrapper.Transform.position, actionPrototype.ActionID, SendInput, FinishSkill);m_CurrentSkillInput = skillPlayer;
}
else
{PerformSkill(actionPrototype.ActionID, m_ActionRequests[i].TriggerStyle, m_ActionRequests[i].TargetId);
}

rpc

//分主机和客机,serverRpc默认只有主机能调用
//如果声明了RequireOwnership=false,客机也能用,如角色选择/// <summary>/// RPC to notify the server that a client has chosen a seat./// </summary>[ServerRpc(RequireOwnership = false)]public void ChangeSeatServerRpc(ulong clientId, int seatIdx, bool lockedIn){OnClientChangedSeat?.Invoke(clientId, seatIdx, lockedIn);}

相关的语法

inject

一知半解:网上看到的描述说这是种注入,实际使用效果更接近跨类共享数据

Inject注入介绍

readonly

声明后,可以在构造函数做一次赋值,之后就不能改变

event 事件处理

//注册事件
/// <summary>
/// Server notification when a client requests a different lobby-seat, or locks in theiseat choice
/// </summary>
public event Action<ulong, int, bool> OnClientChangedSeat;
//响应事件
public void OnNetworkSpawn()
{if (!NetworkManager.Singleton.IsServer){enabled = false;}else{NetworkManager.Singleton.OnClientDisconnectCallback +OnClientDisconnectCallback;networkCharSelection.OnClientChangedSeat += OnClientChangedSeatNetworkManager.Singleton.SceneManager.OnSceneEvent += OnSceneEvent;}
}
public void OnNetworkDespawn()
{if (NetworkManager.Singleton){NetworkManager.Singleton.OnClientDisconnectCallback -OnClientDisconnectCallback;NetworkManager.Singleton.SceneManager.OnSceneEvent -= OnSceneEvent;}if (networkCharSelection){networkCharSelection.OnClientChangedSeat -= OnClientChangedSeat;}
}
//触发事件 这个函数声明了ServerRpc,并且函数名格式是xxxServerRpc
//也就是客户端调用,到服务器触发,然后服务器再通过事件到具体执行的函数上
/// <summary>
/// RPC to notify the server that a client has chosen a seat.
/// </summary>
[ServerRpc(RequireOwnership = false)]
public void ChangeSeatServerRpc(ulong clientId, int seatIdx, bool lockedIn)
{OnClientChangedSeat?.Invoke(clientId, seatIdx, lockedIn);
}

Unity UGS官方例子BossRoom,NetCode部分的读代码笔记相关推荐

  1. 【Netty】入门Netty官方例子解析(二)Time Server

    本文承接上文<[Netty]入门Netty官方例子解析(一)写个 Discard Server> ,接下来讲解官网文档中Netty入门官方例子第二个例子 Time Server 原文这个章 ...

  2. Mule 官方例子研究

    Mule 官方例子研究 一.编译导入Mule自带的例子 1.准备 安装Mule.这里就不介绍mule的安装了,请参考<Mule安装部署手册>. 2. 编译Mule自带例子中的Hello例子 ...

  3. [FFmpeg] 编译官方例子

    官方例子地址 如果下载了源码,可在 ffmpeg-4.3.1/doc/examples/ 中查看 在Ubuntu 16.04 上,通过源码安装好 ffmpeg 后,运行官方例子 gcc -o demu ...

  4. 【TensorFlow】官方例子mnist_with_summaries.py在windows下运行tensorboard

    一.修改方案   TensorFlow官方例子mnist_with_summaries.py里面的主函数里面是按照linux的路径在写的,这在windows运行后生成的log文件路径会出现问题. 官方 ...

  5. leo-editor 关于 flutter 企业级路由 fluro 官方例子文学化编程分析

    fluro 是 flutter 企业级路由插件,在学习官方例子时有一些难度. fluro官方例子 下面是官方例子运行图 下面使用 leo-editor 对此例子进行文学化编程分析,按照从顶至底的顺序, ...

  6. Android-图像识别项目OpenCV(2):运行官方例子中的脸部识别程序

    上一篇文章:Android-图像识别项目OpenCV(1):技术选型以及NDK搭建 3.下载OpenCV4Android官方SDK并进行导入 SDK下载地址:http://pt.sourceforge ...

  7. 关于Unity的官方认证

    上个月我参加了Unity的官方认证专业程序员的考试,这个考试是马上考马上出成绩.幸运的是,我考过了. 在这我想分享一下考试相关的内容吧~毕竟我一开始想考试的时候,找遍全网都没有这方面的内容给我参考0. ...

  8. VTK之MPR重建源码分析(VTK官方例子)

    VTK之MPR重建源码分析(VTK官方例子) 一,核心的控制十字轴旋转的代码 //----------------------------------------------------------- ...

  9. docx 图片预处理 Java_java使用poi给docx文档添加图片(官方例子)

    java使用poi给docx文档添加图片(官方例子) package org.apache.poi.xwpf.usermodel.examples; import java.io.FileInputS ...

最新文章

  1. 问题 c: 插入排序_插入排序:它是什么,以及它如何工作
  2. Java集合篇:ConcurrentHashMap详解(JDK1.8)
  3. 实例化Model的三种方式
  4. html盒子模型子元素怎么水平占满父元素_前端面试常考问题之css盒模型
  5. iOS:const的使用
  6. 台式计算机无线网卡怎么找,怎么找出台式电脑中的无线网卡驱动
  7. php在线翻译,PHP 在线翻译函数代码
  8. 路由在电话网和计算机网中的区别,计算机猫和路由器之间有什么区别
  9. 必读论文 | 机器交互必读论文8篇
  10. 20sccm_SCCM 2016 使用PXE 部署操作系统(一)
  11. python程序30行_30行Python代码刷王者荣耀金币,还怕没有金币买英雄?
  12. 滴水三期:day44.2-虚函数表
  13. 图片转文字怎么转换?分享你个简单的方法
  14. 关于用鲁大师对显卡性能进行评测时抛出“评测中切换页面”的异常的解决方案。
  15. 国外服务器和国内服务器 不同时区的问题
  16. vscode调出大纲
  17. 自由职业者要当心如何保护自己免遭身份盗用
  18. 怎么做服务器压力测试?
  19. 网站建设方案书包括什么内容?
  20. git 原理 基础学习

热门文章

  1. 天翼智联与九州云正式达成边缘战略合作
  2. ajax跨域请求问题:Access to XMLHttpRequest at……has been blocked by CORS policy: Cross origin requests
  3. 实现掘金登录框中熊猫状态的改变案例
  4. 上海科技创新资源数据中心--免费下载知网、万方等平台论文
  5. 作为PM,PMP证书重要吗?
  6. 中国移动一键免密登录103102 Android 包签名错误
  7. opensuse 安装java_OpenSUSE下安装JDK
  8. 计算机科学技术想象作文,未来科技想象作文
  9. 树莓派初体验,超级便宜的服务器~
  10. 备战蓝桥杯 这样准没错!