类东方游戏:东方巴普特
本文最后更新于 321 天前,其中的信息可能已经有所发展或是发生改变。

一直以来都想做的一个项目(当然,这个想法咕了多少年了都哈哈哈)。这篇博客就当作纪念&开发心得吧

仓库链接:https://github.com/dthylacetate/TouHou_BUPT

demo演示:https://www.bilibili.com/video/BV1Af1aYXEBh/?spm_id_from=333.999.0.0

大概率加载不出来,直接点链接去看吧hh

1.配置SFML

这个没啥好说的,直接找安装教程即可,没啥大问题

2.显示自机

sf::RenderWindow mWindow(sf::VideoMode(1280, 960), "TouHou20.0-chs");
sf::Texture Reimu;
if (!Reimu.loadFromFile(".res/pl00.png", sf::IntRect(0, 0, 30, 45)))
{
	puts("Error: Load Reimu failed!");
}
sf::Sprite pl01(Reimu);
mWindow.draw(pl01);

实际实现的时候:代码采用了动画效果,即根据当前帧数来选定Sprite

3.BGM

这个简单但也不简单

if (!music.openFromFile("./res/menu.wav"))
{
	puts("Error: Open menu.wav failed!");
}
// Play the music
music.play();

music 不会预先把文件读进缓冲区,所以适合大文件(1分钟以上?)的播放,在小音效的处理上应该使用预先把文件读进缓冲区的 sound,可以极大的节省CPU的开销(实测大概是20倍的差距)。

4.左右键控制

if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
    // move left...
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
{
    // move right...
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
{
    // quit...
}

这里在实际的代码中其实采取了一个监听和处理分开的策略,这样结构更清晰一下,效率也更高(大概)。

5.限制区域

if (mIsMovingUp == true && player.hero.getPosition().y > 40)
{
	player.hero.move(0.0, -player.speed);
}
if (mIsMovingDown == true && player.hero.getPosition().y < 850)
{
	player.hero.move(0.0, player.speed);
}
if (mIsMovingLeft == true && player.hero.getPosition().x > 69)
{
	player.hero.move(-player.speed, 0.0);
}
if (mIsMovingRight == true && player.hero.getPosition().x < 751)
{
	player.hero.move(player.speed, 0.0);
}

STL 里的 list 下的 remove_if 因为其内部实现的原因,是不支持函数重载的,所以后来写了一个针对 FO 类的越界判定函数。之后有机会最好能把两个函数整合一下,都针对 FO 对象来判定会更灵活一些。

6.发射子弹&越界回收

if (mIsFire)
{
	//playerAmmo = (mIsGrazing) ? player.LSAmmo : player.HSAmmo;
	if (i % 2 == 1)
	{
		player.LSAmmo.setPosition(sf::Vector2f(player.hero.getPosition().x + 4, player.hero.getPosition().y + 100));
		playerBullets.push_back(player.LSAmmo);
		layer.LSAmmo.setPosition(sf::Vector2f(player.hero.getPosition().x + 20, player.hero.getPosition().y + 100));
		playerBullets.push_back(player.LSAmmo);
		player.HSAmmo.setPosition(sf::Vector2f(player.hero.getPosition().x + 4, player.hero.getPosition().y + 130));
		playerBullets.push_back(player.HSAmmo);
		player.HSAmmo.setPosition(sf::Vector2f(player.hero.getPosition().x + 20, player.hero.getPosition().y + 130));
		playerBullets.push_back(player.HSAmmo);
	}
}
playerBullets.remove_if(isOutOfBoard);
for (list<sf::Sprite>::iterator it = playerBullets.begin(); it != playerBullets.end(); it++)
{
	it->setPosition(it->getPosition().x, it->getPosition().y - 60);

	mWindow.draw(*it);
}

bool isOutOfBoard(sf::Sprite value)
{
	if (value.getPosition().y <= 136)
	{
		return true;
	}
	return false;
}

7.时间轴管理

void Game::Stage1()
{
	static sf::Time elapsed1 = clock.restart();
	elapsed1 = clock.getElapsedTime();
	
	static int evts[20] = { 0 };

	static int curTime = 1;
	if (curTime < elapsed1.asSeconds())
	{
		printf("%.0f\n", elapsed1.asSeconds());
		curTime++;
	}

	switch ((int)elapsed1.asSeconds())
	{
	case 1:
		//pre
		evts[1] = 1;
		break;
	case 12:
		//title
		evts[2] = 1;
		break;
	case 37:
		//wave
		evts[3] = 1;
		break;
	case 50:
		//middle
		evts[4] = 1;
		break;
	case 63:
		//spellCard1
		evts[5] = 1;
		break;
	case 100:
		//boss
		evts[6] = 1;
		break;
}

如果 Clock 达到了触发条件,就把某个事件开关置 1,新世界的大门就打开了(明明只是奇奇怪怪的弹幕游戏来着)。

8.时间轴事件

int Stage01Event01()
{
	static int curFrame = 0;
	curFrame++;
	static list<FO> wave1, wave2;
	double gapTime = 0.4;
	int gapFrame = gapTime * 60;
	static int gap = 0, temp = 0;
	if (curFrame % gapFrame == 1 && curFrame < 17 * gapFrame)
	{
		//wave1
	}
	if (curFrame == 270)
	{
		//wave2
	}
	wave1.remove_if(isFOOutOfBoard);
	for (list<FO>::iterator it = wave1.begin(); it != wave1.end(); it++)
	{
		//Trajectory equation 01
	}
	wave2.remove_if(isFOOutOfBoard);
	for (list<FO>::iterator it = wave2.begin(); it != wave2.end(); it++)
	{
		//Trajectory equation 02
	}
	if (i1 > 15 * 60)
	{
		wave1.clear();//Final clear for accident
		wave2.clear();
		return 1;
	}
	return 0;
}

9.碰撞检测

bool Game::checkCollision(sf::Sprite obj1, sf::Sprite obj2)
{
	sf::FloatRect f1 = obj1.getGlobalBounds();
	sf::FloatRect f2 = obj2.getGlobalBounds();
	if (f1.intersects(f2))
	{
		return true;
	}
	return false;
}

10.碰撞处理

void Game::enemyCollisionProcessing(list<FO>::iterator it)
{
	for (list<sf::Sprite>::iterator itAmmo = playerBullets.begin(); itAmmo != playerBullets.end(); itAmmo++)
	{
		if (checkCollision(it->hero, *itAmmo))
		{
			enemyUnderAttack(it, itAmmo);

			if (it->HealthPoint <= 0)
			{
				enemyCrash(it);
			}
		}
	}
}

自机的处理也类似。改改就能用。

11.敌机爆炸过程

void Game::enemyCrash(list<FO>::iterator it)
{
	breakSound.play();
	score += it->score;
	deathEff.setTexture(deathCircle);
	deathEff.setTextureRect(sf::IntRect(64, 0, 64, 64));
	deathEff.setOrigin(32, 32);
	deathEff.setPosition(it->hero.getPosition().x + it->width * 0.25, it->hero.getPosition().y + it->height * 0.25);
	deathEff.setScale(0.1, 0.1);
	deathEffs.push_back(deathEff);
	deathEff.setScale(0.3, 0.06);
	deathEff.setRotation(rand() % 360);
	deathEffs.push_back(deathEff);
	it->hero.setPosition(-100, -100);
}

12.计分和显示

void Game::boardDisplay()
{
	mWindow.draw(front01);//Display main background
	mWindow.draw(front02);
	mWindow.draw(front03);
	mWindow.draw(front04);

	switch (remnant)
	{
	case 3:
		lifeBoard.setTextureRect(sf::IntRect(0, 0, 272, 36));
		break;
	case 2:
		lifeBoard.setTextureRect(sf::IntRect(0, 44, 272, 36));
		break;
	case 1:
		lifeBoard.setTextureRect(sf::IntRect(0, 90, 272, 36));
		break;
	default:
		;
	}

	lifeBoard.setScale(1.5, 1.5);
	lifeBoard.setPosition(830, 300);
	mWindow.draw(lifeBoard);

	static string scoreStr;
	scoreStr = "Score:             ";
	scoreStr += to_string(score);
	tempScore.setString(scoreStr);
	tempScore.setStyle(sf::Text::Italic);
	tempScore.setFont(font);
	tempScore.setCharacterSize(50);
	tempScore.setPosition(840, 50);
	mWindow.draw(tempScore);
}

13.敌机弹幕处理

void Game::setSharpRandom(list<FO>::iterator it, double speed)
{
	enemyBulletSound.play();
	FO SharpRandom;
	SharpRandom.speed = speed;
	SharpRandom.theta = rand()%360;
	SharpRandom.width = 16;
	SharpRandom.height = 16;
	SharpRandom.hero.setTexture(allBullets1);
	SharpRandom.hero.setTextureRect(sf::IntRect(64, 64, 16, 16));
	SharpRandom.hero.setOrigin(8, 8);
	SharpRandom.hero.setScale(1.5, 1.5);
	SharpRandom.hero.setPosition(it->hero.getPosition().x, it->hero.getPosition().y + it->height);
	SharpRandom.hero.setRotation(SharpRandom.theta  / PI * 180.0 + 90);
	enemyBullets.push_back(SharpRandom);
}

随便举了个随机弹作为例子

14.自机判定

bool Game::checkPlayerCollision()
{
	sf::Vector2f JP = julgePoint.getPosition();
	JP.x -= 8;
	JP.y -= 8;

	for (list<FO>::iterator it = enemyBullets.begin(); it != enemyBullets.end(); it++)
	{
		sf::FloatRect f = it->hero.getGlobalBounds();

		f.width /= 2.0;
		f.height /= 2.0;
		if (f.contains(JP))
		{
			return true;
		}
	}
	return false;
}

目前还是只做了一个能玩的demo,虽然多写了很多东西,但是都没有实装,以后有机会再更新吧(笑)

评论

  1. luoyu
    11 月前
    2024-10-30 16:07:07

    好厉害诶

  2. Daniel
    11 月前
    2024-10-30 20:43:11

    太屌啦!!

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇