六大设计原则
单一职责原则 (SRP)
只做一件事,不要所有事都掺乎到一起
一个模块只做一件事 按功能分
一个类只做一件事 一个脚本就放一个类 一个类制作一个功能 也看代码体量
一个方法只做一件事
多态是解决分支的好办法
using UnityEngine;
/*
有两种攻击方式 一种近战 一种远程
*/
#region 没有遵循单一职责的
public enum HeroType {
CloseCombat,//近战
LongDistance//远程
}
public class Hero {
private HeroType heroType;
public void Attack(){
if(heroType == HeroType.CloseCombat){
Debug.Log("近战攻击");
}
else if(heroType == HeroType.LongDistance){
Debug.Log("远程攻击");
}
}
}
#endregion
#region 遵循单一职责的
public class HeroBase {
//不同英雄攻击方式是不一样的
public virtual void Attack(){
Debug.Log("攻击");
}
}
//创建子类继承HeroBase
public class CloseCombatHero : HeroBase {
//近战英雄重写继承过来的Attack
public override void Attack(){
//要不要父类里的内容 看情况
//base.Attack(); 执行父类的
Debug.Log("近战攻击");
}
}
public class LongDistanceHero : HeroBase {
public override void Attack(){
Debug.Log("远程攻击");
}
}
#endregion
里氏替换原则(LSP)
使用抽象和多态
父类有的,只要是public 子类都能访问得到 类的强继承性
里氏替换是将 不合理的继承关系就要断掉
多态里不要用关键词new 覆盖 要重写的话 父类用abstruct virtual关键词
using UnityEngine;
/*
开启瞄准镜的功能 手枪没有开镜功能
*/
#regine 没有遵循里氏替换的
public class Gun {
//开启瞄准镜
public virtual void OpenSight(){
}
}
/*
这里不合理 解决办法是再创建一个类 把开镜的功能放入 这样就继承不到了
*/
public class Pistol : Gun {
//手枪没有开镜功能 这样就不合理
public override void OpenSight(){
base.OpenSight();
}
}
#endregine
迪米特法则(LKP)
越少知道越好 只和最密切的联系 迪米特法则让我们遵循的是高内聚 低耦合
比如: A-----B-----C-----D
A只和B联系 B只和C联系 C只和D联系
UML类图 可以显示出来 具体的关系
using UnityEngine;
#regine 没有遵循迪米特法则的
public class Student {
public string name;
public int age;
//这样写 如果这时候我需要变更 Student里的内容 ShowMe()方法都需要动 这是跨越性的改动
}
//年级
public class Grade {
public Student[] students;
}
public class School {
public Grade[] grades;
public void ShowMe(){
for(int i = 0; i < grades.Length; i++){
for(int j = 0; j < grades[i].students.Length; j++){
Debug.Log("name: " + grades[i].students[j].name);
Debug.Log("age: " + grades[i].students[j].age);
}
}
}
}
#endregine
//修改
#regine 遵循迪米特法则的
public class Student {
private string name;
private int age;
private string hobby;
public void ShowMe(){
Debug.Log("name: " + grades[i].students[j].name);
Debug.Log("age: " + grades[i].students[j].age);
Debug.Log("hobby: " + grades[i].students[j].hobby);
}
}
//年级
public class Grade {
private string gradeNumber;
private Student[] students;
public void ShowMe(){
Debug.Log("轮到" + gradeNumber + "介绍");
for(int i = 0; i < students.Length; i++){
students[i].ShowMe();
}
}
}
public class School {
private string schoolName;
private Grade[] grades;
public void ShowMe(){
Debug.Log("该" + schoolName + "介绍");
for(int i = 0; i < grades.Length; i++){
grades[i].ShowMe();
}
}
}
#endregine
依赖倒置原则(DIP)
面向抽象编程
using UnityEngine;
using System.Collections.Generic;
public abstract class ABGun {
public abstract void Fire();
}
public class AK47 : ABGun {
public override void Fire(){
Debug.Log("哒哒哒");
}
}
public class MP5 : ABGun {
public override void Fire(){
Debug.Log("突突突");
}
}
接口隔离原则(ISP)
using UnityEngine;
interface IBaseOperation{
void Move();
void Jump();
}
interface IFire{
void Fire();
}
interface IUseSkill{
void UseSkill();
}
interface IBaseRole : IUseSkill, IFire, IBaseOperation {
}
class Role : IUseSkill, IFire, IBaseOperation {
public void UseSkill(){
}
public void Fire(){
}
public void Move(){
}
public void Jump(){
}
}
//这时候我假如只用的到两个 我也不能删 只能空着
class Player : IBaseRole {
public void UseSkill(){
}
public void Fire(){
}
public void Move(){
}
public void Jump(){
}
}
#region 使用接口隔离原则
class Player : IBaseOperation {
public void Move(){
}
public void Jump(){
}
}
#endregion
开闭原则(OCP)【总则】
对拓展是开放的,对修改是关闭的
当框架写完之后 如果发生需求的变更 就需要拓展 不要修改(增加需求时候)
在原代码的基础上 不动原来的代码,新增加一个类,来写新的功能
设计模式
GoF 《设计模式》
简单工厂模式
经常调用的
using UnityEngine;
#regine 没有遵守单一原则的
//这样写没有遵守单一原则 如果要添加乘法除法,那就要在原来的代码上去添加新的代码
public class CountFactory {
public void GetResult(int num01,int num02,string sign){
switch(sign){
case "+":
return num01 + num02;
case "-":
return num01 - num02;
default:
return 0;
}
}
}
#endregine
/*
把加减乘除分别做成类,特点是无论加减乘除都需要结果 抽象
这里改下需求比如需要QWER
*/
public class MyHero {
public float magic;
public float[] cds;
}
public abstract class Skill {
//技能编号
private string sign = "Q";
public int skillIndex = 0;
//技能cd
private float cd;
//耗蓝
protected float magicNeed;
public abstract void SkillRelease();
}
public class WWW : Skill {
//释放的坐标
public Vector3 pos;
public override void SkillRelease(){
skillIndex = 1;
//释放就不判断范围了
if (pos != null){
Debug.Log("使用技能");
}
}
}
public class QQQ : Skill {
public MyHero targetHero;
public override void SkillRelease(){
skillIndex = 0;
//蓝够 cd转完了 目标不是空
if (targetHero.magic >= this.magicNeed &&
targetHero.cds[skillIndex] == 0 && targetHero != null){
Debug.Log("使用技能");
}
}
}
//技能都写好了 写工厂
public class SkillOperationFactory {
public static Skill GetSkillOperation(string sign){
if (sign == "Q"){
return new QQQ();
}
else{
return new WWW();
}
}
}
public class SimplerFactoryDemo : MonoBehaviour {
private void Start(){
Skill skill = SkillOperationFactory.GetSkillOperation("Q");
skill.SkillRelease();
}
}
关于Resources的缺点
Unity里 Resources.Load<> 里有一个缺点 文件是存在硬盘里的 但是每次加载到内存都会开辟一块新的空间,造成浪费,而以前加载的还在
解决办法 缓存机制
整理工具库
using UnityEngine;
using System.Collections.Generic;//引入命名空间
//通用框架
namespace Utility {
//通用工厂 获取各种资源
//资源管理器
public class AssetsManager : Singleton<AssetsManager> {
//非第一次的加载要从缓存里加载
//构造 里实例化
protected AssetsManager(){
//key 路径 value 资源
assetsCache = new Dictionary<string,Object>();
}
//声明一个缓存池 缓存字典
private Dictionary<string,Object> assetsCache;
//只有第一次的会加载,后面的都直接从缓存池里加载
public virtual T GetAssets<T>(string path) where T : Object{
//先查看缓存池有没有这个资源
if(assetsCache.ContainsKey(path)){
//如果有直接返回 将缓存池里的资源返回
return assetsCache[path] as T;
}
else{
//通过Resources.load加载资源
T assets = Resources.Load<T>(path);
//将新资源放到缓存池里
// key value
assetsCache.Add(path,assets);
//返回资源
return assets;
}
}
//卸载未使用的资源
public void UnloadUnuseAssets(){
Resources.UnloadUnusedAssets();
}
}
//通过预设体获取
public class PrefabManager : Singleton<PrefabManager> {
private PrefabManager(){
}
//生成 获取预设体生成资源
public GameObject CreateGameObjectByPrefab(string path){
//获取预设体
GameObject prefab = AssetsManager.GetInstance().GetAssets<GameObject>(path);
//生成
GameObject obj = Object.Instantiate(prefab);
//返回
return obj;
}
//重载 增加坐标旋转
public GameObject CreateGameObjectByPrefab(string path,Vector3 pos,Quaternion qua){
GameObject obj = CreateGameObjectByPrefab(path);
/*
//获取预设体
GameObject prefab = base.GetAssets<GameObject>(path);
//生成
GameObject obj = Instantiate(prefab);
*/
//设置坐标和旋转
obj.transform.position = pos;
obj.transform.rotation = qua;
//返回
return obj;
}
//重载 增加父对象 增加缩放
public GameObject CreateGameObjectByPrefab(string path,Transform parent,Vector3 localPos,Quaternion localQua){
GameObject obj = CreateGameObjectByPrefab(path);
/*
//获取预设体
GameObject prefab = base.GetAssets<GameObject>(path);
//生成
GameObject obj = Instantiate(prefab);
*/
//设置父物体
obj.transform.SetParent(parent);
//设置坐标和旋转
obj.transform.localPosition = localPos;
obj.transform.localRotation = localQua;
//返回
return obj;
}
public GameObject CreateGameObjectByPrefab(string path,Transform parent,Vector2 anchoredPosition){
//生成
GameObject obj = CreateGameObjectByPrefab(path);
//设置父物体
obj.transform.SetParent(parent);
obj.transform.localScale = Vector3.one;
obj.GetComponent<RectTransform>().anchoredPosition = anchoredPosition;
//返回
return obj;
}
//这个重载用于UI显示的
public GameObject CreateGameObjectByPrefab(string path,Transform parent,Vector3 localPos,Vector3 localScale){
//生成
GameObject obj = CreateGameObjectByPrefab(path);
//设置父物体
obj.transform.SetParent(parent);
//调整UI显示位置 缩放
obj.transform.localPosition = localPos;
obj.transform.localScale = localScale;
//返回
return obj;
}
}
}
策略模式
解决一件事的多种策略
英雄攻击的三种策略
圆形攻击 矩形攻击 扇形攻击
using UnityEngine;
namespace StrategyMode {
public class Hero {
private string name;
public Transform heroTra;
public float hp = 100;
public Hero(string name,Transform heroTra){
this.name = name;
this.heroTra = heroTra;
}
public void Attack(AttackStrategy attackStrategy,Hero targetHero){
//被攻击的英雄
float damage = attackStrategy.TakeDamage(targetHero);
//受到伤害
targetHero.TakeDamage(damage);
}
public void TakeDamage(float damage){
hp -= damage;
//DOTO:死亡啥啥的
}
}
//攻击策略 (基类)
public abstract class AttackStrategy {
/*
矩形:中心点,长度,宽度,释放的位置 坐标
圆形:中心,半径
扇形:方向,角度
*/
public abstract float TakeDamage(Hero hero);
}
****************************************************************************************
#regine 圆形
//圆形
public class CircleAttack : AttackStrategy {
//需要个点
//中心点
private Vector3 skillCenter;
//半径
private float skillRadius;
//这里就看游戏怎么定的了 这里是递减
//中心伤害
private float centerDamage;
//边缘伤害
private float edgeDamage;
public CircleAttack(Vector3 skillCenter,float skillRadius,
float centerDamage,float edgeDamage){
this.skillCenter = skillCenter;
this.skillRadius = skillRadius;
this.centerDamage = centerDamage;
this.edgeDamage = edgeDamage;
}
public override float TakeDamage(Hero hero){
//判断英雄位置在没在圈里
//判断圆心到英雄的距离 是否大于半径
//计算玩家(英雄)与技能中心点之间的距离
float dis = Vector3.Distance(hero.heroTra.position,skillCenter);
//如果这个距离 小于半径(在圈里)那就有伤害 大于半径(圈外边)那就没伤害
if(dis <= skillRadius){
/*
比如中心点伤害100 最边上伤害10 半径长度10 当前英雄所在位置8
(10-8)/10*(100-10)=18 18+10(这个10是最边上的伤害)=28
*/
return edgeDamage + (skillRadius - dis) / skillRadius * (centerDamage - edgeDamage);
}
else{
return 0;
}
}
}
#endregine
****************************************************************************************
#regine 矩形
public class RectangleAttack : AttackStrategy {
//中心点
private Vector3 skillCenter;
/*
矩形 在长方形范围里 就有伤害 不在没伤害
可以画个图 中心点到英雄连一条线 然后投影
投影的长 小于 长度的一半
投影的宽 小于 宽度的一半
那就在矩形里
有一个不满足就不在里边
这里没有设置玩家释放的方向 都是朝着正面释放
*/
private float width;//宽
private float height;//高
//伤害值 如果在这个范围就有伤害
private float damage;
//技能释放者
private Hero releaseHero;
public RectangleAttack(Vector3 skillCenter,float width,float height,
float damage,Hero releaseHero){
this.skillCenter = skillCenter;
this.width = width;
this.height = height;
this.damage = damage;
this.releaseHero = releaseHero;
}
public override float TakeDamage(Hero hero){
//方向向量
//技能中心指向英雄
Vector3 dir = hero.heroTra.position - skillCenter;
//求一个向量在某一个向量上的投影
//方向向量在释放者水平方向的投影向量
Vector3 horDir = Vector3.Project(dir,releaseHero.heroTra.right);
//方向向量在释放者垂直方向的投影向量
Vector3 verDir = Vector3.Project(dir,releaseHero.heroTra.forward);
//算距离
if(horDir.magnitude <= width && verDir.magnitude <= height){
return damage;
}
return 0;
}
}
#endregine
****************************************************************************************
#regine 扇形
public class FanshapedAttack : AttackStrategy {
//中心点
private Vector3 skillCenter;
//伤害值 如果在这个范围就有伤害
private float damage;
//技能释放者
private Hero releaseHero;
//扇形技能的角度
private float angle;
//扇形半径
private float skillRadius;
/*
先算英雄所在位置的长度是否超过半径
然后再看范围是不是在技能的角度里
判断方法 中心点 和 英雄做一条连线 判断夹角是否小于扇形夹角的一半
*/
public RectangleAttack(Vector3 skillCenter,float angle,
float damage,Hero releaseHero){
this.skillCenter = skillCenter;
this.angle = angle;
this.damage = damage;
this.releaseHero = releaseHero;
}
public override float TakeDamage(Hero hero){
//要判断两个 一个是在没在范围内
//另一个是 是否超过角度
//距离 被攻击的英雄 释放技能的英雄
float dis = Vector3.Distance(hero.heroTra.position,releaseHero.heroTra.position);
//夹角 释放者自身前方
float angle = Vector3.Angle(releaseHero.heroTra.forward,hero.heroTra.position - releaseHero.heroTra.position);
//超出距离
if (dis > skillRadius)
return 0;
//不在夹角范围内
if (angle > this.angle / 2)
return 0;
return damage;
}
}
#endregine
public class StrategyDemo : MonoBehaviour {
//释放技能的英雄
public Transform releaseHero;
//受伤的英雄
public Transform hurtHero;
private void Start(){
Hero aaa = new Hero("aaa",releaseHero);
Hero bbb = new Hero("bbb",hurtHero);
//攻击的策略
CircleAttack circleAttack = new CircleAttack(Vector3.zero,10,100,5);
//圆形攻击
aaa.Attack(circleAttack,bbb);
}
}
}
装饰模式
基础功能以外的拓展功能 这个在unity里用的比较少
工厂方法模式
与简单工厂的区别 工厂抽象 让子类去实现内容
原型模式
模板克隆一份
模板方法模式
抽象模板类 子类实体
外观模式
有很多功能,只留一个外部接口
unity场景里就摄像机 灯光 顶多在留有一个空对象 外观作为启动窗口 运行后就都显示出来了
using UnityEngine;
using Utility;//引用之前写的资源管理框架
public class FacadeDemo : MonoBehaviour {
private void Start(){
//“这里写的是预设体的名字”,预设体要作为画布的子物体,当前脚本挂在画布上
PrefabManager.GetInstance().CreateGameObjectByPrefab(
"RedPanel",transform,Vector3.zero,Quaternion.identity);
}
}
建造者模式
复杂对象构建
观察者模式
持续观察 做某件事 通知者委托观察者观察,条件成立了 观察者通知给通知者
using UnityEngine;
using System.Collections.Generic;
//观察者等待消息
public class ObserverWaitMsg {
public string name;
public ObserverWaitMsg(string name){
this.name = name;
}
//接受消息
public void ReceiveMsg(string msg){
Debug.Log(name + "收到了消息:" + msg);
}
}
//通知者发送消息
public class SubjectSendMsg {
//通知的列表
private IList<ObserverWaitMsg> Observers;
//构造函数实例化
public SubjectSendMsg(){
Observers = new List<ObserverWaitMsg>();
}
//添加列表
public void AddObserver(ObserverWaitMsg observer){
//判断是否有该观察者
if(!observers.Contains(observer)){
observers.Add(observer);
}
}
//移除列表
public void RemoveObserver(ObserverWaitMsg observer){
//判断是否有该观察者
if(observers.Contains(observer)){
observers.Remove(observer);
}
}
//通知
public void Notify(string msg){
for(int i = 0;i < observers.Count;i++){
//通知给每个观察者
observers[i].ReceiveMsg(msg);
}
}
//检测时间是不是够了
//观察游戏时间
public void WatchGameTime(float targetTime,float currentTime){
if(currentTime >= targetTime){
Notify("挂机任务完成");
}
}
}
public class ObserverDemo : MonoBehaviour {
//普通任务观察者
private ObserverWaitMsg normalTask;
//成就任务观察者
private ObserverWaitMsg achievementTask;
//通知者
private SubjectSendMsg subject;
private void Start(){
normalTask = new ObserverWaitMsg("普通任务观察者");
achievementTask = new ObserverWaitMsg("成就任务观察者");
subject = new SubjectSendMsg();
//添加观察者到观察者列表
subject.AddObserver(normalTask);
subject.AddObserver(achievementTask);
//移除成就任务观察者
subject.RemoveObserver(achievementTask);
}
private void Update(){
subject.WatchGameTime(5,Time.time);
}
}
代理模式
using Engine;
using System;
//任务类
public class Task {
private int taskTargetCount;
private int currentCount;
//任务目标
public void SetTarget(int targetCount){
this.taskTargetCount = targetCount;
}
public void TaskCallback(){
currentCount++;
Debug.Log("检测抽了一张牌(" + currentCount + "/" + taskTargetCount + ")")
if(currentCount == taskTargetCount){
Debug.Log("抽卡任务完成");
}
}
}
public class CardManager {
//委托
public Action getCardCallbacks;
public void AddGetCardListener(Action action){
if(getCardCallbacks == null){
getCardCallbacks = action;
}
else{
getCardCallbacks += action;
}
}
public void RemoveGetCardListener(Action action){
try{
getCardCallbacks -= action;
}
catch (Exception e){
Debug.LogWarning(e);
}
}
public void GetOneCard(){
if(getCardCallbacks != null)
getCardCallbacks();
}
}
public class ProxyDemo : MonoBehaviour {
private CardManager cardManager;
private void Start(){
cardManager = new CardManager();
Task task = new Task();
task.SetTarget(5);
//绑定监听
cardManager.AddGetCardListener(task.TaskCallback);
}
private void Update(){
if(Input.GetKeyDown(KeyCode.Space)){
//抽一张
cardManager.GetOneCard();
}
}
}