【Unity】动作游戏开发实战详细分析-09-SpawnPoint

2023-11-02 20:59

本文主要是介绍【Unity】动作游戏开发实战详细分析-09-SpawnPoint,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【Unity】动作游戏开发实战详细分析-09-SpawnPoint


基本思想

在关卡编辑时,我们通常不会将敌人或者NPC直接放在场景中,为了能够循环使用角色对象,为了能够便于管理,我们可以使用SpawnPoint作为创建点来动态的创建他们。

对于创建对象,我们需要考虑几个比较重要的问题

  • 实例化源:Resouces/AssetBundle
  • 时序:在脚本生命周期的那个阶段创建
  • 场景依赖:例如创建出的怪物需要拿到静态配置在场景里的巡逻路径等

第一个问题可以使用一个枚举字段来进行选择,第二个时序问题也一样。

不过主角的SpawnPoint应当优先创建;第三个场景依赖问题可以给SpawnPoint提供一个获取对象的接口,这样场景中的脚本就能通过它来获取已创建完成的对象。

根据上述几点,开始编写脚本

代码实现

基本字段

首先就是最基本的层级,实例化源,时序

然后就是对象缓存,创建回调函数

public enum EHierarchyMode { Child, EqulsParent }//层级模式枚举
public enum EResourcesLocation { AssetBundle, Resources }//实例源位置枚举
public enum ESpawnOrder { None, Manual, OnEnabled, Start, Message }//时序枚举
public string resourcePath;//Resource路径
public ESpawnOrder spawnOrder;//实例化时序
public EResourcesLocation resourceLocation;//实例化源
public EHierarchyMode createHierMode;//层级模式
GameObject mSpawnedGO;//已创建的对象缓存public GameObject SpawnedGO { get { return mSpawnedGO; } }//已创建的对象实例
public bool IsSpawned { get { return SpawnedGO != null; } }//是否已创建
public event Action<GameObject> OnSpawned;//创建回调

主要我们来看我们的Spawn函数

首先,需要从特定的实例化源获取资源并完成实例化,初始化

并且需要检查是否实例化失败的问题

然后,通过事先定好的规则来对创建好的对象进行层级初始化

最后将对象存储到缓存,并触发回调函数,这里详细说明一下回调函数的作用

回调函数可以完成类似监听的作用,当创建完成时,触发回调,如果你需要一个功能在创建时调用,就注册这个回调即可完成。它不像观察者模式,需要发出创建完成信息然后接收其信息后再进行调用。

protected virtual void Spawn()
{var instancedGO = default(GameObject);switch (resourceLocation)//开始从特定源实例数据{case EResourcesLocation.AssetBundle://AB包需要自己实现一些内容//AssetBundleManager do something...break;case EResourcesLocation.Resources://Resource直接Load进来然后实例化var resHandle = Resources.Load<GameObject>(resourcePath);instancedGO = Instantiate(resHandle) as GameObject;//实例化instancedGO.name.Substring(0, instancedGO.name.Length - "(Clone)".Length);//删除Cloneh后缀break;}if (instancedGO == null)//检测创建失败情况,多数时为面板路径填错Debug.LogError("实例化失败!请检查面板填写是否正确!");switch (createHierMode)//层级模式{case EHierarchyMode.Child://创建为SpawnPoint的子对象instancedGO.transform.parent = transform;instancedGO.transform.localPosition = Vector3.zero;instancedGO.transform.localRotation = Quaternion.identity;break;case EHierarchyMode.EqulsParent://与SpawnPoint一致的父级instancedGO.transform.parent = transform.parent;instancedGO.transform.position = transform.position;instancedGO.transform.rotation = transform.rotation;break;}mSpawnedGO = instancedGO;//赋值到内部字段if (OnSpawned != null)//触发回调OnSpawned(mSpawnedGO);
}

然后就是其他的一些功能:时序检测,回收功能等等

protected virtual void OnEnable()//OnEnable事件
{if (spawnOrder != ESpawnOrder.OnEnabled) return;//时序检测Spawn();//创建
}protected virtual void Start()//Start事件
{if (spawnOrder != ESpawnOrder.Start) return;//时序检测Spawn();//创建
}protected virtual void OnDestroy()//OnDestroy事件
{Recycle();//执行回收
}//尝试创建,若已创建则跳出
public virtual bool TrySpawn()
{if (IsSpawned) return false;//跳出逻辑Spawn();//创建return true;
}//处理回收的逻辑
//未来加入池的话这里还会增加一些内容。
public virtual void Recycle()
{mSpawnedGO = null;
}

扩展SpawnPoint

基本功能我们已经实现了,现在需要在编辑器下对其预览调试功能进行扩展,以达到如图效果
在这里插入图片描述

理解下面的内容需要对Unity编辑器有一定的编程基础

[CustomEditor(typeof(SpawnPoint))]
[CanEditMultipleObjects]
public class SpawnPointInspector : Editor
{public override void OnInspectorGUI(){base.OnInspectorGUI();//执行基础GUI绘制GUILayout.BeginVertical(GUI.skin.box);//布局自定义GUIif (GUILayout.Button("Preview"))//预览按钮{}if (GUILayout.Button("Clear Preview"))//清楚预览按钮{}GUILayout.EndVertical();}
}
预览按钮的具体逻辑

首先通过target拿到脚本对象,然后存储原实例化源,并设置自定义实例化源(这里的代码仅限Resources,AB包需要自行编写代码)

然后通过反射调用Spawn函数,最后查看缓存,并将对象标记为不可保存与不可编辑状态,最后恢复实例源

var spawnPoint = base.target as SpawnPoint;//拿到SpawnPoint具体对象
var cacheResourceLocation = spawnPoint.resourceLocation;//只支持调试Resources目标的对象
//但也可以将调试路径填入Resources字段供编辑器内调试使用。
spawnPoint.resourceLocation = SpawnPoint.EResourcesLocation.Resources;
spawnPoint.GetType().GetMethod("Spawn", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(spawnPoint, null);
//通过反射调用Spawn方法。
if (spawnPoint.SpawnedGO != null)spawnPoint.SpawnedGO.hideFlags = HideFlags.DontSaveInEditor | HideFlags.NotEditable;
//创建出来的对象标记为不可保存不可编辑状态
spawnPoint.resourceLocation = cacheResourceLocation;//恢复实例源
清楚预览按钮距离逻辑

这个就比较简单了,直接获取缓存并销毁即可

var spawnPoint = base.target as SpawnPoint;//拿到SpawnPoint具体对象
if (spawnPoint.SpawnedGO != null)//是否已经创建了对象DestroyImmediate(spawnPoint.SpawnedGO);//直接将已创建的对象销毁

SpawnPointBrush功能

在关卡编辑时,有遇到开放式的关卡,他们涉及地形,因此我们需要一个类似于地形笔刷功能对SpawnPoint进行批量创建

他的主要功能都在编辑器下,因此类本身仅需要声明一个字段即可

public class SpawnPointBrush : MonoBehaviour
{[HideInInspector]//在检视面板内隐藏public string templateCachePath;//缓存模板路径
}

然后创建Editor自定义检视面板

基本字段

  • mMouseClickMark
  • mSpawnPointTemplate
  • mCreatedList
  • mSpawnPointBrush

在Awake中进行初始化,注意要绑定场景视图事件,因为需要在场景中绘制一些标记

[CustomEditor(typeof(SpawnPointBrush))]//标记自定义编辑器目标类
public class SpawnPointBrushInspector : Editor
{bool mMouseClickMark;//缓存鼠标按下标记,以便GUI处理SpawnPoint mSpawnPointTemplate;//模板实例List<SpawnPoint> mCreatedList = new List<SpawnPoint>();//已创建内容列表SpawnPointBrush mSpawnPointBrush;//链接的Target对象void Awake(){mSpawnPointBrush = target as SpawnPointBrush;//缓存目标SceneView.duringSceneGui += OnDuringSceneGui;//绑定场景视图事件}void OnDestroy(){SceneView.duringSceneGui -= OnDuringSceneGui;//取消绑定场景视图事件}
}

首先是OnInspectorGUI函数

该函数可以对检视面板进行扩展

这里主要是三个功能

  • 显示已经常见的SpawnPoint数量并进行统计
  • 对缓存路径进行加载
  • 清除所有已创建的内容
public override void OnInspectorGUI()
{base.OnInspectorGUI();GUILayout.Box("Created SpawnPoint: " + mCreatedList.Count);//已创建的SpawnPoint对象数mSpawnPointTemplate = EditorGUILayout.ObjectField("SpawnPoint Template", mSpawnPointTemplate, typeof(SpawnPoint), true) as SpawnPoint;//通过ObjectField控件设置SpawnPoint模板if (mSpawnPointTemplate != null)mSpawnPointBrush.templateCachePath = AssetDatabase.GetAssetPath(mSpawnPointTemplate);//将模板转换为缓存路径储存进字段,这样再下次打开时不需要重新设置if (GUILayout.Button("Clear Spawned Point"))//清除所有已创建的内容{foreach (var item in mCreatedList)DestroyImmediate(item.gameObject);//遍历销毁mCreatedList.Clear();//执行清空}
}

然后就是十分重要的场景视图中的笔刷绘制

主要思路:获取鼠标位置,然后从鼠标位置射出射线检测是否检测到目标,并绘制笔刷,检测是否按下规定按键并进行SpawnPoint的创建,将SpawnPoint创建在地形的目标点,并设置初始化,然后将该对象加入存储集合中

void OnDuringSceneGui(SceneView sceneview)
{var mousePosition = Event.current.mousePosition;mousePosition.y = Screen.height - mousePosition.y - 40;//拿到鼠标屏幕空间坐标,反转后减去头部GUI高度var cam = SceneView.lastActiveSceneView.camera;//拿到当前编辑器内相机if (cam == null) return;//若当前面板无相机则跳出var aimRay = cam.ScreenPointToRay(mousePosition);var hit = default(RaycastHit);if (!Physics.Raycast(aimRay, out hit, Mathf.Infinity)) return;//射线检测任意目标碰撞Vector3 brushPos = hit.point;//笔刷中心点Handles.DrawAAPolyLine(brushPos, brushPos + new Vector3(0, 10, 0));//绘制白色提示线DrawBrush(brushPos, 2f, Color.blue);//绘制蓝色内提示圈DrawBrush(brushPos, 4f, Color.blue);//绘制蓝色外提示圈if (Event.current.button == 1 && Event.current.alt && !mMouseClickMark)//确定是按住alt并点击鼠标右键{if (mSpawnPointTemplate != null){var go = Instantiate(mSpawnPointTemplate.gameObject) as GameObject;//实例化GameObjectUndo.RegisterCreatedObjectUndo(go, "SpawnPointBrush");//加入Undo列表go.name = mSpawnPointTemplate.name;//修改名称go.transform.parent = mSpawnPointBrush.transform;//设置父级go.transform.position = brushPos;mCreatedList.Add(go.GetComponent<SpawnPoint>());//加入创建列表}mMouseClickMark = true;//鼠标点击标记设置}if (Event.current.type == EventType.MouseUp)//鼠标点击标记重置mMouseClickMark = false;SceneView.RepaintAll();//执行重绘,保证SceneView视图一直在更新。
}

笔刷绘制函数

void DrawBrush(Vector3 pos, float radius, Color color, float thickness = 3f, int numCorners = 32)
{Handles.color = color;//设置颜色var corners = new Vector3[numCorners + 1];//创建转角点float step = 360f / numCorners;//计算每个转角点步幅for (int i = 0; i <= corners.Length - 1; i++)//计算转角点坐标corners[i] = new Vector3(Mathf.Sin(step * i * Mathf.Deg2Rad), 0, Mathf.Cos(step * i * Mathf.Deg2Rad)) * radius + pos;Handles.DrawAAPolyLine(thickness, corners);//执行绘制
}

这篇关于【Unity】动作游戏开发实战详细分析-09-SpawnPoint的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Python实现Word转PDF全攻略(从入门到实战)

《Python实现Word转PDF全攻略(从入门到实战)》在数字化办公场景中,Word文档的跨平台兼容性始终是个难题,而PDF格式凭借所见即所得的特性,已成为文档分发和归档的标准格式,下面小编就来和大... 目录一、为什么需要python处理Word转PDF?二、主流转换方案对比三、五套实战方案详解方案1:

SpringBoot实现RSA+AES自动接口解密的实战指南

《SpringBoot实现RSA+AES自动接口解密的实战指南》在当今数据泄露频发的网络环境中,接口安全已成为开发者不可忽视的核心议题,RSA+AES混合加密方案因其安全性高、性能优越而被广泛采用,本... 目录一、项目依赖与环境准备1.1 Maven依赖配置1.2 密钥生成与配置二、加密工具类实现2.1

Nginx进行平滑升级的实战指南(不中断服务版本更新)

《Nginx进行平滑升级的实战指南(不中断服务版本更新)》Nginx的平滑升级(也称为热升级)是一种在不停止服务的情况下更新Nginx版本或添加模块的方法,这种升级方式确保了服务的高可用性,避免了因升... 目录一.下载并编译新版Nginx1.下载解压2.编译二.替换可执行文件,并平滑升级1.替换可执行文件

Python38个游戏开发库整理汇总

《Python38个游戏开发库整理汇总》文章介绍了多种Python游戏开发库,涵盖2D/3D游戏开发、多人游戏框架及视觉小说引擎,适合不同需求的开发者入门,强调跨平台支持与易用性,并鼓励读者交流反馈以... 目录PyGameCocos2dPySoyPyOgrepygletPanda3DBlenderFife

使用Python开发一个Ditto剪贴板数据导出工具

《使用Python开发一个Ditto剪贴板数据导出工具》在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读... 目录前言运行结果项目需求分析技术选型核心功能实现1. Ditto数据库结构分析2. 数据库自动定位3

Django开发时如何避免频繁发送短信验证码(python图文代码)

《Django开发时如何避免频繁发送短信验证码(python图文代码)》Django开发时,为防止频繁发送验证码,后端需用Redis限制请求频率,结合管道技术提升效率,通过生产者消费者模式解耦业务逻辑... 目录避免频繁发送 验证码1. www.chinasem.cn避免频繁发送 验证码逻辑分析2. 避免频繁

精选20个好玩又实用的的Python实战项目(有图文代码)

《精选20个好玩又实用的的Python实战项目(有图文代码)》文章介绍了20个实用Python项目,涵盖游戏开发、工具应用、图像处理、机器学习等,使用Tkinter、PIL、OpenCV、Kivy等库... 目录① 猜字游戏② 闹钟③ 骰子模拟器④ 二维码⑤ 语言检测⑥ 加密和解密⑦ URL缩短⑧ 音乐播放

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)

《java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)》:本文主要介绍java中pdf模版填充表单踩坑的相关资料,OpenPDF、iText、PDFBox是三... 目录准备Pdf模版方法1:itextpdf7填充表单(1)加入依赖(2)代码(3)遇到的问题方法2:pd