diff --git a/docs/_static/css/algolia.css b/docs/_static/css/algolia.css
deleted file mode 100644
index d27f55e..0000000
--- a/docs/_static/css/algolia.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.wy-nav-side { overflow: visible; }
-.wy-side-scroll { overflow-x: inherit; }
-
-.algolia-autocomplete {
- display: block !important;
-}
diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css
index 7267e85..4300af9 100644
--- a/docs/_static/css/custom.css
+++ b/docs/_static/css/custom.css
@@ -592,6 +592,11 @@ html.writer-html5 .rst-content dl:not(.docutils) > dt, html.writer-html5 .rst-co
background: var(--highlight-background-color);
}
+dl.simple dt {
+ border-bottom: 1px solid var(--hr-color);
+ background: none !important;
+}
+
html.writer-html5 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt, html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) > dt {
border-left-color: var(--highlight-background-emph-color);
background: var(--highlight-background-color);
@@ -1905,8 +1910,3 @@ p + .classref-constant {
padding: 3px 5px;
}
}
-
-/* Giscus */
-#godot-giscus {
- margin-bottom: 1em;
-}
diff --git a/docs/_static/js/custom.js b/docs/_static/js/custom.js
index bdc4a32..2d29ce2 100644
--- a/docs/_static/js/custom.js
+++ b/docs/_static/js/custom.js
@@ -1,431 +1,39 @@
-// Handle page scroll and adjust sidebar accordingly.
-
-// Each page has two scrolls: the main scroll, which is moving the content of the page;
-// and the sidebar scroll, which is moving the navigation in the sidebar.
-// We want the logo to gradually disappear as the main content is scrolled, giving
-// more room to the navigation on the left. This means adjusting the height
-// available to the navigation on the fly. There is also a banner below the navigation
-// that must be dealt with simultaneously.
-const registerOnScrollEvent = (function () {
- // Configuration.
-
- // The number of pixels the user must scroll by before the logo is completely hidden.
- const scrollTopPixels = 84;
- // The target margin to be applied to the navigation bar when the logo is hidden.
- const menuTopMargin = 70;
- // The max-height offset when the logo is completely visible.
- const menuHeightOffset_default = 162;
- // The max-height offset when the logo is completely hidden.
- const menuHeightOffset_fixed = 80;
- // The distance between the two max-height offset values above; used for intermediate values.
- const menuHeightOffset_diff = (menuHeightOffset_default - menuHeightOffset_fixed);
-
- // Media query handler.
- return function (mediaQuery) {
- // We only apply this logic to the "desktop" resolution (defined by a media query at the bottom).
- // This handler is executed when the result of the query evaluation changes, which means that
- // the page has moved between "desktop" and "mobile" states.
-
- // When entering the "desktop" state, we register scroll events and adjust elements on the page.
- // When entering the "mobile" state, we clean up any registered events and restore elements on the page
- // to their initial state.
-
- const $window = $(window);
- const $sidebar = $('.wy-side-scroll');
- const $search = $sidebar.children('.wy-side-nav-search');
- const $menu = $sidebar.children('.wy-menu-vertical');
- const $ethical = $sidebar.children('.ethical-rtd');
-
- // This padding is needed to correctly adjust the height of the scrollable area in the sidebar.
- // It has to have the same height as the ethical block, if there is one.
- let $menuPadding = $menu.children('.wy-menu-ethical-padding');
- if ($menuPadding.length == 0) {
- $menuPadding = $('
');
- $menu.append($menuPadding);
- }
-
- if (mediaQuery.matches) {
- // Entering the "desktop" state.
-
- // The main scroll event handler.
- // Executed as the page is scrolled and once immediately as the page enters this state.
- const handleMainScroll = (currentScroll) => {
- if (currentScroll >= scrollTopPixels) {
- // After the page is scrolled below the threshold, we fix everything in place.
- $search.css('margin-top', `-${scrollTopPixels}px`);
- $menu.css('margin-top', `${menuTopMargin}px`);
- $menu.css('max-height', `calc(100% - ${menuHeightOffset_fixed}px)`);
- } else {
- // Between the top of the page and the threshold we calculate intermediate values
- // to guarantee a smooth transition.
- $search.css('margin-top', `-${currentScroll}px`);
- $menu.css('margin-top', `${menuTopMargin + (scrollTopPixels - currentScroll)}px`);
-
- if (currentScroll > 0) {
- const scrolledPercent = (scrollTopPixels - currentScroll) / scrollTopPixels;
- const offsetValue = menuHeightOffset_fixed + menuHeightOffset_diff * scrolledPercent;
- $menu.css('max-height', `calc(100% - ${offsetValue}px)`);
- } else {
- $menu.css('max-height', `calc(100% - ${menuHeightOffset_default}px)`);
- }
- }
- };
-
- // The sidebar scroll event handler.
- // Executed as the sidebar is scrolled as well as after the main scroll. This is needed
- // because the main scroll can affect the scrollable area of the sidebar.
- const handleSidebarScroll = () => {
- const menuElement = $menu.get(0);
- const menuScrollTop = $menu.scrollTop();
- const menuScrollBottom = menuElement.scrollHeight - (menuScrollTop + menuElement.offsetHeight);
-
- // As the navigation is scrolled we add a shadow to the top bar hanging over it.
- if (menuScrollTop > 0) {
- $search.addClass('fixed-and-scrolled');
- } else {
- $search.removeClass('fixed-and-scrolled');
- }
-
- // Near the bottom we start moving the sidebar banner into view.
- if (menuScrollBottom < ethicalOffsetBottom) {
- $ethical.css('display', 'block');
- $ethical.css('margin-top', `-${ethicalOffsetBottom - menuScrollBottom}px`);
- } else {
- $ethical.css('display', 'none');
- $ethical.css('margin-top', '0px');
- }
- };
-
- $search.addClass('fixed');
- $ethical.addClass('fixed');
-
- // Adjust the inner height of navigation so that the banner can be overlaid there later.
- const ethicalOffsetBottom = $ethical.height() || 0;
- if (ethicalOffsetBottom) {
- $menuPadding.css('height', `${ethicalOffsetBottom}px`);
- } else {
- $menuPadding.css('height', `0px`);
- }
-
- $window.scroll(function () {
- handleMainScroll(window.scrollY);
- handleSidebarScroll();
- });
-
- $menu.scroll(function () {
- handleSidebarScroll();
- });
-
- handleMainScroll(window.scrollY);
- handleSidebarScroll();
- } else {
- // Entering the "mobile" state.
-
- $window.unbind('scroll');
- $menu.unbind('scroll');
-
- $search.removeClass('fixed');
- $ethical.removeClass('fixed');
-
- $search.css('margin-top', `0px`);
- $menu.css('margin-top', `0px`);
- $menu.css('max-height', 'initial');
- $menuPadding.css('height', `0px`);
- $ethical.css('margin-top', '0px');
- $ethical.css('display', 'block');
- }
- };
-})();
-
-// Subscribe to DOM changes in the sidebar container, because there is a
-// banner that gets added at a later point, that we might not catch otherwise.
-const registerSidebarObserver = (function () {
- return function (callback) {
- const sidebarContainer = document.querySelector('.wy-side-scroll');
-
- let sidebarEthical = null;
- const registerEthicalObserver = () => {
- if (sidebarEthical) {
- // Do it only once.
- return;
- }
-
- sidebarEthical = sidebarContainer.querySelector('.ethical-rtd');
- if (!sidebarEthical) {
- // Do it only after we have the element there.
- return;
- }
-
- // This observer watches over the ethical block in sidebar, and all of its subtree.
- const ethicalObserverConfig = {childList: true, subtree: true};
- const ethicalObserverCallback = (mutationsList, observer) => {
- for (let mutation of mutationsList) {
- if (mutation.type !== 'childList') {
- continue;
- }
-
- callback();
- }
- };
-
- const ethicalObserver = new MutationObserver(ethicalObserverCallback);
- ethicalObserver.observe(sidebarEthical, ethicalObserverConfig);
- };
- registerEthicalObserver();
-
- // This observer watches over direct children of the main sidebar container.
- const observerConfig = {childList: true};
- const observerCallback = (mutationsList, observer) => {
- for (let mutation of mutationsList) {
- if (mutation.type !== 'childList') {
- continue;
- }
-
- callback();
- registerEthicalObserver();
- }
- };
-
- const observer = new MutationObserver(observerCallback);
- observer.observe(sidebarContainer, observerConfig);
-
- // Default TOC tree has links that immediately navigate to the selected page. Our
- // theme adds an extra button to fold and unfold the tree without navigating away.
- // But that means that the buttons are added after the initial load, so we need to
- // improvise to detect clicks on these buttons.
- const scrollElement = document.querySelector('.wy-menu-vertical');
- const registerLinkHandler = (linkChildren) => {
- linkChildren.forEach(it => {
- if (it.nodeType === Node.ELEMENT_NODE && it.classList.contains('toctree-expand')) {
- it.addEventListener('click', () => {
- // Toggling a different item will close the currently opened one,
- // which may shift the clicked item out of the view. We correct for that.
- const menuItem = it.parentNode;
- const baseScrollOffset = scrollElement.scrollTop + scrollElement.offsetTop;
- const maxScrollOffset = baseScrollOffset + scrollElement.offsetHeight;
-
- if (menuItem.offsetTop < baseScrollOffset || menuItem.offsetTop > maxScrollOffset) {
- menuItem.scrollIntoView();
- }
-
- callback();
- });
- }
- });
- }
-
- const navigationSections = document.querySelectorAll('.wy-menu-vertical ul');
- navigationSections.forEach(it => {
- if (it.previousSibling == null || typeof it.previousSibling === 'undefined' || it.previousSibling.tagName != 'A') {
- return;
- }
-
- const navigationLink = it.previousSibling;
- registerLinkHandler(navigationLink.childNodes);
-
- const linkObserverConfig = {childList: true};
- const linkObserverCallback = (mutationsList, observer) => {
- for (let mutation of mutationsList) {
- registerLinkHandler(mutation.addedNodes);
- }
- };
-
- const linkObserver = new MutationObserver(linkObserverCallback);
- linkObserver.observe(navigationLink, linkObserverConfig);
- });
- };
-})();
-
-$(document).ready(() => {
- // Remove the search match highlights from the page, and adjust the URL in the
- // navigation history.
- const url = new URL(location.href);
- if (url.searchParams.has('highlight')) {
- Documentation.hideSearchWords();
- }
-
- window.addEventListener('keydown', function (event) {
- if (event.key === '/') {
- var searchField = document.querySelector('#rtd-search-form input[type=text]');
- if (document.activeElement !== searchField) {
- searchField.focus();
- searchField.select();
- event.preventDefault();
- }
- }
- });
-
- // Initialize handlers for page scrolling and our custom sidebar.
- const mediaQuery = window.matchMedia('only screen and (min-width: 769px)');
-
- registerOnScrollEvent(mediaQuery);
- mediaQuery.addListener(registerOnScrollEvent);
-
- registerSidebarObserver(() => {
- registerOnScrollEvent(mediaQuery);
- });
-
- // Add line-break suggestions to the sidebar navigation items in the class reference section.
- //
- // Some class reference pages have very long PascalCase names, such as
- // VisualShaderNodeCurveXYZTexture
- // Those create issues for our layout, as we can neither make them wrap with CSS without
- // breaking normal article titles, nor is it good to have them overflow their containers.
- // So we use a tag to insert mid-word suggestions for appropriate splits, so the browser
- // knows that it's okay to split it like
- // Visual Shader Node Curve XYZTexture
- // and add a new line at an opportune moment.
- const classReferenceLinks = document.querySelectorAll('.wy-menu-vertical > ul:last-of-type .reference.internal');
- for (const linkItem of classReferenceLinks) {
- let textNode = null;
- linkItem.childNodes.forEach(it => {
- if (it.nodeType === Node.TEXT_NODE) {
- // If this is a text node and if it needs to be updated, store a reference.
- let text = it.textContent;
- if (!(text.includes(" ") || text.length < 10)) {
- textNode = it;
- }
- }
- });
-
- if (textNode != null) {
- let text = textNode.textContent;
- // Add suggested line-breaks and replace the original text.
- // The regex looks for a lowercase letter followed by a number or an uppercase
- // letter. We avoid splitting at the last character in the name, though.
- text = text.replace(/([a-z])([A-Z0-9](?!$))/gm, '$1$2');
-
- linkItem.removeChild(textNode);
- linkItem.insertAdjacentHTML('beforeend', text);
- }
- }
-
- // Change indentation from spaces to tabs for codeblocks.
- const codeBlocks = document.querySelectorAll('.rst-content div[class^="highlight"] pre');
- for (const codeBlock of codeBlocks) {
- const classList = codeBlock.parentNode.parentNode.classList;
- if (!classList.contains('highlight-gdscript') && !classList.contains('highlight-cpp')) {
- // Only change indentation for GDScript and C++.
- continue;
- }
- let html = codeBlock.innerHTML.replace(/^()?( {4})/gm, '\t');
- let html_old = "";
- while (html != html_old) {
- html_old = html;
- html = html.replace(/\t( {4})/gm, '\t\t')
- }
- codeBlock.innerHTML = html;
- }
-
- // See `godot_is_latest` in conf.py
- const isLatest = document.querySelector('meta[name=doc_is_latest]').content.toLowerCase() === 'true';
- if (isLatest) {
- // Add a compatibility notice using JavaScript so it doesn't end up in the
- // automatically generated `meta description` tag.
-
- const baseUrl = [location.protocol, '//', location.host, location.pathname].join('');
- // These lines only work as expected in the production environment, can't test this locally.
- const fallbackUrl = baseUrl.replace('/latest/', '/stable/');
- const homeUrl = baseUrl.split('/latest/')[0] + '/stable/';
- const searchUrl = homeUrl + 'search.html?q=';
-
- const noticeLink = document.querySelector('.latest-notice-link');
-
- // Insert a placeholder to display as we're making a request.
- noticeLink.innerHTML = `
- Checking the stable version
- of the documentation...
- `;
+document.addEventListener("DOMContentLoaded", () => {
+ const menuHeaders = document.querySelectorAll('.wy-menu-vertical .caption');
+ let hasCurrent = false;
- // Make a HEAD request to the possible stable URL to check if the page exists.
- fetch(fallbackUrl, {method: 'HEAD'})
- .then((res) => {
- // We only check the HTTP status, which should tell us if the link is valid or not.
- if (res.status === 200) {
- noticeLink.innerHTML = `
- See the stable version
- of this documentation page instead.
- `;
- } else {
- // Err, just to fallthrough to catch.
- throw Error('Bad request');
- }
- })
- .catch((err) => {
- let message = `
- This page does not exist in the stable version
- of the documentation.
- `;
+ menuHeaders.forEach(header => {
+ const connectedMenu = header.nextElementSibling; // The dropdown menu (e.g., `ul`)
- // Also suggest a search query using the page's title. It should work with translations as well.
- // Note that we can't use the title tag as it has a permanent suffix. OG title doesn't, though.
- const titleMeta = document.querySelector('meta[property="og:title"]');
- if (typeof titleMeta !== 'undefined') {
- const pageTitle = titleMeta.getAttribute('content');
- message += `
- You can try searching for "${pageTitle}" instead.
- `;
- }
+ // Toggle dropdown visibility on click
+ header.addEventListener('click', () => {
+ const isActive = connectedMenu.classList.contains('active');
- noticeLink.innerHTML = message;
+ // Close all dropdowns
+ menuHeaders.forEach(otherHeader => {
+ const otherMenu = otherHeader.nextElementSibling;
+ otherMenu.classList.remove('active');
+ otherHeader.classList.remove('active');
});
- }
- // Load instant.page to prefetch pages upon hovering. This makes navigation feel
- // snappier. The script is dynamically appended as Read the Docs doesn't have
- // a way to add scripts with a "module" attribute.
- const instantPageScript = document.createElement('script');
- instantPageScript.toggleAttribute('module');
- /*! instant.page v5.1.0 - (C) 2019-2020 Alexandre Dieulot - https://instant.page/license */
- instantPageScript.innerText = 'let t,e;const n=new Set,o=document.createElement("link"),i=o.relList&&o.relList.supports&&o.relList.supports("prefetch")&&window.IntersectionObserver&&"isIntersecting"in IntersectionObserverEntry.prototype,s="instantAllowQueryString"in document.body.dataset,a="instantAllowExternalLinks"in document.body.dataset,r="instantWhitelist"in document.body.dataset,c="instantMousedownShortcut"in document.body.dataset,d=1111;let l=65,u=!1,f=!1,m=!1;if("instantIntensity"in document.body.dataset){const t=document.body.dataset.instantIntensity;if("mousedown"==t.substr(0,"mousedown".length))u=!0,"mousedown-only"==t&&(f=!0);else if("viewport"==t.substr(0,"viewport".length))navigator.connection&&(navigator.connection.saveData||navigator.connection.effectiveType&&navigator.connection.effectiveType.includes("2g"))||("viewport"==t?document.documentElement.clientWidth*document.documentElement.clientHeight<45e4&&(m=!0):"viewport-all"==t&&(m=!0));else{const e=parseInt(t);isNaN(e)||(l=e)}}if(i){const n={capture:!0,passive:!0};if(f||document.addEventListener("touchstart",function(t){e=performance.now();const n=t.target.closest("a");if(!h(n))return;v(n.href)},n),u?c||document.addEventListener("mousedown",function(t){const e=t.target.closest("a");if(!h(e))return;v(e.href)},n):document.addEventListener("mouseover",function(n){if(performance.now()-e{v(o.href),t=void 0},l)},n),c&&document.addEventListener("mousedown",function(t){if(performance.now()-e1||t.metaKey||t.ctrlKey)return;if(!n)return;n.addEventListener("click",function(t){1337!=t.detail&&t.preventDefault()},{capture:!0,passive:!1,once:!0});const o=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!1,detail:1337});n.dispatchEvent(o)},n),m){let t;(t=window.requestIdleCallback?t=>{requestIdleCallback(t,{timeout:1500})}:t=>{t()})(()=>{const t=new IntersectionObserver(e=>{e.forEach(e=>{if(e.isIntersecting){const n=e.target;t.unobserve(n),v(n.href)}})});document.querySelectorAll("a").forEach(e=>{h(e)&&t.observe(e)})})}}function p(e){e.relatedTarget&&e.target.closest("a")==e.relatedTarget.closest("a")||t&&(clearTimeout(t),t=void 0)}function h(t){if(t&&t.href&&(!r||"instant"in t.dataset)&&(a||t.origin==location.origin||"instant"in t.dataset)&&["http:","https:"].includes(t.protocol)&&("http:"!=t.protocol||"https:"!=location.protocol)&&(s||!t.search||"instant"in t.dataset)&&!(t.hash&&t.pathname+t.search==location.pathname+location.search||"noInstant"in t.dataset))return!0}function v(t){if(n.has(t))return;const e=document.createElement("link");e.rel="prefetch",e.href=t,document.head.appendChild(e),n.add(t)}';
- document.head.appendChild(instantPageScript);
-
- // Make sections in the sidebar togglable.
- let hasCurrent = false;
- let menuHeaders = document.querySelectorAll('.wy-menu-vertical .caption[role=heading]');
- menuHeaders.forEach(it => {
- let connectedMenu = it.nextElementSibling;
-
- // Enable toggling.
- it.addEventListener('click', () => {
- if (connectedMenu.classList.contains('active')) {
- connectedMenu.classList.remove('active');
- it.classList.remove('active');
- } else {
+ // Open the clicked dropdown if it was closed
+ if (!isActive) {
connectedMenu.classList.add('active');
- it.classList.add('active');
+ header.classList.add('active');
}
+ });
- // Hide other sections.
- menuHeaders.forEach(ot => {
- if (ot !== it && ot.classList.contains('active')) {
- ot.nextElementSibling.classList.remove('active');
- ot.classList.remove('active');
- }
- });
-
- registerOnScrollEvent(mediaQuery);
- }, true);
-
- // Set the default state, expand our current section.
- if (connectedMenu.classList.contains('current')) {
+ // Automatically expand the dropdown if it contains the "current" page
+ if (connectedMenu.querySelector('.current')) {
connectedMenu.classList.add('active');
- it.classList.add('active');
-
+ header.classList.add('active');
hasCurrent = true;
}
});
- // Unfold the first (general information) section on the home page.
+ // If no dropdown contains the "current" page, expand the first section by default
if (!hasCurrent && menuHeaders.length > 0) {
menuHeaders[0].classList.add('active');
menuHeaders[0].nextElementSibling.classList.add('active');
-
- registerOnScrollEvent(mediaQuery);
}
});
-
-// Override the default implementation from doctools.js to avoid this behavior.
-Documentation.highlightSearchWords = function () {
- // Nope.
-}
\ No newline at end of file
diff --git a/docs/_static/rect.png b/docs/_static/rect.png
new file mode 100644
index 0000000..115a1c4
Binary files /dev/null and b/docs/_static/rect.png differ
diff --git a/docs/conf.py b/docs/conf.py
index b46ae46..d62754d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,15 +1,15 @@
import os
import subprocess
-breathe_projects = {'KrakenEngine': 'xml'}
-breathe_default_project = 'KrakenEngine'
+breathe_projects = {"KrakenEngine": "xml"}
+breathe_default_project = "KrakenEngine"
needs_sphinx = "7.1"
extensions = [
- 'breathe',
- 'sphinx.ext.autosectionlabel',
- 'notfound.extension',
+ "breathe",
+ "sphinx.ext.autosectionlabel",
+ "notfound.extension",
]
notfound_context = {
@@ -35,22 +35,22 @@
on_rtd = os.environ.get("READTHEDOCS", None) == "True"
if on_rtd:
- subprocess.call('doxygen', shell=True)
+ subprocess.call("doxygen", shell=True)
else:
notfound_urls_prefix = ''
templates_path = ["_templates"]
-source_suffix = '.rst'
+source_suffix = ".rst"
source_encoding = "utf-8-sig"
-master_doc = 'index'
+master_doc = "index"
-project = 'Kraken Engine'
-copyright = '2024, Derrick Martinez'
-author = 'Derrick Martinez'
+project = "Kraken Engine"
+copyright = "2024, Derrick Martinez"
+author = "Derrick Martinez"
-version = os.getenv("READTHEDOCS_VERSION", "0.0.3")
+version = os.getenv("READTHEDOCS_VERSION", "0.0.6")
release = version
exclude_patterns = ["_build"]
@@ -66,7 +66,6 @@
html_theme_options = {
"logo_only": True,
"collapse_navigation": False,
- "display_version": False,
}
html_title = f"Kraken Engine ({version}) documentation in English"
@@ -75,8 +74,8 @@
"display_github": True, # Integrate GitHub
"github_user": "durkisneer1", # Username
"github_repo": "Kraken-Engine", # Repo name
- "github_version": "0.0.3", # Version
- "conf_py_path": "/", # Path in the checkout to the docs root
+ "github_version": "main", # Version
+ "conf_py_path": "/docs/", # Path in the checkout to the docs root
"kraken_docs_title": html_title,
"kraken_docs_basepath": "https://kraken-engine.readthedocs.io/",
"kraken_docs_suffix": ".html",
@@ -84,24 +83,16 @@
"kraken_canonical_version": "stable",
# Set this to `True` when in the `latest` branch to clearly indicate to the reader
# that they are not reading the `stable` documentation.
- "kraken_is_latest": False,
- "kraken_version": "0.0.3",
+ "kraken_is_latest": True,
+ "kraken_version": "0.0.6",
# Enables a banner that displays the up-to-date status of each article.
"kraken_show_article_status": True,
}
html_logo = "_static/kraken-engine-banner.png"
-html_static_path = ['_static']
-
-html_css_files = [
- 'css/algolia.css',
- 'https://cdn.jsdelivr.net/npm/@docsearch/css@3',
- "css/custom.css",
-]
-
-html_js_files = [
- "js/custom.js",
-]
+html_static_path = ["_static"]
+html_css_files = ["css/custom.css"]
+html_js_files = ["js/custom.js"]
file_insertion_enabled = False
@@ -110,4 +101,4 @@
gettext_compact = False
-epub_tocscope = 'includehidden'
+epub_tocscope = "includehidden"
diff --git a/docs/contributing/guidelines.rst b/docs/contributing/guidelines.rst
new file mode 100644
index 0000000..148f3eb
--- /dev/null
+++ b/docs/contributing/guidelines.rst
@@ -0,0 +1,70 @@
+Coding Guidelines
+=================
+
+This document outlines the coding guidelines for Kraken Engine.
+These guidelines are intended to ensure that Kraken stays in its best shape and documentation is consistent and easy to read.
+
+General Principles
+------------------
+
+- **Consistency**: Follow the style of the surrounding code. This is elaborated in the next section.
+- **Clarity**: Write code that's easy to read and understand. This means using descriptive names for variables, functions, and classes.
+- **Documentation**: Write clear, concise, and useful comments.
+- **Testing**: Write tests for your code (you may ask an experienced contributor to assist you).
+- **Error Handling**: Check for errors and handle them appropriately.
+
+
+Style Guidelines
+----------------
+
+Kraken Engine uses a consistent code style defined by the ``.clang-format`` file in the root directory. Additionally, the ``.pre-commit-config.yaml`` file automatically runs clang-format on staged files before committing, ensuring proper formatting.
+
+However, the formatter doesn't catch everything. Please follow these additional guidelines:
+
+- **Naming Conventions**:
+ - Classes, Structs, and Enums: ``PascalCase``
+ - Functions and Variables: ``camelCase``
+ - Constants: ``ALL_CAPS``
+ - Header Files: ``PascalCase.hpp``
+ - Source Files: ``snake_case.cpp``
+- **Header Includes**: Only include headers that are strictly necessary in a given file. Avoid including general-purpose headers (e.g., ````) in header files unless they are directly needed there. Instead, include them in source files where applicable. This practice reduces unnecessary dependencies and improves compile times.
+- **Comments**: Focus comments on the *why* behind your code, not the *what*. Clear and descriptive function and variable names should make the code's intent self-explanatory.
+
+Documentation Guidelines
+------------------------
+
+Kraken Engine uses **Sphinx** and **Doxygen** for generating documentation.
+Follow these guidelines to maintain consistency and clarity:
+
+Writing Docstrings
+~~~~~~~~~~~~~~~~~~
+
+When writing docstrings, adhere to the **Doxygen** format. Here's an example:
+
+.. code-block:: cpp
+
+ /**
+ * @brief Adds two numbers.
+ *
+ * @param a The left-hand side operand.
+ * @param b The right-hand side operand.
+ *
+ * @return The sum of a and b.
+ */
+ int addNums(int a, int b);
+
+For more details on Doxygen formatting, refer to the `official Doxygen documentation `_.
+
+Writing Documentation
+~~~~~~~~~~~~~~~~~~~~~
+
+Use **reStructuredText (RST)** format for creating and editing documentation files.
+
+- If you add a new class, function, or namespace to the codebase, create a corresponding ``.rst`` file in the appropriate directory under ``docs/``.
+- Include the following details in the ``.rst`` file:
+ - A clear description of the class, function, or namespace.
+ - Example usage code (if applicable).
+ - Doxygen's generated documentation.
+
+For guidance on RST syntax, check the `official Sphinx documentation `_.
+You can also review existing ``.rst`` files in the repository as examples.
diff --git a/docs/contributing/how_to_contribute.rst b/docs/contributing/how_to_contribute.rst
index ffcf026..6b0a08e 100644
--- a/docs/contributing/how_to_contribute.rst
+++ b/docs/contributing/how_to_contribute.rst
@@ -1,7 +1,71 @@
How To Contribute
=================
-This page is currently under construction. Please check back later for updates.
+Welcome!
+Your contributions help make Kraken Engine better for everyone,
+whether it's fixing a bug, adding a feature, or improving the documentation.
-.. image:: ../_static/under_construction.gif
- :alt: Page Under Construction
\ No newline at end of file
+Prerequisites
+-------------
+
+Before you begin, ensure you have:
+
+- A GitHub account
+- Git installed locally
+- A C/C++ compiler (e.g., GCC, Clang, MSVC)
+- Python 3.9 or later
+- Doxygen
+- Familiarity with our :doc:`guidelines`
+
+Setting Up
+----------
+
+The Kraken Engine repository uses a `dev` branch (e.g., ``0.0.6dev``) for upcoming releases.
+Base your work on this branch.
+
+1. Fork and clone the current `dev` branch.
+2. Run ``pip install -r requirements.txt`` to install dependencies.
+
+Compiling
+---------
+
+To control which of the following targets you want to build, change the options in ``meson_options.txt``.
+For example, both ``build_example`` and ``build_tests`` set to ``false`` builds only the library.
+Then, run either of the following commands:
+
+Library Only
+~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ meson setup build
+ meson compile kraken -C build
+
+Library + Example Demo
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ meson setup build
+ meson compile krakenapp -C build
+
+Library + Test Suite
+~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: bash
+
+ meson setup build
+ meson compile krakentests -C build
+
+
+Making Changes
+--------------
+
+*Before making changes to Kraken, please read the* :doc:`guidelines`.
+
+1. Commit and push your changes to your fork.
+2. Submit a pull request to the `dev` branch of the Kraken Engine repository.
+3. Address any feedback from maintainers.
+4. Once approved, your changes will be merged, and you will be credited in the changelog.
+
+Thank you for contributing to Kraken Engine!
diff --git a/docs/contributing/testing.rst b/docs/contributing/testing.rst
new file mode 100644
index 0000000..2948d05
--- /dev/null
+++ b/docs/contributing/testing.rst
@@ -0,0 +1,37 @@
+Testing Your Contributions
+==========================
+
+Before submitting a pull request, you should run tests and preview the documentation to ensure that your changes are correct.
+
+Testing Code
+----------------
+
+If you haven't already, read through building the *test suite* in the :doc:`how_to_contribute` section.
+
+Tests are written using the Google Test framework and can be found in the ``tests`` directory.
+After making changes or adding new features, run the test suite to ensure that your changes do not break existing or new functionality.
+
+Previewing Documentation
+------------------------
+
+1. Install the required dependencies via pip:
+
+.. code-block:: bash
+
+ pip install -r docs/requirements.txt
+
+2. Generate the documentation with Doxygen:
+
+.. code-block:: bash
+
+ cd docs
+ doxygen
+
+1. Build the documentation:
+
+.. code-block:: bash
+
+ make -C docs html
+
+You will find the generated documentation in the ``docs/_build/html`` directory.
+If everything looks as intended, you can submit your pull request.
diff --git a/docs/getting_started/create_window.rst b/docs/getting_started/create_window.rst
index 36b3e83..0b19486 100644
--- a/docs/getting_started/create_window.rst
+++ b/docs/getting_started/create_window.rst
@@ -2,30 +2,51 @@ Creating a Window
=================
After following the :doc:`installation` guide, you are ready for your first Kraken Engine program.
+The following code creates a window and keeps it open until the user closes it.
.. code-block:: c++
:linenos:
#include
- int main()
- {
+ int main() {
kn::window::init({800, 600});
- kn::time::Clock clock;
+ kn::Event event;
- bool done = false;
- while (!done)
- {
- clock.tick();
-
- for (const auto &event : kn::window::getEvents())
- if (event.type == kn::QUIT)
- done = true;
-
- kn::window::clear();
+ while (kn::window::isOpen()) {
+ while (kn::window::pollEvent(event)) {
+ // handle events
+ }
kn::window::flip();
}
-
kn::window::quit();
+
return EXIT_SUCCESS;
}
+
+Explanation
+-----------
+
+This simple program serves as a basic skeleton for applications built with the Kraken Engine.
+Let's break down the key components that make this program function:
+
+* Initializing the Window
+ ``kn::window::init({800, 600})`` sets up a rendering window with a resolution of 800x600 pixels.
+ This function prepares the underlying graphical context, ensuring the application is ready to display content.
+
+* Event Handling
+ Before entering the main game loop, an ``Event`` union is instantiated.
+ This union acts as a container for user inputs and system events.
+ The call to ``kn::window::pollEvent(event)`` continuously monitors for user interactions or system-generated signals, populating the event union with the details of the most recent occurrence.
+
+* Closing the Application
+ By default, Kraken Engine applications close when the user clicks the window’s close button.
+ Upon detecting this event internally, the call to ``kn::window::close()`` ends the game loop.
+
+* Updating the Display
+ ``kn::window::flip()`` acts as the final piece in the game loop, swapping the back buffer to the front.
+ This step ensures that any rendering operations performed during the loop are visually updated on the screen.
+ Think of it as flipping the pages of a book—ensuring smooth, sequential updates for the user.
+
+This template establishes a foundation for more advanced game logic and rendering.
+In-depth tutorials on the next page cover the more intricate aspects of the Kraken Engine.
diff --git a/docs/getting_started/installation.rst b/docs/getting_started/installation.rst
index 23b4473..b5aae2e 100644
--- a/docs/getting_started/installation.rst
+++ b/docs/getting_started/installation.rst
@@ -9,9 +9,13 @@ Before starting, ensure you have the Meson build system and a C++ compiler insta
mkdir subprojects
-2. Run ``meson wrap install kraken-engine`` to install the latest version of Kraken Engine.
+2. Use the following command to install the latest version of Kraken Engine:
-3. Include the Kraken Engine dependency in your Meson build file.
+.. code-block:: bash
+
+ meson wrap install kraken-engine
+
+3. Include the Kraken Engine dependency in your Meson build file (usually ``meson.build``).
.. code-block:: python
@@ -19,4 +23,21 @@ Before starting, ensure you have the Meson build system and a C++ compiler insta
kraken_dep = dependency('kraken-engine')
executable('MyProject', 'main.cpp', dependencies: kraken_dep)
-4. Continue with the typical Meson build and compile process.
+4. Build and compile your project using Meson:
+
+.. code-block:: bash
+
+ meson setup build
+ cd build
+ meson compile
+
+You can also use the ``-j`` flag to specify the number of threads to use to speed up compilation (e.g., ``meson compile -j4``).
+
+Updating
+--------
+
+To update Kraken Engine to the latest version, use the following command:
+
+.. code-block:: bash
+
+ meson wrap update kraken-engine
diff --git a/docs/index.rst b/docs/index.rst
index 20b0cd2..61ae93c 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,35 +1,40 @@
Kraken Engine Docs
==================
-The **Kraken Engine** is a powerful extension to *SDL2*, designed to streamline your
-game development process. It provides a suite of easy-to-use features such as texture
-caching and collision logic, allowing you to focus on creating immersive experiences
-for your players.
+The **Kraken Engine** is a robust extension to *SDL2*, designed to strike a balance between high- and low-level control in game development.
+It supports input handling, audio management, and extra utilities like tile maps and animation controllers.
About
-----
-This documentation is a comprehensive guide to the Kraken Engine, covering everything
-from installation to advanced features. It's intended for both beginners and
-experienced developers.
+This documentation provides a comprehensive guide to the Kraken Engine, catering to developers with a foundational understanding of C++.
+As the engine evolves, it will also become increasingly accessible to beginners, with a friendlier API and enhanced documentation.
Future of Kraken Engine
-----------------------
-Future plans for the Kraken Engine include the implementation of essential mathematical
-functions like pathfinding, ray casting, and a built-in physics engine, further enhancing
-its capabilities and versatility.
+Planned features for the Kraken Engine include implementing essential mathematical tools like pathfinding, ray casting, and a built-in physics engine.
+These additions will further expand the engine’s capabilities and make it a one-stop solution for 2D game development.
+
+Advanced Features
+-----------------
+
+The Kraken Engine stands out with its:
+
+- **Animation Controller Class**: A utility for managing sprite sheet animations efficiently.
+- **Tile Map Class**: Simplifying the process of loading, rendering, and managing tile maps.
Community
---------
-Join our `discord `_ to get help, share your projects, and contribute to the Kraken Engine!
+Join our `Discord `_ to connect with fellow developers, share your projects, and contribute to the Kraken Engine.
-We look forward to seeing the incredible games you create with the Kraken Engine.
+We’re excited to see how you push the boundaries of creativity with the Kraken Engine!
.. toctree::
:hidden:
:maxdepth: 1
+ :caption: Getting Started
getting_started/installation
getting_started/create_window
@@ -37,17 +42,36 @@ We look forward to seeing the incredible games you create with the Kraken Engine
.. toctree::
:hidden:
:maxdepth: 1
+ :caption: Manual
tutorials/index
.. toctree::
:hidden:
:maxdepth: 1
+ :caption: Contributing
contributing/how_to_contribute
+ contributing/guidelines
+ contributing/testing
.. toctree::
:hidden:
:maxdepth: 1
-
- reference/index
\ No newline at end of file
+ :caption: API Reference
+
+ reference/animation_controller
+ reference/font
+ reference/rect
+ reference/sound
+ reference/texture
+ reference/tile_map
+ reference/constants
+ reference/controller
+ reference/draw
+ reference/key
+ reference/math
+ reference/mouse
+ reference/music
+ reference/time
+ reference/window
\ No newline at end of file
diff --git a/docs/reference/animation_controller.rst b/docs/reference/animation_controller.rst
index 6049823..9d824a0 100644
--- a/docs/reference/animation_controller.rst
+++ b/docs/reference/animation_controller.rst
@@ -11,21 +11,24 @@ What the AnimationController class does **NOT** do is provide a draw function.
This design decision is to give users the flexibility to draw the current frame in a desired position and size.
Usage
--------------
+-----
.. code-block:: cpp
- // Create an AnimationController with a frame rate of 10 frames per second.
- kn::AnimationController animController(10);
+ // Instantiate an AnimationController.
+ kn::AnimationController animController;
// Load animations from sprite sheets where every frame is 16x16 pixels.
- animController.addAnim("assets/idle.png", "idle", 16, 16);
- animController.addAnim("assets/walk.png", "walk", 16, 16);
+ animController.addAnim("idle", "assets/idle.png", {16, 16}, 5); // 5fps
+ animController.addAnim("walk", "assets/walk.png", {16, 16}, 10); // 10fps
// Since the current animation set is the last one added,
// we can switch to the first animation by name.
animController.setAnim("idle");
+ // Change the animation controller's playback speed.
+ animController.setPlaybackSpeed(-1.5f); // Reverse playback at 1.5x speed.
+
// Using AnimationController::nextFrame is simple.
// It returns the current frame texture, srcRect, and advances the animation.
const Frame frame = animController.nextFrame(deltaTime);
@@ -33,5 +36,16 @@ Usage
// Draw the current frame texture at position (50, 50) and size (16, 16).
kn::window::blit(*frame.tex, {50, 50, 16, 16}, frame.rect);
+Members
+-------
+
+.. doxygenstruct:: kn::Frame
+ :members:
+ :undoc-members:
+
+.. doxygenstruct:: kn::Animation
+ :members:
+ :undoc-members:
+
.. doxygenclass:: kn::AnimationController
:members:
diff --git a/docs/reference/controller.rst b/docs/reference/controller.rst
new file mode 100644
index 0000000..418053f
--- /dev/null
+++ b/docs/reference/controller.rst
@@ -0,0 +1,40 @@
+controller
+==========
+
+Description
+-----------
+
+The **controller** namespace contains functions to handle input from a game controller.
+
+.. note:: The default dead zone for the joysticks is 0.1f.
+
+Usage
+-----
+
+.. code-block:: cpp
+
+ // Get the vector of the left joystick
+ kn::Vec2 leftJoystick = kn::controller::getLeftJoystick();
+
+ // Change the joysticks' dead zones
+ kn::controller::setDeadZone(0.2f);
+
+ // Get how far the right trigger is pressed
+ if (kn::controller::getRightTrigger() > 0.5f) {
+ // Do something
+ }
+
+Functions
+---------
+
+.. doxygenfunction:: kn::controller::getLeftJoystick
+
+.. doxygenfunction:: kn::controller::getRightJoystick
+
+.. doxygenfunction:: kn::controller::getLeftTrigger
+
+.. doxygenfunction:: kn::controller::getRightTrigger
+
+.. doxygenfunction:: kn::controller::setDeadZone
+
+.. doxygenfunction:: kn::controller::getDeadZone
diff --git a/docs/reference/font.rst b/docs/reference/font.rst
index 3ad2e13..b21ddf2 100644
--- a/docs/reference/font.rst
+++ b/docs/reference/font.rst
@@ -20,5 +20,8 @@ Usage
// Draw the text texture at position (50, 50).
kn::window::blit(text, {50, 50});
+Members
+-------
+
.. doxygenclass:: kn::Font
:members:
\ No newline at end of file
diff --git a/docs/reference/index.rst b/docs/reference/index.rst
deleted file mode 100644
index bb07367..0000000
--- a/docs/reference/index.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-=============
-API Reference
-=============
-
-.. toctree::
- :maxdepth: 1
-
- animation_controller.rst
- font.rst
- rect.rst
- sound.rst
- texture.rst
- tile_map.rst
- constants.rst
- draw.rst
- input.rst
- math.rst
- music.rst
- time.rst
- window.rst
\ No newline at end of file
diff --git a/docs/reference/input.rst b/docs/reference/input.rst
deleted file mode 100644
index c42560a..0000000
--- a/docs/reference/input.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-input
-=====
-
-Description
------------
-
-The **input** namespace contains functions to get input from the keyboard and mouse.
-:code:`kn::input::getVector` is going to be your best friend when you need to get 8-directional input.
-This function is planned to also support controller joystick input in the future.
-
-Usage
------
-
-.. code-block:: cpp
-
- // Check if the left mouse button is pressed.
- if (kn::input::isMouseButtonPressed(kn::BUTTON_LEFT)) {
- // Handle left mouse button press.
- }
-
- // Check if the 'W' key is pressed.
- if (kn::input::isKeyPressed(kn::S_w)) {
- // Handle 'W' key press.
- }
-
- // Using the getVector function to get 8-directional input.
- std::vector left = {kn::S_a, kn::S_LEFT};
- std::vector right = {kn::S_d, kn::S_RIGHT};
- std::vector up = {kn::S_w, kn::S_UP};
- std::vector down = {kn::S_s, kn::S_DOWN};
-
- kn::math::Vec2 inputDir = kn::input::getVector(left, right, up, down);
-
-.. doxygenfunction:: kn::input::getMousePos
-
-.. doxygenfunction:: kn::input::isMouseButtonPressed
-
-.. doxygenfunction:: kn::input::isKeyPressed
-
-.. doxygenfunction:: kn::input::getVector
\ No newline at end of file
diff --git a/docs/reference/key.rst b/docs/reference/key.rst
new file mode 100644
index 0000000..cc09ff9
--- /dev/null
+++ b/docs/reference/key.rst
@@ -0,0 +1,28 @@
+key
+===
+
+Description
+-----------
+
+The **key** namespace contains functions to handle input from the keyboard.
+The two types of key codes are `scancodes` and `keycodes`.
+Scancodes are the physical location of the key on the keyboard.
+Keycodes are the character that the key represents.
+For example, 'W' has a scancode of ``kn::S_w`` and a keycode of ``kn::K_w``.
+
+.. note:: A function similar to the removed ``kn::input::getVector`` will eventually be added to this namespace.
+
+Usage
+-----
+
+.. code-block:: cpp
+
+ // Check if the 'W' key is pressed.
+ if (kn::key::isPressed(kn::S_w)) {
+ // Handle 'W' key press.
+ }
+
+Functions
+---------
+
+.. doxygenfunction:: kn::key::isPressed
\ No newline at end of file
diff --git a/docs/reference/mouse.rst b/docs/reference/mouse.rst
new file mode 100644
index 0000000..c51ce2d
--- /dev/null
+++ b/docs/reference/mouse.rst
@@ -0,0 +1,28 @@
+mouse
+=====
+
+Description
+-----------
+
+The **mouse** namespace contains functions to handle input from the mouse.
+
+Usage
+-----
+
+.. code-block:: cpp
+
+ // Check if the left mouse button is pressed.
+ if (kn::mouse::isPressed(kn::BUTTON_LEFT)) {
+ // Handle left mouse button press.
+ }
+
+ // Get the mouse position and draw a circle at that position.
+ kn::math::Vec2 mousePos = kn::mouse::getPos();
+ kn::draw::circle(mousePos, 10, {255, 0, 0});
+
+Functions
+---------
+
+.. doxygenfunction:: kn::mouse::getPos
+
+.. doxygenfunction:: kn::mouse::isPressed
\ No newline at end of file
diff --git a/docs/reference/music.rst b/docs/reference/music.rst
index c8ab7e0..3f7ed3e 100644
--- a/docs/reference/music.rst
+++ b/docs/reference/music.rst
@@ -28,6 +28,9 @@ Usage
// Fade out the music over 2 seconds.
kn::music::fadeOut(2000);
+Functions
+---------
+
.. doxygenfunction:: kn::music::load
.. doxygenfunction:: kn::music::unload
diff --git a/docs/reference/rect.rst b/docs/reference/rect.rst
index da7047e..348557a 100644
--- a/docs/reference/rect.rst
+++ b/docs/reference/rect.rst
@@ -1,6 +1,42 @@
Rect
====
+Description
+-----------
+
+The **Rect** class represents a rectangle in 2D space. It is defined by its top-left corner and its size.
+Its most common uses are source and destination rectangles in blitting operations, as well as collision detection.
+
+It may be undocumented, but there are getter and setter methods for all 9 positions in the rectangle, as shown below:
+
+.. image:: ../_static/rect.png
+ :alt: The 9 positions of a Rect
+ :width: 500px
+
+Usage
+-----
+
+.. code-block:: cpp
+
+ // Instantiate a Rect.
+ kn::Rect rectA{50, 50, 16, 16};
+
+ // Instantiate another Rect.
+ kn::Rect rectB{100, 100, 12, 12};
+
+ // Check if they collide.
+ if (rectA.collideRect(rectB)) {
+ // handle collision
+ }
+
+ // Move the top left of rectB to the bottom right of rectA
+ rectB.setTopLeft(rectA.getBottomRight());
+
+ // Clamp rectA inside rectB
+ rectA.clamp(rectB);
+
+Members
+-------
+
.. doxygenstruct:: kn::Rect
:members:
- :undoc-members:
\ No newline at end of file
diff --git a/docs/reference/sound.rst b/docs/reference/sound.rst
index f57ef6f..306b7db 100644
--- a/docs/reference/sound.rst
+++ b/docs/reference/sound.rst
@@ -25,5 +25,8 @@ Usage
// Play the sound 3 times after it fades in over 2 seconds.
sound.play(2, -1, 2000);
+Members
+-------
+
.. doxygenclass:: kn::Sound
:members:
diff --git a/docs/reference/texture.rst b/docs/reference/texture.rst
index 7b207fb..95a8904 100644
--- a/docs/reference/texture.rst
+++ b/docs/reference/texture.rst
@@ -29,6 +29,8 @@ Usage
kn::window::blit(imageTexture, {50, 50});
kn::window::blit(colorTexture, {100, 100});
+Members
+-------
.. doxygenclass:: kn::Texture
:members:
\ No newline at end of file
diff --git a/docs/reference/tile_map.rst b/docs/reference/tile_map.rst
index bac1474..88d1791 100644
--- a/docs/reference/tile_map.rst
+++ b/docs/reference/tile_map.rst
@@ -41,6 +41,9 @@ Usage
tileMap.drawMap();
tileMap.drawLayer("walls");
+Members
+-------
+
.. doxygenstruct:: kn::Tile
:members:
:undoc-members:
diff --git a/docs/reference/time.rst b/docs/reference/time.rst
index fe0ecc1..b695103 100644
--- a/docs/reference/time.rst
+++ b/docs/reference/time.rst
@@ -21,7 +21,13 @@ Usage
// Get the time since Kraken was initialized.
double elapsedTime = kn::time::getTicks();
+Members
+-------
+
.. doxygenclass:: kn::time::Clock
:members:
+Functions
+---------
+
.. doxygenfunction:: kn::time::getTicks
diff --git a/docs/reference/window.rst b/docs/reference/window.rst
index c6e90be..9632772 100644
--- a/docs/reference/window.rst
+++ b/docs/reference/window.rst
@@ -1,4 +1,6 @@
window
======
+.. doxygenvariable:: kn::camera
+
.. doxygennamespace:: kn::window
\ No newline at end of file
diff --git a/example/main.cpp b/example/main.cpp
index 31fd963..1daf011 100644
--- a/example/main.cpp
+++ b/example/main.cpp
@@ -12,15 +12,16 @@ int main()
Player player(tileMap);
- bool done = false;
- while (!done)
+ kn::Event event;
+ while (kn::window::isOpen())
{
- const double dt = clock.tick();
+ const double dt = clock.tick() / 1000.0;
- for (const auto& event : kn::window::getEvents())
- if (event.type == kn::QUIT ||
- (event.type == kn::KEYDOWN && event.key.keysym.sym == kn::K_ESCAPE))
- done = true;
+ while (kn::window::pollEvent(event))
+ {
+ if (event.type == kn::KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
+ kn::window::close();
+ }
kn::window::clear({21, 18, 37});
diff --git a/example/src/player.cpp b/example/src/player.cpp
index e868b59..c4e6686 100644
--- a/example/src/player.cpp
+++ b/example/src/player.cpp
@@ -4,10 +4,10 @@
#define AxisY 1
Player::Player(const kn::TileMap& tileMap)
- : animController(5), rect(48, 120, 13, 16), collisionLayer(tileMap.getLayer("Wall"))
+ : rect(48, 120, 13, 16), collisionLayer(tileMap.getLayer("Wall"))
{
- animController.addAnim("../example/assets/player_idle.png", "idle", 13, 16);
- animController.addAnim("../example/assets/player_walk.png", "walk", 13, 16);
+ animController.addAnim("idle", "../example/assets/player_idle.png", {13, 16}, 5);
+ animController.addAnim("walk", "../example/assets/player_walk.png", {13, 16}, 5);
interactables = tileMap.getTileCollection({"Mirror", "Bed", "Desk"});
}
@@ -16,7 +16,7 @@ void Player::update(const double dt)
{
if (onGround)
{
- if (kn::input::isKeyPressed(kn::S_SPACE))
+ if (kn::key::isPressed(kn::S_SPACE))
{
velocity.y = -200;
onGround = false;
@@ -25,20 +25,20 @@ void Player::update(const double dt)
else
velocity.y += 980.0 * dt;
- const auto dirVec = kn::input::getVector({kn::S_a}, {kn::S_d});
- if (dirVec.x != 0.0)
+ const int xDir = kn::key::isPressed(kn::S_d) - kn::key::isPressed(kn::S_a);
+ if (xDir != 0)
{
animController.setAnim("walk");
- if (dirVec.x > 0.0)
+ if (xDir > 0)
facingRight = true;
- else if (dirVec.x < 0.0)
+ else if (xDir < 0)
facingRight = false;
}
else
animController.setAnim("idle");
- velocity.x = dirVec.x * moveSpeed;
+ velocity.x = xDir * moveSpeed;
rect.x += static_cast(velocity.x * dt);
handleCollision(AxisX);
diff --git a/include/AnimationController.hpp b/include/AnimationController.hpp
index 953c208..aa20b3c 100644
--- a/include/AnimationController.hpp
+++ b/include/AnimationController.hpp
@@ -10,7 +10,7 @@ namespace kn
{
/**
- * @brief Container for a texture pointer and rect
+ * @brief Container for a texture pointer and source rect
*/
struct Frame
{
@@ -18,6 +18,15 @@ struct Frame
Rect rect;
};
+/**
+ * @brief Container for a list of frames.
+ */
+struct Animation
+{
+ std::vector frames;
+ int fps;
+};
+
/**
* @brief Handler for sprite sheet animations.
*/
@@ -25,25 +34,30 @@ class AnimationController final
{
public:
/**
- * @brief Construct a new Animation Controller object at a given frame rate.
- *
- * @param fps The frame rate of the animation.
+ * @brief Construct a new Animation Controller object.
*/
- explicit AnimationController(int fps = 24);
+ AnimationController() = default;
~AnimationController() = default;
/**
* @brief Set up the animation controller.
*
- * @param filePath The path to the sprite sheet image file.
* @param name The name of the animation.
- * @param frameWidth The width of each frame.
- * @param frameHeight The height of each frame.
+ * @param filePath The path to the sprite sheet image file.
+ * @param frameSize The size of each frame in the sprite sheet.
+ * @param fps The frame rate of the animation.
*
* @return true if the setup was successful, false otherwise.
*/
- [[maybe_unused]] bool addAnim(const std::string& filePath, const std::string& name,
- int frameWidth, int frameHeight);
+ [[maybe_unused]] bool addAnim(const std::string& name, const std::string& filePath,
+ const math::Vec2& frameSize, int fps);
+
+ /**
+ * @brief Remove an animation from the controller.
+ *
+ * @param name The name of the animation to remove.
+ */
+ void removeAnim(const std::string& name);
/**
* @brief Change the active animation.
@@ -64,13 +78,26 @@ class AnimationController final
[[nodiscard]] const Frame& nextFrame(double deltaTime);
/**
- * @brief Set the frame rate of the animation.
+ * @brief Set the playback speed of animations.
*
- * @param fps The frame rate of the animation.
+ * @param speed The speed to animate.
+ * YES, IT WORKS WITH NEGATIVE VALUES!
+ */
+ void setPlaybackSpeed(double speed);
+
+ /**
+ * @brief Check if the current animation playing is finished.
*
- * @note Will pause the animation if set to 0.
+ * @return true if the animation is finished, false otherwise.
*/
- void setFPS(int fps);
+ [[nodiscard]] bool isFinished();
+
+ /**
+ * @brief Get the current animation name.
+ *
+ * @return The name of the current animation.
+ */
+ [[nodiscard]] const std::string& getCurrentAnim() const { return m_currAnim; }
/**
* @brief Pause the animation.
@@ -85,14 +112,12 @@ class AnimationController final
void resume();
private:
- int m_fps;
- int m_index = 0;
+ double m_playbackSpeed = 1.0;
+ double m_index = 0.0;
+ double m_prevIndex = 0.0;
bool m_paused = false;
std::string m_currAnim;
- double m_frameTime_ms;
- double m_timeLeftInFrame_ms = 0.0;
-
- std::unordered_map> m_animMap;
+ std::unordered_map m_animMap;
};
} // namespace kn
diff --git a/include/GameController.hpp b/include/GameController.hpp
new file mode 100644
index 0000000..45f1799
--- /dev/null
+++ b/include/GameController.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+namespace kn
+{
+namespace math
+{
+class Vec2;
+} // namespace math
+
+namespace controller
+{
+/**
+ * @brief Get the direction vector from the controller's left joystick.
+ *
+ * @return A direction vector. If the controller is not connected, a zero vector is returned.
+ */
+[[nodiscard]] math::Vec2 getLeftJoystick();
+
+/**
+ * @brief Get the direction vector from the controller's right joystick.
+ *
+ * @return A direction vector. If the controller is not connected, a zero vector is returned.
+ */
+[[nodiscard]] math::Vec2 getRightJoystick();
+
+/**
+ * @brief Get how far the controller's left trigger is pressed.
+ *
+ * @return The value of the left trigger from the range 0.0 to 1.0. If the controller is not
+ * connected, 0.0 is returned.
+ */
+[[nodiscard]] double getLeftTrigger();
+
+/**
+ * @brief Get how far the controller's right trigger is pressed.
+ *
+ * @return The value of the right trigger from the range 0.0 to 1.0. If the controller is not
+ * connected, 0.0 is returned.
+ */
+[[nodiscard]] double getRightTrigger();
+
+/**
+* @brief Change the dead zone for controller joystick input.
+*
+* @param deadZone The dead zone for the controller from the range 0.0 to 1.0.
+*
+* @note Negative values will be clamped to 0.0 and values greater than 1.0 will be clamped
+to 1.0.
+*/
+void setDeadZone(float deadZone);
+
+/**
+ * @brief Get the dead zone for controller joystick input.
+ *
+ * @return The dead zone for the controller from the range 0.0 to 1.0.
+ */
+[[nodiscard]] float getDeadZone();
+
+} // namespace controller
+} // namespace kn
diff --git a/include/Input.hpp b/include/Input.hpp
deleted file mode 100644
index c26e4b3..0000000
--- a/include/Input.hpp
+++ /dev/null
@@ -1,55 +0,0 @@
-#pragma once
-
-#include
-#include
-
-#include "Constants.hpp"
-
-namespace kn
-{
-namespace math
-{
-class Vec2;
-} // namespace math
-
-namespace input
-{
-/**
- * @brief Get the mouse position.
- *
- * @return The mouse position.
- */
-math::Vec2 getMousePos();
-
-/**
- * @brief Check if the argument mouse button is pressed.
- *
- * @param button The mouse button to check.
- *
- * @return If the argument mouse button is pressed.
- */
-bool isMouseButtonPressed(int button);
-
-/**
- * @brief Check if the argument key is pressed.
- *
- * @param key The key to check.
- *
- * @return If the argument key is pressed.
- */
-bool isKeyPressed(int key);
-
-/**
- * @brief Get the vector of the keys pressed.
- *
- * @param left The keys to move left.
- * @param right The keys to move right.
- * @param up The keys to move up.
- * @param down The keys to move down.
- *
- * @return The vector of the keys pressed.
- */
-math::Vec2 getVector(const std::vector& left = {}, const std::vector& right = {},
- const std::vector& up = {}, const std::vector& down = {});
-} // namespace input
-} // namespace kn
diff --git a/include/Key.hpp b/include/Key.hpp
new file mode 100644
index 0000000..919525e
--- /dev/null
+++ b/include/Key.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+namespace kn
+{
+namespace math
+{
+class Vec2;
+} // namespace math
+
+namespace key
+{
+/**
+ * @brief Check if the argument key is pressed.
+ *
+ * @param key The key to check.
+ *
+ * @return If the argument key is pressed.
+ */
+bool isPressed(int key);
+} // namespace key
+} // namespace kn
\ No newline at end of file
diff --git a/include/KrakenEngine.hpp b/include/KrakenEngine.hpp
index 0ebabe9..c52381f 100644
--- a/include/KrakenEngine.hpp
+++ b/include/KrakenEngine.hpp
@@ -6,10 +6,11 @@
#include "Constants.hpp"
#include "Draw.hpp"
#include "Font.hpp"
-#include "Input.hpp"
+#include "GameController.hpp"
+#include "Key.hpp"
#include "Math.hpp"
+#include "Mouse.hpp"
#include "Music.hpp"
-#include "Overflow.hpp"
#include "Rect.hpp"
#include "Sound.hpp"
#include "Texture.hpp"
diff --git a/include/Mouse.hpp b/include/Mouse.hpp
new file mode 100644
index 0000000..a6f1fac
--- /dev/null
+++ b/include/Mouse.hpp
@@ -0,0 +1,30 @@
+#pragma once
+#include
+
+namespace kn
+{
+namespace math
+{
+class Vec2;
+} // namespace math
+
+namespace mouse
+{
+/**
+ * @brief Get the mouse position.
+ *
+ * @return The mouse position as a vector.
+ */
+math::Vec2 getPos();
+
+/**
+ * @brief Check if the argument mouse button is pressed.
+ *
+ * @param button The mouse button to check.
+ *
+ * @return If the argument mouse button is pressed.
+ */
+bool isPressed(uint32_t button);
+
+} // namespace mouse
+} // namespace kn
\ No newline at end of file
diff --git a/include/Rect.hpp b/include/Rect.hpp
index e8fd099..e24a613 100644
--- a/include/Rect.hpp
+++ b/include/Rect.hpp
@@ -18,13 +18,17 @@ struct Rect : SDL_FRect
/**
* @brief Construct a new rectangle.
*
- * @param x The x position.
- * @param y The y position.
+ * @param x The x ordinate of its top left.
+ * @param y The y ordinate of its top left.
* @param w The width.
* @param h The height.
*/
- Rect(float x, float y, float w, float h);
- Rect(int x, int y, int w, int h);
+ template
+ Rect(T x, T y, T w, T h)
+ : SDL_FRect{static_cast(x), static_cast(y), static_cast(w),
+ static_cast(h)}
+ {
+ }
/**
* @brief Get the position of the rectangle.
diff --git a/include/Time.hpp b/include/Time.hpp
index 2c3c9c5..a5eea90 100644
--- a/include/Time.hpp
+++ b/include/Time.hpp
@@ -16,9 +16,9 @@ class Clock final
~Clock() = default;
/**
- * @brief Get the delta time between frames.
+ * @brief Get the delta time between frames in milliseconds.
*
- * @param frameRate The frame rate to calculate the delta time.
+ * @param frameRate The frame rate to cap the program at.
*
* @return The delta time between frames.
*/
@@ -27,10 +27,9 @@ class Clock final
private:
double m_frameTime = 0.0;
double m_targetFrameTime = 0.0;
- double m_deltaTime = 0.0;
double m_frequency = static_cast(SDL_GetPerformanceFrequency());
double m_now = static_cast(SDL_GetPerformanceCounter());
- double m_last = static_cast(SDL_GetPerformanceCounter());
+ double m_last = m_now;
};
/**
diff --git a/include/Window.hpp b/include/Window.hpp
index f86fc87..140659f 100644
--- a/include/Window.hpp
+++ b/include/Window.hpp
@@ -26,8 +26,23 @@ namespace window
* @param resolution The size of the window.
* @param title The title of the window.
* @param scale The scale of the window.
+ *
+ * @return Whether the window was successfully initialized.
+ */
+[[maybe_unused]] bool init(const math::Vec2& resolution, const std::string& title = "Kraken Window",
+ int scale = 1);
+
+/**
+ * @brief Get whether the window is open.
+ *
+ * @return Whether the window is open.
*/
-void init(const math::Vec2& resolution, const std::string& title = "Kraken Window", int scale = 1);
+[[nodiscard]] bool isOpen();
+
+/**
+ * @brief Close the window.
+ */
+void close();
/**
* @brief Clear the screen.
@@ -63,35 +78,37 @@ void blit(const Texture& texture, const math::Vec2& position = {});
*
* @return The window renderer.
*/
-SDL_Renderer* getRenderer();
+[[nodiscard]] SDL_Renderer* getRenderer();
/**
- * @brief Get user events.
+ * @brief Populate the event object with the concurrent user events.
+ *
+ * @param event The event object to populate.
*
- * @return The user events.
+ * @return
*/
-const std::vector& getEvents();
+int pollEvent(Event& event);
/**
* @brief Get whether the window is fullscreen or not.
*
* @return Whether the window is fullscreen or not.
*/
-bool getFullscreen();
+[[nodiscard]] bool getFullscreen();
/**
* @brief Get the scale of the window.
*
* @return The scale of the window.
*/
-int getScale();
+[[nodiscard]] int getScale();
/**
* @brief Get the size of the screen.
*
* @return The size of the screen.
*/
-math::Vec2 getSize();
+[[nodiscard]] math::Vec2 getSize();
/**
* @brief Set the title of the window.
@@ -105,7 +122,7 @@ void setTitle(const std::string& newTitle);
*
* @return The title of the window.
*/
-std::string getTitle();
+[[nodiscard]] std::string getTitle();
/**
* @brief Set whether the window is fullscreen or not.
diff --git a/include/_globals.hpp b/include/_globals.hpp
new file mode 100644
index 0000000..f8a7023
--- /dev/null
+++ b/include/_globals.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "Math.hpp"
+#include
+
+namespace kn
+{
+inline SDL_GameController* _controller;
+} // namespace kn
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..3627f76
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+meson
+ninja
+pre-commit
\ No newline at end of file
diff --git a/src/animation_controller.cpp b/src/animation_controller.cpp
index e97ae7c..e46ced6 100644
--- a/src/animation_controller.cpp
+++ b/src/animation_controller.cpp
@@ -1,33 +1,20 @@
#include "AnimationController.hpp"
-#include "Constants.hpp"
#include "ErrorLogger.hpp"
#include "Math.hpp"
-#include "Overflow.hpp"
namespace kn
{
-AnimationController::AnimationController(const int fps) : m_fps(fps)
+bool AnimationController::addAnim(const std::string& name, const std::string& filePath,
+ const math::Vec2& frameSize, const int fps)
{
- m_frameTime_ms = (fps == 0) ? 0.0 : MILLISECONDS_PER_SECOND / fps;
-}
-
-bool AnimationController::addAnim(const std::string& filePath, const std::string& name,
- const int frameWidth, const int frameHeight)
-{
- std::shared_ptr texPtr(new Texture());
+ const std::shared_ptr texPtr(new Texture());
if (!texPtr->loadFromFile(filePath))
return false;
- if (m_animMap.find(name) != m_animMap.end()) // if animation name already exists
- {
- m_animMap.at(name).clear();
- m_timeLeftInFrame_ms = (m_fps == 0) ? 0.0 : m_frameTime_ms;
- m_index = 0;
- }
-
- m_animMap[name] = {};
const math::Vec2 size = texPtr->getSize();
+ const int frameWidth = static_cast(frameSize.x);
+ const int frameHeight = static_cast(frameSize.y);
if (static_cast(size.x) % frameWidth || static_cast(size.y) % frameHeight)
{
@@ -35,25 +22,34 @@ bool AnimationController::addAnim(const std::string& filePath, const std::string
return false;
}
+ if (m_animMap.find(name) != m_animMap.end())
+ m_animMap.erase(name);
+
+ Animation newAnim;
+ newAnim.fps = fps;
for (int x = 0; x < size.x; x += frameWidth)
for (int y = 0; y < size.y; y += frameHeight)
{
const Frame frame{texPtr, {x, y, frameWidth, frameHeight}};
- m_animMap.at(name).emplace_back(frame);
+ newAnim.frames.emplace_back(frame);
}
+ m_animMap[name] = std::move(newAnim);
m_currAnim = name;
return true;
}
+void AnimationController::removeAnim(const std::string& name)
+{
+ if (m_animMap.find(name) != m_animMap.end())
+ m_animMap.erase(name);
+}
+
bool AnimationController::setAnim(const std::string& name)
{
if (m_animMap.find(name) == m_animMap.end())
- {
- ERROR(name + " does not exist in animation controller.")
return false;
- }
m_currAnim = name;
return true;
@@ -61,58 +57,41 @@ bool AnimationController::setAnim(const std::string& name)
const Frame& AnimationController::nextFrame(const double deltaTime)
{
- if (m_paused)
- return m_animMap.at(m_currAnim).at(m_index);
+ Animation& currAnim = m_animMap.at(m_currAnim);
- if (!isProductValid(static_cast(MILLISECONDS_PER_SECOND), deltaTime))
- {
- ERROR("Cannot multiply values");
- return m_animMap.at(m_currAnim).at(m_index);
- }
+ if (m_paused)
+ return currAnim.frames.at(static_cast(m_index));
- if (const double msTakenLastFrame = MILLISECONDS_PER_SECOND * deltaTime;
- m_timeLeftInFrame_ms < msTakenLastFrame)
- {
- const int numberOfFramesPassed =
- static_cast((msTakenLastFrame - m_timeLeftInFrame_ms) / m_frameTime_ms) + 1;
- m_timeLeftInFrame_ms = m_frameTime_ms;
- m_index =
- (m_index + numberOfFramesPassed) % static_cast(m_animMap.at(m_currAnim).size());
- }
- else
- {
- if (!isSumValid(m_timeLeftInFrame_ms, -msTakenLastFrame))
- {
- ERROR("Cannot subtract values");
- return m_animMap.at(m_currAnim).at(m_index);
- }
- m_timeLeftInFrame_ms -= msTakenLastFrame;
- }
+ m_index += deltaTime * currAnim.fps * m_playbackSpeed;
+ m_index = fmod(m_index + static_cast(currAnim.frames.size()), currAnim.frames.size());
- return m_animMap.at(m_currAnim).at(m_index);
+ return currAnim.frames.at(static_cast(m_index));
}
void AnimationController::pause() { m_paused = true; }
void AnimationController::resume()
{
- if (m_fps > 0)
+ if (m_playbackSpeed > 0.0)
m_paused = false;
}
-void AnimationController::setFPS(const int fps)
+void AnimationController::setPlaybackSpeed(const double speed)
{
- m_fps = fps;
+ m_playbackSpeed = speed;
+ if (speed == 0)
+ pause();
+}
- if (m_fps == 0)
+bool AnimationController::isFinished()
+{
+ if (m_prevIndex > m_index)
{
- pause();
- m_frameTime_ms = 0.0;
+ m_prevIndex = m_index;
+ return true;
}
- else
- m_frameTime_ms = MILLISECONDS_PER_SECOND / m_fps;
-
- m_timeLeftInFrame_ms = m_frameTime_ms;
+ m_prevIndex = m_index;
+ return false;
}
} // namespace kn
diff --git a/src/game_controller.cpp b/src/game_controller.cpp
new file mode 100644
index 0000000..1843e95
--- /dev/null
+++ b/src/game_controller.cpp
@@ -0,0 +1,71 @@
+#include
+
+#include "GameController.hpp"
+#include "Math.hpp"
+#include "_globals.hpp"
+
+namespace kn::controller
+{
+static float _deadZone = 0.1f;
+
+math::Vec2 getLeftJoystick()
+{
+ if (!_controller)
+ {
+ WARN("Game controller is not connected")
+ return {};
+ }
+
+ math::Vec2 controllerDir = {SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_LEFTX),
+ SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_LEFTY)};
+ controllerDir /= 32767.0;
+ if (controllerDir.getLength() > _deadZone)
+ return controllerDir;
+
+ return {};
+}
+
+math::Vec2 getRightJoystick()
+{
+ if (!_controller)
+ {
+ WARN("Game controller is not connected")
+ return {};
+ }
+
+ math::Vec2 controllerDir = {SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_RIGHTX),
+ SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_RIGHTY)};
+ controllerDir /= 32767.0;
+ if (controllerDir.getLength() > _deadZone)
+ return controllerDir;
+
+ return {};
+}
+
+double getLeftTrigger()
+{
+ if (!_controller)
+ {
+ WARN("Game controller is not connected")
+ return 0.0;
+ }
+
+ return SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / 32768.0;
+}
+
+double getRightTrigger()
+{
+ if (!_controller)
+ {
+ WARN("Game controller is not connected")
+ return 0.0;
+ }
+
+ return SDL_GameControllerGetAxis(_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / 32768.0;
+}
+
+void setControllerDeadZone(const float deadZone) { _deadZone = std::clamp(deadZone, 0.0f, 1.0f); }
+
+float getControllerDeadZone() { return _deadZone; }
+
+} // namespace kn::controller
diff --git a/src/input.cpp b/src/input.cpp
deleted file mode 100644
index 800657f..0000000
--- a/src/input.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-#include
-
-#include "Constants.hpp"
-#include "Input.hpp"
-#include "Math.hpp"
-#include "Window.hpp"
-
-namespace kn::input
-{
-math::Vec2 getMousePos()
-{
- int x, y;
- SDL_GetMouseState(&x, &y);
- const int scale = window::getScale();
- const math::Vec2 pos{x / scale, y / scale};
- return pos + camera;
-}
-
-bool isMouseButtonPressed(const int button) { return SDL_GetMouseState(nullptr, nullptr) == button; }
-
-bool isKeyPressed(const int key) { return SDL_GetKeyboardState(nullptr)[key]; }
-
-math::Vec2 getVector(const std::vector& left, const std::vector& right,
- const std::vector& up, const std::vector& down)
-{
- math::Vec2 vector;
-
- if (std::any_of(up.begin(), up.end(), [&](auto scancode) { return isKeyPressed(scancode); }))
- vector.y -= 1;
- if (std::any_of(left.begin(), left.end(), [&](auto scancode) { return isKeyPressed(scancode); }))
- vector.x -= 1;
- if (std::any_of(down.begin(), down.end(), [&](auto scancode) { return isKeyPressed(scancode); }))
- vector.y += 1;
- if (std::any_of(right.begin(), right.end(), [&](auto scancode) { return isKeyPressed(scancode); }))
- vector.x += 1;
-
- if (vector.getLength() > 0)
- vector.normalize();
-
- return vector;
-}
-} // namespace kn::input
diff --git a/src/key.cpp b/src/key.cpp
new file mode 100644
index 0000000..3055717
--- /dev/null
+++ b/src/key.cpp
@@ -0,0 +1,9 @@
+#include "Key.hpp"
+#include
+
+namespace kn::key
+{
+
+bool isPressed(const int key) { return SDL_GetKeyboardState(nullptr)[key]; }
+
+} // namespace kn::key
\ No newline at end of file
diff --git a/src/meson.build b/src/meson.build
index 224cacc..3d99cb6 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -2,8 +2,10 @@ sources = [
'src/animation_controller.cpp',
'src/draw.cpp',
'src/font.cpp',
- 'src/input.cpp',
+ 'src/game_controller.cpp',
+ 'src/key.cpp',
'src/math.cpp',
+ 'src/mouse.cpp',
'src/music.cpp',
'src/overflow.cpp',
'src/rect.cpp',
diff --git a/src/mouse.cpp b/src/mouse.cpp
new file mode 100644
index 0000000..e5dff1d
--- /dev/null
+++ b/src/mouse.cpp
@@ -0,0 +1,16 @@
+#include "Mouse.hpp"
+#include "Math.hpp"
+#include "Window.hpp"
+
+namespace kn::mouse
+{
+math::Vec2 getPos()
+{
+ int x, y;
+ SDL_GetMouseState(&x, &y);
+ const math::Vec2 pos{x, y};
+ return pos / window::getScale() + camera;
+}
+
+bool isPressed(const uint32_t button) { return SDL_GetMouseState(nullptr, nullptr) == button; }
+} // namespace kn::mouse
diff --git a/src/rect.cpp b/src/rect.cpp
index 833a78f..b598449 100644
--- a/src/rect.cpp
+++ b/src/rect.cpp
@@ -7,14 +7,6 @@ math::Vec2 Rect::getPos() const { return {x, y}; }
math::Vec2 Rect::getSize() const { return {w, h}; }
-Rect::Rect(const float x, const float y, const float w, const float h) : SDL_FRect{x, y, w, h} {}
-
-Rect::Rect(const int x, const int y, const int w, const int h)
- : SDL_FRect{static_cast(x), static_cast(y), static_cast(w),
- static_cast(h)}
-{
-}
-
void Rect::setSize(const math::Vec2& size)
{
w = static_cast(size.x);
diff --git a/src/tile_map.cpp b/src/tile_map.cpp
index 2df68fd..2419b90 100644
--- a/src/tile_map.cpp
+++ b/src/tile_map.cpp
@@ -180,7 +180,6 @@ Rect TileMap::getFittedRect(const SDL_Surface* surface, const Rect& srcRect,
const int bytesPerPixel = surface->format->BytesPerPixel;
for (int y = rectInt.y; y < rectInt.y + rectInt.h; y++)
- {
for (int x = rectInt.x; x < rectInt.x + rectInt.w; x++)
{
const Uint8* pixel = pixels + y * pitch + x * bytesPerPixel;
@@ -192,7 +191,6 @@ Rect TileMap::getFittedRect(const SDL_Surface* surface, const Rect& srcRect,
left = std::min(left, x - rectInt.x);
right = std::max(right, x - rectInt.x);
}
- }
return {static_cast(position.x + left), static_cast(position.y + top),
right - left + 1, bottom - top + 1};
diff --git a/src/time.cpp b/src/time.cpp
index a08ee40..0422e5e 100644
--- a/src/time.cpp
+++ b/src/time.cpp
@@ -3,30 +3,26 @@
namespace kn::time
{
+
double Clock::tick(int frameRate)
{
if (frameRate < 1)
frameRate = 1;
m_targetFrameTime = 1000.0 / frameRate;
- m_frameTime =
- (static_cast(SDL_GetPerformanceCounter()) / m_frequency - m_last / m_frequency) *
- 1000.0;
+ m_now = static_cast(SDL_GetPerformanceCounter());
+ m_frameTime = (m_now - m_last) / m_frequency * 1000.0;
+
if (m_frameTime < m_targetFrameTime)
SDL_Delay(static_cast(m_targetFrameTime - m_frameTime));
m_now = static_cast(SDL_GetPerformanceCounter());
- m_deltaTime = m_now / m_frequency - m_last / m_frequency;
+ m_frameTime = (m_now - m_last) / m_frequency * 1000.0;
m_last = m_now;
- return std::min(m_deltaTime, 0.033); // Limit delta time at 33ms or 30fps
+ return m_frameTime;
}
-double getTicks()
-{
- const auto now = static_cast(SDL_GetPerformanceCounter());
- const auto frequency = static_cast(SDL_GetPerformanceFrequency());
- return now / frequency;
-}
+double getTicks() { return SDL_GetTicks() / 1000.0; }
} // namespace kn::time
\ No newline at end of file
diff --git a/src/window.cpp b/src/window.cpp
index 0c7415a..2a480bd 100644
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -5,84 +5,136 @@
#include "ErrorLogger.hpp"
#include "Music.hpp"
#include "Window.hpp"
+#include "_globals.hpp"
namespace kn::window
{
static SDL_Renderer* _renderer;
static SDL_Window* _window;
-static Event _event;
-static std::vector _events;
+static bool _isOpen;
-void init(const math::Vec2& resolution, const std::string& title, const int scale)
+bool init(const math::Vec2& resolution, const std::string& title, const int scale)
{
if (_renderer)
+ {
WARN("Cannot initialize renderer more than once")
+ return false;
+ }
- if (SDL_Init(SDL_INIT_VIDEO))
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0)
+ {
FATAL("SDL_Init Error: " + std::string(SDL_GetError()))
+ return false;
+ }
+
if (!IMG_Init(IMG_INIT_PNG))
{
FATAL("IMG_Init Error: " + std::string(IMG_GetError()))
SDL_Quit();
+ return false;
}
+
if (TTF_Init())
{
FATAL("TTF_Init Error: " + std::string(TTF_GetError()))
IMG_Quit();
SDL_Quit();
+ return false;
}
+
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048))
{
FATAL("Mix_OpenAudio Error: " + std::string(Mix_GetError()))
TTF_Quit();
IMG_Quit();
SDL_Quit();
+ return false;
}
+ const int resolutionWidth = static_cast(resolution.x);
+ const int resolutionHeight = static_cast(resolution.y);
_window = SDL_CreateWindow("Kraken Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
- static_cast(resolution.x) * scale,
- static_cast(resolution.y) * scale, SDL_WINDOW_SHOWN);
+ resolutionWidth * scale, resolutionHeight * scale, SDL_WINDOW_SHOWN);
if (!_window)
+ {
FATAL("SDL_CreateWindow Error: " + std::string(SDL_GetError()))
+ return false;
+ }
_renderer = SDL_CreateRenderer(_window, -1, SDL_RENDERER_ACCELERATED);
if (!_renderer)
- FATAL("SDL_CreateRenderer Error: " + std::string(SDL_GetError()));
-
- if (scale > 1)
{
- SDL_RenderSetLogicalSize(_renderer, static_cast(resolution.x),
- static_cast(resolution.y));
+ FATAL("SDL_CreateRenderer Error: " + std::string(SDL_GetError()))
+ return false;
}
+ if (scale > 1)
+ SDL_RenderSetLogicalSize(_renderer, resolutionWidth, resolutionHeight);
+
setTitle(title);
setIcon("../example/assets/kraken_engine_window_icon.png");
+
+ _isOpen = true;
+
+ return true;
}
+bool isOpen() { return _isOpen; }
+
+void close() { _isOpen = false; }
+
void quit()
{
+ music::unload();
+
if (_renderer)
SDL_DestroyRenderer(_renderer);
if (_window)
SDL_DestroyWindow(_window);
+ if (_controller)
+ SDL_GameControllerClose(_controller);
- music::unload();
Mix_CloseAudio();
IMG_Quit();
TTF_Quit();
SDL_Quit();
}
-const std::vector& getEvents()
+int pollEvent(Event& event)
{
if (!_window)
WARN("Cannot get events before creating the window")
- _events.clear();
- while (SDL_PollEvent(&_event))
- _events.push_back(_event);
+ const int pending = SDL_PollEvent(&event);
+
+ if (pending)
+ {
+ switch (event.type)
+ {
+ case QUIT:
+ close();
+ break;
+ case CONTROLLERDEVICEADDED:
+ {
+ if (!_controller)
+ _controller = SDL_GameControllerOpen(event.cdevice.which);
+ break;
+ }
+ case CONTROLLERDEVICEREMOVED:
+ if (_controller &&
+ event.cdevice.which ==
+ SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(_controller)))
+ {
+ SDL_GameControllerClose(_controller);
+ _controller = nullptr;
+ }
+ break;
+ default:
+ break;
+ }
+ }
- return _events;
+ return pending;
}
void clear(const Color color)