《游戏开发设计模式》课程大作业试卷
二、设计方案论文写作框架
2. 摘要
本项目旨在开发一个基于Raylib库的双人水果切割游戏,通过C#语言实现。游戏的主要功能包括水果的生成、下落、切割以及玩家得分的计算。项目采用了多种设计模式:单例模式、策略模式、命令模式、工厂模式、状态模式和观察者模式,以提高代码的可维护性和扩展性。通过本项目,我深入理解了设计模式在实际开发中的应用,并掌握了Raylib库的使用技巧。最终,我成功实现了一个具有良好用户体验的双人水果切割游戏。
3. 目录
- 引言
- 项目背景和意义
- 设计目标
- 开发环境和工具
- 需求分析
- 功能需求
- 非功能需求
- 系统设计
- 总体架构设计
- 设计模式的选择与应用
- UML类图
- 详细设计与实现
- 主要模块的设计与实现
- 设计模式的具体实现
- 总结与体会
- 项目收获
- 存在的问题
- 改进和展望
4. 正文
4.1 引言
-
项目背景和意义
选择水果切割游戏作为项目主题,是因为这类游戏简单易上手,同时具有较高的娱乐性和挑战性。通过切割水果,玩家可以体验到快速反应和精准操作的乐趣。此外,水果切割游戏在移动设备上非常流行,具有广泛的用户基础。本项目的创新点在于结合了Raylib框架和C#语言,并通过设计模式的应用,提高了代码的可维护性和扩展性。预期成果是一个功能完善、性能优越的水果切割游戏,能够为玩家提供良好的游戏体验。
-
设计目标
游戏的功能目标包括:
- 随机生成水果并使其从屏幕顶部下落。
- 玩家1通过鼠标点击切割水果
- 玩家2通过篮子接住水果
- 计算并显示玩家的得分和剩余生命值。
- 提供游戏暂停和恢复功能。
- 游戏结束后显示最终得分并提供重新开始选项。
技术目标包括:
- 使用Raylib框架实现图形渲染和用户输入处理。
- 通过C#语言实现游戏逻辑和数据管理。
- 应用设计模式提高代码的可维护性和扩展性。
用户群体主要是休闲游戏玩家,他们对游戏的简单性和趣味性有较高要求。
-
开发环境和工具
- 开发语言:C#
- 开发工具:Visual Studio 2022
- 图形库:Raylib
4.2 需求分析
-
功能需求
游戏应具备以下功能模块:
- 水果生成模块:随机生成不同类型的水果,并使其从屏幕顶部下落。
- 玩家1输入模块:处理玩家通过鼠标点击点击水果的操作。
- 玩家2输入模块:处理玩家键盘操控篮子处理接住水果的操作。
- 得分计算模块:根据玩家点击或者接住的水果数量计算得分。
- 生命值管理模块:管理玩家的生命值,当水果掉落到屏幕底部时减少生命值。
- 游戏状态管理模块:管理游戏的运行状态,包括运行、暂停和结束状态。
- 游戏暂停模块:在游戏进行中按p键暂停游戏。
- 游戏重置模块:在游戏结束后按p重新开始游戏。
-
非功能需求
- 性能要求:游戏应具有较高的帧率,确保水果生成和下落过程流畅。
- 可用性:游戏界面应简洁直观,易于上手。
- 安全性:确保游戏代码无内存泄漏和崩溃问题。
- 可维护性:代码结构清晰,注释充分,便于后续维护和扩展。
4.3 系统设计
-
总体架构设计
系统采用分层架构设计,主要分为以下几个模块:
- 游戏逻辑层:负责游戏的核心逻辑,包括水果生成、玩家输入处理、得分计算和生命值管理。
- 图形渲染层:负责游戏的图形渲染,使用Raylib框架实现。
- 状态管理层:负责管理游戏的不同状态,如运行、暂停和结束状态。
- 资源管理层:负责加载和管理游戏所需的资源,如纹理和音效。
-
设计模式的选择与应用
1. 单例模式
- 选择理由:单例模式用于确保一个类只有一个实例,并提供一个全局访问点。在游戏中,资源管理器(ResourceManager)需要确保全局只有一个实例,以便统一管理游戏资源。
- 适用性分析:单例模式适用于需要全局唯一实例的场景,如资源管理、配置管理等。在游戏中,资源管理器通过单例模式实现,确保所有资源加载和卸载操作由同一个实例管理。
- 预期解决方案:通过单例模式,定义
ResourceManager
类,确保全局只有一个资源管理器实例,从而实现资源的统一管理。
2. 策略模式
- 选择理由:策略模式用于定义一系列算法,并将每个算法封装起来,使它们可以互换。在游戏中,水果的生成策略可以通过策略模式实现,便于后续添加新的生成策略。
- 适用性分析:策略模式适用于需要在运行时选择不同算法的场景。在游戏中,水果的生成策略可以通过策略模式实现,便于后续添加新的生成策略,如生成不同类型的水果或调整生成频率。
- 预期解决方案:通过策略模式,定义
IFruitGenerationStrategy
接口,并实现RandomFruitGenerationStrategy
类,从而实现水果生成策略的解耦和扩展。
3. 状态模式
- 选择理由:状态模式用于允许对象在其内部状态改变时改变其行为。在游戏中,游戏状态(如运行、暂停和结束)可以通过状态模式实现,便于管理不同状态下的行为。
- 适用性分析:状态模式适用于需要在运行时改变对象行为的场景。在游戏中,游戏状态可以通过状态模式实现,便于管理不同状态下的行为,如暂停状态下的更新和绘制逻辑。
- 预期解决方案:通过状态模式,定义
IGameState
接口,并实现PlayingState
、PausedState
和GameOverState
类,从而实现游戏状态的解耦和扩展。
4. 工厂模式
- 选择理由:工厂模式用于创建对象,而不需要指定具体的类。在游戏中,水果的创建可以通过工厂模式实现,便于后续添加新的水果类型。
- 适用性分析:工厂模式适用于需要创建多个相似对象的场景。在游戏中,水果的创建可以通过工厂模式实现,便于后续添加新的水果类型,如苹果、香蕉和橙子。
- 预期解决方案:通过工厂模式,定义
FruitFactory
类,通过工厂方法创建不同类型的水果,从而实现水果创建的解耦和扩展。
5. 观察者模式(委托)
- 选择理由:观察者模式用于定义对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。在游戏中,玩家的生命值变化可以通过观察者模式实现,便于通知其他模块更新状态。
- 适用性分析:观察者模式适用于需要对象之间松耦合的场景。在游戏中,玩家的生命值变化可以通过观察者模式实现,便于通知其他模块更新状态,如更新UI显示。
- 预期解决方案:通过观察者模式,定义
Player
类,使用委托(Delegate)实现生命值变化的通知机制,从而实现生命值变化的解耦和扩展。
-
UML 类图
- 必须包含
- 清晰展示类的结构、属性、方法和关系
- 标注设计模式的应用位置
4.4 详细设计与实现
主要模块的设计与实现
1. 资源管理模块
-
设计思路:通过
ResourceManager
类实现资源的统一管理。该类采用单例模式,确保全局只有一个资源管理器实例。 -
实现过程:在
ResourceManager
类中定义静态属性Instance
,通过私有构造函数和静态方法LoadResources
和UnloadResources
实现资源的加载和卸载。对图片和音乐进行资源进行管理 -
代码示例:
using Raylib_cs; namespace ProjectBasedOnRaylib.last { class ResourceManager { private static ResourceManager _instance; public static ResourceManager Instance { get { if (_instance == null) _instance = new ResourceManager(); return _instance; } } //水果纹理 public Texture2D appleTexture; public Texture2D bananaTexture; public Texture2D orangeTexture; //篮子 public Texture2D basketTexture; //背景纹理 public Texture2D bg1Texture; public Texture2D bg2Texture; public Texture2D bg3Texture; public Texture2D bg4Texture; public Sound sliceSound; public Sound bgmFruitSound; private ResourceManager() { } //加载资源 public void LoadResources() { //水果图片加载和纹理制作 Image appleImage = Raylib.LoadImage("Assets\\apple.png"); Raylib.ImageResize(ref appleImage, 64, 64); // 调整图像大小为 64x64 appleTexture = Raylib.LoadTextureFromImage(appleImage); Raylib.UnloadImage(appleImage); // 卸载图像,因为我们已经将其转换为纹理 Image bananaImage = Raylib.LoadImage("Assets\\banana.png"); Raylib.ImageResize(ref bananaImage, 64, 64); // 调整图像大小为 64x64 bananaTexture = Raylib.LoadTextureFromImage(bananaImage); Raylib.UnloadImage(bananaImage); Image orangeImage = Raylib.LoadImage("Assets\\orange.png"); Raylib.ImageResize(ref orangeImage, 64, 64); // 调整图像大小为 64x64 orangeTexture = Raylib.LoadTextureFromImage(orangeImage); Raylib.UnloadImage(orangeImage); //篮子 Image basketImage = Raylib.LoadImage("Assets\\basket.png"); Raylib.ImageResize(ref basketImage,180 , 80); // 调整图像大小为 64x64 basketTex`t`ure = Raylib.LoadTextureFromImage(basketImage); Raylib.UnloadImage(basketImage); //背景图片加载和图片制作 //Image bg1Image = Raylib.LoadImage("D:\\code\\cs\\ProjectBasedOnRaylib\\Assets\\bg1.png"); Image bg1Image = Raylib.LoadImage("Assets\\小鸭子.png"); Raylib.ImageResize(ref bg1Image, Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); // 调整图像大小为 屏幕大小 bg1Texture = Raylib.LoadTextureFromImage(bg1Image); Raylib.UnloadImage(bg1Image); // 卸载图像,因为我们已经将其转换为纹理 Image bg2Image = Raylib.LoadImage("Assets\\哆啦A梦.png"); Raylib.ImageResize(ref bg2Image, Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); // 调整图像大小为 屏幕大小 bg2Texture = Raylib.LoadTextureFromImage(bg2Image); Raylib.UnloadImage(bg2Image); // 卸载图像,因为我们已经将其转换为纹理 Image bg3Image = Raylib.LoadImage("Assets\\天空.png"); Raylib.ImageResize(ref bg3Image, Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); // 调整图像大小为 屏幕大小 bg3Texture = Raylib.LoadTextureFromImage(bg3Image); Raylib.UnloadImage(bg3Image); // 卸载图像,因为我们已经将其转换为纹理 Image bg4Image = Raylib.LoadImage("Assets\\猫猫水果车.png"); Raylib.ImageResize(ref bg4Image, Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); // 调整图像大小为 屏幕大小 bg4Texture = Raylib.LoadTextureFromImage(bg4Image); Raylib.UnloadImage(bg4Image); // 卸载图像,因为我们已经将其转换为纹理 sliceSound = Raylib.LoadSound("Assets\\M4.wav"); bgmFruitSound = Raylib.LoadSound("Assets\\bgmFruit.wav"); } //卸载资源 public void UnloadResources() { Raylib.UnloadTexture(appleTexture); Raylib.UnloadTexture(bananaTexture); Raylib.UnloadTexture(orangeTexture); Raylib.UnloadSound(sliceSound); Raylib.UnloadSound(bgmFruitSound); } } }
2. 水果生成模块
-
设计思路:使用工厂模式,通过
RandomFruitGenerationStrategy
类实现水果的随机生成。该类实现了IFruitGenerationStrategy
接口,通过CreateFruit
方法生成不同类型的水果。 -
实现过程:在
Game
类的FruitSpawner
方法中,使用RandomFruitGenerationStrategy
生成水果,并将其添加到fruits
列表中。 -
代码示例:
在Game里调用FruitSpawner()函数添加随机生成的水果
public void FruitSpawner() { Random rand = new Random(); while (!cts.IsCancellationRequested) { Thread.Sleep(rand.Next(200, 800)); IFruitGenerationStrategy strategy = new RandomFruitGenerationStrategy(); Fruit fruit = strategy.CreateFruit(); fruits.Add(fruit); } }
随机生成水果的脚本,把随机生成的水果反射到对应的水果类中
using ProjectBasedOnRaylib.ProjectBasedOnRaylib; using Raylib_cs; using System.Numerics; namespace ProjectBasedOnRaylib.last { class RandomFruitGenerationStrategy : IFruitGenerationStrategy { // 存储不同类型水果的列表 private readonly List<Type> _fruitTypes = new List<Type> { typeof(Apple), typeof(Banana), typeof(Orange) }; // 切水果时的音效 public Sound sliceSound; public Fruit CreateFruit() { // 随机选择一种水果类型 Type fruitType = _fruitTypes[Raylib.GetRandomValue(0, _fruitTypes.Count - 1)]; // 随机生成水果的初始位置,确保水果从屏幕顶部开始下落 Vector2 position = new Vector2(Raylib.GetRandomValue(0, Raylib.GetScreenWidth() - 40), -50); // 随机生成水果的下落速度 float fallSpeed = Raylib.GetRandomValue((int)150.0f, (int)400.0f); // 使用反射创建指定类型的水果实例 return (Fruit)Activator.CreateInstance(fruitType, position, fallSpeed); } } }
水果种类的策略接口
public interface IFruitGenerationStrategy { Fruit CreateFruit(); }
具体的水果类继承IFruitGenerationStrategy接口进行重写
using ProjectBasedOnRaylib.ProjectBasedOnRaylib; using Raylib_cs; using System.Numerics; namespace ProjectBasedOnRaylib.last { class Apple : Fruit { public Apple(Vector2 position, float fallSpeed) : base(position, fallSpeed, ResourceManager.Instance.appleTexture) { } public override void Update() { Position.Y += FallSpeed * Raylib.GetFrameTime(); } public override void Draw() { Raylib.DrawTexture(Texture, (int)Position.X, (int)Position.Y, Color.White); } } }
using ProjectBasedOnRaylib.ProjectBasedOnRaylib; using Raylib_cs; using System.Numerics; namespace ProjectBasedOnRaylib.last { class Banana : Fruit { public Banana(Vector2 position, float fallSpeed) : base(position, fallSpeed, ResourceManager.Instance.bananaTexture) { } public override void Update() { Position.Y += FallSpeed * Raylib.GetFrameTime(); } public override void Draw() { Raylib.DrawTexture(Texture, (int)Position.X, (int)Position.Y, Color.White); } } }
using ProjectBasedOnRaylib.ProjectBasedOnRaylib; using Raylib_cs; using System.Numerics; namespace ProjectBasedOnRaylib.last { class Orange : Fruit { public Orange(Vector2 position, float fallSpeed) : base(position, fallSpeed, ResourceManager.Instance.orangeTexture) { } public override void Update() { Position.Y += FallSpeed * Raylib.GetFrameTime(); } public override void Draw() { Raylib.DrawTexture(Texture, (int)Position.X, (int)Position.Y, Color.White); } } }
3. 玩家2输入模块
-
设计思路:使用命名模式,通过
MoveBasketCommand
类实现篮子的移动操作。该类实现了ICommand
接口,通过Execute
方法处理键盘输入并更新篮子的位置。 -
实现过程:在
Game
类的Update
方法中,创建MoveBasketCommand
对象,并将其传递给Basket
类的Update
方法。 -
代码示例:
ICommand接口
public interface ICommand { void Execute(); }
MoveBasketCommand继承ICommand类并实现Execute()方法
public class MoveBasketCommand : ICommand { private Basket _basket; private float _deltaTime; public MoveBasketCommand(Basket basket, float deltaTime) { _basket = basket; _deltaTime = deltaTime; } public void Execute() { if (Raylib.IsKeyDown(KeyboardKey.Left)) { _basket.Position.X -= _basket.Speed * _deltaTime; } if (Raylib.IsKeyDown(KeyboardKey.Right)) { _basket.Position.X += _basket.Speed * _deltaTime; } // 确保篮子不会移出屏幕 if (_basket.Position.X < 0) { _basket.Position.X = 0; } if (_basket.Position.X + 64 > Raylib.GetScreenWidth()) { _basket.Position.X = Raylib.GetScreenWidth() - 64; } } }
public void Update()
{
float deltaTime = Raylib.GetFrameTime();
ICommand moveCommand = new MoveBasketCommand(_basket, deltaTime);
_basket.Update(moveCommand);
}
4. 生命值管理模块
-
设计思路:通过
Player
类实现玩家的生命值管理。该类采用观察者模式(委托),通过LifeDecreaseHandler
委托实现生命值变化的通知机制。 -
实现过程:在
Player
类中定义LifeDecreaseHandler
委托,并在DecreaseLife
方法中触发该委托,通知其他模块更新状态。 -
代码示例:
public delegate void LifeDecreaseHandler(); public class Player { public int life { get; private set; } = 35; public event LifeDecreaseHandler LifeDecreased; public Player() { LifeDecreased += OnLifeDecreased; } private void OnLifeDecreased() { if (life > 0) { life--; } } public void DecreaseLife() { LifeDecreased?.Invoke(); } }
-
设计模式的具体实现
1. 单例模式
- 实现细节:
ResourceManager
类采用单例模式,通过静态属性Instance
确保全局只有一个资源管理器实例。 - 解决问题:通过单例模式,确保资源管理器全局唯一,从而实现资源的统一管理。
2. 策略模式
- 实现细节:
RandomFruitGenerationStrategy
类实现了IFruitGenerationStrategy
接口,通过CreateFruit
方法生成不同类型的水果。 - 解决问题:通过策略模式,定义
IFruitGenerationStrategy
接口,并实现RandomFruitGenerationStrategy
类,从而实现水果生成策略的解耦和扩展。
3. 状态模式
- 实现细节:
PlayingState
、PausedState
和GameOverState
类实现了IGameState
接口,分别处理游戏的不同状态。 - 解决问题:通过状态模式,定义
IGameState
接口,并实现不同状态类,从而实现游戏状态的解耦和扩展。
4. 工厂模式
- 实现细节:通过
FruitFactory
类实现水果的创建。该类通过工厂方法创建不同类型的水果,如苹果、香蕉和橙子。 - 解决问题:通过工厂模式,定义
FruitFactory
类,通过工厂方法创建不同类型的水果,从而实现水果创建的解耦和扩展。
5. 观察者模式(委托)
- 实现细节:
Player
类采用观察者模式(委托),通过LifeDecreaseHandler
委托实现生命值变化的通知机制。 - 解决问题:通过观察者模式,定义
Player
类,使用委托实现生命值变化的通知机制,从而实现生命值变化的解耦和扩展。
- 实现细节:
4.5 总结与体会
-
项目收获
- 通过本项目,我们深入理解了设计模式在实际开发中的应用,特别是命令模式、策略模式和状态模式。这些设计模式不仅提高了代码的可维护性和扩展性,还使我们能够更灵活地应对需求变化。此外,通过使用Raylib框架和C#语言,我们掌握了图形渲染和用户输入处理的基本技能。
-
存在的问题
- 项目中存在一些不足之处,如水果生成策略的多样性不足,游戏状态管理的复杂度较高。此外,游戏的性能优化仍有提升空间,特别是在水果数量较多时,帧率可能会有所下降。
-
改进和展望
- 未来可以进一步优化水果生成策略,增加更多类型的水果和生成规则。同时,可以引入更多的游戏状态,如难度选择和排行榜功能。在性能方面,可以通过优化渲染逻辑和减少不必要的计算来提高游戏的流畅度。此外,可以考虑将游戏移植到移动设备上,以扩大用户群体。
5. 参考文献
- 列出在项目中参考的所有文献资料,格式规范(必须使用 GB/T 7714-2015 格式)
6. 附录(可选)
-
开发日志
- 附上详细的开发日志或部分关键内容
-
完整代码清单
- 可提供主要代码文件的列表
指导教师:孙力
日期:2024 年 11 月 15 日# 《游戏开发设计模式》课程大作业试卷
二、设计方案论文写作框架
1. 封面
- 学院名称、课程名称、大作业题目
- 学生姓名、学号、班级
- 指导教师姓名
- 日期
2. 摘要
本项目旨在开发一个基于Raylib库的双人水果切割游戏,通过C#语言实现。游戏的主要功能包括水果的生成、下落、切割以及玩家得分的计算。项目采用了多种设计模式:单例模式、策略模式、命令模式、工厂模式、状态模式和观察者模式,以提高代码的可维护性和扩展性。通过本项目,我深入理解了设计模式在实际开发中的应用,并掌握了Raylib库的使用技巧。最终,我成功实现了一个具有良好用户体验的双人水果切割游戏。
3. 目录
- 引言
- 项目背景和意义
- 设计目标
- 开发环境和工具
- 需求分析
- 功能需求
- 非功能需求
- 系统设计
- 总体架构设计
- 设计模式的选择与应用
- UML类图
- 详细设计与实现
- 主要模块的设计与实现
- 设计模式的具体实现
- 总结与体会
- 项目收获
- 存在的问题
- 改进和展望
4. 正文
4.1 引言
-
项目背景和意义
选择水果切割游戏作为项目主题,是因为这类游戏简单易上手,同时具有较高的娱乐性和挑战性。通过切割水果,玩家可以体验到快速反应和精准操作的乐趣。此外,水果切割游戏在移动设备上非常流行,具有广泛的用户基础。本项目的创新点在于结合了Raylib框架和C#语言,并通过设计模式的应用,提高了代码的可维护性和扩展性。预期成果是一个功能完善、性能优越的水果切割游戏,能够为玩家提供良好的游戏体验。
-
设计目标
游戏的功能目标包括:
- 随机生成水果并使其从屏幕顶部下落。
- 玩家1通过鼠标点击切割水果
- 玩家2通过篮子接住水果
- 计算并显示玩家的得分和剩余生命值。
- 提供游戏暂停和恢复功能。
- 游戏结束后显示最终得分并提供重新开始选项。
技术目标包括:
- 使用Raylib框架实现图形渲染和用户输入处理。
- 通过C#语言实现游戏逻辑和数据管理。
- 应用设计模式提高代码的可维护性和扩展性。
用户群体主要是休闲游戏玩家,他们对游戏的简单性和趣味性有较高要求。
-
开发环境和工具
- 开发语言:C#
- 开发工具:Visual Studio 2022
- 图形库:Raylib
4.2 需求分析
-
功能需求
游戏应具备以下功能模块:
- 水果生成模块:随机生成不同类型的水果,并使其从屏幕顶部下落。
- 玩家1输入模块:处理玩家通过鼠标点击点击水果的操作。
- 玩家2输入模块:处理玩家键盘操控篮子处理接住水果的操作。
- 得分计算模块:根据玩家点击或者接住的水果数量计算得分。
- 生命值管理模块:管理玩家的生命值,当水果掉落到屏幕底部时减少生命值。
- 游戏状态管理模块:管理游戏的运行状态,包括运行、暂停和结束状态。
- 游戏暂停模块:在游戏进行中按p键暂停游戏。
- 游戏重置模块:在游戏结束后按p重新开始游戏。
-
非功能需求
- 性能要求:游戏应具有较高的帧率,确保水果生成和下落过程流畅。
- 可用性:游戏界面应简洁直观,易于上手。
- 安全性:确保游戏代码无内存泄漏和崩溃问题。
- 可维护性:代码结构清晰,注释充分,便于后续维护和扩展。
4.3 系统设计
-
总体架构设计
系统采用分层架构设计,主要分为以下几个模块:
- 游戏逻辑层:负责游戏的核心逻辑,包括水果生成、玩家输入处理、得分计算和生命值管理。
- 图形渲染层:负责游戏的图形渲染,使用Raylib框架实现。
- 状态管理层:负责管理游戏的不同状态,如运行、暂停和结束状态。
- 资源管理层:负责加载和管理游戏所需的资源,如纹理和音效。
-
设计模式的选择与应用
1. 单例模式
- 选择理由:单例模式用于确保一个类只有一个实例,并提供一个全局访问点。在游戏中,资源管理器(ResourceManager)需要确保全局只有一个实例,以便统一管理游戏资源。
- 适用性分析:单例模式适用于需要全局唯一实例的场景,如资源管理、配置管理等。在游戏中,资源管理器通过单例模式实现,确保所有资源加载和卸载操作由同一个实例管理。
- 预期解决方案:通过单例模式,定义
ResourceManager
类,确保全局只有一个资源管理器实例,从而实现资源的统一管理。
2. 策略模式
- 选择理由:策略模式用于定义一系列算法,并将每个算法封装起来,使它们可以互换。在游戏中,水果的生成策略可以通过策略模式实现,便于后续添加新的生成策略。
- 适用性分析:策略模式适用于需要在运行时选择不同算法的场景。在游戏中,水果的生成策略可以通过策略模式实现,便于后续添加新的生成策略,如生成不同类型的水果或调整生成频率。
- 预期解决方案:通过策略模式,定义
IFruitGenerationStrategy
接口,并实现RandomFruitGenerationStrategy
类,从而实现水果生成策略的解耦和扩展。
3. 状态模式
- 选择理由:状态模式用于允许对象在其内部状态改变时改变其行为。在游戏中,游戏状态(如运行、暂停和结束)可以通过状态模式实现,便于管理不同状态下的行为。
- 适用性分析:状态模式适用于需要在运行时改变对象行为的场景。在游戏中,游戏状态可以通过状态模式实现,便于管理不同状态下的行为,如暂停状态下的更新和绘制逻辑。
- 预期解决方案:通过状态模式,定义
IGameState
接口,并实现PlayingState
、PausedState
和GameOverState
类,从而实现游戏状态的解耦和扩展。
4. 工厂模式
- 选择理由:工厂模式用于创建对象,而不需要指定具体的类。在游戏中,水果的创建可以通过工厂模式实现,便于后续添加新的水果类型。
- 适用性分析:工厂模式适用于需要创建多个相似对象的场景。在游戏中,水果的创建可以通过工厂模式实现,便于后续添加新的水果类型,如苹果、香蕉和橙子。
- 预期解决方案:通过工厂模式,定义
FruitFactory
类,通过工厂方法创建不同类型的水果,从而实现水果创建的解耦和扩展。
5. 观察者模式(委托)
- 选择理由:观察者模式用于定义对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知并自动更新。在游戏中,玩家的生命值变化可以通过观察者模式实现,便于通知其他模块更新状态。
- 适用性分析:观察者模式适用于需要对象之间松耦合的场景。在游戏中,玩家的生命值变化可以通过观察者模式实现,便于通知其他模块更新状态,如更新UI显示。
- 预期解决方案:通过观察者模式,定义
Player
类,使用委托(Delegate)实现生命值变化的通知机制,从而实现生命值变化的解耦和扩展。
-
UML 类图
- 必须包含
- 清晰展示类的结构、属性、方法和关系
- 标注设计模式的应用位置
4.4 详细设计与实现
主要模块的设计与实现
1. 资源管理模块
-
设计思路:通过
ResourceManager
类实现资源的统一管理。该类采用单例模式,确保全局只有一个资源管理器实例。 -
实现过程:在
ResourceManager
类中定义静态属性Instance
,通过私有构造函数和静态方法LoadResources
和UnloadResources
实现资源的加载和卸载。对图片和音乐进行资源进行管理 -
代码示例:
using Raylib_cs; namespace ProjectBasedOnRaylib.last { class ResourceManager { private static ResourceManager _instance; public static ResourceManager Instance { get { if (_instance == null) _instance = new ResourceManager(); return _instance; } } //水果纹理 public Texture2D appleTexture; public Texture2D bananaTexture; public Texture2D orangeTexture; //篮子 public Texture2D basketTexture; //背景纹理 public Texture2D bg1Texture; public Texture2D bg2Texture; public Texture2D bg3Texture; public Texture2D bg4Texture; public Sound sliceSound; public Sound bgmFruitSound; private ResourceManager() { } //加载资源 public void LoadResources() { //水果图片加载和纹理制作 Image appleImage = Raylib.LoadImage("Assets\\apple.png"); Raylib.ImageResize(ref appleImage, 64, 64); // 调整图像大小为 64x64 appleTexture = Raylib.LoadTextureFromImage(appleImage); Raylib.UnloadImage(appleImage); // 卸载图像,因为我们已经将其转换为纹理 Image bananaImage = Raylib.LoadImage("Assets\\banana.png"); Raylib.ImageResize(ref bananaImage, 64, 64); // 调整图像大小为 64x64 bananaTexture = Raylib.LoadTextureFromImage(bananaImage); Raylib.UnloadImage(bananaImage); Image orangeImage = Raylib.LoadImage("Assets\\orange.png"); Raylib.ImageResize(ref orangeImage, 64, 64); // 调整图像大小为 64x64 orangeTexture = Raylib.LoadTextureFromImage(orangeImage); Raylib.UnloadImage(orangeImage); //篮子 Image basketImage = Raylib.LoadImage("Assets\\basket.png"); Raylib.ImageResize(ref basketImage,180 , 80); // 调整图像大小为 64x64 basketTexture = Raylib.LoadTextureFromImage(basketImage); Raylib.UnloadImage(basketImage); //背景图片加载和图片制作 //Image bg1Image = Raylib.LoadImage("D:\\code\\cs\\ProjectBasedOnRaylib\\Assets\\bg1.png"); Image bg1Image = Raylib.LoadImage("Assets\\小鸭子.png"); Raylib.ImageResize(ref bg1Image, Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); // 调整图像大小为 屏幕大小 bg1Texture = Raylib.LoadTextureFromImage(bg1Image); Raylib.UnloadImage(bg1Image); // 卸载图像,因为我们已经将其转换为纹理 Image bg2Image = Raylib.LoadImage("Assets\\哆啦A梦.png"); Raylib.ImageResize(ref bg2Image, Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); // 调整图像大小为 屏幕大小 bg2Texture = Raylib.LoadTextureFromImage(bg2Image); Raylib.UnloadImage(bg2Image); // 卸载图像,因为我们已经将其转换为纹理 Image bg3Image = Raylib.LoadImage("Assets\\天空.png"); Raylib.ImageResize(ref bg3Image, Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); // 调整图像大小为 屏幕大小 bg3Texture = Raylib.LoadTextureFromImage(bg3Image); Raylib.UnloadImage(bg3Image); // 卸载图像,因为我们已经将其转换为纹理 Image bg4Image = Raylib.LoadImage("Assets\\猫猫水果车.png"); Raylib.ImageResize(ref bg4Image, Raylib.GetScreenWidth(), Raylib.GetScreenHeight()); // 调整图像大小为 屏幕大小 bg4Texture = Raylib.LoadTextureFromImage(bg4Image); Raylib.UnloadImage(bg4Image); // 卸载图像,因为我们已经将其转换为纹理 sliceSound = Raylib.LoadSound("Assets\\M4.wav"); bgmFruitSound = Raylib.LoadSound("Assets\\bgmFruit.wav"); } //卸载资源 public void UnloadResources() { Raylib.UnloadTexture(appleTexture); Raylib.UnloadTexture(bananaTexture); Raylib.UnloadTexture(orangeTexture); Raylib.UnloadSound(sliceSound); Raylib.UnloadSound(bgmFruitSound); } } }
2. 水果生成模块
-
设计思路:使用工厂模式,通过
RandomFruitGenerationStrategy
类实现水果的随机生成。该类实现了IFruitGenerationStrategy
接口,通过CreateFruit
方法生成不同类型的水果。 -
实现过程:在
Game
类的FruitSpawner
方法中,使用RandomFruitGenerationStrategy
生成水果,并将其添加到fruits
列表中。 -
代码示例:
在Game里调用FruitSpawner()函数添加随机生成的水果
public void FruitSpawner() { Random rand = new Random(); while (!cts.IsCancellationRequested) { Thread.Sleep(rand.Next(200, 800)); IFruitGenerationStrategy strategy = new RandomFruitGenerationStrategy(); Fruit fruit = strategy.CreateFruit(); fruits.Add(fruit); } }
随机生成水果的脚本,把随机生成的水果反射到对应的水果类中
using ProjectBasedOnRaylib.ProjectBasedOnRaylib; using Raylib_cs; using System.Numerics; namespace ProjectBasedOnRaylib.last { class RandomFruitGenerationStrategy : IFruitGenerationStrategy { // 存储不同类型水果的列表 private readonly List<Type> _fruitTypes = new List<Type> { typeof(Apple), typeof(Banana), typeof(Orange) }; // 切水果时的音效 public Sound sliceSound; public Fruit CreateFruit() { // 随机选择一种水果类型 Type fruitType = _fruitTypes[Raylib.GetRandomValue(0, _fruitTypes.Count - 1)]; // 随机生成水果的初始位置,确保水果从屏幕顶部开始下落 Vector2 position = new Vector2(Raylib.GetRandomValue(0, Raylib.GetScreenWidth() - 40), -50); // 随机生成水果的下落速度 float fallSpeed = Raylib.GetRandomValue((int)150.0f, (int)400.0f); // 使用反射创建指定类型的水果实例 return (Fruit)Activator.CreateInstance(fruitType, position, fallSpeed); } } }
水果种类的策略接口
public interface IFruitGenerationStrategy { Fruit CreateFruit(); }
具体的水果类继承IFruitGenerationStrategy接口进行重写
using ProjectBasedOnRaylib.ProjectBasedOnRaylib; using Raylib_cs; using System.Numerics; namespace ProjectBasedOnRaylib.last { class Apple : Fruit { public Apple(Vector2 position, float fallSpeed) : base(position, fallSpeed, ResourceManager.Instance.appleTexture) { } public override void Update() { Position.Y += FallSpeed * Raylib.GetFrameTime(); } public override void Draw() { Raylib.DrawTexture(Texture, (int)Position.X, (int)Position.Y, Color.White); } } }
using ProjectBasedOnRaylib.ProjectBasedOnRaylib; using Raylib_cs; using System.Numerics; namespace ProjectBasedOnRaylib.last { class Banana : Fruit { public Banana(Vector2 position, float fallSpeed) : base(position, fallSpeed, ResourceManager.Instance.bananaTexture) { } public override void Update() { Position.Y += FallSpeed * Raylib.GetFrameTime(); } public override void Draw() { Raylib.DrawTexture(Texture, (int)Position.X, (int)Position.Y, Color.White); } } }
using ProjectBasedOnRaylib.ProjectBasedOnRaylib; using Raylib_cs; using System.Numerics; namespace ProjectBasedOnRaylib.last { class Orange : Fruit { public Orange(Vector2 position, float fallSpeed) : base(position, fallSpeed, ResourceManager.Instance.orangeTexture) { } public override void Update() { Position.Y += FallSpeed * Raylib.GetFrameTime(); } public override void Draw() { Raylib.DrawTexture(Texture, (int)Position.X, (int)Position.Y, Color.White); } } }
3. 玩家2输入模块
-
设计思路:使用命名模式,通过
MoveBasketCommand
类实现篮子的移动操作。该类实现了ICommand
接口,通过Execute
方法处理键盘输入并更新篮子的位置。 -
实现过程:在
Game
类的Update
方法中,创建MoveBasketCommand
对象,并将其传递给Basket
类的Update
方法。 -
代码示例:
ICommand接口
public interface ICommand { void Execute(); }
MoveBasketCommand继承ICommand类并实现Execute()方法
public class MoveBasketCommand : ICommand { private Basket _basket; private float _deltaTime; public MoveBasketCommand(Basket basket, float deltaTime) { _basket = basket; _deltaTime = deltaTime; } public void Execute() { if (Raylib.IsKeyDown(KeyboardKey.Left)) { _basket.Position.X -= _basket.Speed * _deltaTime; } if (Raylib.IsKeyDown(KeyboardKey.Right)) { _basket.Position.X += _basket.Speed * _deltaTime; } // 确保篮子不会移出屏幕 if (_basket.Position.X < 0) { _basket.Position.X = 0; } if (_basket.Position.X + 64 > Raylib.GetScreenWidth()) { _basket.Position.X = Raylib.GetScreenWidth() - 64; } } }
public void Update()
{
float deltaTime = Raylib.GetFrameTime();
ICommand moveCommand = new MoveBasketCommand(_basket, deltaTime);
_basket.Update(moveCommand);
}
4. 生命值管理模块
-
设计思路:通过
Player
类实现玩家的生命值管理。该类采用观察者模式(委托),通过LifeDecreaseHandler
委托实现生命值变化的通知机制。 -
实现过程:在
Player
类中定义LifeDecreaseHandler
委托,并在DecreaseLife
方法中触发该委托,通知其他模块更新状态。 -
代码示例:
public delegate void LifeDecreaseHandler(); public class Player { public int life { get; private set; } = 35; public event LifeDecreaseHandler LifeDecreased; public Player() { LifeDecreased += OnLifeDecreased; } private void OnLifeDecreased() { if (life > 0) { life--; } } public void DecreaseLife() { LifeDecreased?.Invoke(); } }
-
设计模式的具体实现
1. 单例模式
- 实现细节:
ResourceManager
类采用单例模式,通过静态属性Instance
确保全局只有一个资源管理器实例。 - 解决问题:通过单例模式,确保资源管理器全局唯一,从而实现资源的统一管理。
2. 策略模式
- 实现细节:
RandomFruitGenerationStrategy
类实现了IFruitGenerationStrategy
接口,通过CreateFruit
方法生成不同类型的水果。 - 解决问题:通过策略模式,定义
IFruitGenerationStrategy
接口,并实现RandomFruitGenerationStrategy
类,从而实现水果生成策略的解耦和扩展。
3. 状态模式
- 实现细节:
PlayingState
、PausedState
和GameOverState
类实现了IGameState
接口,分别处理游戏的不同状态。 - 解决问题:通过状态模式,定义
IGameState
接口,并实现不同状态类,从而实现游戏状态的解耦和扩展。
4. 工厂模式
- 实现细节:通过
FruitFactory
类实现水果的创建。该类通过工厂方法创建不同类型的水果,如苹果、香蕉和橙子。 - 解决问题:通过工厂模式,定义
FruitFactory
类,通过工厂方法创建不同类型的水果,从而实现水果创建的解耦和扩展。
5. 观察者模式(委托)
- 实现细节:
Player
类采用观察者模式(委托),通过LifeDecreaseHandler
委托实现生命值变化的通知机制。 - 解决问题:通过观察者模式,定义
Player
类,使用委托实现生命值变化的通知机制,从而实现生命值变化的解耦和扩展。
- 实现细节:
4.5 总结与体会
-
项目收获
- 通过本项目,我们深入理解了设计模式在实际开发中的应用,特别是命令模式、策略模式和状态模式。这些设计模式不仅提高了代码的可维护性和扩展性,还使我们能够更灵活地应对需求变化。此外,通过使用Raylib框架和C#语言,我们掌握了图形渲染和用户输入处理的基本技能。
-
存在的问题
- 项目中存在一些不足之处,如水果生成策略的多样性不足,游戏状态管理的复杂度较高。此外,游戏的性能优化仍有提升空间,特别是在水果数量较多时,帧率可能会有所下降。
-
改进和展望
- 未来可以进一步优化水果生成策略,增加更多类型的水果和生成规则。同时,可以引入更多的游戏状态,如难度选择和排行榜功能。在性能方面,可以通过优化渲染逻辑和减少不必要的计算来提高游戏的流畅度。此外,可以考虑将游戏移植到移动设备上,以扩大用户群体。
5. 参考文献
- 列出在项目中参考的所有文献资料,格式规范(必须使用 GB/T 7714-2015 格式)
6. 附录(可选)
-
开发日志
- 附上详细的开发日志或部分关键内容
-
完整代码清单
- 可提供主要代码文件的列表