当前位置: 首页 > news >正文

Unity2D:从零开始制作一款跑酷游戏!

目录

成品展示

美术资源

制作步骤

场景预布设:

实现人物基础功能:

移动背景——横向卷轴:

生成障碍物:

生成敌人与攻击逻辑:

UI制作与重新开始:

导出游戏:

小结

大家小时候都玩过《天天酷跑》吧?这类游戏的原理很简单,非死即跑,主要用到的是2D卷轴技术。现在,我们一起来把经典复刻,从零开始做一款简单的跑酷类小游戏。


成品展示

回家的路

关于这款游戏的源文件与exe文件我已经同步上传到了github,想游玩的话复制下面的地址双击下载zip文件即可。

https://github.com/starmatch9/simple-parkour-game-Ascendant-Path-/releases/tag/v1.0.0

这里也提供了百度网盘的下载链接,点击最下方阅读原文即可下载: 

https://pan.baidu.com/s/1AuaV1R0l9dwORnzzMSs1rg?pwd=y4mu

提取码: y4mu 

美术资源

这里采用的都是Unity资源商店中的免费资源,可以根据喜好自行选择,在此列出我使用的资源。

背景:2D Adventure Backgrounds Pack

https://assetstore.unity.com/packages/2d/environments/2d-adventure-backgrounds-pack-309711

人物:Cute 2D - College Student

https://assetstore.unity.com/packages/2d/characters/cute-2d-college-student-198684

障碍物:Free Platform Game Assets

https://assetstore.unity.com/packages/2d/environments/free-platform-game-assets-85838

怪物:Fantasy Monster Pack: 5 Handcrafted 2D Creatures

https://assetstore.unity.com/packages/2d/characters/fantasy-monster-pack-5-handcrafted-2d-creatures-296211

GUI:Buttons Set

https://assetstore.unity.com/packages/2d/gui/buttons-set-211824

字体:Bubble Font (Free Version)

https://assetstore.unity.com/packages/2d/fonts/bubble-font-free-version-24987

制作步骤

场景预布设:

我们可以在包管理器:我的资产界面看到我们下载好的资源,现在将准备好的资源导入到编辑器中,能够看到名称为资源名称的文件夹出现在Asset目录下。

然后就可以先创建背景了:在场景中创建一个名为“Background”的空物体,用来管理我们的背景精灵。然后把我们的背景资源依次拖入到该物体的下方。因为我们的背景用不到Unity的物理系统,所以就可以不用更改精灵导入PPU,要调整大小的话,放入场景后改变Scale即可。

现在添加名为“Background”的排序图层,将所有的背景精灵放在这个排序图层上,用于以后确定这些背景图像渲染在最下方。

并调整图层顺序以获得正确的遮盖方式,对每个部分的位置进行移动以达到最佳状态。

当然不能忘记设置摄像机的正交大小,因为我选取的角色是带有骨骼动画的,覆盖了PPU等精灵导入设置,所以我们调整摄像机的正交大小以显示人物合适的大小。

按照同样的方法将角色导入,然后将预制件拖入场景中,调整摄像机正交大小,也别忘了缩放我们背景(父物体)的大小。

现在我们把平台放入场景,即人物脚踩的地方。因为需要在平台拼接时保持平滑,我采用了瓦片地图的方式。在场景中创建一个瓦片地图,会自动生成名为Grid的父物体,用来确定瓦片地图使用的网格坐标系。将瓦片地图改名为Platforms用来放置平台。

然后创建瓦片调色板(即平铺调色板),目标瓦片地图设置为我们刚刚创建的Platforms。将瓦片调色板的资源分文件存放便于区分。

然后用调色板绘制平台,注意要将平台地图的排序图层设置为Background,图层顺序放置中景与前景之间以达到预期效果。

到现在,我们的场景布局就完成了,现在开始考虑游戏各种功能的实现。

实现人物基础功能:

在这种跑酷类游戏中,是不需要真正控制角色移动的,只需要让背景、平台和障碍物不断朝左边运动即可。所以我们的人物只需要能够原地奔跑以及原地跳跃就足够了。

首先角色得能跑吧?创建一个动画控制器专门用来控制角色的动画。然后将场景中人物的控制器替换为我们自己创建的控制器,将资源中的奔跑动画拖入其中。

现在开始实现角色跳跃。详细的实现在之前的文章谈到过,所以这里不作过多说明。为我们的平台添加碰撞器,并添加一个复合碰撞器让碰撞器们平滑连接。

为角色添加胶囊碰撞器以及刚体组件,并调整其大小,用于参与物理模拟。同时为了防止其移动,我们冻结其水平方向的位置与旋转。

然后在动画动画器中添加跳跃动画,同时创建bool参数isJump判断是否要从奔跑切换为跳跃。在奔跑和跳跃之间添加过渡,一定不能勾选有退出时间,且过渡持续时间为0。

然后编写以下角色跳跃脚本。

Animator animator;
Rigidbody2D body;
public float jumpVelocity = 20;
//用来判断角色是否在平台上
bool onPlatform = true;
void Awake()
{animator = GetComponent<Animator>();body = GetComponent<Rigidbody2D>();
}
void Update()
{//按键J用来检测跳跃(暂定)if (onPlatform && Input.GetKeyDown(KeyCode.J)){body.velocity = new Vector2 (body.velocity.x, jumpVelocity);}
}
//这里同之前的文章一样,记得改平台的图层名称用来识别
private void OnCollisionEnter2D(Collision2D collision)
{if(collision.gameObject.layer == LayerMask.NameToLayer("Platform")){onPlatform = true;animator.SetBool("isJump", false);}
}
private void OnCollisionExit2D(Collision2D collision)
{if (collision.gameObject.layer == LayerMask.NameToLayer("Platform")){onPlatform = false;animator.SetBool("isJump", true);}
}

然后我们的角色跳跃功能就做好了。

因为这个角色资源里也包含了攻击动画(Attack)与滑板车动画(KickBoard),所以我们用相同的方式也加入这两个动画。

然后编写脚本:滑板车设置为按住空格键就一直滑行,松开就回到跑步状态。攻击就通过协程的方式让动作持续一段时间,后续可以加入攻击效果。

bool isAttack = false;
void Update()
{//按键J用来检测跳跃(暂定)if (onPlatform && Input.GetKeyDown(KeyCode.J)){body.velocity = new Vector2 (body.velocity.x, jumpVelocity);}//按住空格检测滑板车if (Input.GetKey(KeyCode.Space)){animator.SetBool("isKickBoard", true);}else{animator.SetBool("isKickBoard", false);}if (!isAttack && Input.GetKey(KeyCode.F)){isAttack = true;StartCoroutine(Attack());}
}
IEnumerator Attack()
{animator.SetBool("isAttack", true);yield return new WaitForSeconds(0.8f);animator.SetBool("isAttack", false);//等等一段时间后才能进行下一次攻击,相当于冷却时间yield return new WaitForSeconds(0.5f);isAttack = false;
}

那么现在角色的基本操作就已经实现完毕了。

移动背景——横向卷轴:

我们现在需要考虑的是角色的以上功能会用在什么场景下,跳跃用来避开障碍物,攻击用来打倒怪物,可以是释放出一个强大的冲击波,滑板车就暂且当一个装饰吧。

但想到这些物体都是随着背景朝角色移动的,我们就先来利用横向卷轴实现背景的移动吧。

其原理很简单,就是两个相同的背景图像,并在一起。为了方便理解称左边的为A,右边的为B,A与B一起滚动,当A已经滚过摄像头时,让A赶紧跟在B的后面,继续参与滚动。

那么我们复制粘贴一下我们的背景出来。

调整之后,当A的水平坐标处于0,B的水平坐标处于40时,摄像机只有A的画面,A与B无缝衔接。那么说明在滚动过程中,如果A的水平坐标为-40,那么B的水平坐标就一定为0,这时间摄像机的画面是B,赶紧把A的坐标更改为40,那么前后两者之间的差依然是40,依然无缝衔接,我们简单的卷轴滚动就做好了。

实现的脚本如下。

public class Background_Roll : MonoBehaviour
{public float rollSpeed = 10.0f;void Update(){if (transform.position.x <= -40){transform.position = new Vector3(40, transform.position.y, transform.position.z);}transform.Translate(Vector2.left * rollSpeed * Time.deltaTime);}
}

当然平台的滚动也可以这样写,但是平台可能涉及到角色碰撞等因素,最好通过移动刚体来实现。这样的话记住不能冻结X轴上的位置,也要排除Platform层防止平台间发生碰撞。

public class Platform_Roll : MonoBehaviour
{public float rollSpeed = 10.0f;//因为涉及到碰撞器更新,使用需要移动刚体Rigidbody2D rb;void Start(){rb = GetComponent<Rigidbody2D>();}void Update(){if (transform.position.x <= -40){transform.position = new Vector3(40, transform.position.y, transform.position.z);}}void FixedUpdate(){rb.MovePosition(rb.position + Vector2.left * rollSpeed * Time.fixedDeltaTime);}
}

现在再来看看我们角色跑动的效果。

发现角色动不动就给我跳一下。这是因为A与B平台的碰撞器是分开,当离开一个平台的碰撞器时,就会触发跳跃。解决方法也十分简单,加入碰撞体的持续检测即可。

private void OnCollisionStay2D(Collision2D collision)
{if (collision.gameObject.layer == LayerMask.NameToLayer("Platform")){onPlatform = true;animator.SetBool("isJump", false);}
}

可以看到基本的效果已经有了。

生成障碍物:

现在该考虑角色要靠跳跃才能越过的障碍物了。

这里选择的是素材中的Mace与Saw图像。那么我们先来调整这两个障碍物的预制件,让他们以更动态的方式生成在场景中,便于后续随机生成。将Saw拖入场景,首先我们在资产下创建一个名为Saw的动画,用来编辑这个锯齿的动画,并将其附在场景中的Saw上,并同时生成了动画控制器。

双击该动画资产,就可以进行编辑了。我们点击右下角的“曲线”,一般默认的动画编辑数值改变是曲线的,我们让我们的锯齿持续转动我们需要设置为线性。然后在动画中添加Saw的旋转属性,将关键帧添加在30帧的位置,Z轴设置为360度,同下图一样设置为线性。

记住在检查其中将循环时间选中,这样我们的齿轮就可以转动起来了。

然后以同样的方式,为我们的Mace添加一上一下的动态效果。

但要注意,动画系统中位置的动画不能单独对一个轴编辑,所以为了防止动画系统对我们后来的物理系统产生影响,我们为Mace创建一个父物体,在后续,不管是制成预制件还是添加物理逻辑,都对这个父物体操作。

为二者添加合适的碰撞器以及刚体,冻结y轴位置以及旋转,排除Platform层,同时将这二者的标签更改为“Obstacle”,以便后续逻辑的进行。然后将这二者拖入资产当中作为预制件。然后可以将场景中的两个物体删掉了。

现在考虑如何将障碍物生成在场景中。这里使用C#中的标准队列结构将这两个预制件存放在对象池中,通过其先进先出的特性让物体能够回收利用,避免多次销毁,增加性能。将生成点设置在摄像机范围右侧,在生成的已经存在的物体消失后,生成下一个物体,达到效果。

创建一个名为“SpawnPoint”的空物体用来确定生成障碍物的位置,编写以下脚本挂载到这个生成点上。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawn_Obstacle : MonoBehaviour
{public GameObject obstacle1;public GameObject obstacle2;GameObject current;public float rollSpeed = 10.0f;//因为涉及到碰撞器更新,使用需要移动刚体Rigidbody2D rb;//创建对象池public Queue<GameObject> pool = new Queue<GameObject>();//初始化对象池void InitializePool(){//在池中创建六个对象for (int i = 0; i < 3; i++){GameObject obj1 = Instantiate(obstacle1);obj1.SetActive(false);pool.Enqueue(obj1);//放入队列GameObject obj2 = Instantiate(obstacle2);obj2.SetActive(false);pool.Enqueue(obj2);//放入队列}}//从对象池里获取对象public GameObject GetObject(){return pool.Dequeue();//娶一个队列里的元素}//回收对象public void ReturnObject(GameObject obj){obj.SetActive(false);pool.Enqueue(obj);}void Start(){InitializePool();current = GetObject();current.SetActive(true);rb = current.GetComponent<Rigidbody2D>();//让物体的位置在我们设置的生成点的位置rb.position = transform.position;}void Update(){//-20为摄像机范围左侧的少许距离if (current.transform.position.x <= -20){ReturnObject(current);current = GetObject();current.SetActive(true);rb = current.GetComponent<Rigidbody2D>();//让物体的位置在我们设置的生成点的位置rb.position = transform.position;}}private void FixedUpdate(){rb.MovePosition(rb.position + Vector2.left * rollSpeed * Time.fixedDeltaTime);}
}

将预制件拖入脚本组件,我们的障碍物的基本行为就做好了。

为了更便于角色撞到障碍物就死掉,我们将障碍物的碰撞器都设置为触发器。我们准备好角色的死亡动画,开始添加角色的死亡逻辑。注意这里的过渡不要选可以过渡到自己,会发生错误。

public class Player_Die : MonoBehaviour
{Animator animator;void Awake(){animator = GetComponent<Animator>();}private void OnTriggerEnter2D(Collider2D collision){if (collision.gameObject.CompareTag("Obstacle")){animator.SetBool("die", true);}}
}

现在我们的角色死亡也就安排好了。

生成敌人与攻击逻辑:

下一步要生成敌人了,为了简单起见,我们将敌人的图像同上文中的Mace一样,父物体添加碰撞器刚体,制为预制件,将标签改为“Monster”,因为原资产的文件怪物是朝左的,我们我们选择反转x轴。

然后在原本生成障碍物的脚本里添加几行,好让生成点交替生成怪物。

    public GameObject obstacle1;public GameObject obstacle2;public GameObject monster;void InitializePool(){//在池中创建六个对象for (int i = 0; i < 2; i++){GameObject obj1 = Instantiate(obstacle1);obj1.SetActive(false);pool.Enqueue(obj1);//放入队列GameObject obj2 = Instantiate(obstacle2);obj2.SetActive(false);pool.Enqueue(obj2);//放入队列GameObject obj3 = Instantiate(monster);obj3.SetActive(false);pool.Enqueue(obj3);//放入队列}}

然后修改角色死亡的逻辑。

private void OnTriggerEnter2D(Collider2D collision)
{if (collision.gameObject.CompareTag("Obstacle") || collision.gameObject.CompareTag("Monster")){animator.SetBool("die", true);}
}

现在怪物就会在两个障碍物后出现。

现在我们考虑角色的攻击。毕竟我们手上没有现成的素材,不如让我们搓一个螺旋丸吧。创建一个空物体,放置在角色前方。在其下随意制作我们的武器,这里我随便搓了一个,并加上了和锯齿同款的旋转。

为这个武器添加名为“Weapon”的标签,用于处理后续攻击怪物的逻辑,添加圆形碰撞器。然后为怪物预制件添加如下脚本,记得加上怪物的死亡动画。

public class Monster_Die : MonoBehaviour
{Animator animator;void Awake(){//因为要挂载到父物体上,所以要获取子物体的动画器animator = GetComponentInChildren<Animator>();}private void OnTriggerEnter2D(Collider2D collision){if (collision.gameObject.CompareTag("Weapon")){animator.SetBool("die", true);//需要在生成点脚本中重新使其恢复Collider2D collider = GetComponent<Collider2D>();collider.enabled = false;}}
}

​​​​​​​这样一来,我们的螺旋丸就可以将怪物击倒了。然后我们更改角色控制脚本,编写使用武器攻击的逻辑。

public GameObject weapon;
IEnumerator Attack()
{animator.SetBool("isAttack", true);weapon.SetActive(true);yield return new WaitForSeconds(0.8f);animator.SetBool("isAttack", false);weapon.SetActive(false);//等等一段时间后才能进行下一次攻击,相当于冷却时间yield return new WaitForSeconds(0.5f);isAttack = false;
}

​​​​​​​现在我们攻击怪物的逻辑就做好了。

UI制作与重新开始:

现在我们基本的游戏内容就已经做好了,也该进入收尾阶段了,现在就该讲讲如何在角色死亡后如何重置我们的场景以及GUI布置。

先来实现重新开始的逻辑,因为我们这个简陋的小游戏并不需要保存状态什么的需求,所以直接通过重置整个场景即可。我们在场景中创建一个名为“GameManager”的空物体,专门用来挂载需要对游戏全局控制的脚本。然后挂载以下脚本。

public class GameManager : MonoBehaviour
{public void ResetScene(){//等一秒再重新开始StartCoroutine(ReloadWithFade());}IEnumerator ReloadWithFade(){yield return new WaitForSeconds(1f);SceneManager.LoadScene(SceneManager.GetActiveScene().name);}
}

​​​​​​​由于我们的角色是和游戏结束关联的,所以需要在角色死亡脚本中添加我们GameManager类的实例。保存后将场景GameManager游戏对象拖入该脚本组件即可。

public class Player_Die : MonoBehaviour
{private void OnTriggerEnter2D(Collider2D collision){if (collision.gameObject.CompareTag("Obstacle") || collision.gameObject.CompareTag("Monster")){animator.SetBool("die", true);//游戏结束gameManager.ResetScene();}}
}

​​​​​​​

现在我们就完成了极其简单的场景重置。如果想要添加什么效果(如淡入淡出)可以直接在游戏管理脚本中的协程中添加。

现在开始添加GUI,我想让我们游戏一开始,角色处于待机状态,场景也没有变化,需要点击“开始游戏”以后才能够让场景与各种对象进行移动。那么将思路转换为具体方法,就是角色刚进入游戏时,需要禁用背景滚动脚本、平台滚动脚本、角色控制脚本,以及生成点脚本。

为了批量控制这些脚本,我们将所有目标脚本所挂载的游戏对象的标签更改为“ScriptControl”,然后修改GameManager脚本,禁用所有脚本,具体如下。

public class GameManager : MonoBehaviour
{GameObject[] targets;private void Awake(){//初始化所有的带有标签的物体targets = GameObject.FindGameObjectsWithTag("ScriptControl");DisableScripts();}//批量禁用脚本public void DisableScripts(){foreach (GameObject obj in targets){//禁用所有继承自MonoBehaviour脚本MonoBehaviour[] scripts = obj.GetComponents<MonoBehaviour>();foreach (MonoBehaviour script in scripts){if (script != null && script.enabled){script.enabled = false;//这个方法可以用来Debug是否真的禁用了Debug.Log($"已禁用 {obj.name} 上的 {script.GetType().Name}");}}}}public void ResetScene(){//等一秒再重新开始StartCoroutine(ReloadWithFade());}IEnumerator ReloadWithFade(){yield return new WaitForSeconds(1f);SceneManager.LoadScene(SceneManager.GetActiveScene().name);}
}

​​​​​​​虽然这时检查我们的脚本确实被禁用了,但是会发现我们的控制台会持续出现报错。

查阅后得知,可以是因为OnCollisionStay2D等方法是Unity物理系统的回调,不受脚本启用状态影响。所以我们要同时禁用他们的碰撞器与刚体。

    //批量禁用碰撞器和刚体public void DisableColliders(){foreach (GameObject obj in targets){//禁用所有碰撞器和刚体(防止人物掉落)if (obj.GetComponent<Collider2D>()){Rigidbody2D[] rbs = obj.GetComponents<Rigidbody2D>();foreach (Rigidbody2D rb in rbs){if (rb != null && rb.simulated){//Rigidbody2D特有的方法,用来定义刚体是否参与物理模拟rb.simulated = false;//这个方法可以用来Debug是否真的禁用了Debug.Log($"已禁用 {obj.name} 上的 {rb.GetType().Name}");}}Collider2D[] colliders = obj.GetComponents<Collider2D>();foreach (Collider2D collider in colliders){if (collider != null && collider.enabled){collider.enabled = false;//这个方法可以用来Debug是否真的禁用了Debug.Log($"已禁用 {obj.name} 上的 {collider.GetType().Name}");}}}}}

​​​​​​​然后我们再将角色一开始的动画设置为“闲置”,即创建默认过渡为idel动画。

那么我们游戏一开始的场景就布置完成了。

现在就在我们的右半边屏幕布设GUI。在场景中创建一块画布。选择屏幕空间覆盖渲染,按屏幕大小缩放。在这个物体下创建文本,用来写我们的标题,字体格式用我们导入的资源。同时创建一个按钮,这个用来让我们开始游戏。

现在我们来编写按钮按下后触发的逻辑,那就是让我们刚刚禁用的脚本还有碰撞器刚体什么的统统恢复原状,还有要想之前一样,让角色的动画过渡到奔跑动画。对我们的GameManager脚本作如下修改。

   //批量激活脚本public void EnableScripts(){foreach (GameObject obj in targets){//禁用所有继承自MonoBehaviour脚本MonoBehaviour[] scripts = obj.GetComponents<MonoBehaviour>();foreach (MonoBehaviour script in scripts){if (script != null && !script.enabled){script.enabled = true;}}}}//批量激活碰撞器和刚体public void EnableColliders(){foreach (GameObject obj in targets){//禁用所有碰撞器和刚体(防止人物掉落)if (obj.GetComponent<Collider2D>()){//先激活碰撞器Collider2D[] colliders = obj.GetComponents<Collider2D>();foreach (Collider2D collider in colliders){if (collider != null && !collider.enabled){collider.enabled = true;}}Rigidbody2D[] rbs = obj.GetComponents<Rigidbody2D>();foreach (Rigidbody2D rb in rbs){if (rb != null && !rb.simulated){//Rigidbody2D特有的方法,用来定义刚体是否参与物理模拟rb.simulated = true;}}}}}

​​​​​​​然后在角色控制脚本中,添加如下代码。

    public void startRun(){animator = GetComponent<Animator>();animator.SetBool("start", true);}

​​​​​​​然后再在我们的按钮组件中,依次使用这些方法,再将画布禁用,好让我们的玩家回到游戏状态。

哦,不能忘了告诉玩家操作方法。为了手感舒适一点,我将跳跃改为了W键,将滑板车改为了S键,攻击改为了D键。将这些信息写在我们的UI界面中。

那么我们禁用脚本的方法当然也不是白写的,在角色死亡之后,可以让场景中的物体都停止移动一段时间,再加载我们的新场景。对加载新场景的协程作如下更改。

IEnumerator ReloadWithFade()
{DisableColliders();DisableScripts();yield return new WaitForSeconds(2f);SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}

​​​​​​​那么到现在我们的跑酷小游戏就大功告成了!

导出游戏:

终于到了激动人心的导出游戏时刻。点击生成设置。

在“玩家设置“中调整分辨率等参数后(一般默认就行),点击构建和运行,我们的游戏就到处完成了。


小结

当然,这款小游戏还有许多许多可以优化的地方,这种多层背景也可以做出像《空洞骑士》那样的景深效果,更有立体感;经典的设置生命值增加受伤次数,增加容错率;在到达一定时间后可以将场景切换为别的,更有新鲜感;在屏幕上方显示这一次跑酷坚持了多长时间,更有成就感;让障碍物与敌人随机生成,更有可玩性;甚至可以加入bgm,更具舒适感……这些方法的实践,我会陆续写在后续的开发笔记中,也会在github中同步更新,感兴趣的话请多多关注。

如有补充纠正,欢迎留言。

相关文章:

Unity2D:从零开始制作一款跑酷游戏!

目录 成品展示 美术资源 制作步骤 场景预布设&#xff1a; 实现人物基础功能&#xff1a; 移动背景——横向卷轴&#xff1a; 生成障碍物&#xff1a; 生成敌人与攻击逻辑&#xff1a; UI制作与重新开始&#xff1a; 导出游戏&#xff1a; 小结 大家小时候都玩过《…...

维拉工时自定义字段:赋能项目数据的深度洞察 | 上新预告

原文链接&#xff1a;维拉工时自定义字段&#xff1a;赋能项目数据的深度洞察 | 上新预告 在项目管理实践中&#xff0c;每个企业都有独特的数据统计与分析需求。为了帮助用户实现个性化数据建模&#xff0c;从而更精准地衡量项目进度和预算投入&#xff0c;维拉工时全新升级的…...

C++ | 文件读写(ofstream/ifstream/fstream)

一、C文件操作核心类 C标准库通过<fstream>提供了强大的文件操作支持&#xff0c;主要包含三个关键类&#xff1a; 类名描述典型用途ofstream输出文件流&#xff08;Output File Stream&#xff09;文件写入操作ifstream输入文件流&#xff08;Input File Stream&#…...

flux文生图部署笔记

目录 依赖库: 文生图推理代码cpu: cuda版推理: 依赖库: tensorrt安装: pip install nvidia-pyindex # 添加NVIDIA仓库索引 pip install tensorrt 文生图推理代码cpu: import torch from diffusers import FluxPipelinemodel_id = "black-forest-labs/FLUX.1-s…...

二语习得理论(Second Language Acquisition, SLA)如何学习英语

二语习得理论&#xff08;Second Language Acquisition, SLA&#xff09;是研究学习者如何在成人或青少年阶段学习第二语言&#xff08;L2&#xff09;的理论框架。该理论主要关注语言习得过程中的认知、社会和文化因素&#xff0c;解释了学习者如何从初学者逐渐变得流利并能够…...

策略模式实际用处,改吧改吧直接用,两种方式

controller RestController RequestMapping("admin/test") RequiredArgsConstructor(onConstructor __(Autowired)) public class TestController {Autowiredprivate VideoFactory VideoFactory;GetMapping("getList")public R getList(){// 第一种方式T…...

计算机网络-TCP的流量控制

内容来源&#xff1a;小林coding 本文是对小林coding的TPC流量控制的精简总结 什么是流量控制 发送方不能无脑的发数据给接收方&#xff0c;要考虑接收方处理能力 如果一直无脑的发数据给对方&#xff0c;但对方处理不过来&#xff0c;那么就会导致触发重发机制 从而导致网…...

搬砖--贪心+排序的背包

a在上面b在下面->a.v-M-b.m>b.v-M-a.m->剩余率大 所以我先遍历a&#xff0c;让a在上面 这就是要考虑贪心排序的01背包 因为它有放的限制条件 #include<bits/stdc.h> using namespace std; #define N 100011 typedef long long ll; typedef pair<ll,int>…...

git克隆数据失败

场景&#xff1a;当新到一家公司&#xff0c;然后接手了上一个同时的电脑&#xff0c;使用git克隆代码一直提示无法访问&#xff0c;如图 原因&#xff1a;即使配置的新的用户信息。但是window记录了上一个同事的登录信息&#xff0c;上一个同事已经被剔除权限&#xff0c;再拉…...

Dart 语法

1. 级联操作符 … var paint Paint()..color Colors.black..strokeCap StrokeCap.round..strokeWidth 5.0;2. firstWhereOrNull 3. 隐藏或导入部分组件 // Import only foo. import package:lib1/lib1.dart show foo;// Import all names EXCEPT foo. import package:lib…...

34、web前端开发之JavaScript(三)

十. DOM操作详解 1、DOM简介 文档对象模型&#xff08;DOM&#xff0c;Document Object Model&#xff09;是JavaScript与网页内容交互的接口。它将HTML文档表示为一种树状结构&#xff08;DOM树&#xff09;&#xff0c;其中每个节点代表文档的一部分&#xff08;例如元素、…...

自适应卡尔曼滤波

目录 自适应卡尔曼滤波 自适应卡尔曼滤波 主要作用,去抖动 AdaptiveKalmanFilter.py import cv2 import numpy as np from collections import deque# ------------------ 核心去抖算法 ------------------ import cv2 import numpy as np from collections import deque#…...

EIP-712:类型化结构化数据的哈希与签名

1. 引言 以太坊 EIP-712: 类型化结构化数据的哈希与签名&#xff0c;是一种用于对类型化结构化数据&#xff08;而不仅仅是字节串&#xff09;进行哈希和签名 的标准。 其包括&#xff1a; 编码函数正确性的理论框架&#xff0c;类似于 Solidity 结构体并兼容的结构化数据规…...

Day 3:Leetcode 比特位计数+只出现一次的数字 II

比特位计数 本质是一个递推&#xff0c;时间复杂度O(n)&#xff0c;空间复杂度O(n)。 class Solution { public:vector<int> countBits(int n) {vector<int> ans(n 1);ans[0] 0;//ans[1] 1;for(int i 1; i < n; i){if(i & 1){ans[i] ans[i/2] 1;}else…...

CentOS7安装conda

root用户登录虚拟机后更新yum yum update 下载Miniconda&#xff0c;路径就在/root下 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh conda分为Miniconda和Anaconda, Miniconda是Conda的轻量级版本&#xff0c;只包含conda和Python&#xf…...

[Linux]从零开始的vs code交叉调试arm Linux程序教程

一、前言 最近的项目中需要集成rknn的视觉识别&#xff0c;在这之前我并且没有将rknn集成到自己项目的经验。这里我需要在rknn原本demo的基础上我还需要集成自己的业务代码。但是又有一个问题&#xff0c;原本rknn我们都是使用交叉编译编译到开发板上的&#xff0c;并且我们还要…...

【顶刊级科研绘图】AI支持下Nature级数据可视化(如何画图、如何标注、如何改图、如何美化、如何组合、如何排序)

技术点目录 第一章、绘图原则与概念&#xff1a;规范清晰简洁自明第二章、DeepSeek、ChatGPT、R绘图系统&#xff1a;八仙过海各显神通第三章、美学设计与细节&#xff1a;完美图表华丽呈现第四章、数据类型与图表&#xff1a;宝典在手各个击破第五章、统计分析与可视化&#x…...

CSRF跨站请求伪造——入门篇【DVWA靶场low级别writeup】

CSRF跨站请求伪造——入门篇 0. 前言1. 什么是CSRF2. 一次完整的CSRF攻击 0. 前言 本文将带你实现一次完整的CSRF攻击&#xff0c;内容较为基础。需要你掌握的基础知识有&#xff1a; 了解cookie&#xff1b;已经安装了DVWA的靶场环境&#xff08;本地的或云的&#xff09;&am…...

Spring Boot应用中实现Jar包热更新的实践指南

Spring Boot应用中实现Jar包热更新的实践指南 一、引言 在现代软件开发中&#xff0c;快速迭代和持续交付是至关重要的。对于基于Spring Boot的应用程序&#xff0c;一旦部署到生产环境&#xff0c;传统的更新方式通常是重新打包并重启应用&#xff0c;这不仅耗时&#xff0c…...

JVM深入原理(七)(一):运行时数据区

目录 7. JVM运行时数据区 7.1. 运行时数据区-总览 7.2. 运行时数据区-查看内存对象 7.3. 运行时数据区-程序计数器 7.3.1. 程序计数器-作用 7.3.2. 字节码指令执行流程 7.4. 运行时数据区-Java虚拟机栈 7.4.1. 栈-概述 7.4.2. 栈帧-组成 7.4.2.1. 栈帧-帧数据 7.4.2…...

约瑟夫环的四种(数组,链表,递归,迭代)解决方案,与空间、时间复杂度分析

以下方法均没有考虑结果集的空间与时间复杂度 1.数组解法 实现代码&#xff08;未优化&#xff09; class Main {public static void main(String[] args){Scanner read new Scanner(System.in);int n read.nextInt();int m read.nextInt();int[] people new int[n]; //…...

skynet.start 的作用详细解析

目录 skynet.start 的作用详细解析1. 功能概述2. 基本用法3. 关键作用(1) 注册消息处理函数(2) 启动事件循环(3) 服务生命周期管理 4. 与其他函数的协作5. 未调用 skynet.start 的后果6. 高级场景&#xff1a;何时不需要 skynet.start7. 总结 skynet.start 的作用详细解析 在 …...

Apache Doris 2025 Roadmap:构建 GenAI 时代实时高效统一的数据底座

在全球 290 位开发者的协作下&#xff0c;Apache Doris 在 2024 年完成了 7000 次代码提交&#xff0c;并发布了 22 个版本&#xff0c;实现在实时分析、湖仓一体和半结构化数据分析等核心场景的技术突破及创新。 2025 年&#xff0c;Apache Doris 社区将秉承“以场景驱动创新…...

springboot+easyexcel实现下载excels模板下拉选择

定义下拉注解 Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface ExcelDropDown {/*** 固定下拉选项*/String[] source() default {};/*** 动态数据源key&#xff08;从上下文中获取&#xff09;*/String sourceMethod() default "";…...

vue3+ts+element-plus 开发一个页面模块的详细过程

目录、文件名均使用kebab-case&#xff08;短横线分隔式&#xff09;命名规范 子组件目录&#xff1a;./progress-ctrl/comps 1、新建页面文件 progress-ctrl.vue <script setup lang"ts" name"progress-ctrl"></script><template>&l…...

软考《信息系统运行管理员》- 7.1 物联网运维

物联网的概念及特征 物联网是在计算机互联网的基础上&#xff0c;通过射频识别 (RFID) 、 无线传感器、红外感应器、 全球定位系统、激光扫描器等信息传感设备&#xff0c;按约定的协议&#xff0c;把物与物之间通过网络连接起来&#xff0c; 进行信息交换和通信&#xff0c;以…...

【GPT入门】第33 课 一文吃透 LangChain:chain 结合 with_fallbacks ([]) 的实战指南

[TOC](【GPT入门】第33课 一文吃透 LangChain&#xff1a;chain 结合 with_fallbacks ([]) 的实战指南) 1. fallback概述 模型回退&#xff0c;可以设置在llm上&#xff0c;也可以设置在chain上&#xff0c;都带有with_fallbacks([])函数 2. llm的回退 2.1 代码 核心代码&…...

裴蜀定理:整数解的奥秘

裴蜀定理&#xff1a;整数解的奥秘 在数学的世界里&#xff0c;裴蜀定理&#xff08;Bzout’s Theorem&#xff09;是数论中一个非常重要的定理&#xff0c;它揭示了二次方程和整数解之间的关系。它不仅仅是纯粹的理论知识&#xff0c;还在计算机科学、密码学、算法优化等多个…...

Table as Thought论文精读

标题&#xff1a;Table as Thought: Exploring Structured Thoughts in LLM Reasoning 作者&#xff1a;Zhenjie Sun, Naihao Deng, Haofei Yu, Jiaxuan You 单位&#xff1a;University of Illinois Urbana-Champaign, University of Michigan 摘要&#xff1a; llm的推理…...

PyQt6实例_A股日数据维护工具_使用

目录 前置&#xff1a; 下载预备更新的数据 使用工具更新 用工具下载未复权、前复权、权息数据 在PostgreSQL添加两个数据表 工具&视频 前置&#xff1a; 1 本系列将以 “PyQt6实例_A股日数据维护工具” 开头放置在“PyQt6实例”专栏 2 日数据可在“数据库”专栏&…...

MySQL客户端工具-图形化工具-DataGrip 安装与使用

一. 常见的图形化工具 二. DataGrip 安装 官网&#xff1a;DataGrip&#xff1a;由 JetBrains 开发的数据库和 SQL 跨平台 IDE 二. DataGrip 使用...

企业管理系统的功能架构设计与实现

一、企业管理系统的核心功能模块 企业管理系统作为现代企业的中枢神经系统&#xff0c;涵盖了多个核心功能模块&#xff0c;以确保企业运营的顺畅与高效。这些功能模块通常包括&#xff1a; 人力资源管理模块&#xff1a;负责员工信息的录入、维护、查询及统计分析&#xff0c…...

1.Qt信号与槽

本篇主要介绍信号和槽&#xff0c;如何关联信号和槽以及用QPixmap在窗口中自适应显示图片 本文部分ppt、视频截图原链接&#xff1a;[萌马工作室的个人空间-萌马工作室个人主页-哔哩哔哩视频] 1. 信号 一般不需要主动发送信号&#xff0c;只有自定义的一些控件才需要做信号的…...

再生认证体系有哪些?不同标准对应的要求及可以做的审核机构

再生认证体系 标准 GRS再生回收认证要求 再生原材料的上游企业&#xff1a;需要具备GRS认证证书&#xff0c;以确保原材料的可追溯性和再生成分。 认证条件&#xff1a; 最终商品的再生成分比例必须至少为20%。 只有由至少50%的回收材料制成的产品才能贴上GRS标签。 认证机构…...

[CISSP] [6] 密码学和对称密钥算法

密码学的目标 1. 机密性&#xff08;Confidentiality&#xff09; 目标&#xff1a;保护信息不被未授权访问。 通过 加密&#xff08;Encryption&#xff09;技术确保数据只能被授权方解密和读取。主要方法&#xff1a; 对称加密&#xff08;AES、3DES&#xff09;&#xff…...

thinkphp每条一级栏目中可自定义添加多条二级栏目,每条二级栏目包含多个字段信息

小程序客户端需要展示团购详情这种结构的内容,后台会新增多条套餐,每条套餐可以新增多条菜品信息,每条菜品信息包含菜品名称,价格,份数等字段信息,类似于购物网的商品多规格属性,数据表中以json类型存储,手写了一个后台添加和编辑的demo 添加页面 编辑页面(json数据…...

混杂模式(Promiscuous Mode)与 Trunk 端口的区别详解

一、混杂模式&#xff08;Promiscuous Mode&#xff09; 1. 定义与工作原理 定义&#xff1a;混杂模式是网络接口的一种工作模式&#xff0c;允许接口接收通过其物理链路的所有数据包&#xff0c;而不仅是目标地址为本机的数据包。工作层级&#xff1a;OSI 数据链路层&#x…...

Spring Boot项目信创国产化适配指南

将 Spring Boot 项目适配信创国产化环境&#xff0c;需要从底层基础设施到上层应用组件进行全面替换和调整。以下是主要替换点和适配步骤的总结&#xff1a; 一、基础软件替换 1. JDK 替换 国外JDK&#xff1a;Oracle JDK、OpenJDK国产JDK&#xff1a; 阿里龙井&#xff08;D…...

MySQL:数据类型

数值类型 数值类型用于存储整数、小数、浮点数等&#xff0c;主要分为整数类型和浮点类型。 整数类型 数据类型存储大小取值范围&#xff08;有符号&#xff09;取值范围&#xff08;无符号&#xff09;说明TINYINT1字节-128 ~ 1270 ~ 255小整数&#xff0c;如布尔值&#x…...

maven引入项目内本地包方法

最近在写java实现excel转pdf功能&#xff1b; 网上有个包很好用&#xff0c;免费&#xff1a;spire.xls.free-5.3.0.jar。 但是maven打包项目时报错&#xff0c;找不到这个包。 jar包位置如下&#xff1a; 在项目/src/jar/spire.xls.free-5.3.0.jar。 解决方法&#xff1a…...

ARP协议

ARP协议 ARP协议的作用 当网络设备有数据要发送给另一台网络设备时&#xff0c;必须要知道对方的网络层地址&#xff08;即IP地址&#xff09;。IP地址由网络层来提供&#xff0c;但是仅有IP地址是不够的&#xff0c;IP数据报文必须封装成帧才能通过数据链路进行发送。数据帧…...

科技赋能安居梦:中建海龙以模块化革新重塑城市更新范式

在北京市西城区桦皮厂胡同&#xff0c;一栋始建于上世纪70年代的住宅楼正经历着一场脱胎换骨的蜕变。这座曾被鉴定为D级危房的建筑&#xff0c;在中建海龙科技有限公司&#xff08;以下简称“中建海龙”&#xff09;的匠心打造下&#xff0c;仅用三个月便完成"原拆原建&qu…...

2025 AI智能数字农业研讨会在苏州启幕,科技助农与数据兴业成焦点

4月2日&#xff0c;以"科技助农数据兴业”为主题的2025AI智能数字农业研讨会在苏州国际博览中心盛大启幕。本次盛会吸引了来自全国各地相关部门领导、知名专家学者、行业协会组织&#xff0c;以及县级市农业企业代表、县级市农产品销售商等万名嘉宾齐聚姑苏城&#xff0c;…...

2000-2021年 全国各地区城镇登记失业率数据

全国各地区城镇登记失业率数据2000-2021年.ziphttps://download.csdn.net/download/2401_84585615/90259723 https://download.csdn.net/download/2401_84585615/90259723 城镇登记失业率是衡量地区就业状况的重要指标&#xff0c;反映了在一定时期内&#xff0c;符合就业条件的…...

Cursor的主要好处

以下是Cursor的主要好处&#xff1a; 代码生成与优化 • 快速生成代码&#xff1a;根据简短描述或部分代码片段&#xff0c;Cursor能快速生成完整代码模块&#xff0c;还能智能预测下一步操作&#xff0c;将光标放在合适位置&#xff0c;让开发者一路Tab键顺滑编写代码。 • …...

超便捷语音转文字工具CapsWriter-Offline本地部署与远程使用全流程

文章目录 前言1. 软件与模型下载2. 本地使用测试3. 异地远程使用3.1 内网穿透工具下载安装3.2 配置公网地址3.3 修改config文件3.4 异地远程访问服务端 4. 配置固定公网地址4.1 修改config文件 5. 固定tcp公网地址远程访问服务端 前言 今天给大家安利一个绝对能让你工作效率飙…...

什么是数据仓库

什么是数据仓库 Data warehouse 是面向主题的 主要根据各种数据来源&#xff0c;来进行历史分析 形成一个趋势分析 为数据挖掘、预测建模、机器学习提供基础数据 与传统数据库比如gaussdb的区别。数据仓库注重历史数据分析&#xff0c;guassdb注重实时事务处理 数据仓库时企业的…...

【动态规划】二分优化最长上升子序列

最长上升子序列 II 题解 题目传送门&#xff1a;AcWing 896. 最长上升子序列 II 一、题目描述 给定一个长度为 N 的数列&#xff0c;求数值严格单调递增的子序列的长度最长是多少。 输入格式&#xff1a; 第一行包含整数 N第二行包含 N 个整数&#xff0c;表示完整序列 输…...

MySQL的安装与初始化流程

MySQL概述 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB公司开发&#xff0c;MySQL AB公司被Sun公司收购&#xff0c;Sun公司又被Oracle公司收购&#xff0c;目前属于Oracle公司。 MySQL是目前最流行的关系型数据库管理系统&#xff0c;在WEB应用方面MySQL是最…...

flink standalone集群模式部署

一. 环境准备 1、下载并安装jdk11 2、下载flink 并解压 3、确保服务器之间的免密登录 二、集群搭建 搭建集群至少有三台机器&#xff0c;每台机器的分配角色如下 master: jobManager salve01&#xff1a;taskManager salve02&#xff1a;taskManager 1、在JobManager(…...