【Unity】RPG2D龙城纷争(二)关卡、地块

2024-06-13 00:04

本文主要是介绍【Unity】RPG2D龙城纷争(二)关卡、地块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

更新日期:2024年6月12日。
项目源码:在第四章发布

索引

  • 简介
    • 地块(Block)
      • 一、定义地块类
      • 二、地块类型
      • 三、地块渲染
      • 四、地块索引
    • 关卡(Level)
      • 一、定义关卡类
      • 二、关卡基础属性
      • 三、地块集合
      • 四、关卡初始化
      • 五、关卡销毁
      • 六、回合制逻辑

简介

本章我们将从零开始实现关卡,不过,我们的目的是实现关卡与地块的基本逻辑,更复杂的功能我们打算在开发关卡编辑器时再做涉及,正所谓一步一个坎,就没有迈不过去的高山。

地块(Block)

地块作为关卡的组成元素,他是一个正方形的格子(当然也可以是菱形,针对斜视角游戏,不过这不在框架的支持范畴内,但做出一定的修改即可实现),每一个地块都有属于他自己的一些属性。

一、定义地块类

首先,我们定义地块类Block

    /// <summary>/// 地块/// </summary>[DisallowMultipleComponent]public class Block : HTBehaviour{}

Block继承至HTBehaviour,使得它可以挂载到游戏物体上,然后作为一个地块对象(为不需要一个物体上多次挂载的组件定义DisallowMultipleComponent是一个好习惯,这将提高容错率,如果你的代码会给别人使用的话)。

很多同学热衷于在类继承链上规避MonoBehaviour,其实完全没有这个必要,只要你不滥用MonoBehaviour的生命周期,它将提供给你几大优势:
1.属性可编辑(Inspector面板);
2.属性可调试(Inspector面板);
3.对象可追踪(Scene里面有则有,无则无);
4.对象可销毁(当确定不再使用时,Destroy它,而不用置null后交给GC)。

二、地块类型

经过深思熟虑,我们将地块类型划分为如下几种:

  • 地面
  • 山体
  • 森林
  • 湖泊
  • 雪地
  • 障碍

编写代码:

    /// <summary>/// 地块/// </summary>[DisallowMultipleComponent]public class Block : HTBehaviour{/// <summary>/// 类型/// </summary>[Label("类型")] public BlockType Type;}/// <summary>
/// 地块类型
/// </summary>
public enum BlockType
{/// <summary>/// 地面/// </summary>[Remark("地面")]Ground = 0,/// <summary>/// 山体/// </summary>[Remark("山体")]Moutain = 1,/// <summary>/// 森林/// </summary>[Remark("森林")]Forest = 2,/// <summary>/// 湖泊/// </summary>[Remark("湖泊")]Water = 3,/// <summary>/// 雪地/// </summary>[Remark("雪地")]Snow = 4,/// <summary>/// 障碍/// </summary>[Remark("障碍")]Obstacle = 5
}

然后,我们设计每种类型的地块拥有的交互权限如下:

类型角色可行走角色可攻击(站在上面的敌人或穿过它攻击其他敌人)
地面
山体受限(拥有飞檐走壁可行走,行走速度减1/2
森林受限(行走速度减1/2
湖泊受限(拥有踏水神行可行走,行走速度减1/2
雪地受限(行走速度减2/3
障碍受限(拥有隔山打牛可穿透障碍进行攻击

需注意的是,任意地块,当上面站有敌人时,将不可行走,不可跨越,当站有队友时,不可行走,可跨越。

也即是说,我们可以使用角色摆出特定的阵型,以拦住敌方的行走路线,或达到包围的效果。

三、地块渲染

地块的渲染我们决定选择SpriteRenderer,且一个地块仅渲染一张图片,那么,在Block类中需要持有地块渲染器的引用,我们添加代码:

    /// <summary>/// 地块/// </summary>[DisallowMultipleComponent]public class Block : HTBehaviour{/// <summary>/// 目标渲染器/// </summary>[Label("目标渲染器")] public SpriteRenderer Target;/// <summary>/// 类型/// </summary>[Label("类型")] public BlockType Type;}

四、地块索引

然后,我们想一想地块还需要些什么属性,哦对了,我想我们在后续一定会需要检索地块(也即是根据索引找到一个地块),而我们的关卡采用二维平铺布局,所有地块存储的数据结构应当是一个二维数组最合适,所以地块的索引我们定义为二维的下标Vector2Int

    /// <summary>/// 地块/// </summary>[DisallowMultipleComponent]public class Block : HTBehaviour{/// <summary>/// 目标渲染器/// </summary>[Label("目标渲染器")] public SpriteRenderer Target;/// <summary>/// 类型/// </summary>[Label("类型")] public BlockType Type;/// <summary>/// 位置/// </summary>[Label("位置")] public Vector2Int Pos;}

地块类的属性暂时就想到这么多,不必追求一次就考虑全面,后续根据情况补充即可,接下来我们定义关卡类。

关卡(Level)

关卡用于容纳并绘制一系列地块,以及后期容纳角色等其他的一系列东西。

一、定义关卡类

我们定义关卡类Level

    /// <summary>/// 关卡/// </summary>[DisallowMultipleComponent]public class Level : SingletonBehaviourBase<Level>{}

Level继承至单例行为基类SingletonBehaviourBase,使得它可以挂载到游戏物体上,并作为单例始终全局唯一(同一时刻,场景中的关卡肯定只能有一个)。

二、关卡基础属性

我们先为关卡设计一些基础的属性:

        /// <summary>/// 关卡索引/// </summary>[Label("关卡索引")] public int Index;/// <summary>/// 关卡名称/// </summary>[Label("关卡名称")] public string Name;/// <summary>/// 关卡背景音乐/// </summary>[Label("关卡背景音乐")] public AudioClip BGAudio;/// <summary>/// 地图/// </summary>[Label("地图")] public AStarGrid Map;/// <summary>/// 地图尺寸/// </summary>  [Label("地图尺寸")] public Vector2Int MapSize;/// <summary>/// 角色根节点/// </summary>[Label("角色根节点")] public Transform RolesRoot;/// <summary>/// 特效根节点/// </summary>[Label("特效根节点")] public Transform EffectsRoot;
属性名称属性详解
关卡索引关卡唯一标识符,也用作保存、加载关卡时的索引
地图所有地块对象的根节点,此处类型为AStarGrid(A*寻路网格),兼并寻路功能
地图尺寸地图的尺寸,用于描述地图的宽、高
角色根节点所有角色对象的根节点
特效根节点所有特效对象的根节点

地块、角色、特效都归于单一的根节点,即方便管理所有对象,又方便统一划层,也即是规定渲染器的遮挡层,这三者的遮挡层关系应当是:特效 > 角色 > 地块,具体如何实现遮挡,我们先不考虑这么多。

完事后我们的Level在层级面板应当是这样的(三个子对象也即是地块、角色、特效的根节点):
在这里插入图片描述

三、地块集合

定义一个二维数组存储所有地块:

        /// <summary>/// 所有的地块/// </summary>public Block[,] Blocks { get; private set; }

此处注意,将其定义为property的原因是,使其规避序列化功能(因为不需要序列化,关卡在初始化时搜寻所有地块即可),且提升访问安全性。

四、关卡初始化

然后,定义一个初始化方法,用于在关卡加载到场景中后,执行他自身的所有初始化操作,此处我们避开MonoBehaviour的生命周期方法AwakeStart,因为在此处他们是不受控的,这也是我上面所提到的不滥用生命周期的另一个意思。

        /// <summary>/// 初始化/// </summary>public virtual void Initialize(){//Map为所有地块根节点,即可从Map搜寻所有地块Blocks = new Block[MapSize.x, MapSize.y];Block[] blocks = Map.GetComponentsInChildren<Block>(true);for (int i = 0; i < blocks.Length; i++){//地块的Pos下标,即代表了在二维数组中的索引,我们后续会开发关卡编辑器,Pos的赋值交由编辑器来完成,所以这里只管取Pos值Block block = blocks[i];Blocks[block.Pos.x, block.Pos.y] = block;}}

Initialize方法作为主动调用方法,在我们自行加载关卡完成后主动调用即可。

五、关卡销毁

同理,应当定义一个销毁方法,用于在关卡销毁时执行一些操作,虽然我们现在还没有需要做的(地块、角色、特效等都属于关卡物体子节点,会跟着一起销毁,无需额外操作),但事先将其规划好总没错。

        /// <summary>/// 销毁/// </summary>public virtual void Dispose(){}

六、回合制逻辑

为了实现回合制逻辑,我们先定义如下几个属性:

        /// <summary>/// 当前的回合/// </summary>[PropertyDisplay("当前的回合")]public int Round { get; private set; } = 1;/// <summary>/// 当前回合的行动阵营/// </summary>[PropertyDisplay("当前回合的行动阵营")]public RoleCamp RoundCamp { get; private set; } = RoleCamp.Player;/// <summary>/// 关卡状态/// </summary>[PropertyDisplay("关卡状态")]public LevelState State { get; private set; } = LevelState.InProgress;/// <summary>/// 角色阵营/// </summary>public enum RoleCamp{/// <summary>/// 玩家/// </summary>Player = 0,/// <summary>/// 敌人/// </summary>Enemy = 1}/// <summary>/// 关卡状态/// </summary>public enum LevelState{/// <summary>/// 进行中/// </summary>InProgress,/// <summary>/// 已通关/// </summary>Passed,/// <summary>/// 已失败/// </summary>Failed}

此处应该很好理解了,我们按字面意思来就行了,根据最初的设计,每一个回合:玩家先行动,然后是敌人行动,敌人行动完毕后此回合结束,进入下一回合(循环往复)

此时,我们发现,完整的回合制逻辑在未编写角色(Role)类前,并不太好写出来,所以我们先放下,将复杂的事情留到后面一步步拆解。

接下来我们准备引入角色(Role)类,不过,看了一眼窗外,今日天色已晚,不宜working…

那么,择日再战吧。

这篇关于【Unity】RPG2D龙城纷争(二)关卡、地块的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Unity Post Process Unity后处理学习日志

Unity Post Process Unity后处理学习日志 在现代游戏开发中,后处理(Post Processing)技术已经成为提升游戏画面质量的关键工具。Unity的后处理栈(Post Processing Stack)是一个强大的插件,它允许开发者为游戏场景添加各种视觉效果,如景深、色彩校正、辉光、模糊等。这些效果不仅能够增强游戏的视觉吸引力,还能帮助传达特定的情感和氛围。 文档

Unity协程搭配队列开发Tips弹窗模块

概述 在Unity游戏开发过程中,提示系统是提升用户体验的重要组成部分。一个设计良好的提示窗口不仅能及时传达信息给玩家,还应当做到不干扰游戏流程。本文将探讨如何使用Unity的协程(Coroutine)配合队列(Queue)数据结构来构建一个高效且可扩展的Tips弹窗模块。 技术模块介绍 1. Unity协程(Coroutines) 协程是Unity中的一种特殊函数类型,允许异步操作的实现

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光 一,前言二,资源包内容三,免费获取资源包 一,前言 在创意的世界里,每一个细节都能决定一个项目的独特魅力。今天,要向大家介绍一款令人惊艳的粒子效果包 ——Super Confetti FX。 二,资源包内容 💥充满活力与动态,是 Super Confetti FX 最显著的标签。它宛如一位

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(4)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)-CSDN博客  这节就是真正的存储数据了   理清一下思路: 1.存储路径并检查 //2进制文件类存储private static string Data_Binary_Pa

Unity Adressables 使用说明(一)概述

使用 Adressables 组织管理 Asset Addressables 包基于 Unity 的 AssetBundles 系统,并提供了一个用户界面来管理您的 AssetBundles。当您使一个资源可寻址(Addressable)时,您可以使用该资源的地址从任何地方加载它。无论资源是在本地应用程序中可用还是存储在远程内容分发网络上,Addressable 系统都会定位并返回该资源。 您

Unity Adressables 使用说明(六)加载(Load) Addressable Assets

【概述】Load Addressable Assets Addressables类提供了加载 Addressable assets 的方法。你可以一次加载一个资源或批量加载资源。为了识别要加载的资源,你需要向加载方法传递一个键或键列表。键可以是以下对象之一: Address:包含你分配给资源的地址的字符串。Label:包含分配给一个或多个资源的标签的字符串。AssetReference Obj

在Unity环境中使用UTF-8编码

为什么要讨论这个问题         为了避免乱码和更好的跨平台         我刚开始开发时是使用VS开发,Unity自身默认使用UTF-8 without BOM格式,但是在Unity中创建一个脚本,使用VS打开,VS自身默认使用GB2312(它应该是对应了你电脑的window版本默认选取了国标编码,或者是因为一些其他的原因)读取脚本,默认是看不到在VS中的编码格式,下面我介绍一种简单快

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(2) (*****生成数据结构类的方式特别有趣****)-CSDN博客 做完了数据结构类,该做一个存储类了,也就是生成一个字典类(只是声明)  实现和上一节的数据结构类的方式大同小异,所

【Unity小技巧】URP管线遮挡高亮效果

前言 在URP渲染管线环境下实现物体遮挡高亮显示效果,效果如下: Unity URP遮挡高亮 实现步骤 创建层级,为需要显示高亮效果的物体添加层级,比如Player 创建一个材质球,也就是高亮效果显示的材质球找到Universal Renderer Data Assets 4.在Assets上添加两个Render Objects组件 第一个做如下三处设置 指定遮挡层级指

【Unity面经】实习篇:面试官常问的一百个面试题

👨‍💻个人主页:@元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 专栏交流🧧🟥Unity100个实战基础✨🎁🟦 Unity100个精华一记✨🎁🟩 Unity50个demo案例教程✨🎁🟨 Unity100个精华细节BUG✨🎁🟨 Unity100个面试题✨🎁 文章