unity3D游戏——魔鬼与牧师(Devil and Priest)动作分离版本的实现

2023-11-23 00:30

本文主要是介绍unity3D游戏——魔鬼与牧师(Devil and Priest)动作分离版本的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

前言

魔鬼与牧师游戏的MVC实现回顾

MVC的UML设计图

Models

View

Controllers

动作分离基本思路

动作分离版本的UML设计图

动作回调函数接口(ISSActionCallback)

动作基类(SSAction)

简单平移动作子类(CCMoveToAction)

组合移动动作子类(CCSequenceAction)

动作管理基类(SSActionManager)

本游戏项目(魔鬼与牧师)的动作管理类(CCActionManager)

裁判类(JudgeController)

其它操作

结语


前言

魔鬼与牧师(Devil and Priest)是一个经典的智力解谜游戏,玩家需要帮助三位魔鬼和三位牧师,将所有的牧师带过河,同时还要确保牧师不会被魔鬼吃掉。

下面是游戏的规则和背景故事:

规则:
1. 在河的一边有三个魔鬼、三个牧师以及一条船。
2. 船最多可以承载两个角色(牧师或魔鬼)且必须承载一个角色。
3. 当河的一侧的魔鬼数量大于牧师数量时,魔鬼会吃掉牧师。
4. 玩家需要将所有的牧师都安全地运送到另一侧的河岸,而且保证没有牧师被魔鬼吃掉。

背景故事:
有三个牧师和三个魔鬼被困在一座孤岛上,他们想要回到对岸。但是,这座孤岛上有一条河,河上只有一艘小船,而且这条河非常危险,同时魔鬼会吃掉牧师。他们需要找到一种方法使所有的牧师都能够安全地过河。

玩家需要根据规则,使用合适的策略移动牧师和魔鬼,以确保所有的牧师都能够安全地过河。游戏中的挑战在于玩家需要平衡每一步的移动,避免牧师被魔鬼吃掉,并且在规定的条件下完成游戏。

魔鬼与牧师游戏是一个简单而有趣的逻辑解谜游戏,可以帮助玩家锻炼思维能力和问题解决能力。

首先我们先来看一下这个游戏的视频演示吧:

魔鬼与牧师的视频演示

本次项目的代码地址为:

Github地址​​​​​​​

魔鬼与牧师游戏的MVC实现回顾

在上一个实验(上一个实验的博客地址)中,我们利用基础的MVC架构来实现了魔鬼与牧师的游戏,在这里我们将会简单地回顾一下魔鬼与牧师游戏的MVC实现,具体的内容实现请参考上一篇博客内容。

MVC的UML设计图

我们在实现游戏时,将会创建model、controller以及view的脚本文件,分别实现相对应的功能。同时最核心的脚本代码就是controller里面的firstcontroller,同时还有导演类、用户交互类以及GUI类等等,我们首先看一下该游戏项目的UML设计图吧:

Models

Models负责的是各个游戏对象的属性和基本行为,包括人物角色(魔鬼、牧师),船,以及河的两岸。船和岸是作为一个容器,需要有容纳人物的位置,也需要记录每个位置对应的xyz坐标。而且它们都需要有一定的方法去返回自身的信息,比如在左还是右、是第几个人物、返回对象类型的方法等等。同时还需要使用一个models中的position类记录一下各个游戏对象的位置信息,以供后面的类中调用。

View

View就是与用户交互的接口,其中需要接受来自用户的点击信息,并且根据点击的物体不同而传递不同的信息。还有就是反馈,游戏开始或结束需要反馈相应的信息给用户,也就是一个简单的GUI界面。比如游戏结束返回Game Over信息,游戏胜利就返回you win信息,以及倒计时游戏的剩余时间等。

Controllers

控制器Controllers的目的就是需要将M和V连接起来,从而控制全局,不仅需要从Models中获取相应的信息,并且利用这些信息判断他们的位置,还需要从View中获取相应的用户输入,进行相应的物体移动,在Models和View之间充当桥梁的作用。同时在主要的控制器FirstController还需要判断游戏的进行程度,也就是需要判断输赢,并且将输赢信息通过回调函数返回给View,从而达到反馈的目的。总而言之,控制器就是整个游戏得以进行的大脑,控制了整个游戏的进行逻辑以及实现。

动作分离基本思路

从上一个实验我们可以知道,使用MVC架构虽然比较直观,但是会发现我们的控制器Controllers比较臃肿,比较复杂,承担的任务比较重。所以是绝对有必要给控制器分配几个“手下”,为其承担一部分的任务的。那么,应该如何分配“手下”呢?而到了这里就是今天的主题啦——动作

动作分离版本的UML设计图

首先,我们还是需要先看一下该游戏项目的UML设计图,该UML设计图是基于动作分离版本的,在前一个实验的基础上增加了CCAction的动作类,同时对控制器Controllers做了一定的修改,下面是本次实验的UML设计图:

动作回调函数接口(ISSActionCallback)

在动作执行完毕后,都会执行回调函数执行别的操作,以及实现与这个动作相关的代码。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//枚举了动作事件的类型:开始和完成
public enum SSActionEventType:int { Started, Competeted }//一个接口,用于在动作完成时进行回调
public interface ISSActionCallback
{//source为一个SSAction类型的参数,表示触发事件的动作对象//events为一个SSActionEventType类型的参数,表示事件类型,默认为Competetedvoid SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,int intParam = 0 , string strParam = null, Object objectParam = null);
}

动作基类(SSAction)

在上一个实验中,我们学习了解到物体运动(也就是所说的动作),无非就是物体空间属性的改变,而我们目前所接触到的只有三种:平移、旋转、缩放。所以为了简化实现,我们可以直接定义一个类,可是又不能把它定死,万一要用到一个类的时候还要去区分是三种动作的哪一种,那不是很麻烦吗?所以我们就需要抽象出来一个动作的基类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//动作基类
//将平移、旋转、缩放抽象出一个动作基类
public class SSAction : ScriptableObject {//用于标记该动作是否被启用public bool enable = true;//用于标记该动作是否被摧毁public bool destory = false;//表示该动作作用的对象public GameObject gameobject { get; set; }//表示该动作所作用的对象的变换组件public Transform transform { get; set; }//表示该动作完成后的回调接口public ISSActionCallback callback { get; set; }//构造函数protected SSAction () {}// Use this for initializationpublic virtual void Start () {//抛出异常,表示在子类中必须完成相应的实现throw new System.NotImplementedException ();}// Update is called once per framepublic virtual void Update () {//抛出异常,表示在子类中必须完成相应的实现throw new System.NotImplementedException ();}}

该动作类只是一个简单的基类,将缩放、平移、旋转抽象成了一个类,在该类中,我们并没有具体表明这是一个什么动作,而是定义设置好了这个动作是否被启用、销毁、使用该动作的对象以及一些虚函数,用于在具体实现的动作子类中重写从而实现该动作的一些特性。这样子就可以将各个动作进行区分实现了。

简单平移动作子类(CCMoveToAction)

在本游戏项目中,由于对象的运动比较简单,只涉及到了平移的运动操作,而没有旋转、缩放等运动,所以在这里我们只需要实现平移动作的子类即可,而如果在另一个项目中有涉及到旋转、缩放等运动,我们就需要实现这两个子类了。但是在这里我们只需要实现简单移动动作子类即可:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//在本游戏中(魔鬼与牧师),由于只涉及到平移操作,而没有旋转、缩放操作
//所以我们只需要实现一个平移类即可
//主要负责运动
public class CCMoveToAction : SSAction
{//表示动作的目标位置public Vector3 target;//表示动作移动的速度public float speed;//构造函数private CCMoveToAction(){}//创建以及返回一个CCMoveToAction对象public static CCMoveToAction GetSSAction(Vector3 target, float speed){CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction> ();action.target = target;action.speed = speed;return action;}public override void Update (){//在每一帧更新时,将游戏对象的位置逐渐移动到目标位置target,//并根据移动是否完成来设置destory属性和调用回调接口的SSActionEvent()方法if (this.gameobject == null||this.transform.localPosition == target) {//waiting for destroythis.destory = true;  this.callback.SSActionEvent (this);return;}this.transform.localPosition = Vector3.MoveTowards (this.transform.localPosition, target, speed * Time.deltaTime);}//重写了Start函数,但是没有具体实现public override void Start () {}
}

在该移动子类中,我们设置了目标位置和移动速度,然后在更新函数Update中,不断地更新移动后的新的位置即可,同时还需要更新是否摧毁等标记,以及调用回调函数来进行相应的操作。

组合移动动作子类(CCSequenceAction)

可能你会有所疑问——为什么还需要一个组合移动动作子类?上面不是说该游戏项目中只有一个移动的简单动作吗?其实确实如此,但是移动动作也不是简单的直接移动,而是需要分段式地移动,就好像走路,你不可能一直直走而不拐弯。所以这个组合移动动作子类的意思即是移动动作需要进行两段划分。因为在该游戏项目中,角色与船的高度y坐标不相同,角色在岸上的位置是高于船的,所以我们需要先将角色进行向右平移,移动到船的上空后再向下平移,装载到船上。所以这个就是我们需要这一个组合移动动作子类的原因。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//组合动作类,将事物的动作划分为多个小的动作
public class CCSequenceAction : SSAction, ISSActionCallback
{//一个List类型的数据,存储一系列SSAction对象,表示需要执行的动作序列public List<SSAction> sequence;//表示动作序列需要重复的次数,默认为-1,表示无限重复public int repeat = -1;//表示当前执行的动作在序列中的索引public int start = 0;//用于创建CCSequenceAction对象,//接受重复次数repeat、起始索引start和动作序列sequence作为参数,//返回一个CCSequenceAction对象public static CCSequenceAction GetSSAction(int repeat, int start , List<SSAction> sequence){CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction> ();action.repeat = repeat;action.sequence= sequence;action.start = start;return action;}// Update is called once per frame//重写当前的Update函数public override void Update (){//如果sequence中的动作为空,那么直接返回if (sequence.Count == 0) return;  //如果当前还有未执行的动作,就再调用当前动作的Update方法if (start < sequence.Count) {sequence [start].Update ();}}//实现了ISSActionCallback接口的方法,//当一个动作完成时,会调用该方法,//将当前动作的destory属性设置为false,表示不立即销毁,//将索引start加1,判断是否需要重复执行动作序列或者执行完毕,//并根据情况设置destory属性为true以及调用回调接口的SSActionEvent()方法。public void SSActionEvent (SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, Object objectParam = null){source.destory = false;this.start++;if (this.start >= sequence.Count) {this.start = 0;if (repeat > 0) repeat--;if (repeat == 0) {this.destory = true;this.callback.SSActionEvent (this); }}}//重写了基类的Start方法,//在该方法中,遍历动作序列sequence,//并为每个动作设置gameobject、transform和callback属性,最后调用各个动作的Start方法。// Use this for initializationpublic override void Start () {foreach (SSAction action in sequence) {action.gameobject = this.gameobject;action.transform = this.transform;action.callback = this;action.Start ();}}//遍历动作序列sequence,并且销毁每一个动作void OnDestory() {foreach(SSAction action in sequence){Destroy(action);}}
}

在这一个类中,我们就是设置了一个列表,将一个移动动作划分为了几个简单的移动动作,然后将这些移动动作放在列表中。

Start函数主要就是遍历动作序列,并且为每个动作设置gameobject、transform和callback属性,最后调用各个动作的Start方法。

Update函数则是不断遍历动作序列,如果sequence中的动作为空,那么直接返回,如果当前还有未执行的动作,就再调用当前动作的Update方法。

在每一个简单的动作执行完毕后,都会不断地调用回调函数,此时会执行SSActionEvent这一个函数,检查repeat是否为0,如果不为0,就再继续回调。

动作管理基类(SSActionManager)

我们在实现了前面的一些基本动作后,我们就需要考虑如何调用这一些函数,然后使得角色运动起来了。在动作管理基类中,其实就是将许多的动作集中在一起,即使是不同的运动动作、不同的角色的运动、不同的游戏项目的运动,我们都可以将它们的运动放在同一个基类中,即将所有的运动动作整合在一起,然后再顺序地调用这些动作即可。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//动作管理基类
//将许多个动作整合到一起,然后顺序调用
public class SSActionManager : MonoBehaviour {//为一个字典,用于存储所有的SSAction对象,以其实例ID为键。private Dictionary <int, SSAction> actions = new Dictionary <int, SSAction> ();//一个列表,用于存储等待添加的SSAction对象private List <SSAction> waitingAdd = new List<SSAction> ();//一个列表,用于存储等待删除的SSAction对象 private List<int> waitingDelete = new List<int> ();// Update is called once per frameprotected void Update () {//首先将所有等待添加的SSAction对象添加到actions字典中foreach (SSAction ac in waitingAdd){actions[ac.GetInstanceID ()] = ac;}//清空等待添加的列表waitingAdd.Clear ();//遍历actions字典中的每一个SSAction对象foreach (KeyValuePair <int, SSAction> kv in actions) {SSAction ac = kv.Value;//如果action的destory属性为true时,将该对象添加到等待删除的队列中if (ac.destory) { waitingDelete.Add(ac.GetInstanceID()); // release action}//如果action的enable为true时,将调用Update函数更新该action else if (ac.enable) { ac.Update (); // update action}}//遍历等待删除的对象列表,将所有的对象销毁,并且清空该等待删除的列表foreach (int key in waitingDelete) {SSAction ac = actions[key]; actions.Remove(key); Object.Destroy(ac);}waitingDelete.Clear ();}//为外部的一个接口函数,将参数进行绑定public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager) {action.gameobject = gameobject;action.transform = gameobject.transform;action.callback = manager;//将该动作添加到等待添加的对象列表中//同时调用对象的Start方法来进行初始化waitingAdd.Add (action);action.Start ();}// Use this for initializationprotected void Start () {}
}

在该动作管理基类中,我们创建了一个字典用于存储所有的SSAction对象,一个列表用于存储等待添加的SSAction对象,一个列表用于存储等待删除的SSAction对象。主要是在Update函数中,将所有等待添加的SSAction对象添加到actions字典中,将所有等待被删除的SSAction对象销毁,同时提供了一个RunAction函数,用于外部对象可以添加动作。

本游戏项目(魔鬼与牧师)的动作管理类(CCActionManager)

上面我们已经实现了一个管理动作运动的动作管理基类,那么在我们本次的游戏项目中,我们就需要实现一个具体的符合该游戏项目的动作管理类了,其实就是上面父类的一个具体子类。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;//该游戏魔鬼与牧师的动作管理的具体实现类
public class CCActionManager : SSActionManager, ISSActionCallback
{//是否正在进行运动private bool isMoving = false;//表示船移动动作类(只需要往左或者右移动,不需要组合)public CCMoveToAction moveBoatAction;//角色移动动作类(需要组合——先往右平移,然后往下面移动)public CCSequenceAction moveRoleAction;//控制游戏运行的主控制器public FirstController mainController;//重写基类的Start方法protected new void Start(){//获取主控制器的引用,并且将当前的控制器设置为主控制器mainController = (FirstController)SSDirector.GetInstance().CurrentSceneController;mainController.actionManager = this;}//获取该游戏是否正在进行运动的状态public bool IsMoving(){return isMoving;}//移动船//创建移动船的动作并且将其添加到船上public void MoveBoat(GameObject boat, Vector3 target, float speed){if (isMoving)return;isMoving = true;moveBoatAction = CCMoveToAction.GetSSAction(target, speed);this.RunAction(boat, moveBoatAction, this);}//移动人//创建人移动的动作,并且通过组合多个动作来实现从起点到中间位置然后再到目标位置的移动public void MoveRole(GameObject role, Vector3 mid_destination, Vector3 destination, int speed){if (isMoving)return;isMoving = true;moveRoleAction = CCSequenceAction.GetSSAction(0, 0, new List<SSAction> { CCMoveToAction.GetSSAction(mid_destination, speed), CCMoveToAction.GetSSAction(destination, speed) });this.RunAction(role, moveRoleAction, this);}//回调函数//当一个动作完成后,将会调用该方法//将isMoving设置为false,表示动作的运动已经完成public void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competeted,int intParam = 0,string strParam = null,Object objectParam = null){isMoving = false;}
}

在该管理动作类中,我们主要是实现了移动船和移动角色的两个简单运动,然后调用RunAction函数,使得控制器可以直接调用,一步到位。同时我们还需要重定义一下回调函数,当一个动作完成后就会调用该回调函数,同时更新标记,表示该动作的运动已经完成。

裁判类(JudgeController)

在上面的实现中,我们已经完成了动作的分离,将运动的实现从控制器中分离了出来,那么按照实验要求,我们还需要完成一个裁判类,主要用于判断游戏是否结束、胜利等逻辑。具体的游戏规则在上面的前言中已经给出。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class JudgeController : MonoBehaviour
{//控制游戏的主控制器public FirstController mainController;//游戏的左岸public Land leftLandModel;//游戏的右岸public Land rightLandModel;//游戏的船模型public Boat boatModel;// Start is called before the first frame update//在游戏开始时调用,主要用于获取主要控制器、左岸、右岸、船的引用void Start(){mainController = (FirstController)SSDirector.GetInstance().CurrentSceneController;this.leftLandModel = mainController.leftLandController.GetLand();this.rightLandModel = mainController.rightLandController.GetLand();this.boatModel = mainController.boatController.GetBoatModel();}// Update is called once per framevoid Update(){//首先检查游戏是否在运行,如果不是就返回if (!mainController.isRunning)return;//检查游戏时间是否已经变为0,如果是就返回,并且调用回调函数同时传递Game Over以及false参数//同时将游戏是否运行设置为falseif (mainController.time <= 0){mainController.JudgeCallback(false, "Game Over!");mainController.isRunning=false;return;}this.gameObject.GetComponent<UserGUI>().gameMessage = "";//判断游戏是否已经胜利//如果右岸上的牧师数量已经达到了三个,那么游戏胜利,同时利用回调函数传递You Win以及false参数//同时将游戏是否运行设置为falseif (rightLandModel.priestCount == 3){mainController.JudgeCallback(false, "You Win!");mainController.isRunning=false;return;}else{//如果左岸上的牧师数量不为0,而且左岸上的牧师数量小于魔鬼数量,那么判断游戏失败//同时返回Game Over以及false参数//同时将游戏是否运行设置为falseint leftPriestNum, leftDevilNum, rightPriestNum, rightDevilNum;leftPriestNum = leftLandModel.priestCount + (boatModel.isRight ? 0 : boatModel.priestCount);leftDevilNum = leftLandModel.devilCount + (boatModel.isRight ? 0 : boatModel.devilCount);if (leftPriestNum != 0 && leftPriestNum < leftDevilNum){mainController.JudgeCallback(false, "Game Over!");mainController.isRunning=false;return;}rightPriestNum = rightLandModel.priestCount + (boatModel.isRight ? boatModel.priestCount : 0);rightDevilNum = rightLandModel.devilCount + (boatModel.isRight ? boatModel.devilCount : 0);//如果右岸上的牧师数量不为0,而且右岸上的牧师数量小于魔鬼数量,那么判断游戏失败//同时返回Game Over以及false参数//同时将游戏是否运行设置为falseif (rightPriestNum != 0 && rightPriestNum < rightDevilNum){mainController.JudgeCallback(false, "Game Over!");mainController.isRunning=false;return;}}}
}

在裁判类的Update函数中,我们首先判断游戏时间是否归零,如果是就直接判断游戏结束,同时更新参数以及传递信号给GUI。随后我们继续判断游戏是否达到胜利或者失败的规则,如果是,那么更新相对应的参数以及传递信号给GUI完成交互。

其它操作

在实现了上面的动作分离后,我们还需要对我们上一个实验实现的游戏项目进行一定的修改,才可以将游戏项目运行起来。我们需要将Controllers中的Move以及MoveController删除,在主控制器中将移动船、移动角色的操作交给动作管理类来实现,控制器直接调用,其它的代码保持不变即可。同时我们还可以为我们的游戏项目添加一定的天空盒等渲染。

结语

在本次实验中,我们实现了与上一个实验完全相同的项目游戏,我们的大多数的代码实现也是基本相同的,但是我们在上一个实验的基础上,添加了动作分离,将对象的运动分离到了CCAction中,从控制器中分离了出来,大大减轻了控制器的任务以及代码量。同时使得游戏的实现效率更高,代码的书写逻辑更加流畅、直观。而且明显动作分离是更加适合于一些更加复杂的运动项目的,可以将这些动作自由组合,而不是直接写死在控制器中,机动性更加强。

在完成了本次实验后,我对动作的分离以及集合的原理有了更加深入的理解和认识。

这篇关于unity3D游戏——魔鬼与牧师(Devil and Priest)动作分离版本的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/414021

相关文章

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、