1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Merge pull request #90092 from KoBeWi/casually_adding_credits_roll_to_a_GAME_engine

Add credits roll
This commit is contained in:
Thaddeus Crews
2025-04-23 12:01:26 -05:00
4 changed files with 351 additions and 5 deletions

245
editor/credits_roll.cpp Normal file
View File

@@ -0,0 +1,245 @@
/**************************************************************************/
/* credits_roll.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "credits_roll.h"
#include "core/authors.gen.h"
#include "core/donors.gen.h"
#include "core/input/input.h"
#include "core/license.gen.h"
#include "core/string/string_builder.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/color_rect.h"
#include "scene/gui/label.h"
#include "scene/gui/texture_rect.h"
Label *CreditsRoll::_create_label(const String &p_with_text, LabelSize p_size) {
Label *label = memnew(Label);
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
label->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
label->set_text(p_with_text);
switch (p_size) {
case LabelSize::NORMAL: {
label->add_theme_font_size_override(SceneStringName(font_size), font_size_normal);
} break;
case LabelSize::HEADER: {
label->add_theme_font_size_override(SceneStringName(font_size), font_size_header);
label->add_theme_font_override(SceneStringName(font), bold_font);
} break;
case LabelSize::BIG_HEADER: {
label->add_theme_font_size_override(SceneStringName(font_size), font_size_big_header);
label->add_theme_font_override(SceneStringName(font), bold_font);
} break;
}
content->add_child(label);
return label;
}
void CreditsRoll::_create_nothing(int p_size) {
if (p_size == -1) {
p_size = 30 * EDSCALE;
}
Control *c = memnew(Control);
c->set_custom_minimum_size(Vector2(0, p_size));
content->add_child(c);
}
String CreditsRoll::_build_string(const char *const *p_from) const {
StringBuilder sb;
while (*p_from) {
sb.append(String::utf8(*p_from));
sb.append("\n");
p_from++;
}
return sb.as_string();
}
void CreditsRoll::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
set_process_internal(false);
mouse_enabled = false;
}
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
if (project_manager) {
project_manager->set_text(TTR("Project Manager", "Job Title"));
}
} break;
case NOTIFICATION_WM_GO_BACK_REQUEST: {
hide();
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
const Vector2 pos = content->get_position();
if (pos.y < -content->get_size().y - 30) {
hide();
break;
}
if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT)) {
hide();
break;
}
bool lmb = Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT);
if (!mouse_enabled && !lmb) {
// Makes sure that the initial double click does not speed up text.
mouse_enabled = true;
}
if ((mouse_enabled && lmb) || Input::get_singleton()->is_action_pressed(SNAME("ui_accept"))) {
content->set_position(Vector2(pos.x, pos.y - 2000 * get_process_delta_time()));
} else {
content->set_position(Vector2(pos.x, pos.y - 100 * get_process_delta_time()));
}
} break;
}
}
void CreditsRoll::roll_credits() {
if (!project_manager) {
font_size_normal = get_theme_font_size("main_size", EditorStringName(EditorFonts)) * 2;
font_size_header = font_size_normal + 10 * EDSCALE;
font_size_big_header = font_size_header + 20 * EDSCALE;
bold_font = get_theme_font("bold", EditorStringName(EditorFonts));
{
const Ref<Texture2D> logo_texture = get_editor_theme_icon("Logo");
TextureRect *logo = memnew(TextureRect);
logo->set_custom_minimum_size(Vector2(0, logo_texture->get_height() * 3));
logo->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
logo->set_texture(logo_texture);
content->add_child(logo);
}
_create_label(TTRC("Credits"), LabelSize::BIG_HEADER);
_create_nothing();
_create_label(TTRC("Project Founders"), LabelSize::HEADER);
_create_label(_build_string(AUTHORS_FOUNDERS));
_create_nothing();
_create_label(TTRC("Lead Developer"), LabelSize::HEADER);
_create_label(_build_string(AUTHORS_LEAD_DEVELOPERS));
_create_nothing();
project_manager = _create_label(TTR("Project Manager", "Job Title"), LabelSize::HEADER);
_create_label(_build_string(AUTHORS_PROJECT_MANAGERS));
_create_nothing();
_create_label(TTRC("Developers"), LabelSize::HEADER);
_create_label(_build_string(AUTHORS_DEVELOPERS));
_create_nothing();
_create_label(TTRC("Patrons"), LabelSize::HEADER);
_create_label(_build_string(DONORS_PATRONS));
_create_nothing();
_create_label(TTRC("Platinum Sponsors"), LabelSize::HEADER);
_create_label(_build_string(DONORS_SPONSORS_PLATINUM));
_create_nothing();
_create_label(TTRC("Gold Sponsors"), LabelSize::HEADER);
_create_label(_build_string(DONORS_SPONSORS_GOLD));
_create_nothing();
_create_label(TTRC("Silver Sponsors"), LabelSize::HEADER);
_create_label(_build_string(DONORS_SPONSORS_SILVER));
_create_nothing();
_create_label(TTRC("Diamond Members"), LabelSize::HEADER);
_create_label(_build_string(DONORS_MEMBERS_DIAMOND));
_create_nothing();
_create_label(TTRC("Titanium Members"), LabelSize::HEADER);
_create_label(_build_string(DONORS_MEMBERS_TITANIUM));
_create_nothing();
_create_label(TTRC("Platinum Members"), LabelSize::HEADER);
_create_label(_build_string(DONORS_MEMBERS_PLATINUM));
_create_nothing();
_create_label(TTRC("Gold Members"), LabelSize::HEADER);
_create_label(_build_string(DONORS_MEMBERS_GOLD));
_create_nothing();
_create_label(String::utf8(GODOT_LICENSE_TEXT));
_create_nothing(400 * EDSCALE);
_create_label(TTRC("Thank you for choosing Godot Engine!"), LabelSize::BIG_HEADER);
}
Window *root = get_tree()->get_root();
content->set_position(Vector2(content->get_position().x, root->get_size().y + 30));
set_position(root->get_position());
set_size(root->get_size());
popup();
set_process_internal(true);
}
CreditsRoll::CreditsRoll() {
set_wrap_controls(false);
{
ColorRect *background = memnew(ColorRect);
background->set_color(Color(0, 0, 0, 1));
background->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
add_child(background);
}
content = memnew(VBoxContainer);
content->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
add_child(content);
}

68
editor/credits_roll.h Normal file
View File

@@ -0,0 +1,68 @@
/**************************************************************************/
/* credits_roll.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/popup.h"
class Label;
class VBoxContainer;
class Font;
class CreditsRoll : public Popup {
GDCLASS(CreditsRoll, Popup);
enum class LabelSize {
NORMAL,
HEADER,
BIG_HEADER,
};
int font_size_normal = 0;
int font_size_header = 0;
int font_size_big_header = 0;
Ref<Font> bold_font;
bool mouse_enabled = false;
VBoxContainer *content = nullptr;
Label *project_manager = nullptr;
Label *_create_label(const String &p_with_text, LabelSize p_size = LabelSize::NORMAL);
void _create_nothing(int p_size = -1);
String _build_string(const char *const *p_from) const;
protected:
void _notification(int p_what);
public:
void roll_credits();
CreditsRoll();
};

View File

@@ -33,6 +33,8 @@
#include "core/authors.gen.h"
#include "core/donors.gen.h"
#include "core/license.gen.h"
#include "editor/credits_roll.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_version_button.h"
#include "editor/themes/editor_scale.h"
@@ -80,6 +82,19 @@ void EditorAbout::_license_tree_selected() {
_tpl_text->set_text(selected->get_metadata(0));
}
void EditorAbout::_project_manager_clicked() {
if (!EditorNode::get_singleton()) {
// Don't allow in Project Manager.
return;
}
if (!credits_roll) {
credits_roll = memnew(CreditsRoll);
add_child(credits_roll);
}
credits_roll->roll_credits();
}
void EditorAbout::_item_with_website_selected(int p_id, ItemList *p_il) {
const String website = p_il->get_item_metadata(p_id);
if (!website.is_empty()) {
@@ -91,7 +106,7 @@ void EditorAbout::_item_list_resized(ItemList *p_il) {
p_il->set_fixed_column_width(p_il->get_size().x / 3.0 - 16 * EDSCALE * 2.5); // Weird. Should be 3.0 and that's it?.
}
ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_single_column_flags, const bool p_allow_website) {
ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_single_column_flags, const bool p_allow_website, const String &p_easter_egg_section) {
ScrollContainer *sc = memnew(ScrollContainer);
sc->set_name(p_name);
sc->set_v_size_flags(Control::SIZE_EXPAND);
@@ -107,10 +122,12 @@ ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<St
bool single_column = p_single_column_flags & (1 << i);
const char *const *names_ptr = p_src[i];
if (*names_ptr) {
const String &section_name = *itr;
Label *lbl = memnew(Label);
lbl->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
lbl->set_theme_type_variation("HeaderSmall");
lbl->set_text(*itr);
lbl->set_text(section_name);
vbc->add_child(lbl);
ItemList *il = memnew(ItemList);
@@ -152,6 +169,17 @@ ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<St
}
}
} else {
if (section_name == p_easter_egg_section) {
// Easter egg :D
il->set_focus_mode(Control::FOCUS_CLICK);
il->set_mouse_filter(Control::MOUSE_FILTER_PASS);
il->connect("item_activated", callable_mp(this, &EditorAbout::_project_manager_clicked).unbind(1), CONNECT_DEFERRED);
il->add_theme_style_override("focus", empty_stylebox);
il->add_theme_style_override("selected", empty_stylebox);
}
while (*names_ptr) {
il->add_item(String::utf8(*names_ptr++), nullptr, false);
}
@@ -219,7 +247,8 @@ EditorAbout::EditorAbout() {
dev_sections.push_back(TTR("Project Founders"));
dev_sections.push_back(TTR("Lead Developer"));
// TRANSLATORS: This refers to a job title.
dev_sections.push_back(TTR("Project Manager", "Job Title"));
const String project_manager = TTR("Project Manager", "Job Title");
dev_sections.push_back(project_manager);
dev_sections.push_back(TTR("Developers"));
const char *const *dev_src[] = {
AUTHORS_FOUNDERS,
@@ -227,7 +256,7 @@ EditorAbout::EditorAbout() {
AUTHORS_PROJECT_MANAGERS,
AUTHORS_DEVELOPERS,
};
tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 0b1)); // First section (Project Founders) is always one column.
tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 0b1, false, project_manager)); // First section (Project Founders) is always one column.
// Donors.

View File

@@ -37,6 +37,8 @@
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
class CreditsRoll;
/**
* NOTE: Do not assume the EditorNode singleton to be available in this class' methods.
* EditorAbout is also used from the project manager where EditorNode isn't initialized.
@@ -46,15 +48,17 @@ class EditorAbout : public AcceptDialog {
private:
void _license_tree_selected();
void _project_manager_clicked();
void _item_with_website_selected(int p_id, ItemList *p_il);
void _item_list_resized(ItemList *p_il);
ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], int p_single_column_flags = 0, bool p_allow_website = false);
ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], int p_single_column_flags = 0, bool p_allow_website = false, const String &p_easter_egg_section = String());
Tree *_tpl_tree = nullptr;
RichTextLabel *license_text_label = nullptr;
RichTextLabel *_tpl_text = nullptr;
TextureRect *_logo = nullptr;
Vector<ItemList *> name_lists;
CreditsRoll *credits_roll = nullptr;
protected:
void _notification(int p_what);