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

Unity使用新输入系统InputSystem制作飞机大战Demo(敌人生成管理器UI场景跳转)

请添加图片描述

@作者 : SYFStrive

@博客首页 : HomePage

📌:个人社区(欢迎大佬们加入) 👉:社区链接🔗

📌:觉得文章不错可以点点关注 👉:专栏连接🔗

💃:程序员每天坚持锻炼💪

请添加图片描述
在这里插入图片描述
相关专栏

👉 飞机大战专栏(🔥)

目录

  • 游戏单例脚本
    • 非持久化泛型单例
    • 持久化泛型单例
  • 游戏基类
    • 子弹基类实现子弹移动
    • 生命系统的基类
  • 对象池管理器
  • 实现敌人生成管理器
    • 敌人脚本
    • 效果
  • 实现波数UI
    • 效果
  • 音频管理器
    • 场景加载
    • 效果
  • 最后

游戏单例脚本

单例模式是1种设计模式:👉(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

单例使用说明:“单例模式是指在内存中只会创建一次对象的设计模式,并且确保一个类只有实例,而且会自行实例化,并向整个系统提供这个实例。

非持久化泛型单例

using UnityEngine;

//摘要:Base class for everything attached to GameObjects.
//Component中文说明:所有能挂载到游戏对象上的类型基类
public class Singleton<T> : MonoBehaviour where T :Component
{
    public static T Instance { get; private set; }

    protected virtual void Awake()
    {
        Instance = this as T;
    }
}

持久化泛型单例

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

public class PersistentSingleton<T> : MonoBehaviour where T : Component
{
  public static T Instance;

  protected virtual void Awake()
  {
    if(Instance == null)
      Instance = this as T;
    else
      Destroy(this.gameObject);
    DontDestroyOnLoad(this);
  }
}

游戏基类

子弹基类实现子弹移动

实现:子弹生成是就开始移动

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

public class Projectile : MonoBehaviour
{
    //子弹的移动速度
    [SerializeField] float moveSpeed;
    //子弹的移动方向
    [SerializeField] protected Vector3 moveDirection;
    //子弹移动的Obj
    protected GameObject targer;
    
    protected virtual void OnEnable()
    {
        StartCoroutine(ProjectileMoveIE());
    }

    IEnumerator ProjectileMoveIE()
    {
        while (true)
        {
            //子弹移动
            transform.position += moveSpeed * moveDirection * Time.deltaTime;
            yield return null;
        }
    }
}

生命系统的基类

实现:储存人物的血量参数(继承这个脚本的简直爽歪歪)……

代码如 👇

using System;
using System.Collections;
using System.Collections.Generic;
using System.Security.Principal;
using UnityEngine;

public class Characters : MonoBehaviour
{
    [Header("---Header---")]
    //最大生命值
    [SerializeField] protected float maxHp;

    //当前生命值
    protected float currentHp;

    //死亡时生成特效
    [SerializeField] GameObject dieSpecialEffects;

    protected virtual void OnEnable()
    {
        currentHp = maxHp;
    }

    /// <summary>
    /// 玩家受伤
    /// </summary>
    /// <param name="injuredValue">伤害值</param>
    protected virtual void Injured(float injuredValue)
    {
        currentHp -= injuredValue;

        if (currentHp <= 0)
            Die();
    }

    /// <summary>
    /// 玩家死亡
    /// </summary>
    public void Die()
    {
        //血量归0
        currentHp=0;    

        //调用对象池
        PoolManager.Release(dieSpecialEffects,transform.position);
		
		隐藏该对象
        this.gameObject.SetActive(false);
    }

    /// <summary>
    /// 恢复生命值
    /// </summary>
    protected virtual void RecoverHP(float value)
    {
        currentHp = Mathf.Clamp(currentHp + value, 0, maxHp);
    }


    /// <summary>
    /// 自动恢复生命值携程
    /// </summary>
    /// <param name="waitForSeconds">恢复的间隔</param>
    /// <param name="value">恢复值</param>
    /// <returns></returns>
    protected virtual IEnumerator SelfRecoverHpIE(WaitForSeconds waitForSeconds,float value)
    {
        while (currentHp < maxHp)
        {
            yield return waitForSeconds;

            RecoverHP(currentHp * value);
        }
    }

    /// <summary>
    /// 持续受伤
    /// </summary>
    /// <param name="waitForSeconds">受伤的间隔</param>
    /// <param name="value">受伤值</param>
    /// <returns></returns>
    protected virtual IEnumerator SelfInjuredIE(WaitForSeconds waitForSeconds, float value)
    {
        while (currentHp >= 0f)
        {
            yield return waitForSeconds;

            Die(currentHp * value);
        }
    }
}

对象池管理器

说明:这里已经添加了这个项目所有的对象池容器

using System.Collections.Generic;
using UnityEngine;

public class PoolManager : MonoBehaviour
{
  //储存不同类准备的对象池
  [SerializeField] Pool[] playerPoolProjectile; //玩家子弹
  [SerializeField] Pool[] enemyPoolProjectile; //敌人子弹
  [SerializeField] Pool[] poolVFX; //特效
  [SerializeField] Pool[] randomCreateEnemy; //随机敌人
  [SerializeField] Pool[] createProp; 敌人掉落的道具

  //使用字典来存储不同的装备
  public static Dictionary<GameObject, Pool> dictionary;

  private void Awake()
  {
    //实例化字典
    dictionary = new Dictionary<GameObject, Pool>();

    //初始化对象池
    InitializeObj(playerPoolProjectile);
    InitializeObj(enemyPoolProjectile);
    InitializeObj(poolVFX);
    InitializeObj(randomCreateEnemy);
    InitializeObj(createProp);
  }


  #region 测试函数
	#if UNITY_EDITOR
	  //停止游戏时执行
	  private void OnDestroy()
	  {
	    CheckPoolSize(playerPoolProjectile);
	    CheckPoolSize(enemyPoolProjectile);
	    CheckPoolSize(poolVFX);
	    CheckPoolSize(randomCreateEnemy);
	    CheckPoolSize(createProp);
	  }
	#endif
  #endregion

  #region 测试需要对象池的容量
	  private void CheckPoolSize(Pool[] pools)
	  {
	    foreach (Pool pool in pools)
	    {
	      if (pool.sumSize > pool.initializeSize)
	      {
	        Debug.LogWarning(string.Format("Pool:{0}初始大小为{1},需要的大小为{2}",
	            pool.prefabeObjProperty.name,
	            pool.initializeSize,
	            pool.sumSize));
	      }
	    }
	  }
  #endregion

  /// <summary>
  /// 初始化子弹
  /// </summary>
  private void InitializeObj(Pool[] pools)
  {
    foreach (var pool in pools)
    {
      #region //条件编译操作 只有在Unity引起运行
		#if UNITY_EDITOR
		      if (dictionary.ContainsKey(pool.prefabeObjProperty))
		      {
		        Debug.Log("字典有相同的名字!"+pool.prefabeObjProperty.name);
		        continue;
		      }
		#endif
      #endregion

      //添加到字典
      dictionary.Add(pool.prefabeObjProperty, pool);
      //给创建的Obj命名
      Transform poolPatent = new GameObject("对象池Poll" + pool.prefabeObjProperty.name).transform;
      //设置父位置
      poolPatent.parent = transform;
      //初始化对象池
      pool.Initialize(poolPatent);
    }
  }

  #region  释放子弹&&重载
  /// <summary>
  /// 释放子弹
  /// </summary>
  /// <param name="prefabe">指定游戏的预制体</param>
  /// <returns></returns>
  public static GameObject Release(GameObject prefabe)
  {
    #region 条件编译操作 只有在Unity引起运行
		#if UNITY_EDITOR
		    if (!dictionary.ContainsKey(prefabe))
		    {
		      Debug.Log("找不到对应的Key");
		      return null;
		    }
		#endif
    #endregion

    return dictionary[prefabe].PrepareQuene();
  }

  /// <summary>
  /// 释放子弹
  /// </summary>
  /// <param name="prefabe">指定游戏的预制体</param>
  /// <param name="position">指定游戏的位置</param>
  /// <returns></returns>
  public static GameObject Release(GameObject prefabe, Vector3 position)
  {
    #region 条件编译操作 只有在Unity引起运行
		#if UNITY_EDITOR
		    if (!dictionary.ContainsKey(prefabe))
		    {
		      Debug.Log("找不到对应的Key");
		      return null;
		    }
		#endif
    #endregion
    return dictionary[prefabe].PrepareQuene(position);
  }


  /// <summary>
  /// 释放子弹
  /// </summary>
  /// <param name="prefabe">指定游戏的预制体</param>
  /// <param name="position">指定游戏的位置</param>
  /// <param name="quaternion">指定游戏的旋转位置</param>
  /// <returns></returns>
  public static GameObject Release(GameObject prefabe, Vector3 position, Quaternion quaternion)
  {
    #region 条件编译操作 只有在Unity引起运行
		#if UNITY_EDITOR
		
		    if (!dictionary.ContainsKey(prefabe))
		    {
		      Debug.Log("找不到对应的Key");
		      return null;
		    }
		#endif
    #endregion

    return dictionary[prefabe].PrepareQuene(position, quaternion);
  }

  /// <summary>
  /// 释放子弹
  /// </summary>
  /// <param name="prefabe">指定游戏的预制体</param>
  /// <param name="position">指定游戏的位置</param>
  /// <param name="quaternion">指定游戏的旋转位置</param>
  /// <param name="localscale">指定游戏的旋转缩放</param>
  /// <returns></returns>
  public static GameObject Release(GameObject prefabe, Vector3 position, Quaternion quaternion, Vector3 localscale)
  {
    #region 条件编译操作 只有在Unity引起运行
		#if UNITY_EDITOR
		
		    if (!dictionary.ContainsKey(prefabe))
		    {
		      Debug.Log("找不到对应的Key");
		      return null;
		    }
		#endif
    #endregion

    return dictionary[prefabe].PrepareQuene(position, quaternion, localscale);
  }
  #endregion
}

实现敌人生成管理器

实现共能:实现动态生成敌人、关卡递增……

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

public class EnemyManager : Singleton<EnemyManager>
{
  //关卡
  public int WaveNumber => waveNumber;

  //敌人生成的间隔
  public float AgainCreateEnemyTimeIE => againCreateEnemyTimeIE;

  //返回导航的随机敌人
  public GameObject RandomEnemy => enemyList.Count == 0 ? null : enemyList[Random.Range(0, enemyList.Count)];

  //关卡UI显示关卡
  [SerializeField] WaveManager waveManager;

  [Header("---敌人生成相关---")]
  //随机生成不同敌人类型Arr
  [SerializeField] GameObject[] enemyArr;
  //利用列表管理敌人是否全部死亡
  [SerializeField] List<GameObject> enemyList;

  //协程的间隔 与 波数生成敌人的间隔
  WaitForSeconds randomCreateEnemyWaitForSeconds;
  [SerializeField] float randomCreateEnemyTimeIE; 
  WaitForSeconds againCreateEnemyWaitForSeconds;
  [SerializeField] float againCreateEnemyTimeIE;

  //最小敌人数量 与 最大敌人数量
  [SerializeField] int minEnemyAmout = 1;
  [SerializeField] int maxEnemyAmout = 5;

  //是否要生成敌人
  [SerializeField] bool isCreateEnemy;
  //使用WaitUntil事件来判断敌人的死亡与再次生成敌人
  private WaitUntil waitUntil;

  //游戏的波束默认第一波
  private int waveNumber = 1;
  //敌人的数量
  private int enemyAmout;


  protected override void Awake()
  {
    base.Awake();
    //初始化协程时间间隔
    randomCreateEnemyWaitForSeconds = new WaitForSeconds(randomCreateEnemyTimeIE);
    againCreateEnemyWaitForSeconds = new WaitForSeconds(againCreateEnemyTimeIE);
    //利用委托挂起再次生成敌人
    waitUntil = new WaitUntil(() => enemyList.Count == 0);
  }

  IEnumerator Start()
  {
    while (isCreateEnemy && GameManager.GameState!=GameState.GameOver)
    {
      委托判断敌人是否全部死亡
      //yield return waitUntil;

      waveManager.gameObject.SetActive(true);

      //生成的间隔时间
      yield return againCreateEnemyWaitForSeconds;

      waveManager.gameObject.SetActive(false);

      //开始创建敌人
      yield return StartCoroutine(nameof(RandomCreateEnemyIE));
    }
  }

  IEnumerator RandomCreateEnemyIE()
  {
      enemyAmout = Mathf.Clamp(enemyAmout, minEnemyAmout + (int)Mathf.Ceil(waveNumber / 3), maxEnemyAmout);
      for (int i = 0; i <= enemyAmout; i++) 
      {
        //把生成的敌人添加进列表
        enemyList.Add(PoolManager.Release(enemyArr[Random.Range(0, enemyArr.Length)]));

        yield return randomCreateEnemyWaitForSeconds;
      }

    //委托判断敌人是否全部死亡
    yield return waitUntil;
    //全部敌人死亡时关卡值++
    waveNumber++;
  }

  /// <summary>
  /// 把敌人从列表中移除
  /// </summary>
  /// <param name="game">死亡的敌人需要移除</param>
  public void RemoveListEnemy(GameObject game)
  {
    enemyList.Remove(game);
  }
}

敌人脚本

using UnityEngine;

public class Enemy : Characters
{
  [Header("---死亡获得的能量值---")]
  [SerializeField] int destroyEnergyValue=3;
  
  protected override void Die()
  {
    //敌人死亡时执行能量更新
    PlayerEnergy.Instance.OptainEnergy(destroyEnergyValue);
    //敌人死亡从列表中移除
    EnemyManager.Instance.RemoveListEnemy(gameObject);
    
    base.Die();
  }
}

效果

在这里插入图片描述

实现波数UI

时间UI从两边出现 与 显示当前的关卡数

using System.Collections;
using UnityEngine;

public class DynamicWaveUI : MonoBehaviour
{
  #region EnterTheField
  [SerializeField] float animationTime = 1f;

  [Header("---- LINE MOVE ----")]
  [SerializeField] Vector2 lineTopStartPosition = new Vector2(-1250f, 140f);
  [SerializeField] Vector2 lineTopTargetPosition = new Vector2(0f, 140f);
  [SerializeField] Vector2 lineDownStartPosition = new Vector2(1250f, 0f);
  [SerializeField] Vector2 lineDownTargetPosition = Vector2.zero;

  [Header("---- TEXT SCALE ----")]
  [SerializeField] Vector2 waveTextStartScale = new Vector2(1f, 0f);
  [SerializeField] Vector2 waveTextTargetScale = Vector2.one;

  //上划线
  RectTransform lineTop;
  //下划线
  RectTransform lineDown;
  //关卡Text
  RectTransform waveText;
  //携程的等待时间
  WaitForSeconds waitStayTime;
  #endregion

  #region UNITY INITIALIZE FUN
  void Awake()
  {
    //如果使用了动画Animtor那么就移除脚本
    if (TryGetComponent<Animator>(out Animator animator))
    {
      if (animator.isActiveAndEnabled)
      {
        Destroy(this);
      }
    }

    waitStayTime = new WaitForSeconds(EnemyManager.Instance.AgainCreateEnemyTimeIE - animationTime * 2f);

    //获取对应的组件
    lineTop = transform.Find("WAVE TOP").GetComponent<RectTransform>();
    lineDown = transform.Find("WAVE DOWN").GetComponent<RectTransform>();
    waveText = transform.Find("WAVE Text").GetComponent<RectTransform>();

    //初始化UI的位置
    lineTop.localPosition = lineTopStartPosition;
    lineDown.localPosition = lineDownStartPosition;
    waveText.localScale = waveTextStartScale;
  }

  void OnEnable()
  {
    StartCoroutine(LineMoveIE(lineTop, lineTopStartPosition, lineTopTargetPosition));
    StartCoroutine(LineMoveIE(lineDown,lineDownStartPosition, lineDownTargetPosition));
    StartCoroutine(TextScaleCoroutine(waveText, waveTextStartScale, waveTextTargetScale));
  }
  #endregion

  #region LINE MOVE
  IEnumerator LineMoveIE(RectTransform rect, Vector2 startPosition, Vector2 targetPosition)
  {
    //进场
    yield return StartCoroutine(UIMoveIE(rect, targetPosition));

    //等待事件
    yield return waitStayTime;

    //出场
    yield return StartCoroutine(UIMoveIE(rect, startPosition));
  }

  IEnumerator UIMoveIE(RectTransform rect, Vector2 position)
  {
    float t = 0f;

    Vector2 localScale = rect.localScale;

    while (t < 1f)
    {
      t += Time.deltaTime / animationTime;
      
      rect.localPosition = Vector2.Lerp(localScale, position, t);

      yield return null;
    }
  }
  #endregion

  #region TEXT SCALE
  IEnumerator TextScaleCoroutine(RectTransform rect, Vector2 startScale, Vector2 targetScale)
  {
    yield return StartCoroutine(UIScaleCoroutine(rect, targetScale));
    yield return waitStayTime;
    yield return StartCoroutine(UIScaleCoroutine(rect, startScale));
  }

  IEnumerator UIScaleCoroutine(RectTransform rect, Vector2 scale)
  {
    float t = 0f;

    Vector2 localScale = rect.localScale;

    while (t < 1f)
    {
      t += Time.deltaTime / animationTime;
      rect.localScale = Vector2.Lerp(localScale, scale, t);
      yield return null;
    }
  }
  #endregion
}

效果

在这里插入图片描述

音频管理器

实现:继成持久单例完成声音播放、音效播放、音频的播放……

using UnityEngine;

public class AudioManager : PersistentSingleton<AudioManager>
{
  //特效播放器sFXPlayer
  [SerializeField] AudioSource sFXPlayer;

  //pitch范围 最大范围及最小范围
  const float MIN_PITCH = 0.9f;
  const float MAX_PITCH = 1.1f;

  /// <summary>
  /// 播放声音
  /// </summary>
  /// <param name="audioData">播放的音频及音量</param>
  public void PlaySFX(AudioData audioData)
  {
    sFXPlayer.PlayOneShot(audioData.clip, audioData.volume);
  }

  /// <summary>
  /// 播放声音
  /// </summary>
  /// <param name="audioData">播放的音频及音量</param>
  public void RandomPitchPlaySFX(AudioData audioData)
  {
    sFXPlayer.pitch = Random.Range(MIN_PITCH, MAX_PITCH) ;
    PlaySFX(audioData);  
  }


  /// <summary>
  /// 播放声音
  /// </summary>
  /// <param name="audioData">播放音频的数字</param>
  public void RandomPitchPlaySFX(AudioData[] audioData)
  {
    sFXPlayer.pitch = Random.Range(MIN_PITCH, MAX_PITCH);
    PlaySFX(audioData[Random.Range(0, audioData.Length)]);
  }
}

[System.Serializable] public class AudioData{

  public AudioClip clip;

  public float volume;  
}

然后设置发射子弹、发射导弹、受伤等等设置音效就行了

场景加载

实现场景加载:异步加载与简单加载

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class SceneLoadManager : PersistentSingleton<SceneLoadManager>
{
  //游戏场景名 使用常量声明
  const string GAME_SCENE_NAME = "GamePage";
  const string MAIN_NAME = "MainMenu";
  const string SCORING_NAME = "Scoring";

  //渐变的图片 与 颜色
  private Image GradualImage;
  private Color color;

  //加载时间
  float fadeTime = 3.5f;

  private void Start()
  {
  	//获取该画布
    GradualImage = transform.Find("GradualChangeCanvas/GradualLmage").GetComponent<Image>();
  }


  #region 普通加载场景
  public void LoadScoringScene()
  {
    SceneManager.LoadScene(SCORING_NAME);
  }

  #endregion

  #region 异步加载场景
  public void AsynLoadGamePageScene()
  {
    StopAllCoroutines();
    StartCoroutine(LoadSceneGradientEffect(GAME_SCENE_NAME));
  }
  public void AsynLoadMainMenuScene()
  {
    StopAllCoroutines();
    StartCoroutine(LoadSceneGradientEffect(MAIN_NAME));
  }
  public void AsynLoadScoringScene()
  {
    StopAllCoroutines();
    StartCoroutine(LoadSceneGradientEffect(SCORING_NAME));
  }
  #endregion

  #region 携程加载场景
  //携程加载场景
  IEnumerator LoadSceneGradientEffect(string sceneNmae)
  {
  	//异步加载
    var asynchronization = SceneManager.LoadSceneAsync(sceneNmae);
	//设置不能跳转
    asynchronization.allowSceneActivation = false;
	//显示渐变画布
    GradualImage.gameObject.SetActive(true);
	
    while (color.a < 1f)
    {
      color.a = Mathf.Clamp01(color.a + Time.unscaledDeltaTime / fadeTime);
      GradualImage.color = color;
      yield return null;
    }

    yield return new WaitUntil(() => asynchronization.progress >= 0.9f);

    asynchronization.allowSceneActivation = true;

    while (color.a > 0f)
    {
      color.a = Mathf.Clamp01(color.a - Time.unscaledDeltaTime / fadeTime);
      GradualImage.color = color;
      yield return null;
    }
    //隐藏渐变画布
    GradualImage.gameObject.SetActive(false);
  }
  #endregion

}

效果

在这里插入图片描述

最后

在这里插入图片描述
本文到这里就结束了,大佬们的支持是我持续更新的最大动力,希望这篇文章能帮到大家💪

 

                 相关专栏连接🔗
在这里插入图片描述

下篇文章再见ヾ( ̄▽ ̄)ByeBye

在这里插入图片描述

相关文章:

  • Python 文件存储:pickle 和 json 库的使用
  • 【推荐收藏】matplotlib 制作的动态条形图其实很好看
  • 计算机组成原理 ------ 存储系统(1)
  • Open3D (C++) 基于投影点密度的建筑物立面提取
  • SpringCloud Alibaba系列 Nacos(一)
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • JS的精髓,事件详解
  • 高等数学(第七版)同济大学 习题8-6 个人解答
  • 【Linux】进程地址空间
  • 【计算机组成原理】输入/输出系统(四)—— I/O方式
  • 让GPU跑的更快
  • 给课题组师弟师妹们的开荒手册
  • Java操作Excel - Easy Excel
  • 交通状态预测 | Python实现基于LSTM的客流量预测方法
  • 一条sql语句在MySQL的执行流程
  • 【Linux系统编程】快速查找errno错误码信息
  • 10个确保微服务与容器安全的最佳实践
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • Create React App 使用
  • express + mock 让前后台并行开发
  • Fastjson的基本使用方法大全
  • idea + plantuml 画流程图
  • JAVA多线程机制解析-volatilesynchronized
  • JSONP原理
  • orm2 中文文档 3.1 模型属性
  • OSS Web直传 (文件图片)
  • React中的“虫洞”——Context
  • Spring思维导图,让Spring不再难懂(mvc篇)
  • Terraform入门 - 3. 变更基础设施
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • Webpack 4 学习01(基础配置)
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 闭包--闭包之tab栏切换(四)
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 对话:中国为什么有前途/ 写给中国的经济学
  • 看域名解析域名安全对SEO的影响
  • 坑!为什么View.startAnimation不起作用?
  • 盘点那些不知名却常用的 Git 操作
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 微信小程序设置上一页数据
  • 一文看透浏览器架构
  • Mac 上flink的安装与启动
  • 国内开源镜像站点
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (C语言)编写程序将一个4×4的数组进行顺时针旋转90度后输出。
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (二)Eureka服务搭建,服务注册,服务发现
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (转)甲方乙方——赵民谈找工作
  • (转载)Linux 多线程条件变量同步
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .NET MVC之AOP
  • @JsonFormat与@DateTimeFormat注解的使用