【SFML 贪吃蛇】 FDU_OOP 大作业报告

本项目基于原贪吃蛇游戏,通过C++与SFML框架实现多项功能增强,包括鼠标控制、多类型水果计分、网格背景选择及精灵绘制,提供丰富游戏体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面向对象C++大作业报告

193XXXXXXXX 聂XX

作业简介:修改github上的一个snake游戏项目,添加一些特性和功能,需要满足下面的需求。

  • 贪食蛇的控制

    源代码只支持 4 个方向的运行,增加可以通过鼠标控制贪食蛇的运动。当按下鼠标键时,设 置 一 个 方向 向 量 , 该 方 向 向 量 为 鼠 标 所 在 位 置 (MousePosition) 与 蛇 头 所 在 位 置(SnakePosition)的差值。下一时刻,贪食蛇按照该向量的方向运动;运动的距离为 1 个标准单位。

  • 水果的控制

    源代码只支持 1 种水果,颜色随机且贪食蛇增加的长度固定。现增加黑色、棕色、红、蓝色、绿色、共 5 种水果,且贪食蛇吃了黑色、棕色水果不增加其长度,红色、蓝色、绿色水果增加的长度分别为 3、2、1;增加的长度在贪食蛇的尾部—假设初始是叠加在一起的。 系统随机生成上述 5 种水果,保持黑色和褐色水果所占比例为 25%,其他的占 75%。

  • 绘制精灵版本的贪食蛇

    源代码中的贪食蛇绘制过于简单—仅仅使用了矩形绘制。要求更改贪食蛇的绘制方法,头部使用图片,通过 sprite 进行绘制。

  • 整体界面的修改

    可以修改背景的颜色(提供白色、黑色、褐色三种);允许显示(或关闭显示)网格,网格的颜色可以设置(提供白色、黑色、褐色三种)。

  • 5)理清代码

    代码中,要仔细考虑水果、蛇(蛇头、其他节点)、网格等对象的生命周期,确保你设计的对象周期模型是经济可靠的。

原作的源码:https://github.com/jhpy1024/sfSnake


源代码:

编译环境:

MacOS 10.14.6 Xcode

具体方法:成功安装SFML之后,选择new project,在macOS类别里面选择SFML App。新建项目之后,删除掉默认文件,将本项目中的源代码加入项目文件目录中,将Fonts、Images、Music、Sounds文件夹放入到product的app所在目录中 。直接build即可运行。


按照功能介绍修改情况:
  • 移动方式:

    ​ 增加了鼠标控制蛇移动的功能:用鼠标右键单击游戏界面中的某个位置,蛇会朝着这个方向移动。如果转弯角度过大,可能导致死亡判定。

    ​ 修改思路:原文件中作者对于移动设定了四个方向,我顺势添加了一个方向,代表蛇将按照一个由鼠标单击生成的方向向量前进。这个方向向量会储存在Snake类里。在每次检测到鼠标单击的时候,通过蛇头位置和鼠标位置计算并更新方向向量。而同时,没有撤销原作者方向键控制的代码,因此修改后,仍然可以使用方向键控制蛇的移动。

    相关核心代码(只给出修改部分):

void Snake::handleInput(sf::RenderWindow& window)
{
  	if ...
			...
    else if(sf::Mouse::isButtonPressed(sf::Mouse::Right))//鼠标控制-方向处理
    {
        direction_ = Direction::Mouse_Vec;
        sf::Vector2f head_pos = nodes_[0].getPosition();
        sf::Vector2i mouse_pos = sf::Mouse::getPosition(window);
        mouse_diriction_=SnakeNode::Height*sf::Vector2f(mouse_pos.x-head_pos.x,mouse_pos.y-head_pos.y)/
        sqrt((mouse_pos.x-head_pos.x)*(mouse_pos.x-head_pos.x)+(mouse_pos.y-head_pos.y)*(mouse_pos.y-head_pos.y));
    }
}

void Snake::move()
{
	...
	switch (direction_)
	{
		...
    case Direction::Mouse_Vec://如果刚刚是鼠标点击,则朝着对应方向前进
        nodes_[0].move(mouse_diriction_.x,mouse_diriction_.y);
        break;
	}
}
  • 水果颜色、分数、数量和初始化方式:

    ​ 给水果添加了颜色、分数的属性。通过随机数,保证有3/4概率生成“红、绿、蓝”类别的水果,也就是说这三种颜色各占1/4,红色3分,蓝色2分,绿色1分。有1/8概率生成棕色果实,有1/8概率生成黑色果实。这两种果实都是0分。分数表示吃掉一个果实后蛇增加的长度。

    ​ 由于原作者使用vector来维护果实,因此很容易拓展到多个果实的情况,于是规定场上保持五个果实,每当果实被吃掉后都会刷新出新的果实。

    //为果实随机产生属性的函数,可能比较抽象orz
    sf::Color Fruit::Set_Color()
    {
        if(!(rand()%4))
        {
            color_=(rand()%2? sf::Color::Black : sf::Color(90,57,18));//Brown=(90,57,18)
            score_=0;
        }
        else if(rand()%3)
        {
            color_= (rand()%2? sf::Color::Red : sf::Color::Blue);
            score_= (color_==sf::Color::Red?3:2);
        }
        else
        {
            color_= sf::Color::Green;
            score_= 1;
        }
        return color_;
    }
    
  • 背景、网格、和蛇的外貌

    ​ 新增了背景网格的绘制,还给蛇设计了不同的外貌。这部分源于对sprite精灵的运用,通过载入纹理来实现。背景网格的绘制参见BackGround类。

    ​ 蛇的绘制大致结构和原来相同,只不过使用了sprite。

    ​ 蛇节点初始化的代码如下:

    SnakeNode::SnakeNode(sf::Vector2f position,int indx,sf::Vector2f direction)
    : position_(position),direction_(direction)
    {
        if(!body_tex.loadFromFile(Game::snakebody_loc[MenuScreen2::snake_choose]))
        {
            std::cout<<"Failed to load snakenodes' picture"<<std::endl;
        }
        if(!head_tex.loadFromFile(Game::snakehead_loc[MenuScreen2::snake_choose]))
        {
            std::cout<<"Failed to load snakehead's picture"<<std::endl;
        }
        sprite_.setTexture(indx==0?head_tex:body_tex);
        sprite_.setPosition(position);
        sprite_.setOrigin(Width/2, Height/2);
        sprite_.setRotation(std::atan2(direction.y, direction.x)/(2*3.1415926535)*360.0+90.0);
    }
    

设计贪吃蛇皮肤时的灵感来源: 星之卡比(粉色),吃豆人(黄色),微信群上某头像(蓝色)(逃)

(这部分的素材(像素画)全部是自己设计的)

  • 背景、网格、蛇外貌的选择界面

    ​ 既然上面制作好了素材,自然需要一个类似于菜单的界面来选择素材,且要方便玩家。

    ​ 新增MenuScreen2类来实现对背景、网格、蛇外貌的选择。

    ​ 特性:在选择界面时可以通过按键选择对应的背景、网格、蛇外貌

    ​ 背景颜色:【Q】白色背景 【W】黑色背景 【E】棕色背景

    ​ 网格颜色:【A】白色网格 【S】黑色网格 【D】棕色网格 【F】没有网格(其实就是网格和背景同色)

    ​ 贪吃蛇外貌:【Z】星之卡比【X】吃豆人【C】脸萌头像 😃

    ​ 【space】开始游戏

    ​ 反馈机制:为了给玩家选择以反馈,在按下按键选择后,对应的提示文字的颜色会改变成对应的颜色。

    ​ 具体实现见MenuScreen2.cpp


按照.h文件介绍修改情况:
  • Game.h
namespace sfSnake
{
class Game
{
public:
	Game();
	void run();
	void handleInput();
	void update(sf::Time delta);
	void render();
  sf::Vector2i window_pos();
	static const int Width = 640;
	static const int Height = 480;
	static std::shared_ptr<Screen> Screen;
  static std::string back_loc[3],//背景素材的路径
  			 						 snakehead_loc[3],//蛇头素材的路径
                     cell_loc[3],//网格素材的路径
                     snakebody_loc[3];  //蛇身体的路径
private:
	sf::RenderWindow window_;
	sf::Music bgMusic_;
	static const sf::Time TimePerFrame;
};
}

​ 增加了12个静态的string类型的变量,用于表示纹理素材的路径。在Game.cpp里有对应初始化。

​ 该项目中只有一个该类的实例,生命周期贯穿整个项目。

  • Screen.h
class Screen
{
public:
	virtual void handleInput(sf::RenderWindow& window) = 0;
	virtual void update(sf::Time delta) = 0;
	virtual void render(sf::RenderWindow& window) = 0;
};

​ 未作修改,与原来的一致。

  • MenuScreen.h
namespace sfSnake
{
class MenuScreen : public Screen
{
public:
	MenuScreen();
	void handleInput(sf::RenderWindow& window) override;
	void update(sf::Time delta) override;
	void render(sf::RenderWindow& window) override;
private:
	sf::Font font_;
	sf::Text snakeText_;
	sf::Text text_;
};
}

​ 用于菜单界面的显示,未修改,与原来的一致。

​ 在进入菜单界面时创建一个实例,进入下一个界面或者退出游戏时销毁。

  • MenuScreen2.h
namespace sfSnake
{
class MenuScreen2 : public Screen
{
public:
	MenuScreen2();
	void handleInput(sf::RenderWindow& window) override;
	void update(sf::Time delta) override;
	void render(sf::RenderWindow& window) override;
  static int back_choose,cell_choose,snake_choose;//表示当前选择的蛇、网格、背景的号码
private:
	sf::Font font_;//选择界面的字体
	sf::Text snakeText_,backText_,cellText_;//选择界面的显示文字
};
}

​ 这是自己写的一个类,用于实现对蛇、网格、背景的颜色选择。

​ 实现的基本思路:用三个整数变量表示当前对应颜色的选择,通过检测用户输入来更改当前的选择。为了给予用户反馈,会将对应文字的颜色显示为已选择的颜色。

​ 其中,蛇提供“粉红、黄、蓝”三种颜色,网格和背景提供“白、黑、棕”三种颜色。

​ 界面效果图(例如:蛇选择黄色,背景选择白色、网格选择黑色):

​ 支持关闭网格,实质上是采用了和背景颜色相同的网格素材。

​ 从菜单界面按下空格后创建一个该实例,在游戏开始后销毁。

  • BackGround.h
namespace sfSnake
{
class BackGround
{
public:
    BackGround();
    void setCellColor();//设置网格颜色
    void setBackColor();//设置背景颜色
    void drawCell(sf::RenderWindow& window);//绘制网格
    void drawBack(sf::RenderWindow& window);//绘制背景
private:
    sf::Color cell_color_, back_color_;//网格、背景颜色
    sf::Sprite cell_,back_;//网格、背景的精灵
};
}

​ 这也是新增的一个类,用于处理背景图案和网格。

​ 该类的实例是GameScreen类中的成员,随着GameScreen类的创建而创建,随之销毁而销毁。

  • Fruit.h
namespace sfSnake
{
class Fruit
{
public:
	Fruit(sf::Vector2f position = sf::Vector2f(0, 0));
  sf::Color Set_Color();//给该果实按照某一概率随机设置一个颜色,同时会设置该果实的分数
	void render(sf::RenderWindow& window);
	sf::FloatRect getBounds() const;
 	int Score();//获得该果实的分数
private:
	sf::CircleShape shape_;
  sf::Color color_;//该果实的颜色
  int score_;//该果实对应的分数(增加蛇的长度)
	static const float Radius;
};
}

​ 增加了果实的颜色、分数,并且会在果实创建出来的时候设置果实的颜色和分数。可以通过Score()方法从外部访问该果实的分数,便于计算出蛇的生长长度。

​ 该类的实例会在游戏中创建,并且放在vector中,在蛇吃掉后被销毁。

  • SnakeNode.h
namespace sfSnake
{
class SnakeNode
{
public:
	SnakeNode(sf::Vector2f position = sf::Vector2f(0, 0),int indx=1,sf::Vector2f 		direction = sf::Vector2f(0,-1));//初始化需要给出节点的类型(头or身体)和方向向量
	void setPosition(sf::Vector2f position);
  void setDirection(sf::Vector2f position);//设定方向向量
	void setPosition(float x, float y);
	void move(float xOffset, float yOffset);
	void render(sf::RenderWindow& window);
	sf::Vector2f getPosition() const;
  sf::Vector2f getDirection() const;//获取方向向量
	sf::FloatRect getBounds() const;
	static const float Width;
	static const float Height;
private:
	sf::Vector2f position_,direction_;//增加了方向向量
  sf::Sprite sprite_;//该节点的精灵,用于绘图
};
}

​ 为了实现蛇方块的角度变化,新增了节点的方向成员与访问该成员的变量。为了区分蛇头和蛇身,在该类创建出来的时候就必须决定,0代表蛇头,1代表蛇身。蛇头蛇身会加载不同的纹理,也就是不同的素材。

​ 该类的实例会在游戏开始或者蛇生长的时候被创建,在游戏结束时销毁。

  • Snake.h
namespace sfSnake
{
	enum class Direction
	{
		Left, Right, Up, Down, Mouse_Vec//新增Mouse_Vec表示鼠标矢量
	};
class Snake
{
public:
	Snake();
	void handleInput(sf::RenderWindow& window);
	void update(sf::Time delta);
	void render(sf::RenderWindow& window);
	void checkFruitCollisions(std::vector<Fruit>& fruits);
	bool hitSelf() const;
	unsigned getSize() const;
private:
	void move();
	void grow();//在蛇尾方向上延长出新的节点
  void grow2();//在蛇尾的位置上重叠出新的节点
	void checkEdgeCollisions();
	void checkSelfCollisions();
	void initNodes();
	bool hitSelf_;
	sf::Vector2f position_;
	Direction direction_;//键盘命令给出的前进方向
  sf::Vector2f mouse_diriction_;//鼠标命令给出的前进方向
	sf::SoundBuffer pickupBuffer_;
	sf::Sound pickupSound_;
	sf::SoundBuffer dieBuffer_;
	sf::Sound dieSound_;
	std::vector<SnakeNode> nodes_;//用来放蛇的节点
	static const int InitialSize;
};
}

​ 该类的主要改动是蛇的移动方式和蛇的生长方式。在枚举类型中新增Mouse_Vec表示鼠标控制的方向,新增鼠标控制决定的方向矢量。在鼠标右键单击时,通过计算蛇头位置指向鼠标位置的向量,得到蛇的前进方向,同时据此改变蛇头的方向。

​ 这种修改方法使得既可以用鼠标控制也可以用键盘控制。

​ 新增蛇的堆叠生长方式,如果采用后接式生长,在生长多个长度时会出现怪异的现象,突然冒出一截。因此重写了一个grow2()方法。

​ 该类的实例在游戏开始时创建,游戏结束时销毁。

  • GameScreen.h
namespace sfSnake
{
class GameScreen : public Screen
{
public:
	GameScreen();
	void handleInput(sf::RenderWindow& window) override;
	void update(sf::Time delta) override;
	void render(sf::RenderWindow& window) override;
	void generateFruit();

private:
	Snake snake_;
	std::vector<Fruit> fruit_;
  BackGround back_ground_;//背景网格类的实例
};
}

​ 修改不大,增加了网格类的实例。修改了生成水果的方式,开局生成5个水果,并且在游戏中保持5个水果。避免画面空旷。

​ 该类的实例在游戏界面开始时创建,游戏结束时销毁。

  • GameOverScreen.h
namespace sfSnake
{
class GameOverScreen : public Screen
{
public:
	GameOverScreen(std::size_t score);
	void handleInput(sf::RenderWindow& window) override;
	void update(sf::Time delta) override;
	void render(sf::RenderWindow& window) override;
private:
	sf::Font font_;
	sf::Text text_;
	unsigned score_;
};
}

​ 无改动。

​ 该类的实例在游戏结束时创建,退出游戏或者重新进入菜单时销毁。


演示视频:

【SFML 贪吃蛇】面向对象C++大作业 演示视频


小结:

​ 本次大作业是一次很好的动手机会。在添加功能之前,我阅读了原作者的代码,了解了原作者的实现思路。添加功能的过程中,我阅读文档了解了sfml中许许多多的功能。在绘制素材的时候,我体会到了设计游戏素材的快乐。在实现选择界面类的时候,我很好的承接了原作者的设计思路。收获较多。

​ 不过该项目还可以有很多细节可以去改进优化,比如,此项目的窗口大小必须锁定,那么可以思考如何让玩家自由调节窗口大小。况且,完成该作业的时间有限,测试可能不够充分,也许还会有潜在的bug。因此,还值得进一步改进。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值