Java 享元模式:打造高扩展游戏角色模型,优化 MMO 游戏开发

🧑 博主简介:CSDN博客专家历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程高并发设计Springboot和微服务,熟悉LinuxESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
技术合作请加本人wx(注明来自csdn):foreast_sea

在这里插入图片描述


在这里插入图片描述

Java 享元模式:打造高扩展游戏角色模型,优化 MMO 游戏开发

一、引言

在当今的游戏开发领域,大型多人在线游戏(MMO)备受玩家喜爱。这类游戏往往构建了宏大的虚拟世界,其中充斥着海量的游戏角色,例如各种怪物。以常见的哥布林怪物为例,在游戏场景中可能会同时出现成百上千个。如果按照传统的设计思路,每个哥布林都独立存储其所有数据,包括模型数据,这将给游戏的内存管理带来巨大挑战。

在游戏开发中,内存的有效利用直接关系到游戏的性能和玩家体验。当大量相同类型的角色充斥在游戏场景时,数据冗余问题就会凸显出来。就像哥布林这种外观模型基本相同的怪物,它们的身体形状、纹理、颜色等外观细节在同一类型中是固定的。如果为每个哥布林都单独存储一份完整的模型数据,就如同在制造产品时,为每个相同的产品都重新制作一个模具,这显然是一种资源浪费。

享元模式的出现,为解决这一问题提供了巧妙的思路。它的核心在于区分对象的内在状态外在状态

内在状态是可以被共享的部分,对于哥布林来说,其模型数据就是内在状态;外在状态则是每个对象独有,如哥布林在游戏场景中的位置、生命值等。通过享元模式,我们可以将哥布林的模型数据提取出来,只存储一份,然后让所有的哥布林实例共享这份数据,同时各自维护自己的外在状态信息。这样一来,不仅能够显著减少内存占用,提高内存使用效率,还能为游戏的扩展性奠定坚实基础。在后续的游戏开发过程中,当需要添加更多同类型的怪物或者对怪物模型进行修改时,只需要操作共享的模型数据即可,大大简化了开发流程,降低了维护成本

接下来,我们将深入探讨享元模式在 MMO 游戏角色模型开发中的应用细节。

二、享元模式概述

享元模式是一种结构型设计模式,它旨在通过共享对象来减少内存使用和提高性能。其主要思想是将对象的状态分为内部状态外部状态

(一)内部状态

内部状态是对象可共享的部分,它不随对象的外部环境变化而变化。在游戏角色模型的例子中,如哥布林的模型数据,包括身体形状、纹理、颜色等,这些属性对于所有同类型的哥布林来说是固定不变的,属于内部状态。内部状态通常存储在享元对象内部,并且可以被多个对象实例共享。

(二)外部状态

外部状态是对象依赖于具体场景而变化的部分。对于哥布林而言,其在游戏场景中的位置、生命值、当前的行为状态(如攻击、逃跑、巡逻)等都属于外部状态。外部状态不能被共享,每个对象实例都需要单独维护自己的外部状态信息。

通过将内部状态和外部状态分离,享元模式使得在创建大量相似对象时,可以共享相同的内部状态,从而减少内存开销。例如,在游戏中创建多个哥布林对象时,只需要创建一个共享的哥布林模型数据(内部状态),而每个哥布林的位置和生命值(外部状态)则分别存储在各自的对象实例中。

三、享元模式在 MMO 游戏中的应用场景分析

(一)怪物模型数据的共享

MMO 游戏中,同一种类的怪物往往具有相同的外观模型。以哥布林为例,它们的 3D 模型文件包含了身体各个部分的形状纹理贴图颜色配置等信息。如果不采用享元模式,每一个哥布林实例都将独立持有一份完整的模型数据副本。假设游戏中有 1000 个哥布林在不同的场景区域出现,那么内存中将会存储 1000 份相同的哥布林模型数据,这无疑是对内存资源的极大浪费。

而利用享元模式,我们可以将哥布林的模型数据作为内部状态进行共享。只需要在内存中加载一份哥布林模型数据,然后所有的哥布林实例都可以引用这份数据。这样,无论游戏中有多少个哥布林,模型数据在内存中只存在一份,大大减少了数据冗余,提高了内存利用率。

(二)游戏场景的动态性与扩展性

MMO 游戏的场景是动态变化的,怪物会在场景中移动、战斗、死亡等。这些变化涉及到怪物的外部状态,如位置、生命值、状态等。通过享元模式,我们可以方便地处理这种动态性。当一个哥布林的位置发生变化时,只需要更新该哥布林实例对应的外部状态信息,而不需要对共享的模型数据进行任何修改。

在游戏开发过程中,扩展性也是一个重要的考量因素。如果需要添加一种新的怪物类型,如兽人,我们可以按照享元模式的设计思路,创建兽人共享的模型数据,并为每个兽人实例设置独立的外部状态。这种设计使得游戏在添加新的角色类型时,能够更加灵活高效,减少了对整体架构的冲击

四、享元模式实现 MMO 游戏角色模型的步骤

(一)创建享元工厂类

享元工厂类负责创建和管理享元对象。它的主要职责是确保相同内部状态的对象只被创建一次,并提供获取享元对象的方法。以下是一个简单的享元工厂类示例代码:

import java.util.HashMap;
import java.util.Map;

// 享元工厂类,用于创建和管理怪物模型享元对象
public class MonsterModelFactory {

    // 用于存储已经创建的享元对象,键为模型标识,值为享元对象
    private static Map<String, MonsterModel> modelMap = new HashMap<>();

    // 获取怪物模型享元对象的方法
    public static MonsterModel getMonsterModel(String modelId) {
        MonsterModel model = modelMap.get(modelId);
        // 如果模型不存在,则创建新的模型并存储到 map 中
        if (model == null) {
            switch (modelId) {
                case "goblin":
                    // 创建哥布林模型对象,这里可以是加载 3D 模型文件等操作
                    model = new GoblinModel();
                    break;
                default:
                    throw new IllegalArgumentException("Invalid modelId: " + modelId);
            }
            modelMap.put(modelId, model);
        }
        return model;
    }
}

在上述代码中,MonsterModelFactory 类维护了一个 modelMap,用于存储已经创建的怪物模型享元对象。getMonsterModel 方法根据传入的 modelId(模型标识)来获取对应的怪物模型。如果指定 modelId 的模型尚未创建,则根据 modelId 创建相应的模型对象(如 GoblinModel),并将其存储到 modelMap 中,然后返回该模型对象。

(二)定义怪物模型抽象类或接口

创建一个抽象类或接口来表示怪物模型享元对象,该抽象类或接口定义了与怪物模型相关的操作方法。例如:

// 怪物模型抽象类
public abstract class MonsterModel {

    // 抽象方法,用于绘制怪物模型
    public abstract void draw();
}

在这个示例中,MonsterModel 是一个抽象类,它定义了一个抽象方法 draw,用于绘制怪物模型。具体的怪物模型类(如 GoblinModel)将继承自这个抽象类并实现 draw 方法。

(三)创建具体的怪物模型类

根据不同的怪物类型,创建具体的怪物模型类,这些类继承自怪物模型抽象类或实现怪物模型接口,并实现其抽象方法。以哥布林模型类为例:

// 哥布林怪物模型类,继承自 MonsterModel 抽象类
public class GoblinModel extends MonsterModel {

    @Override
    public void draw() {
        // 这里是绘制哥布林模型的具体代码,可能涉及到图形渲染库的调用
        System.out.println("Drawing a goblin model");
    }
}

GoblinModel 类中,实现了 draw 方法,该方法包含了绘制哥布林模型的具体逻辑,这里简单地打印了一条信息表示绘制操作,在实际的游戏开发中,可能会涉及到使用图形渲染库(如 OpenGL 或 DirectX)来加载和绘制 3D 模型文件。

(四)创建怪物实例类

怪物实例类表示游戏中的单个怪物对象,它包含了对怪物模型享元对象的引用以及自身的外部状态信息。例如:

// 怪物实例类
public class Monster {

    // 怪物模型享元对象引用
    private MonsterModel model;
    // 怪物的外部状态:位置
    private int x;
    private int y;
    // 怪物的外部状态:生命值
    private int health;

    // 构造函数,传入怪物模型和初始位置
    public Monster(MonsterModel model, int x, int y) {
        this.model = model;
        this.x = x;
        this.y = y;
        this.health = 100; // 初始生命值为 100
    }

    // 移动怪物的方法,改变其外部状态(位置)
    public void move(int newX, int newY) {
        this.x = newX;
        this.y = newY;
        System.out.println("Monster moved to (" + x + ", " + y + ")");
    }

    // 怪物受到攻击的方法,改变其外部状态(生命值)
    public void takeDamage(int damage) {
        this.health -= damage;
        if (health <= 0) {
            System.out.println("Monster is dead");
        } else {
            System.out.println("Monster took " + damage + " damage. Remaining health: " + health);
        }
    }

    // 绘制怪物的方法,调用怪物模型的 draw 方法
    public void draw() {
        model.draw();
    }
}

Monster 类中,通过构造函数传入怪物模型享元对象和初始位置信息,同时初始化生命值。move 方法用于改变怪物的位置,takeDamage 方法用于处理怪物受到攻击时生命值的减少,draw 方法则调用怪物模型的 draw 方法来绘制怪物模型。

(五)在游戏场景中使用享元模式

在游戏场景类中,我们可以创建怪物实例并使用享元模式来管理怪物模型。以下是一个简单的游戏场景类示例:

// 游戏场景类
public class GameScene {

    public static void main(String[] args) {
        // 获取哥布林模型享元对象
        MonsterModel goblinModel = MonsterModelFactory.getMonsterModel("goblin");

        // 创建多个哥布林怪物实例,它们共享相同的哥布林模型
        Monster goblin1 = new Monster(goblinModel, 10, 20);
        Monster goblin2 = new Monster(goblinModel, 30, 40);

        // 绘制怪物
        goblin1.draw();
        goblin2.draw();

        // 移动怪物
        goblin1.move(50, 60);
        goblin2.move(70, 80);

        // 怪物受到攻击
        goblin1.takeDamage(20);
        goblin2.takeDamage(30);
    }
}

GameScene 类的 main 方法中,首先通过 MonsterModelFactory 获取哥布林模型享元对象,然后创建两个哥布林怪物实例 goblin1goblin2,它们共享同一个哥布林模型。接着分别对怪物进行绘制、移动和攻击操作,这些操作分别调用了怪物实例类中的相应方法。

五、享元模式的优势总结

(一)内存优化

如前面所述,通过共享怪物模型数据(内部状态),大大减少了内存中数据的冗余存储。在大规模的 MMO 游戏中,有成千上万的同类型怪物时,这种内存优化效果尤为显著。例如,原本需要为每个哥布林存储一份完整的模型数据,采用享元模式后只需要存储一份,这对于内存资源紧张的游戏开发来说是非常关键的。

(二)提高性能

由于减少了内存的占用和数据的重复加载,游戏的整体性能得到了提升。在游戏运行过程中,内存的高效利用可以减少内存交换和数据读取的时间,使得游戏更加流畅。例如,当游戏场景切换或者怪物大量生成时,由于享元模式已经预先加载并共享了模型数据,能够快速地创建怪物实例并进行渲染,减少了玩家的等待时间。

(三)增强扩展性

在游戏开发过程中,经常需要添加新的怪物类型或者修改现有怪物的模型。享元模式使得这种扩展变得更加容易。只需要创建新的怪物模型享元对象并在享元工厂中进行注册,就可以在游戏中使用新的怪物类型。而对于现有怪物模型的修改,只需要修改共享的模型数据,所有相关的怪物实例都会受到影响,无需逐个修改每个怪物的模型数据。

六、享元模式的潜在问题与应对策略

(一)线程安全问题

在多线程环境下,如果多个线程同时访问享元工厂类来获取享元对象,可能会出现线程安全问题。例如,两个线程同时判断某个模型对象不存在,然后都尝试创建该对象,这可能会导致重复创建相同的享元对象,破坏了享元模式的共享原则。

应对策略:可以在享元工厂类的获取享元对象的方法上添加同步锁(synchronized),确保同一时间只有一个线程能够进行模型对象的创建和获取操作。例如:

public static synchronized MonsterModel getMonsterModel(String modelId) {
    // 原有代码逻辑不变
}

然而,这种简单的同步方式可能会导致性能瓶颈,尤其是在高并发的游戏场景中。另一种更好的方式是采用双重检查锁定(Double-Checked Locking)机制,结合 volatile 关键字来确保线程安全且提高性能。示例代码如下:

private volatile static Map<String, MonsterModel> modelMap = new HashMap<>();

public static MonsterModel getMonsterModel(String modelId) {
    MonsterModel model = modelMap.get(modelId);
    if (model == null) {
        synchronized (MonsterModelFactory.class) {
            model = modelMap.get(modelId);
            if (model == null) {
                switch (modelId) {
                    case "goblin":
                        model = new GoblinModel();
                        break;
                    default:
                        throw new IllegalArgumentException("Invalid modelId: " + modelId);
                }
                modelMap.put(modelId, model);
            }
        }
    }
    return model;
}

在上述代码中,首先进行一次非同步的检查,如果模型对象已经存在则直接返回。如果不存在,则进入同步块再次检查,这是为了防止在第一次检查后到进入同步块之间有其他线程已经创建了该模型对象。在同步块内创建模型对象并存储到 modelMap 中,最后返回模型对象。volatile 关键字用于确保 modelMap 的可见性,防止指令重排序导致的错误。

(二)对象池管理复杂度

随着游戏的运行,可能会创建大量的享元对象,如何有效地管理这些对象池成为一个问题。如果不进行合理的管理,可能会导致内存泄漏或者内存碎片化。

应对策略:可以定期对享元对象池进行清理,删除长时间未被使用的享元对象。例如,可以为每个享元对象设置一个引用计数器,当某个对象的引用计数器为 0 且超过一定时间未被使用时,将其从对象池中删除。同时,可以采用一些内存优化算法,如内存碎片整理算法,来优化内存的使用。

七、结论

在 MMO 游戏开发中,享元模式为处理大量同类型游戏角色模型提供了一种高效、可扩展的解决方案。通过合理地分离游戏角色的内部状态(可共享的模型数据)和外部状态(位置、生命值等),并利用享元工厂来创建和管理享元对象,能够显著减少内存占用,提高游戏性能,同时增强游戏开发的扩展性。然而,在应用享元模式时,也需要注意线程安全和对象池管理等潜在问题,并采用相应的应对策略。随着游戏开发技术的不断发展,享元模式将继续在优化游戏资源管理、提升玩家体验等方面发挥重要作用。

八、参考资料文献

[1] 《设计模式:可复用面向对象软件的基础》(Erich Gamma 等著)
[2] Java 官方文档:https://docs.oracle.com/javase/8/docs/api/
[3] 游戏开发相关技术博客和论坛,如 Gamasutra:https://www.gamasutra.com/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/930871.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【精选】AI Coding 新范式:Windsurf、Cursor、Coze齐上阵

2AGI.NET | 探索 AI 无限潜力&#xff0c;2AGI 为您带来最前沿资讯。 随着人工智能技术的飞速发展&#xff0c;AI Coding领域迎来了前所未有的变革。Codeium的Windsurf、Cursor的agent模式更新、Copilot的新版本以及Coze的AI应用能力&#xff0c;都在推动着编程领域的创新。本期…

我的世界网易版安装Continuum光影

先去官网下载这个光影压缩包&#xff0c;下载完不要解压&#xff08;网慢的开加速器&#xff09; Downloads - Continuum Graphics 进去后选最新的版本号下载就行&#xff1a; 下载好后&#xff0c;先不管他&#xff0c;通过网易游戏启动器打开我的世界 觉得自己电脑性能将将…

使用uniapp开发小程序场景:在百度地图上调用接口返回的设备相关信息并展示

首先在百度地图开发者平台注册微信小程序开发密钥下载百度地图SDK-bmap-wx.min.js,下载地址在项目入口index.html页面进行引入页面中进行调用&#xff0c;代码示例如下<map id"map" longitude"108.95" latitude"34.34" scale"3" :m…

Push an existing folder和Push an existing Git repository的区别

Push an existing folder 和 Push an existing Git repository 是在使用 Git 服务&#xff08;如 GitHub、GitLab、Bitbucket 等&#xff09;时两个常见的操作选项。它们的区别主要体现在项目的初始化和版本控制状态上&#xff1a; 1. Push an existing folder 适用场景&#…

机器学习详解(2):线性回归之理论学习

文章目录 1 监督学习2 线性回归2.1 简单/多元线性回归2.2 最佳拟合线2.3 成本函数和梯度下降2.4 线性回归的假设2.5 线性回归的评估指标函数 3 总结 机器学习是人工智能的一个分支&#xff0c;主要致力于开发能够从数据中学习并进行预测的算法和统计模型。线性回归是机器学习的…

ASP.NET Core8.0学习笔记(二十五)——EF Core Include导航数据加载之预加载与过滤

一、导航属性数据加载 1.在EF Core中可以使用导航属性来加载相关实体。 2.加载实体的三种方式&#xff1a; (1)预先加载&#xff1a;直接在查询主体时就把对应的依赖实体查出来&#xff08;作为初始查询的一部分&#xff09; (2)显式加载&#xff1a;使用代码指示稍后显式的从…

QT实战--带行号的支持高亮的编辑器实现(1)

本文主要介绍了基于QPlainTextEdit实现的&#xff0c;带有行号的,支持高亮的编辑器实现&#xff0c;话不多说&#xff0c;先上效果图&#xff1a; 1.行号头文件&#xff1a;linenumberarea.h #ifndef LINENUMBERAREA_H #define LINENUMBERAREA_H#include <QWidget> #inc…

基于Matlab的卷积神经网络(CNN)苹果分级检测系统

本研究提出了一种基于卷积神经网络&#xff08;CNN&#xff09;的自动化苹果分级系统&#xff0c;该系统根据苹果的视觉特征进行分类。系统采用了预训练的深度学习模型&#xff0c;使用包含不同等级苹果的图像数据集进行训练。研究方法包括图像预处理、特征提取和苹果等级分类。…

华为、华三交换机纯Web下如何创关键VLANIF、操作STP参数

华为交换机WEB操作 使用的是真机S5735&#xff0c;目前主流的版本都适用&#xff08;V1R5~V2R1的就不在列了&#xff0c;版本太老了&#xff0c;界面完全不一样&#xff0c;这里调试线接的console口&#xff0c;电脑的网络接在ETH口&#xff09; 「模拟器、工具合集」复制整段内…

ACM:均分纸牌

主要思路 整体思路概述&#xff1a; 本题旨在解决给定N堆纸牌&#xff08;纸牌总数是N的倍数&#xff09;&#xff0c;通过按照特定移牌规则移动纸牌&#xff0c;找出用最少移动次数使每堆纸牌数量相等的方法。程序采用了一种逐步调整的思路&#xff0c;先计算出每堆纸牌应有的…

定时任务——xxl-job源码解析

摘要 本文深入解析了xxl-job的源码&#xff0c;xxl-job是一个分布式任务调度平台&#xff0c;其核心设计思想是将调度行为抽象成“调度中心”&#xff0c;而任务逻辑则由“执行器”处理&#xff0c;实现调度与任务的解耦。文章详细介绍了调度器和执行器的初始化流程、任务执行…

vxe-table 键盘操作,设置按键编辑方式,支持覆盖方式与追加方式

vxe-table 全键盘操作&#xff0c;按键编辑方式设置&#xff0c;覆盖方式与追加方式&#xff1b; 通过 keyboard-config.editMode 设置按键编辑方式&#xff1b;支持覆盖方式编辑和追加方式编辑 安装 npm install vxe-pc-ui4.3.15 vxe-table4.9.15// ... import VxeUI from v…

乾元通渠道商中标福州市人防信息化建设项目

乾元通渠道商中标福州市人防信息化建设项目&#xff0c;乾元通作为应急通讯设备厂家&#xff0c;为项目提供车载版多链路聚合通信保障设备 QYT-X1s。 青岛乾元通数码科技有限公司作为国家应急产业企业&#xff0c;深耕于数据调度算法研究&#xff0c;参与了多项国家及省部级信息…

【深度学习|地学应用-地震气溶胶异常解析3】气溶胶异常是地震的前兆现象之一!地震是如何影响气溶胶浓度和分布的异常变化的呢,我们该如何分析?

【深度学习|地学应用-地震气溶胶异常解析3】气溶胶异常是地震的前兆现象之一&#xff01;地震是如何影响气溶胶浓度和分布的异常变化的呢&#xff0c;我们该如何分析&#xff1f; 【深度学习|地学应用-地震气溶胶异常解析3】气溶胶异常是地震的前兆现象之一&#xff01;地震是…

C++析构函数和构造函数

一、构造函数 1.构造函数的基本概念 1.对构造函数的理解&#xff1a; 构造函数是类的一种特殊成员函数&#xff0c;其主要功能是在创建对象时进行初始化操作。它的名字与类名相同&#xff0c;并且没有返回值类型&#xff08;不能是void&#xff09;。例如&#xff0c;对于一个…

【python自动化四】日志打印

我们在进行自动化测试时&#xff0c;需要打印过程日志和结果日志等&#xff0c;这里记录下日志的相关配置。这里我们直接自己新建一个logger。 先贴上日志代码如下&#xff0c;可根据需要修改&#xff1a; import logging import os import timefrom logging.handlers import …

鸿蒙分享(一):添加模块,修改app名称图标

码仓库&#xff1a;https://gitee.com/linguanzhong/share_harmonyos 鸿蒙api:12 新建公共模块common 在entry的oh-package.json5添加dependencies&#xff0c;引入common模块 "dependencies": {"common": "file:../common" } 修改app名称&…

IDE如何安装插件实现Go to Definition

项目背景 框架&#xff1a;Cucumber Cypress 语言&#xff1a;Javascript IDE&#xff1a;vscode 需求 项目根目录cypress-automation的cypress/integration是测试用例的存放路径&#xff0c;按照不同模块不同功能创建了很多子目录&#xff0c;cucumber测试用例.feature文…

【机器学习】CatBoost 模型实践:回归与分类的全流程解析

一. 引言 本篇博客首发于掘金 https://juejin.cn/post/7441027173430018067。 PS&#xff1a;转载自己的文章也算原创吧。 在机器学习领域&#xff0c;CatBoost 是一款强大的梯度提升框架&#xff0c;特别适合处理带有类别特征的数据。本篇博客以脱敏后的保险数据集为例&#x…

HTML Input 文件上传功能全解析:从基础到优化

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…