四、综合案例(Unity2D)

一、2D渲染

1、2D相机基本设置

上面是透视,下面是正交

2、图片资源

在Unity中,常规图片导入之后,一般不在Unity中直接使用,而是转为精灵图Sprite

将图片更改为即可使用Unity内置的图片切割功能

无论精灵图片是单个的还是多个的,都可以选择中心点

3、相关组件

 

在排序图层中,越靠下的,越先渲染

也就是说,靠近摄像机的因该是最下方的图层

二、Unity输入系统

1、按键检测

关于键盘的

if (Input.GetKeyDown(KeyCode.A))
{
    Debug.Log("A按下了!");
}
if (Input.GetKey(KeyCode.A))
{
	Debug.Log("A持续按住!");
}
if (Input.GetKeyUp(KeyCode.A))
{
	Debug.Log("A弹起了!");
}

 

2、鼠标检测

和键盘区别不大,只是调用的API不同

注:0是左键;1是右键;2是鼠标中键

if (Input.GetMouseButtonDown(0))
{
	Debug.Log("鼠标左键按下");
}
if (Input.GetMouseButton(0))
{
	Debug.Log("鼠标左键按住");
}
if (Input.GetMouseButtonUp(0))
{
	Debug.Log("鼠标左键抬起");
}

 

3、InputManager

在Unity的设置中找到输入部分,即可看到默认绑定的按键

一个简单的移动代码

float inputX = Input.GetAxis("Horizontal");
float inputY = Input.GetAxis("Vertical");
float speed = 5;
transform.Translate(Time.deltaTime * speed * new Vector3(inputX, inputY, 0));

注:这里的速度乘以时间,放在前面,避免一些不必要的消耗

三、Unity2D动画概览

1、Animator组件

先在窗口上添加一个动画组件

一定是下面这个。上面这个是比较老的技术了

2、AnimatorController:动画控制器

3、AnimationClip:动画片段 

四、动画录制 

在Unity内制作动画

在录制时,必须开启录制按钮,可以选择数据,也可以修改数据,录制时会自动记录下来

也可以添加颜色

 五、动画切换

 

通过参数进行切换,小于或者大于

 2D人物的简单移动代码

public Animator animator;
public float movespeed = 0.00001f;
public SpriteRenderer spriteRenderer;
private void Start()
{
	spriteRenderer= gameObject.GetComponent<SpriteRenderer>();
}

// Update is called once per frame
void Update()
{
	animator.GetComponent<Animator>();
	float inputX = Input.GetAxis("Horizontal");
    float inputY = Input.GetAxis("Vertical");
	//修改动画
    bool isMove = inputX != 0 || inputY!=0;
	animator.SetBool("Move", isMove);
    //转向
    if (inputX > 0) spriteRenderer.flipX = false;
    else if (inputX < 0) spriteRenderer.flipX = true;

	//位移
	Vector3 pos =
            new Vector3(inputX * Time.deltaTime * movespeed,
                        inputY * Time.deltaTime * movespeed,
                        0);
    transform.Translate(pos, Space.Self);
}

六、动画事件

在动画的某一帧触发某个函数;

原理是:在当前动画物体上的脚本里面 是否有一个事件;必须是公开的

在此处添加动画事件;

选择动画事件代码

public void Colore()
{
    transform.localScale=new Vector3(3, 3, 3);
    Debug.Log("1");
}

 

执行到这个事件时,会执行这个函数 

七、2D物理系统

每个人都需要有碰撞器,只要有一个有刚体

事件触发函数

private void OnCollisionEnter2D(Collision2D collision)
{
	
}
private void OnCollisionStay2D(Collision2D collision)
{
	
}
private void OnCollisionExit2D(Collision2D collision)
{
	
}

碰撞与触发不能共存

触发函数代码

private void OnTriggerEnter2D(Collider2D collision)
{
	
}
private void OnTriggerStay2D(Collider2D collision)
{
	
}
private void OnTriggerExit2D(Collider2D collision)
{
	
}

 八、主角控制(综合案例)

在玩家身上挂载一个触发器,不需要碰撞

挂载脚本

public class Game : MonoBehaviour
{
    public Animator animator;
    public float movespeed;
    public SpriteRenderer spriteRenderer;
    bool Big=false;
	private void Start()
	{
		spriteRenderer= gameObject.GetComponent<SpriteRenderer>();
	}

	// Update is called once per frame
	void Update()
    {
		animator.GetComponent<Animator>();
		float inputX = Input.GetAxis("Horizontal");
        float inputY = Input.GetAxis("Vertical");
		//修改动画
        bool isMove = inputX != 0 || inputY!=0;
		animator.SetBool("Move", isMove);
        //转向
        if (inputX > 0) spriteRenderer.flipX = false;
        else if (inputX < 0) spriteRenderer.flipX = true;

		//位移
		Vector3 pos =
                new Vector3(inputX * Time.deltaTime * movespeed,
                            inputY * Time.deltaTime * movespeed,
                            0);
        transform.Translate(pos, Space.Self);
    }
   
}

由于人物比较小,调节一下摄像机距离

九、发射子弹

子弹一般都是预制体;

需要一个公开获取预制体;一个生成子弹的点位;一个if循环判断按键和实例化

子弹飞行是自己的逻辑;挂载在预制体上面;

private void Shoot()
{
	if (Input.GetKeyDown(KeyCode.J))
	{
		//实例化
		GameObject star=GameObject.Instantiate(starPrefab);
		star.transform.position = starShootPoint.position;
	}
}

创建一个动画,模拟子弹射出

旋转360° 然后旋转720°

将动画勾选为循环

在预制体子弹上面挂在一个动画状态机,放入旋转动画

挂载子弹的代码


    public float moveSpeed;
    private Vector3 moveDir;
    public float destroyTime=3;
    private float destroyTimer;//计时器
    public void Init(Vector3 dir)
    {
        moveDir = dir;
        destroyTimer = destroyTime;
    }

    // Update is called once per frame
    void Update()
    {
        transform.Translate(moveSpeed * Time.deltaTime * moveDir,Space.World);
        if (destroyTimer <= 0)
        {
            Destroy(gameObject);
        }else
        {
            destroyTimer-=Time.deltaTime;
        }
    }

由于子弹自身有旋转,所以坐标会一直更改,需要传入一个方向初始化

在玩家代码中调用

//实例化
GameObject star=GameObject.Instantiate(starPrefab);
star.transform.position = starShootPoint.position;
bool isRightDir = spriteRenderer.flipX == false;
Vector3 moveDir = isRightDir ? transform.right : -transform.right;
Debug.Log(moveDir);
star.GetComponent<Star>().Init(moveDir);

先实例化这个预制体;把生成点赋值给预制体的当前坐标

通过bool判断是左还是右

然后获取预制体代码中的初始化方法,并传入方向

然后写一个计时器,让子弹销毁

public class Game : MonoBehaviour
{
    public Animator animator;
    public float movespeed;
    public SpriteRenderer spriteRenderer;
	public GameObject starPrefab;
	public Transform starShootPoint;

	//CD
	private float shootCD = 1;
	private float shootCDTimer;
	private void Start()
	{
		spriteRenderer= gameObject.GetComponent<SpriteRenderer>();
	}

	// Update is called once per frame
	void Update()
    {
		Move(); Shoot();

	}
   private void Move()
    {
		animator.GetComponent<Animator>();
		float inputX = Input.GetAxis("Horizontal");
		float inputY = Input.GetAxis("Vertical");
		//修改动画
		bool isMove = inputX != 0 || inputY != 0;
		animator.SetBool("Move", isMove);
		//转向
		if (inputX > 0) spriteRenderer.flipX = false;
		else if (inputX < 0) spriteRenderer.flipX = true;

		//位移
		Vector3 pos =
				new Vector3(inputX * Time.deltaTime * movespeed,
							inputY * Time.deltaTime * movespeed,
							0);
		transform.Translate(pos, Space.Self);
	}
	private void Shoot()
	{
		if (shootCDTimer > 0)
		{
			shootCDTimer-= Time.deltaTime;
			return;
		}
		if (Input.GetKeyDown(KeyCode.J))
		{
			shootCDTimer = shootCD;
			//实例化
			GameObject star=GameObject.Instantiate(starPrefab);
			star.transform.position = starShootPoint.position;
			bool isRightDir = spriteRenderer.flipX == false;
			Vector3 moveDir = isRightDir ? transform.right : -transform.right;
			Debug.Log(moveDir);
			star.GetComponent<Star>().Init(moveDir);
		}
	}
}

十、怪物生成

1、配置一些坐标生成怪物

创建一个空物体,挂载怪物的代码

对怪物的基础配置进行设置:

这里有一个方法;使用class类,然后把这个类作为一个数组,公开这个数组为public即可

/// <summary>
/// 怪物生成点
/// </summary>
[Serializable]
public class MonsterSpawnPoint
{
    public Transform spawnPoint;//生成点
    public GameObject monsterPrefab;//怪物预制体
    public float spawnInterval;//怪物生成间隔
    //这里的计时器不想序列化,但是需要获取,所以改为属性
    public float SpawnTimer { get; set; }
}
public MonsterSpawnPoint[] monsterSpawnPoints;

 

小技巧:把动画片段全部选中拖拽进入层级面板中,可以快速地生成动画和动画状态

2、按照一个频率生成怪物

现在面板上设置了预制体和生成间隔

我们做一个计时器,计时器命名为Timer

用一个for循环遍历这5个数组

声明一个局部变量类,让每一次循环都赋值给这个局部变量类,也就是每一次循环Point都获取了数组中的参数;

使用计时器,先让他小于零;

做一个判断如果小于零,那么就把生成间隔赋值给计时器;

并且实例化类中的预制体,把类中的生成点赋值给怪物的当前位置;

private void Update()
{
    for (int i=0;i< monsterSpawnPoints.Length;i++)
    {
		MonsterSpawnPoint point= monsterSpawnPoints[i];
        point.SpawnTimer-=Time.deltaTime;//这里的计时器一开始就会小于零
        if (point.SpawnTimer <= 0)//一定会小于零
        {
            point.SpawnTimer = point.spawnInterval;//就把生成间隔赋值给计时器
            GameObject monster = GameObject.Instantiate(point.monsterPrefab);
            monster.transform.position = point.spawnPoint.position;
        }
	}
}

现在对代码进行一下优化,

public class MonsterManager : MonoBehaviour
{
    /// <summary>
    /// 怪物生成点
    /// </summary>
    [Serializable]
    public class MonsterSpawnPoint
    {
        public Transform spawnPoint;//生成点
        public GameObject monsterPrefab;//怪物预制体
        public float spawnInterval;//怪物生成间隔
        //这里的计时器不想序列化,但是需要获取,所以改为属性
        public float SpawnTimer { get; set; }

       public void Update(float deltaTime)
        {
			SpawnTimer -= Time.deltaTime;//这里的计时器一开始就会小于零
			if (SpawnTimer <= 0)//一定会小于零
			{
				SpawnTimer = spawnInterval;//就把生成间隔赋值给计时器
				GameObject monster = GameObject.Instantiate(monsterPrefab);
				monster.transform.position = spawnPoint.position;
			}
		}
    }
    public MonsterSpawnPoint[] monsterSpawnPoints;
	private void Update()
	{
        for (int i=0;i< monsterSpawnPoints.Length;i++)
        {
			monsterSpawnPoints[i].Update(Time.deltaTime);
           
		}
	}
}

因为update中调用其他类中的变量,不如直接调用update封装起来

所以在Update中定义一个参数(时间),因为调用这个update传入的参数是Time.deltatime

把生成的逻辑封装起来在Update中

在主update中,每一个for循环之后,数组的第i次循环调用这个UPdate,并传入每时每秒。

十一、怪物移动逻辑

一直去追玩家,如果解除了玩家则玩家失败;

首先把玩家设置成单例;在怪物类中先设置一个判断;判断玩家是否为空,也就是死亡状态

if (Game.Instance == null) return;

计算玩家到敌人的距离,方法是使用向量,用敌人的当前坐标 减去 自己的当前坐标

Vector3 moveDir=Game.Instance.transform.position-transform.position;

 然后进行位移

transform.Translate(moveSpeed * Time.deltaTime * moveDir,Space.World);

 此时敌人背面朝着敌人移动,所以使用Bool判断玩家在左或者右

 如果玩家的坐标小于怪物的坐标,说明是在左侧,那么更改怪物的缩放值X

bool isRight=transform.position.x>Game.Instance.transform.position.x;
if (isRight) transform.localScale = new Vector3(-1, 1, 1);
else transform.localScale = new Vector3(1, 1, 1);

 

十二、子弹消灭怪物

1、怪物被攻击,减少血量

首先,把死亡的动画片段拖进两个敌人的动画控制器中

我选择使用触发 作为死亡的条件

//接触子弹
if (collision.gameObject.CompareTag("Bullet"))
{
	
	Destroy(collision.gameObject);//取消子弹穿透
	hp -= 1;
	if (hp == 0)
	{
		animator.SetTrigger("Death");
	}
}

如果血量为零,则播放死亡动画 

2、血量为0,播放死亡动画,销毁自己

#region 动画事件
	private void OnDeathAnimationEnd()
	{
		Destroy(gameObject);
	}
#endregion
	

 在死亡动画的最后一帧执行销毁

十三、鼠标指向为攻击方向 

使用屏幕坐标 到 世界坐标 

public new Camera camera;
		Vector3 mouseWorldPosition=camera.ScreenToWorldPoint(Input.mousePosition);

获取到的屏幕坐标,因为两个方法都需要,所以作为参数传进来 

十四、玩家生命值以及状态 

不适用UI;

把血条作为摄像机的子物体,防止血条跟随摄像机改变位置;

不让摄像机在玩家的子物体下,通过代码跟随

使用LateUpdate,让摄像机慢一帧追逐玩家

public Transform target;
private void LateUpdate()
{
	transform.position = target.position;
}

 此时,出现问题。

因为2d的,摄像机到玩家的距离是-10,运行后摄像机与玩家重合会看不见

修改代码,固定Z轴坐标

Vector3 pos=target.position;
pos.z = -10;
transform.position = pos;

因为血条是几张图片切换实现的

所以,在玩家身上公开一个变量HP,把Hp设置为字段,get无所谓,set设置切换 

[SerializeField]private int hp;
public int Hp 
{
	get => hp;
	set
	{ 
		hp = value;
	} 
}

然后引用血量图,做成一个数组 

public SpriteRenderer hpSpriteRenderer;
public Sprite[] spriteRenderers;

然后在字段中,把精灵图第几个赋值给血条上 

public int Hp 
{
	get => hp;
	set
	{ 
		hp = value;
		hpSpriteRenderer.sprite = hpSprites[hp];
	} 
}

初始化中,把数组长度作为血量值传进来

private void Awake()
{
	Instance = this;
	Hp = hpSprites.Length-1;
}

十五、怪物干掉玩家

怪物碰到玩家,掉血,玩家掉血为0则死亡

在此处遇到问题:怪物与玩家无法碰撞,碰撞代码逻辑未执行;

原因是刚体的数值未设置正确。

public class CameraController : MonoBehaviour
{
	public Transform target;
	private Game game1;
	
	private void Awake()
	{
		game1 = FindObjectOfType<Game>();
		if (game1 == null)
		{
			Debug.Log("没找到");
		}

	}
	private void LateUpdate()
	{
		int xue = game1.Hp;
		Debug.Log(xue);
		if (xue == 0)
		{
			return;
		}
		Vector3 pos=target.position;
		pos.z = -10;
		transform.position = pos;
	}
}

 

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//using UnityEngine.Windows;

public class Game : MonoBehaviour
{
	public static Game Instance;
    public Animator animator;
    public float movespeed;
    public SpriteRenderer spriteRenderer;
	public GameObject starPrefab;
	public Transform starShootPoint;

	public new Camera camera;

	[SerializeField]private int hp;
	public SpriteRenderer hpSpriteRenderer;
	public Sprite[] hpSprites;

	//CD
	private float shootCD = 0.1f;
	private float shootCDTimer;

	public int Hp 
	{
		get => hp;
		set
		{ 
			hp = value;
			hpSpriteRenderer.sprite = hpSprites[hp];
			if (hp == 0)
			{
				Dead();
			}
		} 
	}

	
	private void Awake()
	{
		Instance = this;
		Hp = hpSprites.Length-1;
	}
	private void Start()
	{
		spriteRenderer= gameObject.GetComponent<SpriteRenderer>();
		
	}

	// Update is called once per frame
	void Update()
    {
		Debug.Log(Hp);
		Vector3 mouseWorldPosition=camera.ScreenToWorldPoint(Input.mousePosition);
		//Debug.Log(mouseWorldPosition);
		Move(mouseWorldPosition); Shoot(mouseWorldPosition);

	}
   private void Move(Vector3 mouseWorldPosition)
    {
		animator.GetComponent<Animator>();
		float inputX = Input.GetAxis("Horizontal");
		float inputY = Input.GetAxis("Vertical");
		//修改动画
		bool isMove = inputX != 0 || inputY != 0;
		animator.SetBool("Move", isMove);
		//转向
		if (mouseWorldPosition.x > transform.position.x) spriteRenderer.flipX = false;
		else if (mouseWorldPosition.x < transform.position.x) spriteRenderer.flipX = true;

		//位移
		Vector3 pos =
				new Vector3(inputX * Time.deltaTime * movespeed,
							inputY * Time.deltaTime * movespeed,
							0);
		transform.Translate(pos, Space.Self);
	}
	private void Shoot(Vector3 mouseWorldPosition)
	{
		if (shootCDTimer > 0)
		{
			shootCDTimer-= Time.deltaTime;
			return;
		}
		if (Input.GetMouseButton(0))
		{
			shootCDTimer = shootCD;
			//实例化
			GameObject star=GameObject.Instantiate(starPrefab);
			star.transform.position = starShootPoint.position;
			Vector3 moveDir = (mouseWorldPosition - transform.position).normalized;
			star.GetComponent<Star>().Init(moveDir);
		}
	}

	internal void Hurt()
	{
		if (Hp <= 0) return;
		Hp -= 1;
	}
	private void Dead()
	{
		animator.SetTrigger("Dead");
	}
	private void DestroyPlayer()
	{
		Destroy(gameObject);
	}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Monster : MonoBehaviour
{
	public float moveSpeed;
	public int hp;
	public Animator animator;
	private void Update()
	{
		if (Game.Instance == null) return;
		Vector3 moveDir=Game.Instance.transform.position-transform.position;
		transform.Translate(moveSpeed * Time.deltaTime * moveDir,Space.World);
		bool isRight=transform.position.x>Game.Instance.transform.position.x;
		if (isRight) transform.localScale = new Vector3(-1, 1, 1);
		else transform.localScale = new Vector3(1, 1, 1);
	}
	private void OnTriggerEnter2D(Collider2D collision)
	{
		Debug.Log("碰撞对象标签:" + collision.gameObject.tag);
		if (hp == 0) return;
		//接触子弹
		if (collision.gameObject.CompareTag("Bullet"))
		{
			Debug.Log("子弹接触怪物了");
			Destroy(collision.gameObject);//取消子弹穿透
			hp -= 1;
			if (hp == 0)
			{
				animator.SetTrigger("Death");
			}
		}
		else
		{
			//接触玩家
			if (collision.gameObject.CompareTag("Player"))
			{
				Debug.Log("11");
				collision.gameObject.GetComponent<Game>().Hurt();
				Debug.Log("怪物接触玩家了");
			}
		}
		
	}
	
	#region 动画事件
	private void OnDeathAnimationEnd()
	{
		Destroy(gameObject);
	}
#endregion
	
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/975512.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用大语言模型对接OA系统,实现会议室预定功能

随着人工智能技术的不断进步&#xff0c;越来越多的企业开始借助 AI 助手来提高工作效率&#xff0c;尤其是在日常事务的自动化处理中。比如&#xff0c;在许多公司里&#xff0c;会议室的预定是一个常见且频繁的需求&#xff0c;通常需要员工手动检查空闲时间并做出选择。而通…

游戏引擎学习第113天

仓库:https://gitee.com/mrxiao_com/2d_game_2 黑板&#xff1a;优化的基本过程 在游戏编程中&#xff0c;优化是一个非常重要的学习内容&#xff0c;尤其是想要成为专业开发者时。优化的核心是理解代码的执行速度&#xff0c;以及如何提升其性能。在这个阶段&#xff0c;已经…

Qt 中的线程池QRunnable和QThreadPool

Qt 中的线程池QRunnable和QThreadPool 一、QThreadPool类介绍 QThreadPool 是 Qt 框架中用于管理线程池的类&#xff0c;它提供了一种高效的方式来管理和复用线程&#xff0c;避免频繁创建和销毁线程带来的开销。 1. 基本概念 QThreadPool 是一个全局的线程池&#xff0c;它…

C++中结构体与结构体变量 和 类与对象的区别

具体区别如下&#xff1a; 结构体 -> 结构体变量 { 结构体&#xff1a;struct student{ 具体是多少&#xff0c;年龄&#xff0c;名字&#xff0c;性别&#xff0c;成绩 } 结构体变量&#xff1a; stu{ 名字&#xff1a;张三&#xff0c;年龄&#xff1a;18&#…

一周学会Flask3 Python Web开发-flask3模块化blueprint配置

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们在项目开发的时候&#xff0c;多多少少会划分几个或者几十个业务模块&#xff0c;如果把这些模块的视图方法都写在app.py…

11套免费web登录页面模板分享

1.纯色 2.纯色 3.纯色 4.联系我们 5.纯色 6.动画 7.现代 8.现代 9.单调 10.现代 11.简约

Fences 5深度解析:一键打造超高效整洁桌面

在信息爆炸的时代,电脑桌面往往成为各种文件、图标和快捷方式的“聚集地”。杂乱无章的桌面不仅影响视觉体验,还可能降低工作效率。而Fences 5,这款由Stardock公司精心打造的桌面管理工具,凭借其强大的功能和便捷的操作,成为了众多用户整理桌面的得力助手。本文将带大家深…

一篇docker从入门到精通

Docker Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙盒机制&#xff0c;相互之间不会有任何接口&#xff08;类似 iP…

【深度学习】Unet的基础介绍

U-Net是一种用于图像分割的深度学习模型&#xff0c;特别适合医学影像和其他需要分割细节的任务。如图&#xff1a; Unet论文原文 为什么叫U-Net&#xff1f; U-Net的结构像字母“U”&#xff0c;所以得名。它的结构由两个主要部分组成&#xff1a; 下采样&#xff08;编码…

医疗AI领域中GPU集群训练的关键技术与实践经验探究(上)

医疗AI领域中GPU集群训练的关键技术与实践经验探究(上) 一、引言 1.1 研究背景与意义 在科技飞速发展的当下,医疗 AI 作为人工智能技术与医疗领域深度融合的产物,正引领着医疗行业的深刻变革。近年来,医疗 AI 在疾病诊断、药物研发、健康管理等诸多方面取得了显著进展,…

使用matplotlib绘制柱状图并在下面使用表格显示数值

使用matplotlib绘制柱状图并在下面使用表格显示数值 1、效果 2、流程 1、数据准备 2. 创建可视化布局 3.、绘制柱状图 4、创建表格 5、设置字体大小、标题、图例 6、显示图表3、代码 import matplotlib.pyplot as plt from matplotlib.gridspec import GridSpec import nump…

Windows11安装GPU版本Pytorch2.6教程

1: 准备工作 针对已经安装好的Windows11系统&#xff0c;先检查Nvidia驱动和使用的CUDA版本情况。先打开Windows PowerShell&#xff0c;通过nvidia-smi命令查看GPU的情况&#xff0c;结果如下图1所示&#xff0c;从结果中可知使用的CUDA版本为12.8。 图1&#xff1a;检测安装…

《Spring实战》(第6版) 第3章 使用数据

第3章 使用数据 使用Spring的JdbcTemplate&#xff1b;创建Spring Data JDBC存储库&#xff1b;使用Spring Data声明JPA存储库&#xff1b; 本章对Taco Cloud应用添加对数据库持久化的支持&#xff0c;关注JDBC和JPA。 3.1 使用JDBC读取和写入数据 Spring对JDBC的支持要归功…

设计模式 - Singleton pattern 单例模式

文章目录 定义单例模式的实现构成构成UML图 单例模式的六种实现懒汉式-线程不安全懒汉式-线程安全饿汉式-线程安全双重校验锁-线程安全静态内部类实现枚举实现 总结其他设计模式文章&#xff1a;最后 定义 单例模式是一种创建型设计模式&#xff0c;它用来保证一个类只有一个实…

出行项目案例

spark和kafka主要通过Scala实现&#xff0c;Hadoop和HBase主要基于java实现。 通过该项目&#xff0c;主要达到以下目的&#xff1a; &#xff08;1&#xff09;通用的数据处理流程&#xff0c;入门大数据领域 &#xff08;2&#xff09;真实体验大数据开发工程师的工作 &a…

从零开始制作一个漂亮的悬浮按钮

0.1血版 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head> &l…

安全面试2

文章目录 简单描述一下什么是水平越权&#xff0c;什么是垂直越权&#xff0c;我要发现这两类漏洞&#xff0c;那我代码审计要注意什么地方水平越权&#xff1a;垂直越权&#xff1a;水平越权漏洞的审计重点垂直越权漏洞的审计重点 解释一下ssrf漏洞原理攻击场景修复方法 横向移…

数字电子技术基础(二十一)——双极型三极管的开关特性

目录 1 半导体三极管的开关特性 1.1 双极性三极管的开关特性 1.2 双极型三极管的基本开关电路 1.3 三极管的开关等效电路 1.4 双极型三极管的动态开关特性 TTL门电路是一种基于双极型晶体管的数字逻辑电路&#xff0c;在20世纪60年代到80年代之间&#xff0c;TTL门电路是应…

足疗店会员管理系统,足疗养生全方位会员管理解决方案,佳易王试用版养生会所足疗店推拿按摩会员管理系统操作教程

一、概述 本实例以佳易王试用版养生会所足疗店推拿按摩会员管理系统17.1版本为例说明&#xff0c;其他版本可参考本实例。试用版软件资源可到文章最后了解&#xff0c;下载的文件为压缩包文件&#xff0c;请使用免费版的解压工具解压即可试用。 软件特点&#xff1a; 1、功能…

【HarmonyOS Next】拒绝权限二次申请授权处理

【HarmonyOS Next】拒绝权限二次申请授权处理 一、问题背景&#xff1a; 在鸿蒙系统中&#xff0c;对于用户权限的申请&#xff0c;会有三种用户选择方式&#xff1a; 1.单次使用允许 2.使用应用期间&#xff08;长时&#xff09;允许 3.不允许 当用户选择不允许后&#xff0…