NOTE什么时候我可以完善这个项目呢?
该游戏项目是ELAB的大一寒假多人协作趣味项目,同时也作为计拔尖大一寒假游戏制作作业。同时也是我学习cpp后的第一个由cpp完成的项目。个人认为还是很有意义的吧?虽然做得很简陋吧~
基本信息
- 语言:c++20标准(但其实没用啥新特性)
- 图形库:SFML3.0.2
- 编译器:MSVL
- github:https://github.com/Xiaoyaof-Team/RunawayTechBuilding1.git
你可能会好奇,为什么采用这样的配置,其实很简单,因为我学习的SFML教程是这样的~ 顺带一提教程是BV1tL2TBEETM,基础的cmake配置和视频中一致
说明
游戏本身是类《13F》的找异常游戏,完成度适中。
由于项目是入门级项目,本身难度不高,因此不会详细说明大体实现,而是着重讲一些具体实现。
所有图像都是AI作的,,不许嫌弃美观喵~
具体实现
github协作
根据ELAB寒假项目要求,本作需要使用github进行多人协作。由于人数有限并且对github不够熟悉,实际上并没有过多的功能,主要是我创建了属于自己的副本,然后再推送到大的项目中合并,因此不再展开。
获取SFML
其实最开始时,SFML库是直接官网下载的,但我并没有搞清楚怎么实现引用,因此最后依然采用了教程中的方法进行cmake网络拷贝库文件。因此也不再展开。
异常管理办法
虽然我们做的是一个小游戏,但如果每个异常都要复制基础代码的话作为写码的我肯定不会舒服。但是由于本人的cpp水平就是和看了看黑马程序员的视频,似乎有更高级的方法,但是我不会,所以选择了类与多态的方法实现。
起初,考虑到了玩一局下来不是所有异常都会出现,所有学习了动态创建类的方法。但实际上最后导致了每次加载异常纹理时会导致卡顿的问题,适得其反,而我又不会优化方法也没时间研究优化方法,所以目前也就不了了之了。
异常管理使用的是继承和多态。首先创建一个异常基类,这个基类所有函数均为虚函数,所有虚函数都包含无异常的基础实现,根据继承原则,子类可以被父类指针指向但是父类指针不能访问子类新函数的特性,动态改变指针当前的指向,实现了异常的管理。因此异常只需要改写需要改写的函数,而不是重新复制一份基础类。
由于是合作项目,在创建异常框架时特意进行了细分。为了方便别人写异常函数,将几乎所有初始化和更新进行了大量拆分,比如说设置位置(set)的函数,每个东西的定位函数完全分离,这样就可以实现改写一个东西的位置不需要管这个页面其他的东西是怎么放置的。但这样处理后存在一个问题,由于异常类和基础场景实现在代码上是分离的,基础实现由于先于异常管理实现,异常管理中的所有函数实际上都是指向基础实现函数的指针,这样的问题就是虽然子类改写了某一个东西的定位,但是总体的定位函数(调用了所有小的定位函数)其实是调用了基础实现中的定位函数,而基础实现中的定位函数使用的是基础实现中的其他子定位函数,但子类改写的是父类也就是异常类的子定位函数,使得调用没有生效。为了解决这个问题,将基础实现中的大部分综合实现函数全部转移至异常大类中实现。
为了实现异常的随机且不重复性,每个异常对应了一个bool值,只有bool值为false时才能被随机到。
文字逐个出现的动效实现
在异常2中添加了类似对话框的文字逐个出现的动效,总体是根据时间截取字符串长度实现的。
下面放出基本实现。
void sEvil_Dragon::sEvil_Dragon_AnimateText(const std::wstring &newString, float deltatime, sf::Vector2f position, float displayDuration, int size, sf::Color color){ is_sEvil_dragon_text_animating = true; // 如果已经显示全了,直接退出 if (sEvil_dragon_text_index >= newString.size()) { sEvil_dragon_text_finished = true; return; }
sEvil_dragon_text_timer += deltatime;
bool hasChanged = false; // 使用 while 循环:如果 deltatime 很大,可以一帧弹出多个字符,保证显示速度恒定 while (sEvil_dragon_text_timer >= displayDuration && sEvil_dragon_text_index < newString.size()) { sEvil_dragon_text_timer -= displayDuration; // 减去消耗的时间 sEvil_dragon_text_index++; hasChanged = true; }
if (hasChanged) { sEvil_dragon_text.setString(newString.substr(0, sEvil_dragon_text_index)); fontfSound.play(); // 每次显示新字符时播放音效 auto bounds = sEvil_dragon_text.getLocalBounds(); sEvil_dragon_text.setOrigin(bounds.position + bounds.size / 2.f); sEvil_dragon_text.setPosition(position); sEvil_dragon_text.setCharacterSize(size); sEvil_dragon_text.setFillColor(color); }}这里根据AI的建议,使用while循环以处理一个帧时间达到了多个文字出现的条件情况发生,同时,记录这句话到哪了的index而不是使用时间对应长度的方法决定当前显示多少个字是出于方便考虑,后续还添加了空格键可以快速跳出文字和跳转下一句话的功能,直接用当前显示到第几个字了要方便且好理解很多。
下面展示一下空格快速补全的代码实现
if (key.code == sf::Keyboard::Key::Space) { // 如果当前动画没有播放完,直接将动画设置为完成 if (!sEvil_dragon_text_finished) { sEvil_dragon_text_index = dialogues_ptr->size() - 1; sEvil_dragon_text_finished = true; is_sEvil_dragon_text_animating = false; return true; } // 如果动画已经播放完了,播放下一个对话 else { current_dialogue_index++; if (current_dialogue_index <= sEvil_dragon_dialogues_1.size() - 2) { dialogues_ptr = std::make_unique<std::wstring>(sEvil_dragon_dialogues_1[current_dialogue_index]); sEvil_Dragon_ResetTextAnimation(); return true; } else { // 没有更多对话了,不作任何处理,并标记当前对话已经全部完成 return true; } } }这是整个事件处理(HandleEvent)函数的一部分,该函数返回bool值,说明该事件是否已经被处理了。
视角跟随鼠标动效实现
一开始时,我就很希望视角并不是牢牢固定在人物身上的,而是带有一定的动效,除了视角本身带有三角函数的上下波动外,还添加了视角会向鼠标靠近的动效,其实现也很好理解,首先,每一帧会记录鼠标和人物(画面正中心)的相对位置,然后根据一定的比例算出目标偏离点在哪里,再减去提前记录下来的上一次视角相较于人物的偏移值,再根据余下距离的自然数幂的公式计算出这一帧要偏离多少。
// 计算相机跟随鼠标水平的偏移 if (c_gameState.is_Mouse_Follow_Camera == L"是" && c_menu.c_menuState.currentState == MenuState::None) { // 产生动画效果 // 计算鼠标偏移 float mouseX_change = sf::Mouse::getPosition(c_window).x - (c_window.getSize().x / 2.f); // 鼠标位移的比例 float offsetRatio = mouseX_change / (c_window.getSize().x / 2.f); // 设置最大偏移的范围 float maxOffset = 100.f; // 计算目标最大偏移 float sign = offsetRatio > 0 ? 1.f : -1.f; float targetOffset = sign * std::sqrt(std::abs(offsetRatio)) * maxOffset; // 平滑过渡动画效果 float currentOffset = Last_distance_to_player; float diff = targetOffset - currentOffset;
// 动态计算平滑速度:距离越远,速度越快;距离越近,速度越慢 float baseSpeed = 2.0f; float distanceFactor = std::abs(diff) / maxOffset; float smoothSpeed = baseSpeed + 10.0f * distanceFactor * distanceFactor;
Last_distance_to_player = currentOffset + diff * (1.0f - std::exp(-smoothSpeed * deltaTime)); }总结
大概能讲讲的应该就是如此,总之,这是我作为新人入手的第一个还像点样子的项目,虽然充满各种不足但总体尚可~
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时





