本文最后更新于 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,虽然多写了很多东西,但是都没有实装,以后有机会再更新吧(笑)
好厉害诶
太屌啦!!