摘要
本文提出了一种基于GameMaker Studio(GMS)的轻量级实体组件系统(ECS)实现方案,通过利用GMS的原生对象系统、事件驱动机制和基础变量类型,构建模块化、可扩展的游戏开发框架。
该框架在保留ECS核心思想(数据与逻辑分离、组件化设计)的同时,规避了复杂数据结构(如map、list)的使用,降低了学习成本,适合初学者学习gamemaker的使用方法。
1. 引言
1.1 背景与动机
GameMaker Studio以其直观的拖拽式开发和事件驱动机制受到广泛欢迎,但其传统对象导向(OOP)开发模式在复杂项目中易导致代码耦合度高、可维护性差。
ECS架构通过分离数据(组件)、行为(系统)和实体标识符,成为现代游戏开发的主流选择。然而,GMS的脚本系统对ECS原生支持有限。
本文旨在探索一种基于GMS基础功能的ECS实现方案,兼顾开发效率与架构优势。
1.2 目标与贡献
目标:在GMS中实现轻量级ECS框架,无需依赖高级数据结构(如ds_map/ds_list)。
贡献:
提供一种基于对象实例绑定、事件驱动的ECS实现方案,降低新手学习门槛。
通过案例验证,展示框架在角色移动、动画、攻击等场景中的适用性。
提出组件间协作的优化策略,减少传统OOP模式的耦合问题。
2. 相关技术概述
2.1 ECS架构核心概念
实体(Entity):唯一标识符,通过对象实例ID表示。
组件(Component):数据容器,如Position、Health、Animation等,通过对象属性存储。
系统(System):逻辑处理器,如移动系统、碰撞检测系统,通过全局脚本或步事件实现。
2.2 GameMaker基础功能
对象实例化:通过instance_create创建实体与组件实例。
事件驱动:利用创建事件、步事件等管理组件生命周期。
变量绑定:通过实例变量直接引用其他对象实例ID(如oEntity.main)。
2.3 本框架实现方式
实体(Entity):通过oEntity对象实例的唯一ID标识,存储组件引用(如oEntity.attr = oAttr)。
组件(Component):每个组件为独立对象(如oColl),其变量直接存储数据(如oColl.mask_index)。
系统(System):通过全局对象oGame的步事件统一调度组件逻辑(如调用oColl.move())。
3. 框架设计与实现
3.1 核心架构设计
3.1.1 实体(Entity)
主对象:oEntity作为实体容器,通过实例变量绑定所有组件。
实例化流程:
创建oAttr实例。
通过oAttr的变量定义初始化实体需要绑定的组件(oEntity, oColl, oAnim, oWpn)。
在oAttr的创建事件中,实例化组件,并将组件实例ID存储在oEntity的变量中(如oEntity.attr =oAttr)。
3.1.2 组件(Components)
组件对象
职责
关键变量
oAttr | 属性存储、引导实体实例化 | 组件对象:Entity, oColl, oAnim, oWpn 实体属性:health, speed, maxHp 创建函数:create_entity() |
oColl | 移动实体,碰撞逻辑 | 碰撞盒:mask_index 移动逻辑:move() |
oAnim | 动画控制,受击碰撞 | 碰撞盒:mask_index 动画切换:anim() |
oWpn | 对象交互,创建实例 | 攻击碰撞盒:bulletPrefab 攻击逻辑:wpn() |
3.1.3 系统(System)
全局控制系统:通过oGame对象的步事件遍历所有oEntity实例,调用组件逻辑。
示例代码:
// oGame步事件:执行所有实体的系统逻辑 with (oEntity) { ctrl(); // 调用实体主逻辑 }
系统逻辑分离:将组件行为封装为独立脚本(如move()、wpn()),通过with语句统一调度。
3.2 组件间协作
数据访问:通过主对象oEntity的变量直接访问组件属性。
//在属性组件(oAttr)的变量定义中,初始化实体的类型 entity=oEntity; coll=oColl; // 在属性组件(oAttr)的创建事件中,为碰撞组件绑定实体id entity=instance_create(entity); coll=instance_create(coll); coll.entity=entity; //================================================== // 在移动组件(oColl)中获取实体速度 var speed = entity.attr.speed; // 通过oAttr组件获取速度
事件触发:利用with语句或者对象事件实现组件间通信。
3.3 实现步骤
创建组件对象:为每个组件定义独立对象(如oAttr),在变量定义中初始化变量。
绑定组件到实体:在oAttr的创建事件中实例化组件,并把组件的ID绑定到oEntity的变量中。
实现组件逻辑:在组件对象的创建事件或自定义脚本中编写行为代码(如oColl的移动逻辑)。
系统调度:通过全局对象(如oGame)的步事件统一调用组件逻辑。
4. 案例分析:敌人实体实现
4.1 组件配置
属性组件(oAttr_enm):
//变量定义 entity=oEntity_enm; coll=oColl_enm; anim=oAnim_enm; wpn=oWpn_enm; health=100; speed=2;
移动组件(oColl_enm):实现基于速度的移动逻辑和碰撞检测。
// oColl_enm的移动逻辑(在创建事件中绑定) function move() { // 接收实体属性 var _attr = entity.attr; var _speed = _attr.speed; //控制方向 var _dx=keyboard_check(vk_right)-keyboard_check(vk_left); // 碰撞检测 move_and_collide(_dx*_speed ,0,oWall); //同步实体和各组件的坐标位置,需要单独创建脚本 move_ent(x,y); }
动画组件(oAnim_enm):根据移动方向切换精灵x轴方向。
// oAnim_enm的动画逻辑(在创建事件中绑定) function anim() { // 根据移动方向切换帧 if (xprevious != x) { image_speed = 0.1; if (x > xprevious) image_xscale = 1; else image_xscale = -1; } }
武器组件(oWpn_enm):定义攻击间隔和子弹生成逻辑。
// oWpn_enm的攻击逻辑(在创建事件中绑定) function wpn() {};
4.2 系统集成
实体控制逻辑(oEntity_enm):
//oEntity_enm创建事件:设计实体的系统逻辑 ctrl=function() { with coll {move();} with wpn {wpn();} with anim {anim();} }
5. 对比与讨论
5.1 与传统ECS的差异
特性
本方法
传统ECS(如Unity)
数据存储 | 实例变量 | 独立数据结构(如Buffer) |
逻辑分离度 | 组件内含部分逻辑 | 系统独立处理逻辑 |
扩展性 | 通过新增对象实现 | 通过添加组件类型 |
性能优化 | 依赖GMS原生优化 | 依赖专用调度器 |
5.2 优势与局限
优势:
学习成本低:开发者无需学习复杂数据结构,直接使用GMS原生对象与事件。
调试直观:通过实例变量直接查看组件状态(如oEntity.attr.health)。
轻量级:框架代码结构简单,适合快速原型开发。
局限:
组件逻辑耦合:组件需自行管理数据访问,可能引入依赖。
性能瓶颈:大规模对象(如1000+)时,with遍历可能影响帧率。
6. 结论与展望
本文提出的框架通过GameMaker的原生功能实现了ECS的核心思想,为小型游戏项目提供了模块化、易维护的开发方案。未来可进一步优化方向包括:
- 数据结构优化:引入轻量级数组或自定义“组件池”提升性能。
- 系统解耦:将组件逻辑完全移至独立系统对象。
- 扩展性增强:支持组件热加载与动态绑定。
参考文献
[1] Unity官方文档:ECS架构实现指南(2023)
[2] GameMaker Studio官方手册:对象与事件系统
[3]通义千问:深度思考功能
附录
附录A:框架代码结构示意图
实体和组件 oEntity (主体组件) ├── oAttr (属性组件) ├── oColl (移动与碰撞组件) ├── oAnim (动画组件) ├── oWpn (攻击组件) └── (功能拓展) 实体控制 oGame (全局系统调度器)
暂无关于此日志的评论。