从《LOL》谈游戏中的随机动作优化

游戏开发人员面对的往往是一个长期持续演进的软件产品,如”英雄联盟”(译者注:League of Legends,以下简称LOL,是美国Riot游戏公司开发的3D大型竞技场网游),这些游戏产品在演进过程中会随着剧情的发展在内部的有限状态机上不断地添加越来越多的内容。新加入的内容不仅导致愈加复杂的逻辑实现,同时由于需要更多的纹理绘制,仿真计算,也带来了内存消耗的增加和性能的下降。如果我们忽视这些问题(或者由于实现出现错误)的话,那么必然会损害游戏的运行性能,导致游戏的体验感下降。游戏动画中产生的任何延迟,阻塞都会直接导致玩家感到异常沮丧。

我所在团队的使命是致力于改善LOL这款游戏的性能。通过测试游戏的客户端和服务器端,找到影响性能的代码,并改进它们。同时我们也会把研究成果反馈给其他的团队,并为他们提供分析工具和测量指标,让他们能尽早发现性能上的问题。我们持续地提升LOL的性能表现,为游戏情节设计师和艺术家们腾出更多的空间来添加更酷的新东西:我们的任务是让游戏运行得更快,而他们的工作是让游戏变得更有趣,更好玩。

这篇文章是一个系列中的第一篇,介绍我们的团队是如何在游戏内容不断添加的情况下对游戏的运行速度进行优化的。这是一件非常有挑战性的任务。本文将和各位深入探讨一下在面对如何提高游戏中的粒子系统(particle system)的性能中所遇到的一些有趣的问题,正如下面的动画,其展现了粒子系统在游戏软件中所扮演的举足轻重的作用。


LOL游戏中高密度粒子运动动画场景的例子

优化不是简单地重写一段代码,尽管有时候的确需要这样。我们对代码的修改,其目的不仅是要提升性能,更要保证其正确性,并尽可能地提高代码的可维护性。最后这一点是至关重要的:任何代码,如果可读性和可维护性不好的话,都会在未来给项目带来潜在的问题。

在优化现有的代码时,我们会采取三个基本步骤:识别,理解和迭代。

第一步: 识别

在优化前,我们首先需要确认该代码的确需要优化。有些代码咋看上去运行效率是比较低,但如果它们对系统的整体性能的影响本身就微乎其微的话,那还是不要花精力去改动它(有这时间和精力还不如用在其他的地方)。我们用代码分析工具和测试样本来帮助识别低效的代码。

第二步: 理解

一旦我们识别出低效的代码,就可以更深入地研究这些代码来真正了解发生了什么事情。我们需要知道代码在干什么,以及原来实现的真实意图。然后,我们就可以明白为什么这里的代码是一个瓶颈。

第三步: 迭代

当我们明白为什么这些代码是低效的,以及它的目的是什么,我们就有足够的信息来设计并实现一个可能的解决方案。使用第一步识别步骤所提到的分析工具和样本数据,我们可以对比新代码和老代码在性能上孰优孰劣。如果新的解决方案足够的快,并且经过全面的测试确保没有引入任何新的错误,那么赶紧庆祝一下,至此我们已经为进一步的内部测试做好准备。但大部分情况下,如果新的改动并没有带来性能上的显著提高,那么我们就不断地迭代解决方案,直到它足够地快。

最近我针对LOL的粒子系统做了一次优化工作,就让我们基于这次优化一步一步地来看看这个过程是怎么实现的吧。

第一步: 识别

Riot公司(译者注:Riot直译为拳头,即著名的Riot Games,美国网游开发商,成立于2006年,代表作品LOL《英雄联盟》,2011年被腾讯收购)的工程师们使用各种分析工具检查游戏的客户端和服务器的性能。首先让我们来看看一款叫做Waffles的内部分析工具,通过有选择地测试某些指定的函数,它可以帮助我们检查客户端的帧刷新速度并提供图形化的分析数据。 (Waffles也可被用于许多其他的事情,如在测试中触发调试事件,检查游戏数据,譬如导航网格,还可以暂停或减慢游戏的运行。)


Waffles

Waffles可以动态地显示详细的性能信息。上图是一个客户端性能数据展示的例子。图上方的那些绿色的竖条表示采样点上的帧速(单位为毫秒),竖条越长,表示该采样点的帧速越慢。在游戏中那些异常慢的帧速点被称之为“结点”(hitches)。在绿色竖条的下方是对应每个采样点(即绿色竖条)的函数调用的详细信息,以函数调用的层次结构方式展现。点击任意一个绿色竖条,就可以看到该采样点的详细信息。通过这种方式,我们可以形象地深入了解哪些代码块对整体运行效率有拖累。

在代码中(译者注,本文作者的编程语言应该是C++)我们定义一个简单的宏来帮助我们提取代码里的重要函数在运行期间的性能相关参数。对于正式发布的版本,这个宏不起作用,但对于内部调试的版本,这个宏会构造一个很小的类,在函数被调用时会创建一个该类的对象,该对象会生成一条消息,并将该条消息输出到一个缓存里保存起来用于事后的分析。这条消息中一般会包含一个字符串标识符,一个线程号,一个时间戳,还有其他所有被认为有必要保存的信息(例如,在该函数执行过程中分配的内存大小的值)。当该对象超出其作用范围后其析构函数会根据该对象的构造函数被调用的时间点计算出该对象生存的时长并将该时长信息也更新到缓存中和该对象所对应的消息中。在稍后的一个时间点,缓存中的数据可以被导出并用于进一步的分析,导出的动作一般由另外一个进程执行,以尽量不影响游戏本身的运行。


基于Chrome的可视化跟踪分析插件

在我们的例子中,缓存中的内容将被导出并保存为一个文件(保存为json格式),该文件可以被导入到一个可视化的跟踪工具里,而这个工具软件可以以插件的方式集成在Chrome浏览器中方便使用。 (你可以在这里找到有关这个跟踪工具的更多信息,并通过在Chrome浏览器里键入“chrome://tracing/”试用它。这个插件可以方便我们直接在网页浏览器里查看分析结果。)该图形化的分析工具可以帮助我们找到那些运行较慢的函数,或者发现哪里存在大量的小函数被连续调用的情况(译者注:连续的大量小函数被调用会导致频繁的函数压栈和出栈发生,这会导致CPU运行效率降低),这两种情况都意味着需要优化。

让我来告诉你如何使用这个工具:上面的图是一个Chrome里显示的跟踪视图,它显示了客户端上运行的两个线程。最上面的一个是主线程,主要的处理逻辑由它完成,下面的是粒子线程,用于粒子的处理。每个彩条对应一个函数,彩条的长度指示了函数执行的时间。函数间的调用关系通过彩条的上下位置关系表示,调用函数显示在被调用函数的上面。这种安排很好地展现了函数执行调用之间的复杂关系以及函数的执行时间。当我们感觉某段区域存在不合理的代码时,可以将特定的区域放大,这样我们就可以观察到更多的细节了。


放大后的跟踪显示

让我们放大图的中间部分。观察上方的主线程,我们可以看到一个非常漫长的等待“Wait”过程,其结束的位置对应于下方线程中的Simulate函数的结束点位置。Simulate函数中含有大量的不同的函数调用(见Simulate色带下方的彩色栏)。这些函数都是粒子系统的更新函数用于更新每个粒子对象的位置,方向,以及其他需要显示的特征点。一个明显的优化措施是用多线程来实现Simulate函数,但在本文中我们将主要着眼于优化Simulate函数的代码本身。

在找到了会影响性能的大致地方后,我们可以启用采样分析的方法来更准确地定位性能改进点。这种分析方法周期性地读取 程序计数器(program counter)的内容并记录下来,甚至会访问运行中的进程栈。经过一段时间运行后,此类统计信息大致可以为我们勾勒出代码中指令运行的随机过程状态(译者注:即通过查看PC来统计程序运行过程中某些经常被执行的指令的执行频度,或通过查看程序栈了解函数的调用情况等)。对于那些运行较慢的函数,由于在这些函数里停留的时间较长,会导致采样的结果更多地发生在这些函数中,进一步地,该方法也适用于指令级别,可以检测出那些消耗时间较长的指令段。这样,我们不仅可以发现那些执行最慢的函数,还可以发现执行最慢的代码。有很多很好的采样分析软件,从免费的像 Very Sleepy到功能更全的商业软件,像Intel的 VTune。

通过在游戏客户端上运行VTune检查粒子线程,我们可以得到运行最慢的函数的列表如下。


VTune中显示的运行最慢的函数列表

上表列出了一些和粒子处理相关的函数。最上面的两个函数花费的时间最多,但它们实现的功能也多,主要用于更新和每个粒子的位置和方向都相关的各种矩阵信息和状态。在这里我将特别关注列表的第三项和第九项,即AnimatedVariableWithRandomFactor<>这个类的成员函数Evaluate,因为较于前述的两个函数,这个函数并不大(因此很容易理解),但运行时花费的时间却不短。

第二步:理解

现在,我们已经选择了一个需要优化的函数,我们需要了解它做了什么,以及为什么这么做。在上述例子里,AnimatedVariables这个类是LOL游戏中的情节设计师(artists)用来定义粒子的特征点是如何随时间变化而变化用的。设计师首先为某个特定的粒子对象定义好所有关键帧中的位置数据,然后通过代码对这些数据插值拟合出一条运动轨迹曲线。插值方法可以用线性插值法,或者一阶,甚至二阶积分插值法。在游戏中动态曲线被大量使用 - 譬如在象“Summoner’s Rift”(译者注:LOL中一处著名的游戏场景的地名)里,光一个这样的场景中就要计算近40,000处 - 包括所有粒子的颜色,尺寸到运动速度。在一次游戏中Evaluate这个函数被调用高达数亿次。要知道,在LOL中,粒子的定义是游戏中的关键部分,它们的行为是不能随意改变的。

经过优化,这个类使用了一个查找表(以数组的形式),针对每个关键帧存放了一个预先计算好的值。在运行期间直接读取即可,避免了频繁的计算。这是一个合理的选择,因为采用一阶和二阶积分计算曲线轨迹是一个非常消耗CPU的过程。如果在每个系统中对每个粒子的每个动画都执行类似计算将导致游戏程序的运行速度显著放缓。


动态变化曲线查询表

当查看性能问题时,我们经常会在一些极端条件下进行测试和验证。为了模拟粒子运行的慢动作我新建了一个单人游戏,包括九个中等技能水平的自动机器人(bots),并在在游戏中的下路(bottom lane)上策动了一次大规模的团战(teamfight)。在战斗的同时我在客户端运行VTune,记录下各种数据。对Evaluate这个函数的代码给出了如下分析结果(如下图所示)。

Evaluate函数中第90行涉及到一个函数调用,为了说明方便,我把函数调用中的代码复制出来放在第91-95行,同时注释掉了第90行。


VTune中的分析例子

让我来给你们详细解释一下VTune显示的内容含义,上图以图形化的方式表明了该函数在采样分析运行过程中收集到的结果。右边的红色条状表示采样命中的频度,红色条越长,说明命中的频度越高,频度越高,说明这行代码运行得越慢。条状物的右边显示的时间是CPU运行该行代码大致花费的时间。你也可以选择以汇编的方式查看每个函数,那样就可以在机器指令级别识别具体是哪条指令拖慢了整个函数的执行。

不出意外,第95行就是问题所在了。但是,这行语句所做的事情无非就是将一个Vertor3f对象从查找表中拷贝出来,为何它会成为该函数中执行最慢的部分呢,为何拷贝一个12字节长度的数据会如此之慢?

答案在于现代CPU访问内存的方式。处理器的处理速度相当可靠地遵守着摩尔定律,按照每年60%左右的比例增长,而内存的增长速度则相对缓慢得多,每年只有10%左右。


CPU和内存处理速度的走势图,由John L. Hennessy, David A. Patterson, Andrea C. Arpaci-Dusseau提供

缓存(Cache)可以弥补这种性能上的差距。大部分LOL游戏跑在具有三级缓存的CPU上:一级缓存最快,容量最小,三级缓存最慢,但容量也最大。从一级缓存读取一条数据大约需要4个周期,而从主存(三级缓存)读出可能需要大约300个周期以上。要知道300个周期足够CPU做很多事情了。

优化前的查找算法的问题是,如果是顺序地从查询表读取数据速度是相当快的(基于硬件预取的功能),但游戏中所处理的粒子数据并不是按照时间顺序存储的,所以导致查找总是按照随机方式进行。这造成CPU不得不经常停下来等待数据从主存储器上被读进来。虽然300个周期可能比一阶或二阶积分计算所花费的机器周期要少,我们还是需要知道在游戏中这种延迟发生的频率怎样。

为了找到答案,我们增加了一些辅助代码用于统计AnimatedVariables对象被创建的次数的数量和每次创建对应的类型。我们发现在将近38000次AnimatedVariables对象的创建和销毁过程中:

  • 有37500次是线性插值; 100次一阶积分插值,400次是二阶积分插值。
  • 有31500次查询表中只有一项; 2500次有3项; 1500次有2项或4项。

所以,大部分情况处理的是单项。但代码中每次都会创建一个查找表,显而易见大部分情况该表只包含一项。而每次建表查找(它总是返回相同的值)一般都会重新刷新高速缓存,从而导致了内存和CPU处理周期的浪费。

很多时候,以下四个原因之会造成代码中的效率瓶颈:

  • 函数调用过于频繁。
  • 选择了错误的算法:例如:可以用O(n),却使用了O(n^2)。
  • 做不必要的工作,或者虽然工作是必需的要但执行过于频繁了。
  • 数据的问题:要么数据太多或者数据存储方式和访问方式不好。

这里的问题并不是由于设计或编码造成的。解决方案本身没有问题。问题在于有些事情在程序开发阶段是无法知道的,就像这里,游戏软件的情节经过设计师的不断修改后,造成了在大部分场景下某些用于计算的数据变成了一个单一值(不需要建表查询了)。

顺便说一句,作为一个程序员我所领悟到的最重要的事情之一就是要尊重你所面对的代码。有些代码可能看起来让你很抓狂,但它被写成这样可能是有原因的。不深入研究就放弃并重构现有的代码是愚蠢的行为,修改代码之前一定要保证你完全理解它是如何被使用的,以及它为什么被设计成这样。


来源: http://codesoftly.com/2010/03/ha-code-entropy-explained.html

第三步:迭代

现在知道了问题症结所在,它​​应该做的是什么以及为什么它那么慢。现在可以开始制定解决方案了。首先,我们发现程序最常见的执行路径对应的是单变量,在重新设计时需要考虑到这一点。其次,如果插值点很少,那么采用线性插值将非常快(因为这仅涉及读取少量的高速缓存中的数据并进行简单的计算),这也是我们需要考虑的一点。最后,我们可以认为只有很少的情况才会需要为计算积分曲线执行建表操作并进行查找。

由于大部分情况我们是不需要查表的(译者注:对应上面所说的前两点),所以并不需要每次计算时都执行创建查询表的动作,这样我们可以释放相当数量的内存(绝大多数的表有至少256项甚至更多,每项最多12个字节。这相当于大约每张表占3K字节)。释放的内存中的一小部分可以被另外用来作为缓存存放少量的表项(译者注:对应上面的第二点)或者单一变量(译者注:对应上面的第一点)。

原来的代码如下:

template <typename T>
class AnimatedVariable
{
    // <snip>
private:
    std::vector<float> mTimes;
    std::vector<T>     mValues;
};

template <typename T>
class AnimatedVariablePrecomputed
{
    // <snip>
private:
    std::vector<T> mPrecomutedValues;
};

AnimatedVariablePrecomputed对象由AnimatedVariable对象构造,用于内插和建立一个指定大小的表。Evaluate()函数只会被AnimatedVariablePrecomputed对象调用。

We changed the AnimatedVariable class to look like this:

改动后的AnimatedVariable类如下所示:

template <typename T>
class AnimatedVariable
{
    // <snip>
private:
    int mNumValues;
    T mSingleValue;

    struct Key
    {
        float mTime;
        T     mvalue;
    };
    std::vector<Key> mKeys;
    AnimatedVariablePrecomputed<T> *mPrecomputed;
};

我们增加了一个缓存值mSingleValue,同时增加了一个变量mNumValues用来告诉我们何时使用mSingleValue。如果mNumValues的值是1(单键的情况),Evaluate()函数就立即返回mSingleValue而不需要进一步的处理。同时我们还把分开定义的两个数组mTimes和mValues组合成一个新的结构体数组mKeys,避免了实际匹配的Time和Value在各自数组中位置不对应,这样可以减少缓存不命中的概率。

不考虑数组mKeys(实质是指针数组)所指向的实际数据的大小,由于模版T(mSingleValue的类型)的实际类型不定,这个类现在的大小在24至36个字节之间(当然具体还取决于CPU架构类型,以及数组mKeys的元素的个数多少)。

最初的Evaluate()函数是这样的:

template <typename T>
T AnimatedVariablePrecomputed<T>::Evaluate(float time) const
{
    in numValues = mPrecomputedValues.size();
    RIOT_ASSERT(numValues > 1);

    int index = static_cast<int>(time * numValues);
    // clamp to valid table entry to handle the 1.0 boundary or out of bounds input
    index = Clamp(index, 0, numValues - 1);
    return mPrecomputedValues[index];
}

而经过改造的新的Evaluate()函数如下,通过运行VTune,你可以看到三种情况下运行的情况:单值(红色),线性插值(蓝色),积分查表插值(绿色)。


VTune改进后的代码示例

改进后的代码运行速度大致提高了三倍:在运行最慢的函数列表中从原来的排名第3降到了第22位!改进不仅仅体现在更快,改进后使用的内存也减少了,少了大约750KB。这还没完!不仅速度更快,更省内存,执行线性插值也更准确了!

在本文中由于篇幅的原因我没有介绍改进中所经历的曲折过程。我曾经尝试过基于粒子的生命周期减少插值点表的大小。该方法效果也不错,但较小的表会导致一些快速移动的粒子的显示出现锯齿状条纹。幸运的是这个问题被及早发现,所以最终我采用了本文介绍的解决方案。还有一些其他的数据和代码的修改,但这些修改并没有对运行效率有太大的提升。有些修改甚至带来副作用导致运行速度还变慢了。

总结

我们在这里经历了一次典型的对LOL代码库中的一段代码的优化过程。这些简单的改变为我们节省了750KB的内存,并且使计算粒子运动轨迹的线程速度快了一到两秒,从而使主线程的执行也变得更快了。

文中提到的三个阶段,虽然看似明显,但在程序员寻求优化代码的过程中却常常被忽视,所以再重申一下:

  1. 识别:分析应用程序,并确定表现最差的部分。
  2. 理解:理解代码为什么这么写,以及为何它这么慢。
  3. 迭代:根据第2步的分析修改代码,然后测试检查效果。这样的操作可能需要不断重复,直到性能问题得到解决。

上面的解决方案还不是最快的,但它的思路和方向应该是正确的,只有沿着正确的方向进行迭代才能保证最终的成功。

小白WEB
关注 关注
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
lol英雄联盟阿狸fbx模型全动作
07-03
lol英雄联盟阿狸fbx模型全动作
LOL战绩查询工具 - 精准掌握游戏数据
05-14
探索LOL战绩查询工具,专为《英雄联盟》玩家设计,帮助您轻松追踪和分析游戏战绩。这款工具以其精准的数据收集和直观的用户界面,让您能够深入了解自己及队友的表现。它提供详细的比赛统计,包括KDA、CS、击杀参与率...
Unity一个低成本优化游戏动作的方案探究
u013617851的博客
12-30 2413
本篇博客主要探究一个低成本运用动作捕捉,动态骨骼,以及一个动作标准来提升游戏角色动作表现的方案。 一.什么是游戏动作 顾名思义,就是游戏,人物角色行为举止,走路,跑步,战斗等等。 二.为什么游戏动作如此重要 在一位被卢浮宫邀请过的日本漫画家写的一本书,《荒木飞吕彦的漫画术》,荒木老师认为,漫画有四大基本元素,分别是:角色、剧情、世界观、主题。而其,角色,是终极顶梁柱,如果角色塑造的好,其余剧情世界观主题等元素,甚至可以被忽略。我个人比较认同,因为就像是樱桃小丸子,蜡笔小新,大家都不记得这俩动漫
动作游戏老是卡?试试从这些方面提升流畅度
wubaohu1314的博客
09-15 424
https://www.fgba.net/sitemap.xml
游戏的网络同步机制——Lockstep(转载)
kkgbn的博客
08-20 1782
原文转自http://bindog.github.io/blog/2015/03/10/synchronization-in-multiplayer-networked-game-lockstep 0x00 前言 每个人或多或少都接触过网游,那个虚拟的世界给予了我们无穷的乐趣,而这个虚拟世界是如何完美的将身处天南地北的玩家连接在一起的呢?我们每个人的电脑配置都不一样,网络延迟也不同,但
游戏的网络同步机制——Lockstep
流影
12-23 740
转自:http://bindog.github.io/blog/2015/03/10/synchronization-in-multiplayer-networked-game-lockstep 0x00 前言 每个人或多或少都接触过网游,那个虚拟的世界给予了我们无穷的乐趣,而这个虚拟世界是如何完美的将身处天南地北的玩家连接在一起的呢?我们每个人的电脑配置都不一样,网络延迟也不同,但是
游戏的网络同步机制(一)帧同步Lockstep
木木三的专栏
04-13 951
转载自:https://www.jianshu.com/p/64b3f162dcf4 参考游戏的网络同步机制——Lockstep 一、前言 每个人或多或少都接触过网游,那个虚拟的世界给予了我们无穷的乐趣,而这个虚拟世界是如何完美的将身处天南地北的玩家连接在一起的呢?我们每个人的电脑配置都不一样,网络延迟也不同,但是在玩FPS(第一人称射击)游戏时,战斗感受与真实世界并无二致,网游是如何做到...
150ms流畅体验 NBA2KOnline如何网络同步优化
BigBear的博客
10-22 6807
原文链接:http://gad.qq.com/article/detail/10118 前言 为了不想彻底沦落为技术理论文章, 所以对于技术细节,仅贴图展示一下。希望大家阅读时,能找到一些感兴趣的内容,一起交流。 NBA2KOnline介绍 NBA2KOnline游戏是我们与Take-Tow,2K sports,以及国团队VCC合作开发的一款拟真的篮球游戏。   目前,游戏采用服...
帧同步(LockStep)该如何反外挂 及 优化
gdou_shelin的博客
07-01 2135
原标题:帧同步(LockStep)该如何反外挂 及 优化 01. 帧同步(LockStep)该如何反外挂 在国的游戏环境下,反挂已经成为了游戏开发的重之重,甚至能决定一款游戏的生死,吃鸡就是一个典型的案例。 目前参与了了一款动作射击的MOBA类游戏的开发,同步方案上选择了帧同步技术(LockStep而非snapshots以下同)。那么就有很多人担心起来,客户端会跑全部逻辑帧同步该如何反外挂,和状态同步有什么区别呢? 首先我们来分析一下手...
游戏测试面试总结(网易雷火、飞鱼科技、冰川网络、完美世界、搜狐畅游)
热门推荐
qq_52673687的博客
03-29 4万+
1、英雄联盟手游继承了端游的质感如战场迷雾,整体峡谷色彩偏暗,但建模的精细度高、动作衔接感觉很好,入门的门槛较高、节奏较慢 ,而王者荣耀很明显是偏向于大众化、全民化的游戏,门槛低整体上手难度偏小,色彩艳丽,节奏快、技能打击感强。这主要还包括地图阻碍墙,有些空技能是可以越过墙的,比如王者里面的鸟人、哪吒的大、韩信的跳,闪现等。游戏,活动则是频度更高的一种玩法,测试过程受到的关注度更高,游戏活动的测试更关注时间与资源产出,活动衔接也很重要,任何差错都可能导致更大的损失,而软件上的活动则没这么严格的概念。
Discuz大气游戏风格模板/仿lol英雄联盟游戏DZ游戏模板GBK
12-27
Discuz大气游戏风格模板,lol英雄联盟游戏模板,DZ游戏娱乐模板GBK。模板名称:lol英雄联盟游戏(m0398_lol)。 Discuz大气游戏风格模板,lol英雄联盟游戏模板,DZ游戏娱乐模板GBK。模板名称:lol英雄联盟游戏(m...
绿色系游戏直播吃鸡LOL赛事类网站html模板
02-24
绿色系游戏直播吃鸡LOL赛事类网站html模板
unity3d 类似LOL游戏源码
09-30
unity3d 类似LOL游戏源码unity3d 类似LOL游戏源码unity3d 类似LOL游戏源码unity3d 类似LOL游戏源码
Java开发案例-springboot-66-自定义starter-源代码+文档.rar
05-31
Java开发案例-springboot-66-自定义starter-源代码+文档.rar Java开发案例-springboot-66-自定义starter-源代码+文档.rar Java开发案例-springboot-66-自定义starter-源代码+文档.rar Java开发案例-springboot-66-自定义starter-源代码+文档.rar Java开发案例-springboot-66-自定义starter-源代码+文档.rar Java开发案例-springboot-66-自定义starter-源代码+文档.rar
单家独院式别墅图纸D027-三层-12.80&10.50米-施工图.dwg
05-31
单家独院式别墅图纸D027-三层-12.80&10.50米-施工图.dwg
啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦
最新发布
05-31
啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦
课程大作业基于Vue+PHP开发的简单问卷系统源码+使用说明.zip
05-31
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 课程大作业基于Vue+PHP开发的简单问卷系统源码+使用说明.zip Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Lints and fixes files ``` npm run lint ``` ### Customize configuration See [Configuration Reference](https://cli.vuejs.org/config/).
Django媒体资源学习源代码 (附一套简易Django文件上传源码)
05-31
Django FTP MEDIA_ROOT MEDIA_URL 源码
lol游戏出现的击杀提示语怎么抓取
03-31
2. 打开游戏日志文件,一般可以在游戏设置找到日志文件的位置,或者通过命令行参数来指定日志文件的路径。 3. 在日志文件查找击杀提示语的关键词,例如“kill”、“击杀”、“击毙”等。 4. 使用脚本或程序自动...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • Maya粒子-水滴表面流动效果 4594
  • HotOcean for Maya快速安装指南: 4362
  • 三维电影特效魔术师软件Sidefx Houdini FX v15.0.224 (Win/Mac) 1614
  • VS批量删注释,代码保护第一步 1566
  • 游戏AI之行为树(中) 1211

分类专栏

  • MAYA 2篇
  • 游戏开发 12篇
  • web 6篇

最新评论

  • 用distinct在MySQL中查询多条不重复记录值

    书书书呆子: 你好,关于您的Simple LWRP Reflective Shaders(个人高价官网购买).rar我已经下载购买了,可否请您提供下最新版本?可以继续付积分购买。谢谢~~

  • Maya粒子-水滴表面流动效果

    sinat_35790978: 挺好的

您愿意向朋友推荐“博客详情页”吗?

  • 强烈不推荐
  • 不推荐
  • 一般般
  • 推荐
  • 强烈推荐
提交

最新文章

  • 用distinct在MySQL中查询多条不重复记录值
  • mysql视图学习总结
  • mysql教程导出数据库教程几种方法
2016年31篇
2015年12篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

深圳SEO优化公司徐州营销型网站建设报价张掖优秀网站设计推荐西宁企业网站制作推荐广安SEO按效果付费报价丹竹头网站制作林芝关键词按天扣费公司湛江关键词按天计费商丘关键词按天计费价格海南SEO按天收费上饶网站设计公司厦门百搜标王价格廊坊阿里店铺运营推荐南山关键词按天扣费价格济南优化公司北京优化济宁营销型网站建设公司同乐网站搜索优化坪山网站优化按天收费价格烟台百度网站优化排名推荐襄樊网站优化推广多少钱罗湖建站多少钱大同百度爱采购推荐郴州企业网站改版报价普洱营销型网站建设爱联模板制作哪家好郴州网站改版多少钱木棉湾seo网站优化价格东莞百度关键词包年推广报价醴陵设计网站报价白银推广网站哪家好歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化