• 使用Unity和纯ECS框架重新打造简单RTS游戏:从零开始的详细C#编程教程


    第一部分:简介及Unity与ECS框架的概述

    1.1 RTS游戏简介

    实时战略(RTS,Real-Time Strategy)游戏是一种视频游戏的子类,玩家在这种游戏中需要同时进行资源管理、基地建设、单位生产和战术部署。与回合制策略游戏不同,RTS游戏中的所有行动都是实时发生的,不等待其他玩家的行动。

    1.2 Unity简介

    Unity是一个跨平台的游戏引擎,广泛应用于视频游戏的开发,从手机到主机、从PC到VR设备,Unity都有所涉猎。其优点包括强大的图形渲染、物理模拟和音效处理能力,以及一个丰富的资产库和丰富的第三方工具集成。

    1.3 ECS简介

    ECS,即Entity-Component-System,是一种编程范式,被设计用于高性能、可扩展和易于维护的游戏开发。在ECS中:

    • Entity 是一个标识,代表游戏中的一个对象。
    • Component 存储数据,比如位置、速度或颜色。
    • System 包含游戏逻辑,处理组件的数据。

    使用ECS可以确保代码的模块化和可扩展性,因为每个系统都是隔离的,并且只关心特定的组件。

    1.4 为什么选择Unity和ECS

    结合Unity和ECS框架,我们可以充分发挥Unity的易用性和强大功能,同时确保游戏的高性能和可维护性。对于RTS这种需要处理大量单位和复杂逻辑的游戏,ECS提供了一个非常合适的解决方案。

    第二部分:创建基础游戏框架

    2.1 安装和配置Unity
    1. 访问Unity官方网站,下载最新版本的Unity Hub。
    2. 通过Unity Hub,选择合适的Unity版本进行安装。
    3. 打开Unity并创建一个新项目。
    2.2 项目结构

    为了保持项目整洁和有组织,我们建议以下的文件夹结构:

    Assets/
    |--- Prefabs/
    |--- Scripts/
    |      |--- Components/
    |      |--- Systems/
    |      |--- Utilities/
    |--- Scenes/
    |--- Materials/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2.3 第一个Entity和Component

    在RTS游戏中,我们首先创建一个基地。为此,我们需要一个Entity来代表基地和一些组件来存储它的数据。

    首先,创建一个BaseComponent:

    using Unity.Entities;
    
    public struct BaseComponent : IComponentData
    {
        public int health;
        public int maxHealth;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里,BaseComponent有两个属性:healthmaxHealth

    接下来,我们需要一个Entity来代表我们的基地。在Unity编辑器中,创建一个新的3D对象,并命名为"Base". 然后,为其添加BaseComponent

    注意:为了简洁和清晰,本文中的代码可能不是最优的或最完整的实现。为了获得完整的项目和更多的优化技巧,请下载完整项目

    第三部分:实现基础逻辑和系统

    3.1 创建基地的生命条UI

    对于任何RTS游戏,基地的生命值是非常重要的信息。因此,我们首先需要为基地创建一个生命条UI。

    首先,创建一个新的UI Slider。将其命名为“BaseHealthBar”。调整大小并放置在屏幕上的合适位置。

    然后,创建一个新的C#脚本BaseHealthBarSystem:

    using Unity.Entities;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class BaseHealthBarSystem : ComponentSystem
    {
        protected override void OnUpdate()
        {
            Entities.ForEach((Entity baseEntity, ref BaseComponent baseData) =>
            {
                // 找到BaseHealthBar UI元素
                Slider healthBar = GameObject.Find("BaseHealthBar").GetComponent<Slider>();
                healthBar.maxValue = baseData.maxHealth;
                healthBar.value = baseData.health;
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这个系统每帧都会更新基地的生命条UI,以反映基地的当前生命值。

    3.2 添加单位和其逻辑

    在任何RTS游戏中,单位都是核心元素。为此,我们首先需要一个UnitComponent:

    using Unity.Entities;
    
    public struct UnitComponent : IComponentData
    {
        public int attackPower;
        public int health;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接下来,我们创建一个UnitSystem,处理单位的逻辑,如移动、攻击等:

    using Unity.Entities;
    using UnityEngine;
    
    public class UnitSystem : ComponentSystem
    {
        protected override void OnUpdate()
        {
            Entities.ForEach((Entity unitEntity, ref UnitComponent unitData) =>
            {
                // 此处可以添加单位的移动、攻击等逻辑
                // 例如简单的移动逻辑:
                // Vector3 moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
                // PostUpdateCommands.SetComponent(unitEntity, new Position { Value = moveDirection * Time.deltaTime });
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    3.3 与基地互动

    当单位靠近基地时,它们可能会对基地造成伤害。为了实现这一逻辑,我们需要在UnitSystem中添加一些额外的代码:

    Entities.WithAll<BaseComponent>().ForEach((Entity baseEntity, ref BaseComponent baseData) =>
    {
        Entities.WithAll<UnitComponent>().ForEach((Entity unitEntity, ref UnitComponent unitData) =>
        {
            float distance = Vector3.Distance(baseEntity.position, unitEntity.position);
            if (distance < attackRange)
            {
                baseData.health -= unitData.attackPower;
                if (baseData.health < 0) baseData.health = 0;
            }
        });
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这段代码检查每个单位和基地之间的距离,如果单位在其攻击范围内,它会对基地造成伤害。

    第四部分:资源管理和AI

    4.1 资源管理

    在大多数RTS游戏中,资源是关键要素,玩家需要收集资源来建造单位、建筑或进行升级。

    4.1.1 定义资源组件
    using Unity.Entities;
    
    public struct ResourceComponent : IComponentData
    {
        public int amount;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们可以将这个组件添加到像"GoldMine"或"Forest"这样的实体上。

    4.1.2 收集资源的逻辑

    创建一个新的ResourceSystem来处理资源的收集逻辑:

    using Unity.Entities;
    
    public class ResourceSystem : ComponentSystem
    {
        protected override void OnUpdate()
        {
            Entities.WithAll<UnitComponent>().ForEach((Entity unitEntity, ref UnitComponent unitData) =>
            {
                Entities.WithAll<ResourceComponent>().ForEach((Entity resourceEntity, ref ResourceComponent resourceData) =>
                {
                    // 如果单位在资源附近,它可以收集资源
                    // 例如:if (IsNear(unitEntity, resourceEntity))
                    // {
                    //     int collectedAmount = Mathf.Min(unitData.collectionRate, resourceData.amount);
                    //     resourceData.amount -= collectedAmount;
                    //     PlayerResources.AddResource(collectedAmount);
                    // }
                });
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    4.2 AI 敌人

    一个简单的敌人AI可以使游戏更有趣。我们可以创建敌人单位,它们会自动寻找并攻击玩家的基地。

    4.2.1 敌人组件
    using Unity.Entities;
    
    public struct EnemyComponent : IComponentData
    {
        public float attackCooldown;
        public float currentCooldown;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    4.2.2 敌人AI系统
    using Unity.Entities;
    
    public class EnemyAISystem : ComponentSystem
    {
        protected override void OnUpdate()
        {
            Entities.WithAll<EnemyComponent>().ForEach((Entity enemyEntity, ref EnemyComponent enemyData) =>
            {
                // 寻找玩家基地并攻击
                Entities.WithAll<BaseComponent>().ForEach((Entity baseEntity, ref BaseComponent baseData) =>
                {
                    float distance = DistanceBetween(enemyEntity, baseEntity);
    
                    if (distance < enemyAttackRange)
                    {
                        if (enemyData.currentCooldown <= 0)
                        {
                            baseData.health -= enemyAttackDamage;
                            enemyData.currentCooldown = enemyData.attackCooldown;
                        }
                        else
                        {
                            enemyData.currentCooldown -= deltaTime;
                        }
                    }
                });
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    第五部分:总结和扩展

    使用Unity和纯ECS框架,我们成功地创建了一个简单的RTS游戏。通过本教程,您应该已经了解了如何使用ECS的基本概念,以及如何在Unity中实现它。

    然而,我们介绍的只是冰山一角。RTS游戏可能会非常复杂,包括更多的单位类型、技能、升级、复杂的AI等等。

    为了进一步扩展您的游戏,您可以考虑以下建议:

    1. 添加更多类型的单位和建筑。
    2. 实现更复杂的单位AI,如寻路算法。
    3. 添加多玩家功能,让玩家之间可以对战。
    4. 添加更多的资源类型和复杂的经济系统。
    5. 制作一个详细的教程或关卡,指导玩家如何玩游戏。

    希望您享受本教程,祝您在游戏开发中取得成功!

    注意:为了简洁和清晰,本文中的代码可能不是最优的或最完整的实现。为了获得完整的项目和更多的优化技巧,请下载完整项目

  • 相关阅读:
    FreeRTOS之信号量
    做废20个账号才总结出的短视频运营干货
    电大搜题——搜索难题
    layui input 监听事件
    (附源码)php校园二手交易网站 毕业设计 041148
    Google Earth Engine(GEE)——在影像图层上加载一个可视化的线性图例(以NDVI为例)
    java计算机毕业设计中美医院病历管理系统源代码+系统+数据库+lw文档
    接口 vs 抽象类:如何在Java中做出正确的选择
    河南开放大学与电大搜题微信公众号:携手共进,助力学习之路
    几种后端开发中常用的语言。
  • 原文地址:https://blog.csdn.net/m0_57781768/article/details/133038956