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

开发笔记 | 接口与抽象基类说明以及对象池的实现

最近笔者正在试图做一款塔防游戏的框架,如果说我们要实现子弹、敌人在生成时自带buff,类似于“元素抗性”效果,我们可以试着用装饰器来实现。

接口与抽象基类

在 C# 中,抽象基类(abstract class)和接口(interface)都可以作为实现多态、统一行为的方式。但为了知道什么时候该用那种方式,我在此简单总结一下。

这么说来,各种敌人类就可以通过抽象基类实现,而子弹的特殊效果则可以通过接口来实现。

这里贴上我的敌人类,我想让子类可以重写的,就用virtual方法,想让子类必须写的,就用abstract方法,想让所有类用默认方法的,直接写就行。

using UnityEngine;
public abstract class Enemy : MonoBehaviour
{EnemySpawn enemySpawn; //敌人生成器HealthBar healthBar; //血条float maxHealth = 100f; //最大血量bool isDead = false; //是否死亡//血量值public float health = 100;//移动速度public float speed = 2f;private void Awake(){//获取血条组件healthBar = GetComponentInChildren<HealthBar>();}//游戏对象生成时需要调整的功能public void GameObjectSpawn(){isDead = false;}//重置敌人状态(在对象池中用的到)public void GameObjectReset(){health = maxHealth; //重置血量healthBar.SetHealth(health / maxHealth); //更新血条显示//重置状态时,进行对象回收enemySpawn.ReturnEnemy(gameObject);}//获取血量值public float GetHealth() { return health; }//扣除血量值public void MinusHealth(int attack) {health -= attack;healthBar.SetHealth(health / maxHealth); //更新血条显示if (health <= 0){isDead = true;gameObject.SetActive(false); //敌人死亡GameObjectReset(); //重置敌人状态}}//判断的依据可以在子类中重写//是否不再需要攻击public virtual bool NoMoreShotsNeeded() {return isDead; //如果血量小于等于0,则不再需要攻击}    //获取敌人游戏对象(……好像有点多此一举了)public GameObject GetGameObject(){return gameObject;}//设置敌人生成器public void SetEnemySpawn(EnemySpawn spawn){enemySpawn = spawn;}
}

后面我会通过装饰器模式来进行对子弹buff的编写,这之中会呈现interface的使用。

对象池

众所周知,频繁调用Instantiate()和Destroy()会产生GC(垃圾回收)卡顿,同时Unity销毁延迟的特性会在大量对象同时销毁时造成严重性能问题。

而对象池技术,不仅实现原理十分简单,还通过控制对象的启用与禁用来复用对象,避免了反复的销毁与创建。

在塔防游戏中,我们常常会遇到一大批波次的敌人在同一段时间内生成,那么对象池就是其生成敌人的不二之选。 

对象池一般用Queue创建,其先进先出的天性适合这个。而List的随机访问特性就显得多余。

对象池通常由三个部分组成:初始化对象,获取对象,回收对象。

生成对象后默认置于禁用状态,并依次入队。

然后获取对象与回收对象的方法也编辑完成了。这时又有新的问题,在什么时机去回收对象?

常见的对象池往往会让对象的类本身来存放一段对象池的类,因为只有对象实例本身才能知晓自己需要被回收的时机。放在这里就是Enemy类需要有一个EnemySpawn字段,在对象初始化时同步赋值。另一种方法就是使用事件监听功能了,这样不会把敌人类和敌人生成类的逻辑交织在一起,但我现在不太会。

然后在生成敌人的上下文中调用对象池方法。

那么如何实现对象池的动态扩容呢?听起来什么“动态”,实际上就是在检测到没有可用对象时初始化若干个对象就行。

以下为敌人生成器的完整脚本。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
using static GlobalData;
public class EnemySpawn : MonoBehaviour
{float timer = 0f; //计时器int spawnedEnemyCount = 0; //已生成的敌人数量public GameObject enemyPrefab; //敌人预制体public int enemyNumber = 20; //最大敌人数量int poolSize = 10;   //对象池的容量public Tilemap roadTilemap;private Queue<GameObject> enemyPool; //敌人对象池[Range(0f, 5f)]public float spawnInterval = 1f; //生成间隔时间void Start(){// 初始化对象enemyPool = new Queue<GameObject>();for (int i = 0; i < poolSize; i++){GameObject enemy = Instantiate(enemyPrefab);Enemy enemyScript = enemy.GetComponent<Enemy>();//这一步很重要enemy.GetComponent<Move>().roadTilemap = roadTilemap; //设置路径enemy.SetActive(false); //初始时不激活enemyScript.SetEnemySpawn(this); //设置生成器//依次加入对应数量的敌人enemyPool.Enqueue(enemy);}}//这种计时方法可以后续改进void Update(){timer += Time.deltaTime;if (timer <= spawnInterval) //每秒生成一个敌人{return;}if (spawnedEnemyCount < enemyNumber){SpawnEnemy();spawnedEnemyCount++;}timer = 0f; //重置计时器}//获取对象public GameObject GetEnemy(){if(enemyPool.Count <= 0){/*目前每次扩容4个*/ExpandPool(4);}//取敌人return enemyPool.Dequeue();}//对象池扩容void ExpandPool(int additionalSize){for (int i = 0; i < additionalSize; i++){//要和初始化时一模一样GameObject enemy = Instantiate(enemyPrefab);Enemy enemyScript = enemy.GetComponent<Enemy>();enemy.GetComponent<Move>().roadTilemap = roadTilemap; //设置路径enemy.SetActive(false); //初始时不激活enemyScript.SetEnemySpawn(this); //设置生成器enemyPool.Enqueue(enemy);}}//回收对象public void ReturnEnemy(GameObject enemy){enemy.SetActive(false); //禁用敌人enemyPool.Enqueue(enemy); //重新加入队列}//生成敌人,同时是使用对象池的上下文void SpawnEnemy(){//从对象池获取一个敌人GameObject oneEnemy = GetEnemy(); //设置生成位置oneEnemy.transform.position = transform.position; //激活敌人oneEnemy.SetActive(true);//激活的同时调用想过方法oneEnemy.GetComponent<Enemy>().GameObjectSpawn();Enemy enemyScript = oneEnemy.GetComponent<Enemy>();if (!globalEnemies.Contains(enemyScript)){//添加到全局敌人列表globalEnemies.Add(enemyScript);}}
}

​​​​​​​总的来说,对象池是一种结构简单且极其实用的一个开发技巧,非常好!


小结

好的,这篇图文的重点放在了对象池技术上,简单易用,但是接口与抽象基类的应用却没有详细提及,我会在下次通过接口来实现游戏的相关功能,各位下次见~

如有补充纠正欢迎留言。

http://www.dtcms.com/a/317374.html

相关文章:

  • SpringBoot 3.x整合Elasticsearch:从零搭建高性能搜索服务
  • JSON巴巴 - 专业JSON格式化工具:让任何JSON都能完美格式化
  • 基于 Jenkins Pipeline 实现 DITA 文档自动化构建与发布(开源方案)
  • Jenkinsfile各指令详解
  • 国民技术N32G003实现PMBus从机及使用STM32F103模拟I2C主机访问从机
  • PostgreSQL 通配符指南:解锁 LIKE 查询的魔法 - % 与 _ 详解
  • 区块链技术在供应链管理中的应用案例
  • C语言的综合案例
  • HIVE 窗口函数处理重复数据
  • WebStorm转VSCode:高效迁移指南
  • 用NAS如何远程访问:详细教程与实用技巧
  • 关于C语言连续强制类型转换,有符号数据位移,以及温度传感器int16有符号数据重组处理问题
  • C++之vector类的代码及其逻辑详解 (下)
  • SELinux加固Linux安全2
  • 【数据结构初阶】--排序(四):归并排序
  • 软考软件设计师考点总结
  • [linux] Linux系统中断机制详解及用户空间中断使用方法
  • Linux部署tp5.1,nginx服务器不管访问那个方法,一直访问index/index问题解决方法
  • 阶段二:1-信息技术概述
  • helm下载tiller失败
  • 【数字图像处理系列笔记】Ch04:灰度变换与空间域图像增强(2)
  • 蚊子咬人问题何时休:深度学习引领智能灭蚊新时代
  • qt窗口--02
  • 无人设备遥控器之跳频技术篇
  • 鹧鸪云:光伏电站的“智慧中枢”,精准调控逆变器
  • 使用 Helm 在 Kubernetes 中安装 Milvus
  • 企业知识库:RAG技术实现流程总览(一)
  • 【motion】标签体系设计与检索 1:HumanML3D 和 KIT Motion-Language(KITML)
  • 河南萌新联赛2025第(四)场【补题】
  • 键帽(dp)