大家好,又见面了,我是你们的朋友全栈君。
全是在学官教时遇到的坑,然后数小时后爬出来.同时会添加到处学来的的Unity技巧
———————————————————-
代码:
1.使游戏对象运动的N种方式
更全面的移动方式参考
1、rigidbody.addforce(Vector3 * speed) (见roll-a-ball)
2、rigidbody.velocity(vector3 * speed);(见space shooter,改变位置向量,非常的生硬)
3、rigidbody.MovePositon(vector 3) & rigidbody.MoveRotation(quaternion)(包含移动和转向,细腻,常用。见survival shooter player的移动)(转向包括光标指向转向,和键盘输入转向(见Tanks!))
4、transform.translate(方向 * 速度 * Time.deltatime)
见survival shooter内敌人被消灭后下沉并回收
MoveTowards vs. Lerp vs. Slerp vs. SmoothDamp
2.镜头跟随移动游戏对象移动,脚本内用LateUpdate()而非FixedUpdate()(见roll-a-balll)。
3.Random.Range(min,max),如果两个数都是float则前后都算,如果两个数是int则包前不包后。
4.FindObjectOfType<Inventory>() vs GetComponent<Inventory>()
FindObjectOfType①为外部调用,在所有场景和游戏对象里找,不是很快;通常放在Start()内。
GetComponent②为内部调用,只在挂着该脚本的游戏对象上的其他组件找,他们都在一个inspector里,比如rigidbody,renderer,collider等。
②替代①的方式是给脚本所在的对象选择tag,再用【FindGameObjectWithTag(“tag的名字“).GetComponent<脚本>()】放入Awake()内。
也可以直接 在脚本开头public Inventory inventory; 然后在inspecotor界面把挂着Inventory脚本的游戏对象拖入框。[Adventure Game]
5.Debug
在space shooter中,行星在游戏界面内存在,运行尝试是否子弹能消除时发现行星不见了,这时候在行星的脚本里加了一段“Debug.Log(other.name)”把将行星摧毁的物体的名称发送到了unity的console内。
6.coroutine
在土豆视频内spaceshoot官方教程中行星波生成里解释的很清楚(因为有中文字幕)。coroutine就是等某一段或几段设定好的时间后循环方法内的代码。
调用这种异步(即有时间间隔)的方法要用语句 startCoroutine (方法()); 。含异步的方法要用IEnumerator来代替void,yield return new WaitForSecond(设定好的时间);是指等这段时间后继续循环,写在for语句内表示等N秒后i ,写在循环前表示等N秒后开始while循环(把for循环放入while循环内,判断条件可以按需求写,在 space shooter里想要的是行星不停出直到飞船被毁为止,所以判断条件写的是true(意思是true就是true,一直循环),再在while循环的末尾加上for循环的间隔)。
关于yield return,这篇文章就说的很清楚了:http://www.cnblogs.com/wangchengfeng/p/3724377.html
7.游戏对象的回收(包括落下的行星,行星被射击时的爆炸效果)
在space shooter内行星和飞行的子弹靠的是外置一个boundary(cube,box collider)并用脚本(onTriggerExit)来回收游戏对象。
但行星的爆炸特效并不与boundary碰撞,所以会冗余。在所有爆炸特效下挂载脚本在void start内用Destroy(gameObject, 设定的时间);语句使每帧检测超过设定的时间后被挂载的游戏对象销毁,在survival shooter内对敌人的回收也是如此(先下沉,下沉N秒后摧毁)。
8.unity内脚本的实例化关系
见space shooter内计分板的讲解,即在脚本内写一个public的方法,要在其他脚本内调用要先指定是要调用脚本的哪个实例(即使在只有一个实例的情况下)。
9.Input.GetAxisRaw(); vs Input.GetAxis ();
Raw是指非平滑输入,即输入值只有-1,0,1(左,无输入,右)。这种输入的作用让角色单位输入变小,在操作感官上更有灵敏性,角色反应更快更流畅。(见survival shooter内Player的移动)
10.Time.time vs Time.deltaTime
Time.time是游戏从开始到此刻的持续时间
Time.deltaTime是游戏上一帧的持续时间(增量时间),每一帧完成的秒数都不同,所以在需要固定增量时(比如每秒移动10,每0.5秒攻击一次),要在数值上乘以Time.deltaTime(即单位),否则将变成(每帧移动10,每帧攻击2次)
11.在脚本实例(A)内引用其他脚本实例(B)内方法
【】如果A和B脚本实例都挂在同一对象上(比如survival shooter的敌人上同时挂载Enemy Health和Enemy Attack),则在A内private EnemyAttack enemyAttack; 然后在Awake()内 enemyAttack = GetComponent<EnemyAttack> ();就可以直接调用了。
【】如果A和B在不同对象上(比如survival shooter的Enmey Attack和Player Health)则在A内private PlayerHealth playerHealth;
然后在Awake()内先要player = GameObject.FindGameObjectWithTag<“Player”>();找到挂B脚本的对象(该对象必须添加“Player”的Tag)。
然后再playerHealth = player.GetComponent<PlayerHealth>();,才可调用。
如果是在场景里的GameObject上挂的脚本则可直接Public GameObject xx; 然后在Inspector里拖入。但如果是存成Prefab的的游戏对象上挂的脚本,引用方式必须是上面通过Tag寻找,而且要注意脚本加载顺序找不到引用报错。
还有一种见Adventure Game(仅用于Editor继承),下面的target是Editor特有的字段,表示该Editor的对象,第二个红框里的句子只是把target转换成了ConditionCollection类型,在这之前target仅仅是个Object类型不明
【】如果B在A的子对象上(比如survival shooter的PlayerShooting挂在游戏对象Player的子对象GunBarrelEnd上),在A内引用B的则private PlayerShooting playerShooting;
然后在Awake内playerShooting = GetComponentInChildren<PlayerShooting>();
【】还有一种情况,玩家开枪射击敌人,使敌人生命值降低。player下的游戏对象枪上所挂的脚本PlayerShooting要引用某个敌人上的EnemyHealth来调用生命值降低方法。
则以射线碰撞Raycast,撞到层为“shootable”后返回碰撞点信息,在信息点上再找其脚本实例。(见survival shoot的playershooting脚本)
【】如果一个变量声明方式为public static xx(类型) xx(名称),则在其他脚本内调用不用先声明上面的那一套,直接使用就是。(一般用于分数等不可改元素)
【】游戏内的GameController,SoundController,DataBase等都可以直接在场景里建同名空对象,挂上对应的脚本,设置他们用单例在Awake里加载比较不容易出错。特别是在不同对象在不同场景里无法拖拽引用的情况(见Quiz Game)
【】上面说到trigger可以探测是否挂载特定脚本(EnemyHealth)来判断是否是指定对象(Enemy)。
比如在GameGrind装备系统教程里装备叠加方法中,就用到指定格子下装备上的脚本
那如果要更新该装备下的Text呢,则是该脚本.transform就指代该脚本所在的游戏对象。
12.脚本头的“using …”(见2D Roguelike 4内BoardManager蛮牛)
using System; 这样就可以在脚本里使用Serializable属性。Serializable可以让变量在Inspector里展开或收起。
using System.Collections.Generic;,这样可以使用List
using Random = UnityEngine.Random; 因为系统和unity引擎都有 Random的命名空间。
using UnityEngine.EventSystem; 为了使用Eventsystem的功能,比如在背包拖拽装备(见装备系统)
using System.IO; 为了查找加载文件,比如LitJson(见装备系统)
13.将脚本里的Public属性隐藏,或显示Private属性
有时一些Public属性我们需要在其他脚本调用,但已经调整好了,不想再Inspector内显示
可以在脚本内该属性的上一行加上【HideInInspector】该属性将不会再Inspector面板内显示
显示private属性则在属性上方加上【SerializeField】。
如果不想加语句直接想看到一个脚本内的所有变量并调试,Unity有Debug模式,会显示该游戏对象的所有隐藏组件,包括其脚本的所有属性。Inspector面板右上角选择Debug就好了。
14.脚本内代码分块
有时候代码很长,想分块代码并收起以方便查看,可在想收起的代码块上一行写 #region 代码块名称,结尾写#endregion。
15.for,switch循环和while,if等自动补全代码格式
比如for循环 for(int i =0; i <““`),可以打下for后按两下Tab键系统自动补齐格式
16.脚本内引用工程项目内文件的路径变动问题
在装备系统教程里,我们引用了记录了装备信息Json的文件,如果打包游戏的话会发现游戏无法正常读取该文件,是因为随着打包文件,Json文件的路径也会变化,这时候把该文件放在一个Unity规定的特定文件夹,并用其他语句引用。
具体参见Unity Manual:Streaming Assets页面
17.脚本内注释每个字段并在unity Inspector上悬停显示字段
参数字段太多了不知道什么意思,在声明句上加上[Tooltip(“XX”)],在unity内鼠标停在该属性上出现注释
18.让Unity 内的Button变灰不能摁
用Button.Interactable = false;
19.代码内引用其他GameObject的方法(Find & FindWithTag)
Unity里有3种引用方式
Find 和 FindWithTag都需要该GameObject必须时active的。
1.比如点击Button弹出一个界面,这个界面在平时是隐藏的。
操作方法是:在Unity里该界面Active,然后在Start()或Awake()里用Find或FindWithTag找到然后再SetActive(false),然后就可以使用了。
Find是在所有Active的游戏对象里找,FindWithTag是在所有标签对象里找,所以FindWithTag效率更高,官方不建议在Update()里使用Find因为太耗性能。
2.再比如经常要检测其他对象,比如trigger里的是Enemy还是其他的东西。
这时候除了给Enemy标上Tag,检测other.tag == “Enemy”之外,还有一种方法是if(!other.GetComponent<EnemyHealth>())通过检测trigger物上有没有指定脚本来判断是否是制定物。
20.脚本内比较字符串大小
可直接使用if(str1 == str2)或 if(str1.Equals(str2))
注意string是区分大小写的,我在比较后List元素Add,不停跳出索引有问题的Bug。花了2个半小时才找到原来是str1的首字母时大写的,str2的首字母小写,结果全是false导致没有任何元素添加进List。
21.List清除元素/查找元素/查看是否包含元素
List.Clear(); 清除List内的所有元素,保持List的基本属性(比如List<Enemy> enemy清空后没有元素,但属性仍然是<Enemy>)。
List.Remove(xxx); 清除xxx表达式满足的所有元素,比如删除一个List<int>内为偶数的所有元素:
参考:http://blog.csdn.net/yl2isoft/article/details/17059093
参考:https://msdn.microsoft.com/en-us/library/wdka673a(v=vs.110).aspx
List.Find(xx)
如果找到该语句要求的元素则返回该元素,如果没找到则返回的是该元素类型的默认值,而不是null。参考
List.Contains()
如果找到返回true,其他情况返回false。参考
List.FindIndex(xx)
如果找到该语句要求的元素则返回该元素的的索引,否则返回-1。参考
List的用法 参考
22. console提示:NullReferenceException: Object reference not set to an instance of an object
意思为没有引用.
一种可能是该脚本A引用条目本体所在的脚本B,的执行速度要快,所以造成没有引用。这时候调整脚本执行顺序就好。
另一种可能是该Object本体就在该脚本,那么在脚本初始声明该Object时没有初始化。
(比如把PlayerData playerData = new PlayerData()里后面的初始化语句忘了就会出现上述BUG)
还有一种可能是Prefab在面板上未Apply,比如Prefab GameManager要拖拽引用 DataManager,DataManager必须也存成Prefab,然后把作为Prefab的DataManager拖入才算是引用成功。如果在Hierarchy下的DataManager新增了脚本却忘记Apply,则Prefab DataManager没有挂载新脚本,GameManager在运行时就找不到该脚本而报错。
23.Destory DontDestoryOnLoad GameObject
GameManager通常是DontDestoryOnLoad的,比如Roguelike里就一直存在。那如果想从某一场景切换到另一场景时会发现原先场景的GameManager还是存在,这时候要删除该GameManager GameObject有两种方式:
参考
1.
2.
不过图中的处理不是太好,应该在GameManager 对象所挂的脚本上,写入上面的代理。而代理的启用&取消通常在OnEnable() & OnDisable()里,代理必须取消,否则将导致内存泄漏。
关于delegates和C#内Events的说明见Adventure Game教程。教程
24.Unity内的双击
代码语言:javascript复制using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class MJCard : MonoBehaviour,IPointerClickHandler {
float lastClickTime; //不要申明在方法内
public void OnPointerClick(PointerEventData eventData)
{
if (Time.time - lastClickTime < 0.2)
{
Debug.log("双击");
}
lastClickTime = Time.time;
}
25.点击框外区域关闭对话框(Popup)
要点击对话框Child外区域关闭Child的最简单办法:新建一个Panel命名为Parent,拉伸到全屏,设为透明,把child拉为子对象。然后在该Parent上挂脚本
点击透明Panel就会关闭子对象。
26.物体抛物线移动
27.怎样修改Image的alpha
不可以直接image.color.a进行修改,会提示无法转换,必须中转一下。
28.怎样使对象在移动时转向(比如在2D里)
一般向左向右都是改scale,比如原本向右的改为向左就把scale.x改为-1,移动的时候改transform.localscale。
但这一项不可以直接修改,而是要像image.color.a一样中转一下。
参考:https://www.youtube.com/watch?v=e7I315b74HY
29.怎样平滑的在N秒内填充slider
30.用MD5加密资料的方法
参考来源
31.Unity中渐隐/闪烁/淡出的实现
参考(渐隐/闪烁)
淡出
32.Unity内加载(Load)资源
比如加载image的对象Sprite,一般我们的资源都放在Resourses文件夹(注意,一定是复数形式),用Resources.Load<你要加载的形式,比如Sprite>(“该文件在Resources文件夹下的路径”)就好了。
官方API 额外的参考 打包时的资源加载问题
33.Unity修改transform.rotation的两种方法
transform.localPosition和transform.localScale都是直接赋值三元数,给旋转赋值需要用 方法一: xxx.transform.localEulerAngles = new Vector3 (0.0f,0.0f,0.0f); 方法二: xxx.transform.rotation=Quaternion.Euler(0.0f,0.0f,0.0f); 参考
34.Json内中文为解析后为乱码的解决
json文件一般都是用记事本打开的,打开后另存为选择编码方式为UTF-8保存并覆盖就可以显示了。参考
35.Unity点击鼠标移动2d对象并播放移动动画
移动
动画播放
两个方法在FixedUpdate内执行就好。
注意先把对象的世界坐标转换,计算偏移量后再换回。
还有就是应使用GetMouseButton() 而非GetMouseButtonDown(),因为如果鼠标长按而不是点击的话GetMouseButtonDown()在之后帧都返回false。
36.Unity内的数据保存路径
参考来源
37.引用其他脚本内函数避开次次实例化的方法
① roguelike官教里的fightmanager方法
② 和第一种很像,使用T.GetInstance().xxxxxx来使用
③ 一次性读取数据(不需要写入的情况),然后直接调用
38.Unity内拖拽UI和物体
参考
① 拖拽overlay模式下的ui
② 拖拽世界坐标/camera模式下的UI元素
还有在鼠标点显示信息框等也是先把Input.mouseposition用上面的方法转换后再定位。
悬停框所在的像素显示之前在overlay模式下设置的是100,在camera模式下会很模糊,改为小数(比如1或者2)。
③ 拖拽3d物体
见上面参考链接
39.UI元素显示在最上
overlay永远存在于最上(离屏幕最近),多个overlay的层级取决于sort layer。sort layer越大越上。参考
camera取决于plane distance(canvas所在平面离摄像机的距离,可存在于其他gameobject后),sorting layer(多个camera模式的canvas用此排序),order in layer(多个canvas在同一sorting layer时的排序)。
平常的overlay canvas想暂时在camera canvas后显示时,可直接gameobject.setActive(false),之后再设置为true。
overlay模式下的joystick则不可以,等setactive true后因为要注册horizontal和vertical导致joystick不能用。暂时的方式是先把该joystick设置为camera并指定camera。之后恢复为overlay模式。
40.移动端触屏修改
使用官方joystick组件的(比如player移动)则在电脑端移动端都可使用。
电脑端的点击,双击,拖拽等事件在移动端依旧可以使用(电脑端的左键相当于移动端的触摸点击)。
长按则可利用IPointerDownHandler 和 IPointerUpHandler 参考
41.ios获得设备标识码(UDID)
自己机器的UDID可以通过连接iTunes获得,但代码SystemInfo.deviceUniqueIdentifier 在ios7之后获得的是UIDevice identifierForVendor(unity API)。参考链接
所以单机使用该条就够了,联机则参考。
42.
Unity:
1.无法创建新项目,重启Unity,或者重新登录Unity账号。
2.打包发布时,Build Settings 提示”Because you are not a member of this project this build will not access Unity services.”,重启Unity,或者重新登录Unity账号。
3.2D游戏更改整体背景大小,不要设置背景的Scale,设置摄像机的Size。
4.Console提示
保存了脚本回到unity发现提示“There are inconsistent line endings in the ‘Assets/Scripts/CameraController.cs’ script. Some are Mac OS X (UNIX) and some are Windows.”?,点击VS下File→Advanced Save Options→Line Endings选择Windows(CR LF),确定后再保存一遍OK了。
或者在VS内先关闭这个script,再在File下“最近使用过的文件”打开该script,VS会跳出对话框,YES后再保存一遍就好。
5.碰撞器(Collider)判断
是否有接触的条件是两方都有碰撞器,且至少一方有rigidbody组件。如果任何一方勾选了Is Trigger(勾选这个的是被碰撞对象,比如小方块被碰撞后消失,则勾选小方块,而不是小球),可触发检测是否进入碰撞范围,并按脚本对应表现。所以双方都没勾选触发,可碰撞,不可有其他交互。
6.盒子内的碰撞
要一个平面上的球不滚出平面,直接为平面加Box Collider是不可行的,球滚到边缘会直接粘到平面外缘不动了。自身多个碰撞器重叠如果有一个以上未勾选Istrigger会把重叠部分处理成一体。所以要在平面四条边放四个条状碰撞器粘成一个框状的。[2D UFO]
7.2d模式下所有动的游戏对象(即使只是旋转,不移动)也要添加rigidbody2D
不然尽管挂上脚本不添加rigidbody也可以移动,但是unity每一帧都会重新定位加载计算该对象,会对性能产生极大损耗。[2D UFO]
8.Rigidbody.isKinematic
在脚本和rigidbody组件内选项使用。对象选择Kinematic后,碰撞、力、关节不再对对象产生影响。该对象的移动或运动将完全由Animation(动画)控制或者脚本编辑对象的位置改变来控制。(2D Roguelike,不想player满场乱飞,就是一步一移动,所以用这个。之前的SpaceShooter,RollABall等没有勾选此选项,即默认的Dynamic。Static是将对象设为像背景一样的物件,不可移动不可受力,但是可以和其他dynamic物体碰撞)
9.unity中的sprite(精灵)
unity2D游戏中的所有对象都叫精灵(比如2D Roguelike中的Player,Enemy,以及Floor这些不动的元素),精灵每个都用Sprite Renderer而不是Mesh Renderer。
10.Layer vs Tag
tag是为了对object分类,通常用于脚本(GameObject.FindWithTag())来快速搜索对象。Layer用于区分摄像机的渲染&射线投射对象,灯光的特定照射和物体的碰撞等。Layer虽然可以自己添加并命名,但layer在系统内是以编号区分的,所以layer的属性是int。(比如 int blockingLayer)。sorting layer在Sprite Renderer选项内,因为2D物体是可以覆盖的,所以Sorting Layer最底层的最先渲染,处于场景的最上方。
11.UNITY里的文本
unity的文本层和游戏层不同,游戏层以像素计数,文本层以比例计数,左下角为(0,0),右上角为(1,1),所以要把文本放在左上角只要更改position为(0,1,0)。
要是想给文本留些空隙而不是死贴着边框,在pixel offset里调整(x,y)的像素相对移动(见space shooter内文本的设置)
12.Animation组件内Parameters trigger vs bool
动画状态转换的参数有四种类型:int,float,bool,trigger。前三种都很好理解,第四种是一次性的bool,比如人物死亡触发trigger,发呆和行走转换用bool。
比如SurvivalShooter里人物行走用bool来转换
像攻击比如Roguelike里的Chop和Hit就用trigger来转换。NPC释放一次性动画用Trigger。
13.Unity里快速查找Component讲解
不用在浏览器内打开官网再输入查询,直接点组件右上角的小书图标。
14.场景搭建时用Snap Setting
比如一排围墙,普通用Duplicate,然后手动一点一点挪(比如roll-a-ball),要对齐及其痛苦。我们可以在上方工具栏Edit→Snap Setting里设置好移动单位,移动下一个Duplicate Object时按住Ctrl键同时移动,就可以设置好的单位轻松移好。选定一堆小方块,然后摁Snap All Axes就会自动帮排列整齐(像Word里的对齐)。
15.锁定Inspector面板方便拖拽对象或属性
Unity经常需要确定组件内引用的是哪个对象,我们常在Hierarchy或Project下找到对象然后拖进栏内,但有时文件太多,点击到对象Inspector直接显示该对象的组件群,而不是我们需要拖拽的组件群。这时先把Inspector锁住,再开始查找,拖拽就很方便了。
16.Unity Color取色
在选择如背景Color时,点击小滴管可以取色,不仅可以取Unity Scene窗口里的颜色,也可以取其他颜色,比如一幅网上图取色,我们把浏览器和Unity并行放置,点击取色然后点击图片上需要的颜色就可以了。
17.Unity工程2D和3D模式的区别
unity其实不存在2D或3D的区别,只是摄像机是Orthographic或Perspective的区别。正交模式下镜头没有Z轴就成了2D模式。
具体区别:
2D:
镜头为Orthographic;
Scene面板为2Dmode;
Windows→Lighting→Setting里Skybox为None,AmbientSource为color,Realtime Lighting和Mixed Lighting都取消GI勾选。
3D:
镜头为Perspective;
Scene面板为3Dmode;
Skybox为Default,AmibientSource为Skybox,两个选项都勾选。
关于镜头选择模式,像素型的素材,如果在调整了sprite各种选项后,依然被压缩到高糊状态,尝试把镜头模式换为perspective,再次运行后就发现OK啦。
18.Unity内脚本加载顺序
Edit→Project Settings→Script execution Order里可以设置各脚本的执行顺序和指定多少时间后该脚本开始加载。
为了防止这种BUG:脚本引用json数据,添加json数据List脚本还没加载完,搜索数据并执行其他方法的脚本已经开始运行了。
包括GameController,DataBase等需要提前加载的脚本可放到前面。
19.Unity内image/button等上面叠加序列帧动画
比如在场景切换时一般就显示一个黑图做幕布的效果(比如2DRougelike里的level升级时的Day N),如果想要在这块黑布上再播放动画,把动画拖入Canvas或给黑布Image下加Animator都是没有用的。
比如UICanvas下有一个UIImage为黑色,UIImage下有一个UIText用来显示Day 1.现在要再UIImage上显示帧动画。则再UIImage下再新建一个子Image,比如命名为AnimationImage,然后再选中AnimationImage的情况下,打开Animation窗口,点击creat,选好名字和文件夹,新建好动画后,把序列帧动画包含的sprite全部拖入Animation窗口,调整下播放速度就可以了。
参考:http://blog.csdn.net/mynameiszhuli/article/details/77951668
20.在NPC头上显示对话框,或地图上显示指引箭头
这时候UI作为整个地图的一部分,应该新建一个Canvas设置为WorldSpace。把Canvas拉到所需大小和位置。
比如对话框,有Panel有Text,单纯设置WorldSpace是没法正确显示的。
应该将Dynamic Pixels Per Unit设置的尽量大一些,这个选项表示如Text等的每一单位渲染的像素,值越大Text可以显示的越小。
然后Reference Pixels Per Unit设置为1,表示图片等资源等比像素像素,这样就不会造成如Panel,Image等的虚化。
比如NPC头顶的DialogueBox,NameText字号选择为1,调整canvas像素大小,然后把DialogueText设置为Best Fit,就会自适应Text大小。
21.血条跟随
血条小小的跟在玩家头上,需要Slider所在的Canvas跟随当前主相机。参考
22.Unity设置横屏的方法等相关
参考来源
23.Unity屏幕分辨率自适应
参考来源
要点:
1.UI所在的Canvas设置为screen space-overlay及scale with screen size & expand。
2.UI内部件对应的设置锚点,比如某button保持在左下角则该button的锚点设为左下。
3.勾选Pixel Perfect(防止由高分辨率转为低分辨率时字体模糊)
4.Reference Resolution填为你在制作时的分辨率。比如我在1920*1080时制作的UI,现在要做自适应时填成该分辨率,则在测试时不同分辨率依然保持同样位置大小。
24.
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/193619.html原文链接:https://javaforall.cn