qframework 架构 (作者:凉鞋)使用笔记

news2024/11/24 17:45:32

一些准则:

根据VIEW->SYSTEM->MODEL的分层架构

初始架构:

app.

using FrameworkDesign;

namespace ShootingEditor2D(项目的命名空间)
{
    public class ShootingEditor2D (游戏名称): Architecture<ShootingEditor2D>
    {
        protected override void Init()
        {
      
        }
    }
}

该脚本放到scripts文件夹下。

其他model\system等,注册在app.

using FrameworkDesign;

namespace ShootingEditor2D
{
    public class ShootingEditor2D : Architecture<ShootingEditor2D>
    {
        protected override void Init()
        {
            this.RegisterModel<IPlayerModel>(new PlayerModel());
            this.RegisterSystem<IStatSystem>(new StatSystem());
        }
    }
}

功能列表

在VIEW层和SYSTEM层之间通信

创建VIEW

创建VIEW中的关键角色:UIController,方便快速对UI的显示进行处理。

*注意,VIEW脚本可以获取任何来自SYSTEM和MODEL的信息,以此来更新自己,比如监测是否子弹足够。

namespace ShootingEditor2D
{
    public class Gun : MonoBehaviour,IController // +
    {
        private Bullet mBullet;

        private GunInfo mGunInfo; // +
  
        private void Awake()
        {
            mBullet = transform.Find("Bullet").GetComponent<Bullet>();
      
            mGunInfo = this.GetSystem<IGunSystem>().CurrentGun; // +
        }
  
        public void Shoot()
        {
            if (mGunInfo.BulletCount.Value > 0) // +
            {

                var bullet = Instantiate(mBullet.transform, mBullet.transform.position, mBullet.transform.rotation);
                // 统一缩放值
                bullet.transform.localScale = mBullet.transform.lossyScale;
                bullet.gameObject.SetActive(true);
          
                this.SendCommand<ShootCommand>(); // +
            }
        }

        public IArchitecture GetArchitecture() // +
        {
            return ShootingEditor2D.Interface;
        }

        private void OnDestroy() // +
        {
            mGunInfo = null; //+
        }
    }
}

namespace ShootingEditor2D
{
    public class UIController : MonoBehaviour,IController
    {
        private IStatSystem mStatSystem;
        private IPlayerModel mPlayerModel;


  
        private void Awake()
        {
            mStatSystem = this.GetSystem<IStatSystem>();
            mPlayerModel = this.GetModel<IPlayerModel>();
        }

        /// <summary>
        /// 自定义字体大小
        /// </summary>
        private readonly Lazy<GUIStyle> mLabelStyle = new Lazy<GUIStyle>(()=>new GUIStyle(GUI.skin.label)
        {
            fontSize = 40
        });
  
        private void OnGUI()
        {
            GUI.Label(new Rect(10,10,300,100),$"生命:{mPlayerModel.HP.Value}/3",mLabelStyle.Value);
            GUI.Label(new Rect(Screen.width - 10 - 300,10,300,100),$"击杀数量:{mStatSystem.KillCount.Value}",mLabelStyle.Value);
        }


        public IArchitecture GetArchitecture()
        {
            return ShootingEditor2D.Interface;
        }
    }
}

创建VIEW->SYSTEM的通信方式:命令Command

namespace ShootingEditor2D
{
    public class KillEnemyCommand : AbstractCommand
    {
        protected override void OnExecute()
        {
            this.GetSystem<IStatSystem>().KillCount.Value++;
        }
    }
}
        protected override void OnExecute()
        {
            var gunSystem = this.GetSystem<IGunSystem>();
            gunSystem.CurrentGun.BulletCountInGun.Value--;
            gunSystem.CurrentGun.GunState.Value = GunState.Shooting;
        }

确定在什么运行情况下发出该命令Command。比如,一个小小的子弹销毁时,子弹知道要发出。

namespace ShootingEditor2D
{
    public class Bullet : MonoBehaviour,IController // +
    {
        private Rigidbody2D mRigidbody2D;
  
        private void Awake()
        {
            mRigidbody2D =  GetComponent<Rigidbody2D>();
        }

        private void Start()
        {
            mRigidbody2D.velocity = Vector2.right * 10;
        }

        private void OnCollisionEnter2D(Collision2D other)
        {
            if (other.gameObject.CompareTag("Enemy"))
            {
                this.SendCommand<KillEnemyCommand>(); // +
          
                Destroy(other.gameObject);
            }
        }

        public IArchitecture GetArchitecture() // +
        {
            return ShootingEditor2D.Interface;
        }
    }
}

再比如,如果玩家碰到怪物就掉血,那么针对这个功能可以写一个脚本,挂在再玩家身上

namespace ShootingEditor2D
{
    public class AttackPlayer : MonoBehaviour,IController
    {
        private void OnCollisionEnter2D(Collision2D other)
        {
            if (other.gameObject.CompareTag("Player"))
            {
                this.SendCommand<HurtPlayerCommand>();
            }
        }

        public IArchitecture GetArchitecture()
        {
            return ShootingEditor2D.Interface;
        }
    }

VIEW自己分内的逻辑就自己处理了,比如怪物碰到玩家自己消失。如果不需要记录MODEL以数据的话,就自己删除。

可以建立各种各样的VIEW,别客气。

比如碰到了子弹库来补充弹药:

  public class GunPickItem : MonoBehaviour,IController
    {
        public string Name;
        public int BulletCountInGun;
        public int BulletCountOutGun;

        private void OnTriggerEnter2D(Collider2D other)
        {
            if (other.CompareTag("Player"))
            {
                this.SendCommand(new PickGunCommand(Name,BulletCountInGun,BulletCountOutGun));
            }
        }

        public IArchitecture GetArchitecture()
        {
            return ShootingEditor2D.Interface;
        }
    }

条件没问题的话,发送就行。至于这个pickgun要如何运作,有点复杂,但由command交给system去处理,处理之后,发送回一个event,表示枪支变化:

command这样写:

 public class PickGunCommand : AbstractCommand
    {
        private readonly string mName;
        private readonly int mBulletInGun;
        private readonly int mBulletOutGun;

        public PickGunCommand(string name, int bulletInGun, int bulletOutGun)
        {
            mName = name;
            mBulletInGun = bulletInGun;
            mBulletOutGun = bulletOutGun;
        }

        protected override void OnExecute()
        {
            this.GetSystem<IGunSystem>()
                .PickGun(mName, mBulletInGun, mBulletOutGun);
        }
    }

system这样写

using System.Collections.Generic;
using System.Linq;
using FrameworkDesign;

namespace ShootingEditor2D
{
    public interface IGunSystem : ISystem
    {
        GunInfo CurrentGun { get; }

        void PickGun(string name, int bulletCountInGun, int bulletCountOutGun); //+
    }

    public class OnCurrentGunChanged // +
    {
        public string Name { get; set; }
    }

    public class GunSystem : AbstractSystem, IGunSystem
    {
        protected override void OnInit()
        {

        }

        private Queue<GunInfo> mGunInfos = new Queue<GunInfo>(); // +

        public GunInfo CurrentGun { get; } = new GunInfo()
        {
            BulletCountInGun = new BindableProperty<int>()
            {
                Value = 3
            },
            BulletCountOutGun = new BindableProperty<int>()
            {
                Value = 1
            },
            Name = new BindableProperty<string>()
            {
                Value = "手枪"
            },
            GunState = new BindableProperty<GunState>()
            {
                Value = GunState.Idle
            }
        };

        public void PickGun(string name, int bulletCountInGun, int bulletCountOutGun) // +
        {
            // 当前枪是同类型
            if (CurrentGun.Name.Value == name)
            {
                CurrentGun.BulletCountOutGun.Value += bulletCountInGun;
                CurrentGun.BulletCountOutGun.Value += bulletCountOutGun;
            }
            // 已经拥有这把枪了
            else if (mGunInfos.Any(info => info.Name.Value == name))
            {
                var gunInfo = mGunInfos.First(info => info.Name.Value == name);
                gunInfo.BulletCountOutGun.Value += bulletCountInGun;
                gunInfo.BulletCountOutGun.Value += bulletCountOutGun;
            }
            else
            {
                // 复制当前的枪械信息
                var currentGunInfo = new GunInfo
                {
                    Name = new BindableProperty<string>()
                    {
                        Value = CurrentGun.Name.Value
                    },
                    BulletCountInGun = new BindableProperty<int>()
                    {
                        Value = CurrentGun.BulletCountInGun.Value
                    },
                    BulletCountOutGun = new BindableProperty<int>()
                    {
                        Value = CurrentGun.BulletCountOutGun.Value
                    },
                    GunState = new BindableProperty<GunState>()
                    {
                        Value = CurrentGun.GunState.Value
                    }
                };

                // 缓存
                mGunInfos.Enqueue(currentGunInfo);

                // 新枪设置为当前枪
                CurrentGun.Name.Value = name;
                CurrentGun.BulletCountInGun.Value = bulletCountInGun;
                CurrentGun.BulletCountOutGun.Value = bulletCountOutGun;
                CurrentGun.GunState.Value = GunState.Idle;

                // 发送换枪事件
                this.SendEvent(new OnCurrentGunChanged()
                {
                    Name = name
                });
            }
        }
    }
}

SYSTEM中的数据变化如何告知VIEW?——为SYSTEM中的数据套用BindableProperty!

namespace ShootingEditor2D
{
    public enum GunState
    {
        Idle,
        Shooting,
        Reload,
        EmptyBullet,
        CoolDown
    }
    
    public class GunInfo
    {
        [Obsolete("请使用 BulletCountInGame",true)] // 第二个参数改成了 true
        public BindableProperty<int> BulletCount
        {
            get => BulletCountInGun;
            set => BulletCountInGun = value;
        }

        public BindableProperty<int> BulletCountInGun;

        public BindableProperty<string> Name;

        public BindableProperty<GunState> GunState;

        public BindableProperty<int> BulletCountOutGun;
    }
}

也可以设好初始值(但这个和架构无关)

   public GunInfo CurrentGun { get; } = new GunInfo()
        {
            BulletCountInGun = new BindableProperty<int>()
            {
                Value = 3
            },
            BulletCountOutGun = new BindableProperty<int>() // +
            {
                Value = 1
            },
            Name = new BindableProperty<string>() // +
            {
                Value = "手枪"
            },
            GunState = new BindableProperty<GunState>() // +
            {
                Value = GunState.Idle
            }
        };

事件EVENT:作为SYSTEM通知VIEW的方式。VIEW要自己Register

比如:这样

   public class UIController : MonoBehaviour, IController
    {
        private IStatSystem mStatSystem;
        private IPlayerModel mPlayerModel;
        private IGunSystem mGunSystem;

        private int mMaxBulletCount;

        private void Awake()
        {
            mStatSystem = this.GetSystem<IStatSystem>();
            mPlayerModel = this.GetModel<IPlayerModel>();
            mGunSystem = this.GetSystem<IGunSystem>();

            // 查询代码
            mMaxBulletCount = this.SendQuery(new MaxBulletCountQuery(mGunSystem.CurrentGun.Name.Value));

            this.RegisterEvent<OnCurrentGunChanged>(e =>
            {
                mMaxBulletCount = this.SendQuery(new MaxBulletCountQuery(e.Name));
            }).UnRegisterWhenGameObjectDestroyed(gameObject); // +
        }

MODEL:作为数据记录层

using System.Collections.Generic;
using FrameworkDesign;

namespace ShootingEditor2D
{
    public interface IGunConfigModel : IModel
    {
        GunConfigItem GetItemByName(string name);
    }

    public class GunConfigItem
    {
        public GunConfigItem(string name, int bulletMaxCount, float attack, float frequency, float shootDistance,
            bool needBullet, float reloadSeconds, string description)
        {
            Name = name;
            BulletMaxCount = bulletMaxCount;
            Attack = attack;
            Frequency = frequency;
            ShootDistance = shootDistance;
            NeedBullet = needBullet;
            ReloadSeconds = reloadSeconds;
            Description = description;
        }

        public string Name { get; set; }
        public int BulletMaxCount { get; set; }
        public float Attack { get; set; }
        public float Frequency { get; set; }
        public float ShootDistance { get; set; }
        public bool NeedBullet { get; set; }
        public float ReloadSeconds { get; set; }
        public string Description { get; set; }
    }

    public class GunConfigModel : AbstractModel, IGunConfigModel
    {
        private Dictionary<string, GunConfigItem> mItems = new Dictionary<string, GunConfigItem>()
        {
            { "手枪", new GunConfigItem("手枪", 7, 1, 1, 0.5f, false, 3, "默认强") },
            { "冲锋枪", new GunConfigItem("冲锋枪", 30, 1, 6, 0.34f, true, 3, "无") },
            { "步枪", new GunConfigItem("步枪", 50, 3, 3, 1f, true, 1, "有一定后坐力") },
            { "狙击枪", new GunConfigItem("狙击枪", 12, 6, 1, 1f, true, 5, "红外瞄准+后坐力大") },
            { "火箭筒", new GunConfigItem("火箭筒", 1, 5, 1, 1f, true, 4, "跟踪+爆炸") },
            { "霰弹枪", new GunConfigItem("霰弹枪", 1, 1, 1, 0.5f, true, 1, "一次发射 6 ~ 12 个子弹") },
        };

        protected override void OnInit()
        {
        }

        public GunConfigItem GetItemByName(string name)
        {
            return mItems[name];
        }
    }
}

其他好东西:

1.时间冷却系统

            timeSystem.AddDelayTask(0.33f, () =>
            {
                gunSystem.CurrentGun.GunState.Value = GunState.Idle;
            });
using System;
using System.Collections.Generic;
using FrameworkDesign;
using UnityEngine;

namespace ShootingEditor2D
{
    public interface ITimeSystem : ISystem
    {
        float CurrentSeconds { get; }

        void AddDelayTask(float seconds, Action onFinish);

    }

    public enum DelayTaskState
    {
        NotStart,
        Started,
        Finish
    }

    public class DelayTask
    {
        public float Seconds { get; set; }
        public Action OnFinish { get; set; }
        public float StartSeconds { get; set; }
        public float FinishSeconds { get; set; }
        public DelayTaskState State { get; set; }
    }
    
    public class TimeSystem : AbstractSystem,ITimeSystem
    {
        public class TimeSystemUpdateBehaviour : MonoBehaviour
        {
            public event Action OnUpdate;

            private void Update()
            {
                OnUpdate?.Invoke();
            }
        }
        
        protected override void OnInit()
        {
            var updateBehaviourGameObj = new GameObject(nameof(TimeSystemUpdateBehaviour));
            
            UnityEngine.Object.DontDestroyOnLoad(updateBehaviourGameObj);
            
            // 如果需要销毁,可以缓存为成员变量
            var updateBehaviour = updateBehaviourGameObj.AddComponent<TimeSystemUpdateBehaviour>();

            updateBehaviour.OnUpdate += OnUpdate;
        }

        public float CurrentSeconds { get;private set; } = 0.0f;

        private LinkedList<DelayTask> mDelayTasks = new LinkedList<DelayTask>();
        
        private void OnUpdate()
        {
            CurrentSeconds += Time.deltaTime;

            if (mDelayTasks.Count > 0)
            {
                var currentNode = mDelayTasks.First;
                
                while (currentNode != null)
                {
                    var delayTask = currentNode.Value;
                    var nextNode = currentNode.Next;

                    if (delayTask.State == DelayTaskState.NotStart)
                    {
                        delayTask.State = DelayTaskState.Started;

                        delayTask.StartSeconds = CurrentSeconds;
                        delayTask.FinishSeconds = CurrentSeconds + delayTask.Seconds;
                    
                    } else if (delayTask.State == DelayTaskState.Started)
                    {
                        if (CurrentSeconds > delayTask.FinishSeconds)
                        {
                            delayTask.State = DelayTaskState.Finish;
                            delayTask.OnFinish.Invoke();
                            delayTask.OnFinish = null;
                            mDelayTasks.Remove(currentNode); // 删除节点
                        }
                    }

                    currentNode = nextNode;
                }
            }
        }
        
        public void AddDelayTask(float seconds, Action onFinish)
        {
            var delayTask = new DelayTask()
            {
                Seconds = seconds,
                OnFinish = onFinish,
                State = DelayTaskState.NotStart,
            };

            mDelayTasks.AddLast(new LinkedListNode<DelayTask>(delayTask));
        }
    }
}

2、Query查询类

// 查询代码
var gunConfigModel = this.GetModel<IGunConfigModel>();
var gunConfigItem = gunConfigModel.GetItemByName(mGunSystem.CurrentGun.Name.Value);
mMaxBulletCount = gunConfigItem.BulletMaxCount;

上面的查询显得很臃肿

可以这样:

 mGunSystem = this.GetSystem<IGunSystem>();

// 查询代码
mMaxBulletCount = new MaxBulletCountQuery(mGunSystem.CurrentGun.Name.Value).Do(); // -+

做法就是:写一个查询类

using FrameworkDesign;

namespace ShootingEditor2D
{
    public class MaxBulletCountQuery : IBelongToArchitecture,ICanGetModel
    {
        private readonly string mGunName;

        public MaxBulletCountQuery(string gunName)
        {
            mGunName = gunName;
        }

        public int Do()
        {
            var gunConfigModel = this.GetModel<IGunConfigModel>();
            var gunConfigItem = gunConfigModel.GetItemByName(mGunName);
            return gunConfigItem.BulletMaxCount;
        }

        public IArchitecture GetArchitecture()
        {
            return ShootingEditor2D.Interface;
        }
    }
}

通过一些修改可以直接通过架构Arch来发送查询,做到这样(代码略)

mGunSystem = this.GetSystem<IGunSystem>();

// 查询代码
mMaxBulletCount = this.SendQuery(newMaxBulletCountQuery(mGunSystem.CurrentGun.Name.Value)); // -+

3.凉鞋的话

(GamePix 独立游戏学院 - 让独立游戏不再难做 - Powered By EduSoho)欢迎来这里购买决定版的QFRAMEWORK课程。

此文为 决定版群里的聊天记录。

说一下,第一季的整体流程。

课程的最开始,是没有用任何架构就做一个《点点点》这样的项目,做的方式就是用拖拽加一点代码的方式。

但是这种方式有一个问题,就是对象和对象之间的相互访问特别乱,没有规则,也没有限制,这样下去当项目有一定规模了就会变得非常乱。于是就引入了一个规则,就是只有自顶向下的时候可以直接访问对象或者调用对象的方法。然后自底向上的时候使用事件或者委托。而在讲这个规则之前还介绍了对象之间的三种交互方式:方法调用、委托、事件。

然后自底向上和自顶向再加上对象之间的三种交互方式这个构成了一个大的前提,后边的比如层级、业务模块等只要有高低之分的我们就都用这套规则去约束了。

再往下就介绍了一个简单的模块化,介绍了一个单例的模块化。我们在做设计的时候经常听到一个原则,就是高内聚松耦合,意高内聚意思是相同的代码放在一个地方去管理,这个是高内聚,低耦合就是对象之间的引用不要太多,最好就是单向引用或者没有引用,或者是有一定的规则去约束如何互相访问。

再往下就引入了一个概念,就是 Model,Model 是因为什么引入的呢?是因为就是有一些数据,它需要在多个地方去共享,比如角色的攻击力,需要在 UI 界面上显示,或者计算一个伤害的时候需要使用,总之需要在多个地方去使用它,而这种数据就是需要共享的数据,甚至需要把攻击力存储起来,而存储也是一种 共享方式,比如上次游戏关闭到了,现在打开游戏之后角色的攻击力不能变成初始攻击力了,所以数据的存储也是一种共享方式。而这些需要存储的数据,就需要放到 Model 里管理起来。而 Model 在决定版架构的引入就是因为有了需要共享的数据才引入的。

引入完 Model 之后就要考虑一个问题,就是其他的地方怎么跟这个 Model 进行交互,然后交互的部分,一般的方式就是用类似 MVC 的方式,然后其中 MVC 中的 Controller ,它所管理的逻辑分成了交互逻辑和表现逻辑。

大家都说 MVC 中的 Controller 代码容易臃肿起来,那么罪魁祸首就是交互逻辑,只要是有数据操作或者变更游戏数据状态的逻辑都是交互逻辑,而这部分逻辑是非常多的,要想解决 Controller 代码臃肿的问题,一般的共识就是引入命令模式,也就是 Command,让 Command 去分担 Controller 的交互逻辑,Controller 仅仅只是调用相应的 Command 即可。

好多的框架或者方案都是用 Command 去分担交互逻辑的,所以这里就不赘述笔者为啥用 Command 了。

引入了 Command 之后,Controller 就变成了比较薄的一层了,而 Command 并不适合负责所有的交互逻辑,比如有的交互逻辑最好还是统一放在一个对象里,比如分数统计、成就检测、任务检测等,如果分数统计这种逻辑分散在各种 Command 里,会非常不好管理,而分数统计实际上是一种规则类的逻辑,比如打一个敌人得多少分等等,最好是统一放在一个对象里管理,于是就引入了 System 层,System 层就是管理需要统一管理的交互逻辑而引入的,比如成就系统、任务系统等,这些系统一般会写很多死代码,那么这些死代码分散到各个 Command 里,想想都觉得恐怖,所以最好要弄脏就弄脏一个对象就是系统对象,这也是高内聚的一种体现。

到这里架构的一些核心概念就有了雏形了,像事件机制、BindableProperty 等都是一些通用的工具。

再接着架构雏形有了之后,就开始不断打磨这套架构的实现,这部分的内容就是一些 C# 的高级使用方法,用各种技巧达成设计目的,就不多说了。

总之架构中的每一个概念的引入都是为了解决特定的架构问题的,并不是为了做成架构而引入的,然后只要不断地去解决这些架构问题,就会慢慢迭代出来一个比较成型的框架/架构。

最后笔者简单再说一点,就是第一季的内容就是迭代这套架构,在迭代过程中不仅仅只有代码实现的部分,更重要的还是引入这些概念解决哪些问题,所以在学习课程的时候要重点放在概念解决哪些问题上,只要这块了解了,就会对各个概念的使用不会出现问题。

到了第二季 对一些架构本身的问题做了一些改进,比如 BindableProperty 去掉 IEquatable 接口,因为没必要,然后实现了完整的 CQRS 机制,也就是引入了 Query,有了 Query 之后实现充血模型不再是难事。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue 子页面通过暴露属性,实现主页面的某事件的触发

目录 1.前言2.代码2-1 子页面2-2 主页面 1.前言 需求&#xff1a;当我在子页面定义了一个定时器&#xff0c;点击获取验证码&#xff0c;计时器开始倒计时&#xff0c;在这个定时器没有走完&#xff0c;退出关闭子页面&#xff0c;再次进入子页面&#xff0c;定时器此时会被刷…

cpu 支持内存带宽与内存最大长度的关系《鸟哥的 Linux 私房菜》

鸟哥的 Linux 私房菜 -- 计算机概论 -- 計算机&#xff1a;辅助人脑的好工具 同理&#xff0c;64 位 cpu 一次接受内存传递的 64bit 数据&#xff0c;内存字节地址用 64 位记录&#xff0c;最多能记录2^64个字节2^64Bytes2^34GB17179869184GB2^24TB&#xff0c;理论上&#xff…

【遍历二叉树的非递归算法,二叉树的层次遍历】

文章目录 遍历二叉树的非递归算法二叉树的层次遍历 遍历二叉树的非递归算法 先序遍历序列建立二叉树的二叉链表 中序遍历非递归算法 二叉树中序遍历的非递归算法的关键&#xff1a;在中序遍历过某个结点的整个左子树后&#xff0c;如何找到该结点的根以及右子树。 基本思想&a…

基于SSM+Vue的随心淘网管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

在linux安装单机版hadoop-3.3.6

一、下载hadoop https://mirrors.tuna.tsinghua.edu.cn/apache/hadoop/core/hadoop-3.3.6/ 二、配置环境变量 1、配置java环境变量 2、配置hadoop环境变量 export HADOOP_HOME/usr/local/bigdata/hadoop-3.3.6 export HBASE_HOME/usr/local/bigdata/hbase-2.5.6 export JA…

Python爬虫入门教程之快速理解HTTP协议

文章目录 前言一、HTTP协议是什么&#xff1f;二、HTTP 请求三、请求行四、请求首部五、请求体六、HTTP 响应七、响应行八、响应首部九、响应体总结关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①…

高速信号PCB布局怎么布?(电子硬件)

对于高速信号&#xff0c;pcb的设计要求会更多&#xff0c;因为高速信号很容易收到其他外在因素的干扰&#xff0c;导致实际设计出来的东西和原本预期的效果相差很多。 所以在高速信号pcb设计中&#xff0c;需要提前考虑好整体的布局布线&#xff0c;良好的布局可以很好的决定布…

基于SSM的图书管理借阅系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

网际报文协议ICMP及ICMP重定向实例详解

目录 1、ICMP的概念 2、ICMP重定向 3、利用ICMP重定向进行攻击的原理 4、如何禁止ICMP重定向功能&#xff1f; 4.1、在Linux系统中禁用 4.2、在Windows系统中禁用 5、关于ICMP重定向的问题实例 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xf…

【教学类-40-04】A4骰子纸模制作4.0(4.5CM嵌套+记录表带符号)

作品展示 背景需求 骰子3.0&#xff08;7字形&#xff09;存在问题&#xff1a;6.5骰子体积大大&#xff0c;不适合幼儿操作&#xff08;和幼儿手掌一样大&#xff0c;制作耗时&#xff0c;甩动费力&#xff09; 1.0版本&#xff1a;边缘折线多&#xff0c;幼儿剪起来费力。 …

C语言每日一题(27)链表中倒数第k个结点

牛客网 链表中倒数第k个结点 题目描述 描述 输入一个链表&#xff0c;输出该链表中倒数第k个结点。 思路分析 这是一道经典的快慢指针题&#xff0c;fast和slow最开始都指向头结点&#xff0c;对于输入值k&#xff0c;先让快指针fast先走k步&#xff0c;之后再让两个指针一…

21 移动网络的前世今生

1、移动网络的发展历程 发展过程就是&#xff1a;2G,3G,4G,5G的过程&#xff0c;用2G看txt&#xff0c;用3G看jpg&#xff0c;用4G看avi。 2、2G网络 手机本来是用来打电话的&#xff0c;不是用来上网的&#xff0c;所以原来在2G时代&#xff0c;上网使用的不是IP网络&#…

火爆全网!用 Pyecharts 就能做出来“迁徙图“和“轮播图“

1.pyecharts知识点回顾 1&#xff09;知识回顾 前面我们已经讲述了&#xff0c;如何使用pyecharts进行图形的绘制&#xff0c;一共涉及到如下四步。我们今天就是按照下面这几步来进行迁徙图和轮播图的绘制。 ① 选择图表类型&#xff1b; ② 声明图形类并添加数据&#xff1…

0基础学习VR全景平台篇第119篇:利用蒙版航拍补天 - PS教程

上课&#xff01;全体起立~ 大家好&#xff0c;欢迎观看蛙色官方系列全景摄影课程&#xff01; 嗨&#xff0c;大家好。欢迎收看蛙色VR系列教程之PS利用蒙版航拍补天。 我们之前已经教过大家如何进行航拍调色&#xff0c;不知道大家学的怎么样呢&#xff1f; 会不会发现&…

如何处理【SVC】中的样本不均衡问题

样本不均衡是指在一组数据集中&#xff0c;标签的一类天生 占有很大的比例&#xff0c;但我们有着捕捉出某种特定的分类的需求的状况。比如&#xff0c;我们现在要对潜在犯罪者和普通人进行 分类&#xff0c;潜在犯罪者占总人口的比例是相当低的&#xff0c;也许只有2%左右&…

osgEarth之添加shp

目录 效果 代码 代码分析 加载模式 效果 代码 #include "stdafx.h" #include <osg/Notify> #include <osgGA/StateSetManipulator> #include <osgViewer/Viewer> #include <osgViewer/ViewerEventHandlers>#include <osgEarth/MapNo…

node插件MongoDB(三)—— 库mongoose 的使用和数据类型

前言 提示&#xff1a;使用mongoose 的前提是你安装了node和 MongoDB。 mongoose 官网文档&#xff1a;http://mongoosejs.net/docs/index.html 文章目录 前言一、安装二、基本使用1. 打开bin目录的mongod.exe文件2. 基本使用的代码&#xff08;连接mongodb 服务&#xff09;3.…

后入能先出,一文搞懂栈

目录 什么是栈数组实现链表实现栈能这么玩总结 什么是栈 栈在我们日常编码中遇到的非常多&#xff0c;很多人对栈的接触可能仅仅局限在 递归使用的栈 和 StackOverflowException&#xff0c;栈是一种后进先出的数据结构(可以想象生化金字塔的牢房和生化角斗场的狗洞)。 栈&…

SpringCache(Redis)

一、springcache是什么 springcache是spring的缓存框架&#xff0c;利用了AOP&#xff0c;实现了基于注解的缓存功能&#xff0c;并且进行了合理的抽象&#xff0c;业务代码不用关心底层是使用了什么缓存框架&#xff0c;只需要简单地加一个注解&#xff0c;就能实现缓存功能了…

基于SSM的劳务外包管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…