前言

在上一篇文章 Unity3D热更新之LuaFramework篇[06]--Lua中是怎么实现脚本生命周期的 中,我分析了由LuaBehaviour来实现lua脚本生命周期的方法。

但在实际使用中发现,只有一个这样的脚本还不够。

LuaBehaviour驱动XxxPanel.lua脚本的方法,只适用于界面相对简洁的情况(界面上只有少量的Image、Text和其它UI组件),一但遇到稍微复杂一点的情况,就有点捉襟见肘了,比如一个包含多个子项的排行榜页面。

现以一个排行榜的示例来说明。

一、创建一个排行榜页面

1、创建一个大厅场景,相机及Canvas设置与之前的main场景相同,然后创建一个HallPanel面板。

同时创建HallPanel.lua和HallCtrl.lua脚本并做相应注册(添加到CtrlNames和PanelNames里并做Require)。

面板上放两个按钮(排行榜、商城),且这个面板不做成由PanelMgr加载的预制体,就这么挂在Canvas下好了。

2、创建一个排行榜RankingPanel,其结构主要是几个垂直排序的RankItem,如下图所示。

同时创建RankingPanel.lua和RankingCtrl.lua并做相应注册。

这个面板也不做成由PanelMgr加载的那种,就放在Canvas下,通过SetActive来控制显示与隐藏(开发中这种使用方式应该也很常见)。

3、功能需求:

  1) 点击HallPanel上的排行榜按钮,弹出排行榜面板;

  2)点击排行榜上的子项,弹出各自的名字及顺序;

  难点分析:

  难点1,怎么实现HallPanel的点击事件

    假如不是用的Lua,而是c#,实现这个功能也太简单了,刚入门Unity的新手也知道怎么做。

       假如HallPanel是一个动态加载的,那实现排行榜按钮的点击事件也好做,因为有LuaBehaviour以及之前我们自己实现的UIEventEx。 由于这个是非预制体加载的,所以这条路也走不通。

       思路:手动给这个HallPanel挂载LuaBehaviour.cs脚本试试?不行就自己写个差不多的脚本。

 难点2,怎么让RankItem独自产生行为

前言中有提到过LuaBehavoiur并不适用所有情况,这个就是一种。在一个设计良好的架构中,XxxPanel.lua最好只处理浅层布局的元素,对于复杂的嵌套的UI或者元素较多的UI,最好让它们自行处理自己的行为。

   这个需求放在这里就是,不在RankingCtrl.lua和RankingPanel.lua中处理RankItem的逻辑,而是交由RankItem自行处理。

    思路:创建一个RankItem.lua脚本(拥有事件处理功能以及其它生命周期能力),与RankItem对象绑定。

  这两个难点,其实反映的是一个问题,我有一个unity对象,又创建了一 个lua脚本,怎么让它们产生绑定关系?

  下面来尝试解决问题。

二、处理HallPanel的UI事件

方法1:使用LuaBehaviour脚本

1、直接给HallPall对象添加LuaBehaviour脚本;

  2、在Game.lua中把初始自动加载Panel的语句注释掉。

    CtrlManager.Init();
local ctrl = CtrlManager.GetCtrl(CtrlNames.Login);
if ctrl ~= nil and AppConst.ExampleMode == then
-- ctrl:Awake(); --就是这一句决定首先加载什么面板
end

3、给HallPanel的InitPanel方法添加查找按钮控件的语句,并在HallCtrl中添加按钮事件,具体修改见代码:

local transform;
local gameObject; HallPanel = {};
local this = HallPanel; --启动事件--
function HallPanel.Awake(obj)
gameObject = obj;
transform = obj.transform; this.InitPanel();
logWarn("Awake lua--->>"..gameObject.name);
end --初始化面板--
function HallPanel.InitPanel()
logWarn("我是HallPanel,我被加载了."); --排行榜按钮
HallPanel.rankingBtn = transform:FindChild("BtnRanking").gameObject; --调用Ctrl中panel创建完成时的方法
HallCtrl.OnCreate(gameObject);
end function HallPanel.OnDestroy()
logWarn("OnDestroy---->>>");
end

HallPanel.lua

HallCtrl = {};
local this = HallCtrl; local behaviour;
local transform;
local gameObject; --构建函数--
function HallCtrl.New()
logWarn("HallCtrl.New--->>");
return this;
end function HallCtrl.Awake()
logWarn("HallCtrl.Awake--->>"); logWarn("我是HallCtrl,我被加载了.");
end --启动事件--
function HallCtrl.OnCreate(obj)
gameObject = obj;
transform = obj.transform; UIEventEx.AddButtonClick(HallPanel.rankingBtn, function ()
log("你点击了排行榜按钮");
end); end --单击事件--
function HallCtrl.OnClick(go)
destroy(gameObject);
end --关闭事件--
function HallCtrl.Close()
panelMgr:ClosePanel(CtrlNames.Hall);
end

HallCtrl.lua

有一点需要注意的是,之前UI事件处理的方法是在XxxCtrl中的OnCreate方法里处理,这个方法在XxxPanel预制体加载后被回调。

现在HallPanel没有预制体加载的过程,所以要在InitPanel方法的末尾手动加一句对HallCtrl.OnCreate方法的调用。

4、运行游戏

点击运行后,发现,InitPanel方法中的日志语句没有输出,点击按钮也没有响应。

经跟踪调试发现,在处理HallPanel面板时,其身上的LuaBehaviour脚本中Awake方法的执行时,Lua虚拟机的初始化还没完成,甚至是在执行Start方法时其初始化也没初始化完成。

所以,从LuaBehaviour的Awake中调用HallPanel.lua脚本的Awake是不可能成功的(Lua虚拟机没初始化完成,所有Lua脚本也没被加载)。

LuaBehaviour脚本本身没问题,这个问题的出现,是因为我们想绕过LuaFramework的加载流程引起的。

5、解决问题

想解决这个问题,就需要修改 Awake方法的调用时机。

为了不破坏原有的LuaBehaviour脚本,我们复制一个LuaBehaviour脚本并重命名为"CustomBehaviour"。

并在CustomBehaviour的Awake的0.1秒之后,再调用HallPanel.lua的Awake方法,见下图:

重新给HallPanel对象挂载CustomBehaviour脚本后,再运行游戏,

能看到InitPanel方法被正确执行了,按钮事件也生效了。

说明:用延时的方法去执行Awake,虽然让Lua中的方法执行了,但也破坏了Awake的原本执行顺序。如果对框架了解不深或游戏逻辑处理不够严谨,则会引起问题。

这只是一个临时方法,完善的解决方案可以看看PanelMgr的加载流程,应该能找到答案。

三、显示RankingPanel面板并处理RankItem子项

1、显示RankingPanel面板

在HallPanel.lua中引用RankingPanel面板,并在HallCtrl.lua中添加点击事件,见下图:

如此,当点击排行榜按钮时,就会显示排行榜面板了(运行前要把RankingPanel禁掉)。

完整的HallPanel.lua

local transform;
local gameObject; HallPanel = {};
local this = HallPanel; --启动事件--
function HallPanel.Awake(obj)
gameObject = obj;
transform = obj.transform; this.InitPanel();
logWarn("Awake lua--->>"..gameObject.name);
end --初始化面板--
function HallPanel.InitPanel()
logWarn("我是HallPanel,我被加载了."); --排行榜按钮
HallPanel.rankingBtn = transform:FindChild("BtnRanking").gameObject; --排行榜面板
HallPanel.rankingPanel = transform.parent:Find("RankingPanel"); --调用Ctrl中panel创建完成时的方法
HallCtrl.OnCreate(gameObject);
end function HallPanel.OnDestroy()
logWarn("OnDestroy---->>>");
end

完整的HallCtrl.lua

HallCtrl = {};
local this = HallCtrl; local behaviour;
local transform;
local gameObject; --构建函数--
function HallCtrl.New()
logWarn("HallCtrl.New--->>");
return this;
end function HallCtrl.Awake()
logWarn("HallCtrl.Awake--->>"); logWarn("我是HallCtrl,我被加载了.");
end --启动事件--
function HallCtrl.OnCreate(obj)
gameObject = obj;
transform = obj.transform; UIEventEx.AddButtonClick(HallPanel.rankingBtn, function ()
log("你点击了排行榜按钮"); HallPanel.rankingPanel.gameObject:SetActive (true);
end); end --单击事件--
function HallCtrl.OnClick(go)
destroy(gameObject);
end --关闭事件--
function HallCtrl.Close()
panelMgr:ClosePanel(CtrlNames.Hall);
end

2、处理RankItem

思路: 我们的目标是让RankItem具有独立处理逻辑的能力(包括生命周期函数的执行),想到的第一个办法就是继续使用上边讲到的CustomBehaviour脚本。

CustomBehaviour适用于面板加载,且每个面板要对应一个XxxPanel.lua和XxxCtrl.lua,并且还要注册,用起来有点不方便。所在决定重新创建一个C#脚本,以处理各种Item类型的Unity对象(如RankItem,ShopItem等)与Lua的绑定关系。

考虑到RankItem可能是动态创建的,所以这个脚本应该有绑定unity对象与Lua脚本对象的能力。

步骤:

1)创建一个LuaComponent脚本

将这个脚本放在 “Assets\LuaFramework\Scripts\Utility”下,这个脚本包含将GameObjet与LuaTable进行绑定的Add方法以及调用Lua脚本生命周期函数的方法。见下图

LuaCompnent.cs的完整代码:

/*
* 让Lua脚本也能挂载到游戏物体上的组件
*
* LuaComponent主要有Get和Add两个静态方法,其中Get相当于UnityEngine中的GetComponent方法,Add相当于AddComponent方法,
* 只不过这里添加的是lua组件不是c#组件。每个LuaComponent拥有一个LuaTable(lua表)类型的变量table,它既引用上述的Component表。
* Add方法使用AddComponent添加LuaComponent,调用参数中lua表的New方法,将其返回的表赋予table。
* Get方法使用GetComponents获取游戏对象上的所有LuaComponent(一个游戏对象可能包含多个lua组件,由参数table决定需要获取哪一个),
* 通过元表地址找到对应的LuaComponent,返回lua表
*
* Add by TYQ
*/ using UnityEngine;
using System.Collections;
using LuaInterface;
using LuaFramework; public class LuaComponent : MonoBehaviour
{
//Lua表
public LuaTable table; //添加LUA组件 public static LuaTable Add(GameObject go, LuaTable tableClass)
{ LuaFunction fun = tableClass.GetLuaFunction("New"); if (fun == null) return null; /*object[] rets = fun.Call(tableClass);
if (rets.Length != 1) return null; LuaComponent cmp = go.AddComponent(); cmp.table = (LuaTable)rets[0];
*/ //lua升级后不,Call方法不再返回对象,因此改为Invoke方法实现
object rets = fun.Invoke<LuaTable, object>(tableClass);
if (rets == null)
{
return null;
}
LuaComponent cmp = go.AddComponent<LuaComponent>();
cmp.table = (LuaTable)rets; cmp.CallAwake();
return cmp.table;
} //添加LUA组件,允许携带额外一个参数(args)
public static LuaTable Add(GameObject go, LuaTable tableClass, LuaTable args)
{
LuaFunction fun = tableClass.GetLuaFunction("New");
if (fun == null)
return null; object rets = fun.Invoke<LuaTable, object>(tableClass);
if (rets == null)
{
return null;
}
LuaComponent cmp = go.AddComponent<LuaComponent>();
cmp.table = (LuaTable)rets; cmp.CallAwake(args);
return cmp.table;
} //添加LUA组件
// isAllowOneComponent为true时,表示只添加一次组件,如果已存在,就不再添加
public static LuaTable Add(GameObject go, LuaTable tableClass, bool isAllowOneComponent)
{
//如果已存在,则不再添加
LuaComponent luaComponent = go.GetComponent<LuaComponent>();
if (luaComponent != null)
{
return null;
} LuaFunction fun = tableClass.GetLuaFunction("New"); if (fun == null)
return null; object rets = fun.Invoke<LuaTable, object>(tableClass);
if (rets == null)
{
return null;
}
LuaComponent cmp = go.AddComponent<LuaComponent>();
cmp.table = (LuaTable)rets; cmp.CallAwake();
return cmp.table;
} //获取lua组件 public static LuaTable Get(GameObject go, LuaTable table) {
/*
LuaComponent[] cmps = go.GetComponents();
foreach (LuaComponent cmp in cmps)
{
string mat1 = table.ToString();
string mat2 = cmp.table.GetMetaTable().ToString();
if (mat1 == mat2)
{
return cmp.table;
}
}
*/ LuaComponent cmp = go.GetComponent<LuaComponent>();
string mat1 = table.ToString();
string mat2 = cmp.table.GetMetaTable().ToString();
if (mat1 == mat2)
{
return cmp.table;
} return null; } //删除LUA组件的方法略,调用Destory()即可 //调用lua表的Awake方法
void CallAwake()
{ LuaFunction fun = table.GetLuaFunction("Awake"); if (fun != null)
fun.Call(table, gameObject);
} //调用lua表的Awake方法(携带一个参数)
void CallAwake(LuaTable args)
{ LuaFunction fun = table.GetLuaFunction("Awake");
if (fun != null)
fun.Call(table, gameObject, args);
} private void OnEnable()
{
// Debug.Log("================================================================================");
//Debug.Log(table); if (table == null)
{
//Debug.LogWarning("Table is Null---------------------");
return;
} LuaFunction fun = table.GetLuaFunction("OnEnable"); if (fun != null)
{
fun.Call(table, gameObject);
}
} void Start() {
LuaFunction fun = table.GetLuaFunction("Start"); if (fun != null) fun.Call(table, gameObject);
} void Update()
{
//效率问题有待测试和优化 //可在lua中调用UpdateBeat替代 LuaFunction fun = table.GetLuaFunction("Update"); if (fun != null) fun.Call(table, gameObject);
} private void FixedUpdate()
{
LuaFunction fun = table.GetLuaFunction("FixedUpdate"); if (fun != null) fun.Call(table, gameObject);
} private void LateUpdate()
{
LuaFunction fun = table.GetLuaFunction("LateUpdate"); if (fun != null) fun.Call(table, gameObject);
} void OnCollisionEnter(Collision collisionInfo) { //略 } //更多函数略 private void OnDisable()
{
if (table != null) {
LuaFunction fun = table.GetLuaFunction("OnDisable"); if (fun != null)
{
fun.Call(table, gameObject);
}
}
} private void OnDestroy()
{
if (table != null)
{
LuaFunction fun = table.GetLuaFunction("OnDestroy"); if (fun != null)
{
fun.Call(table, gameObject);
}
}
} }

这个脚本的写法参考了知乎上 罗培羽 大佬的一篇文章 :Unity3D热更新LuaFramework入门实战(4)——Lua组件

该文章里有详细的原理阐述,我这里就不多解释了。

LuaComponent.cs脚本创建完毕后,需要添加到CustomSetting.cs文件中并进行导出操作(Generate All)。

2)创建一个RankItem.Lua的脚本,并放在Controller/Hall目录下。

RankItem的主要功能是在其Start方法中查找子组件并赋值 以及 添加按钮点击事件,见代码:

function RankItem:Start()
-- 这里的id, name, score来源于绑定时的赋值,见RankingPanel的 InitPanel方法
-- 设置Id
self.obj.transform:Find("TextOrder"):GetComponent("Text").text = self.id;
-- 设置name
self.obj.transform:Find("TextName"):GetComponent("Text").text = self.name;
-- 设置score
self.obj.transform:Find("TextScore"):GetComponent("Text").text = self.score; UIEventEx.AddButtonClick(self.obj, function ()
log("你点击了RankItem " .. self.name);
end);
end

RankItem.lua的完整代码在这里:

RankItem = {
--里面可以放一些属性
name = "RankItem",
index = -, --索引
obj = nil --脚本关联的对象
} function RankItem:Awake()
--print("RankItem Awake name = "..self.name );
end function RankItem:Start() -- 设置Id
self.obj.transform:Find("TextOrder"):GetComponent("Text").text = self.id;
-- 设置name
self.obj.transform:Find("TextName"):GetComponent("Text").text = self.name;
-- 设置score
self.obj.transform:Find("TextScore"):GetComponent("Text").text = self.score; UIEventEx.AddButtonClick(self.obj, function ()
log("你点击了RankItem " .. self.name);
end);
end --Item点击事件
function RankItem.OnItemClick (go, selfData) end function RankItem:Update() end --创建对象
function RankItem:New(obj)
local o = {}
setmetatable(o, self)
self.__index = self
return o
end

3)在RankingPanel.lua中查找RankItem的引用,并进行绑定操作

a.声明rankitemData变量,这里存放的是将要显示在RankItem上的数据。

b.查找rankItem子组件并用LuaComponent.Add方法执行绑定操作,代码如下:

--排行榜项数据
local rankItemData = {
{id = , name = "张三1", score = },
{id = , name = "张三2", score = },
{id = , name = "张三3", score = },
{id = , name = "张三4", score = }
} --初始化面板--
function RankingPanel.InitPanel() local rankList = transform:FindChild("RankList");
for i = , rankList.childCount do local go = rankList:GetChild(i - ).gameObject;
log(go.name); local item = LuaComponent.Add(go, RankItem);
item.name = rankItemData[i].name;
item.index = i;
item.obj = go; item.id = rankItemData[i].id;
item.score = rankItemData[i].score;
end RankingCtrl.OnCreate(gameObject);
end

完整的RankingPanel.lua代码在这里:

local transform;
local gameObject; require("Controller/Hall/RankItem") RankingPanel = {};
local this = RankingPanel; --启动事件--
function RankingPanel.Awake(obj)
gameObject = obj;
transform = obj.transform; this.InitPanel();
logWarn("=========Awake lua--->>"..gameObject.name);
end --排行榜项数据
local rankItemData = {
{id = , name = "张三1", score = },
{id = , name = "张三2", score = },
{id = , name = "张三3", score = },
{id = , name = "张三4", score = }
} --初始化面板--
function RankingPanel.InitPanel() local rankList = transform:FindChild("RankList");
for i = , rankList.childCount do local go = rankList:GetChild(i - ).gameObject;
log(go.name); local item = LuaComponent.Add(go, RankItem);
item.name = rankItemData[i].name;
item.index = i;
item.obj = go; item.id = rankItemData[i].id;
item.score = rankItemData[i].score;
end RankingCtrl.OnCreate(gameObject);
end --单击事件--
function RankingPanel.OnDestroy()
logWarn("OnDestroy---->>>");
end

4)运行

运行Hall场景,点出排行榜面板。

能看到在lua脚本给定的值(rankItemData )已经被正确显示到RankItem上了。点击相应项,输出的内容也符合预期。

总结

要用Lua做逻辑开发,怎么让unity对象绑定lua脚本,是一个绕不过去的问题。由于网上相关资料比较少,这一篇讲的都是自己摸出来的一点门道,不知道写得是否对,但勉强还能用,仅供参考。

补充一个在LuaFramework中实现Update的简单方法

要在XxxPane中实现Update等方法,直接在其Awake函数中写 UpdateBeat:Add(Update, self) 就行,见代码

function XxxPanel.Awake(obj)
gameObject = obj;
transform = obj.transform; UpdateBeat:Add(Update, self);
FixedUpdateBeat:Add(FixedUpdate, self);
LateUpdateBeat:Add(LateUpdate, self);
end

Add函数的第一个参数是一个function, 是这个脚本中定义的函数。这个UpdaateBeat应该是框架实现的全局函数。

2019-07-28更新 :

已找到新的启动HallPanel的方式,放弃使用CustomBehaviour并延迟调用Awake的方法,操作如下:

a)移除HallPanel身上的CustomBehaviour;

b)在Game.lua的OnInitOK方法末尾添加如下语句

    --查找HallPanel对象,并发起对HallPanel.Awake的调用
local objHallPanel = UnityEngine.GameObject.Find("Canvas").transform:GetChild().gameObject;
HallPanel.Awake(objHallPanel);

代码位置见下图:

c)重新运行unity,点击排行榜按钮,效果如前。

至于RankItem.lua和LuaComponent.cs,不存在问题,依然用之前介绍的使用方式。

Unity3D热更新之LuaFramework篇[07]--怎么让unity对象绑定Lua脚本的更多相关文章

  1. Unity3D热更新之LuaFramework篇[10]--总结篇

    背景 19年年初的时候,进到一家新单位,公司正准备将现有的游戏做成支持热更的版本.于是寻找热更方案的任务就落在了我头上. 经过搜索了解,能做Unity热更的方案是有好几种,但是要么不够成熟,要么不支持 ...

  2. Unity3D热更新之LuaFramework篇[03]--prefab加载和Button事件

    在上一篇文章 Unity3D热更新之LuaFramework篇[02]--用Lua创建自己的面板 中,我介绍了LuaFramework加载面板的方法,但这个方法并不适用于其它Prefab资源,在这套框 ...

  3. Unity3D热更新之LuaFramework篇[02]--用Lua创建自己的面板

    在上篇文章 Unity3D热更新之LuaFramework篇[01]--从零开始 中,我们了解了怎么获得一个可用的LuaFramework框架. 本篇将我会先介绍一下如何配置Lua开发环境,然后分析在 ...

  4. Unity3D热更新之LuaFramework篇[09]--资源热更新与代码热更新的具体实现

    前言 在上一篇文章 Unity3D热更新之LuaFramework篇[08]--热更新原理及热更服务器搭建 中,我介绍了热更新的基本原理,并且着手搭建一台服务器. 本篇就做一个实战练习,真正的来实现热 ...

  5. Unity3D热更新之LuaFramework篇[05]--Lua脚本调用c#以及如何在Lua中使用Dotween

    在上一篇文章 Unity3D热更新之LuaFramework篇[04]--自定义UI监听方法 中,我对LuaBehaviour脚本进行了扩展,添加了两个新的UI监听方法,也提到最好能单写一个脚本处理此 ...

  6. Unity3D热更新之LuaFramework篇[04]--自定义UI监听方法

    时隔一个多月我又回来啦! 坚持真的是很难的一件事,其它事情稍忙,就很容易说服自己把写博客的计划给推迟了. 好在终于克服了自己的惰性,今天又开始了. 本篇继续我的Luaframework学习之路. 一. ...

  7. Unity3D热更新之LuaFramework篇[06]--Lua中是怎么实现脚本生命周期的

    前言 用c#开发的时候,新建的脚本都默认继承自Monobehaviour, 因此脚本才有了自己的生命周期函数,如Awake,Start, Update, OnDestroy等. 在相应的方法中实现游戏 ...

  8. Unity3D热更新之LuaFramework篇[08]--热更新原理及热更服务器搭建

    前言 前面铺垫了这么久,终于要开始写热更新了. Unity游戏热更新包含两个方面,一个是资源的更新,一个是脚本的更新. 资源更新是Unity本来就支持的,在各大平台也都能用.而脚本的热更新在iOS平台 ...

  9. Unity3D热更新之LuaFramework篇[01]--从零开始

    前言 因工作关系,需要对手头的项目进行热更新支持.了解后发现,Lua的几个变种:XLua.ToLua(原uLua)和Slua都可以做Unity热更,而ToLua更是提供了一个简易的热更框架--LuaF ...

随机推荐

  1. python之数据分析pandas

    做数据分析的同学大部分入门都是从excel开始的,excel也是微软office系列评价最高的一种工具. 但当数据量超过百万行的时候,excel就无能无力了,python第三方包pandas极大的扩展 ...

  2. IOS 数据存储(NSKeyedArchiver 归档篇)

    什么是归档 当遇到有结构有组织的数据时,比如字典,数组,自定义的对象等在存储时需要转换为字节流NSData类型数据,再通过写入文件来进行存储. 归档的作用 之前将数据存储到本地,只能是字符串.数组.字 ...

  3. redis连接错误3种解决方案System Error MISCONF Redis is configured to save RDB snapshots

    redis连接错误System Error MISCONF Redis is configured to save RDB snapshots, but XX   情况1解决办法: 由于强制停止red ...

  4. 区块狗开发可以做出APP吗

    区块狗系统开发林生▉l8l加4896微9698电同步▉,区块狗奖励系统开发,区块狗平台系统开发,区块狗系统开发软件,区块狗系统开发案例,区块狗源码系统开发. 本公司是软件开发公司,华登区块狗/十二生肖 ...

  5. 解析《Effective Java》之多个构造器、Javabeans模式和Builder模式

    最近看<Effective Java>这本被很多同行称为神作的书,但是这本书很多地方缺少了举例不好懂,下面是关于我对书上知识的理解. 一.<Effective Java>中文版 ...

  6. 支持向量机 (二): 软间隔 svm 与 核函数

    软间隔最大化(线性不可分类svm) 上一篇求解出来的间隔被称为 "硬间隔(hard margin)",其可以将所有样本点划分正确且都在间隔边界之外,即所有样本点都满足 \(y_{i ...

  7. 如何正确选择挑选适合的VPS服务器

    就来讲讲,如何挑选适合你的VPS.基本过程就是:1.你使用VPS的用途:2.你需要的线路:3.你要选择的操作系统:4.你购买VPS的大概预算是多少. 一.用途方法,其实买VPS就是:建站.VPN使用. ...

  8. 从零开始一起学习SALM-ICP原理及应用

    点"计算机视觉life"关注,星标更快接收干货! ## 小白:师兄,最近忙什么呢,都见不到你人影,我们的课也好久没更新了呢 师兄:抱歉,抱歉,最近忙于俗事.我后面一起补上,学习劲头 ...

  9. HDU 5527:Too Rich(DFS+贪心)***

    题目链接 题意 给出p块钱,现在要用十种硬币凑出,每种硬币有c[i]个,问最多能用多少个硬币. 思路 首先确定,对于每个硬币就是能用小的替换就不用大的. 所以,可以先把硬币尽量用小的替换,如果小的不够 ...

  10. Spring Boot 最流行的 16 条实践解读,你值得收藏!

    Spring Boot是最流行的用于开发微服务的Java框架.在本文中,我将与你分享自2016年以来我在专业开发中使用Spring Boot所采用的最佳实践.这些内容是基于我的个人经验和一些熟知的Sp ...