commit aa03bf3a2181a09a79291cc02ed1aa91d9e55956 Author: thecookingsenpai Date: Mon Dec 25 13:24:14 2023 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af186c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +README.pdf + +.luacheckrc diff --git a/3DSenpai.3dsx b/3DSenpai.3dsx new file mode 100644 index 0000000..cf5ca3b Binary files /dev/null and b/3DSenpai.3dsx differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff1fc09 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# 3DSenpai + +This documentation describes how the 3DSenpai engine works. + +3DSenpai is a 2D (despite the name) engine that manages the logic and the GUI of Nintendo 3DS +homebrews that use [Lua Player Plus 3DS engine](https://github.com/Rinnegatamante/lpp-3ds) by [Rinnegatamante](https://rinnegatamante.it/). + +Despite being tought for creating games, it is totally possible to use the engine to create +graphical applications (e.g. managing navigation, menus, etc). + +## Attached Example + +The attached example (index.lua) contains a demo application that shows how to use the engine. +Feel free to use it as a reference and a starting point for your applications. + +## Global objects + +To better manage the workflow of the game, some global objects are created at runtime and +are accessed during the game loop. + +### Program + +This object contains informations about the game such as author, version, title and so on. + +### emptyElement + +This object is a prototype of an empty element that is used to initialize an element and standardize its properties. + +### Scene + +This object contains the character object, the currentScene property, the imagesToDraw table +and the environment object. + +#### Scene.environment + +This is the most important object of the scene. It represents every pixel as a tile and is used to define, determine and manipulate the position and behavior of the elements in the scene. + +It contains a size property with the width and height of the screen and the matrix table which is initialized by the setScene function as a 2D array representing every tile content. It is +accessed through: + + Scene.environment.matrix[x][y] + +## initalLoad + +This method is called just after all the variables have been initialized. +Normally, you would use it to set the first scene through setScene method and load +everything you need to start the game. + +### setScene + +This method accepts a scene method that is called to obtain the desired scene properties. +Additionally, it resets the Scene.environment.matrix property to the default values. +These properties are then assigned to the Scene.currentScene and Scene.imagesToDraw objects. + +### Scene methods + +These methods are to be created by the developer. +For setScene to be effective, the scene method should return a table containing two booleans +that define whether or not the background and the main character should be drawn, and a table containing every element that should be drawn. + +## The main loop + +The game engine revolves around a mainLoop function that keep calling +keyEvents and mainGUI functions. + +## keyEvents + +This function keep the inputs monitored for key events and react consequently. + +## mainGUI + +This function loads the Scene object and, if specified, draws both the background and +the main character. + +Then, if any, it parses the Scene.imagesToDraw table and draws the elements images that +are specified in it. + +## Elements methods + +These methods are a set of functions that help the developer create, manage and inspect elements in the current scene. + +### isElement + +This function returns wheter or not an element covers the specified coordinates (x and y). +A boolean is returned. + +### elementPosition + +If an element is in the elementsTable table, it returns the element object. Else, it returns nil. + +### placeElement + +This function is used to place an element in the scene. +It checks whether the element is already placed and if the tiles that should be covered are free +or not. +It returns a boolean and a string containing the result of the operation. + +### removeElement + +This function removes an element from the scene by checking its existence and position through the elementsTable and the the scene environment.Matrix. + +### checkTileCollision + +Given a set of coordinates (x and y), this function checks whether there is an element that covers the specified coordinates and if the element has the Collision property set to true. diff --git a/assets/character.png b/assets/character.png new file mode 100644 index 0000000..8751ec9 Binary files /dev/null and b/assets/character.png differ diff --git a/index.lua b/index.lua new file mode 100644 index 0000000..5c1a8f8 --- /dev/null +++ b/index.lua @@ -0,0 +1,227 @@ +Screen.disable3D() --Disabling 3D for the top screen + +-- SECTION Program object +local Program = { + title = "3DSenpai", + dir = "0_LPP_Test" +} +-- !SECTION Program object + +-- SECTION Misc objects +local emptyElement = { + Element = nil, -- name of the element + Type = 0, -- 0 = empty, 1 = element, 2 = creature, 3 = object + Collision = false, -- true / false + Size = { 0, 0 }, -- Size of the element + StartingPosition = { 0, 0 }, -- Starting position of the element + Image = nil, -- Image of the element (object) + Screen = TOP_SCREEN, -- Screen of the element + Properties = {} -- Properties of the element (e.g. health, damage, etc.) +} + +local elementsTable = {} -- {name = {x,y}} +-- !SECTION Misc objects + +-- SECTION Elements object +local Scene = { + environment = { + size = { x = 400, y = 240 }, + matrix = {} + }, + character = { + image = "assets/character.png", + imageLoaded = nil, + position = { x = 176, y = 96 } + }, + currentScene = nil, -- Current scene method + imagesToDraw = {} -- A table containing the images to draw each frame +} +-- !SECTION Elements object + +-- SECTION Scenes +-- Scenes are functions that are called to draw the scene on the screen at the +-- beginning of the program or when the user changes the scene (e.g. next level) +local firstLevel = function () + local isBackground = true + local isCharacter = true + local imagesToDraw = {} + return {isBackground, isCharacter}, imagesToDraw +end +-- !SECTION Scenes + +local setScene = function (scene) -- Sets the current scene method + -- Reset the environment matrix + for i = 1, Scene.environment.size.x do + Scene.environment.matrix[i] = {} + -- Each pixel of the environment contains the informations about the element on it (if any) + for j = 1, Scene.environment.size.y do + Scene.environment.matrix[i][j] = {emptyElement} + end + end + local settings = scene() -- Call the scene method that returns settings and images to draw + Scene.currentScene = settings[0] + Scene.imagesToDraw = settings[1] + return true +end + +local initialLoad = function () + System.currentDirectory("/3ds/" .. Program.dir .. "/") + Scene.character.imageLoaded = Screen.loadImage(System.currentDirectory() + .. Scene.character.image) + -- Setting the first scene + setScene(firstLevel) +end + +-- SECTION Controls check +local keyEvents = function () + local pad = Controls.read() -- Read Controls + if (Controls.check(pad, KEY_START)) then -- check if start is pressed + System.exit() -- Exit back to HBL + -- Movements with bounds + elseif (Controls.check(pad, KEY_DUP)) then + Scene.character.position.y = Scene.character.position.y - 1 + if (Scene.character.position.y > 195) then + Scene.character.position.y = 195 + end + elseif (Controls.check(pad, KEY_DDOWN)) then + Scene.character.position.y = Scene.character.position.y + 1 + if (Scene.character.position.y < 5) then + Scene.character.position.y = 5 + end + elseif (Controls.check(pad, KEY_DRIGHT)) then + Scene.character.position.x = Scene.character.position.x + 1 + if (Scene.character.position.x > 355) then + Scene.character.position.x = 355 + end + elseif (Controls.check(pad, KEY_DLEFT)) then + Scene.character.position.x = Scene.character.position.x - 1 + if (Scene.character.position.x < 5) then + Scene.character.position.x = 5 + end + end +end +-- !SECTION Controls check + +local mainGUI = function () + -- Load the current scene + local isBackground = Scene.currentScene[1] + local isCharacter = Scene.currentScene[2] + -- Draw the background + if (isBackground) then + Screen.fillRect(5, 395, 5, 235, Color.new(255, 0, 0), TOP_SCREEN) + end + -- Draw the character + if (isCharacter) then + Screen.drawImage(Scene.character.position.x, Scene.character.position.y, + Scene.character.imageLoaded, TOP_SCREEN) + end + -- Draw the images listed in the imagesToDraw table + for i, image in pairs(Scene.imagesToDraw) do + -- REVIEW Should the element be placed in the scene here with 'i'? + Screen.drawImage(image.StartingPosition[0], image.StartingPosition[1], + image.Image, image.Screen) + end +end + +-- SECTION Methods +local isElement = function (x, y) + if (Scene.environment.matrix[x][y].Element == nil) then + return false + else + return true + end +end + +local elementPosition = function (elementName) + if (elementsTable[elementName] == nil) then + return nil + end + return elementsTable[elementName] +end + +local placeElement = function (x, y, element, imagePath) + local screen = element.screen or TOP_SCREEN + -- Ensure the element is not already placed + if (elementPosition(element.name) ~= nil) then + return false, "alreadyplaced" -- Element already placed + end + -- Ensure the tiles are empty + for i = x + 1, x + element.width do + for j = y + 1, y + element.height do + if isElement(x, y) then + return false, "notempty" -- Tile is not empty + end + end + end + -- Loading the image + local imageObject = Screen.loadImage(System.currentDirectory() .. imagePath) + -- Setting the starting cell + Scene.environment.matrix[x][y].Element = element.name + Scene.environment.matrix[x][y].Type = element.type + Scene.environment.matrix[x][y].Collision = element.collision + Scene.environment.matrix[x][y].StartingPosition = {x, y} + Scene.environment.matrix[x][y].Size = {element.width, element.height} + Scene.environment.matrix[x][y].Image = imageObject + Scene.environment.matrix[x][y].Screen = screen + Scene.environment.matrix[x][y].Properties = element.properties + -- Filling the matrix with the element + for i = x + 1, x + element.width do + for j = y + 1, y + element.height do + Scene.environment.matrix[i][j] = Scene.environment.matrix[x][y] + end + end + -- Adding the element to the elements table + elementsTable[element.name] = {x, y} + return true, "placed" +end + +local removeElement = function (elementName) + -- Getting the element coordinates + local coordinates = elementPosition(elementName) + -- Check if the element is in the elements table + if (coordinates == nil) then + return + end + local x = coordinates[1] + local y = coordinates[2] + -- Setting the starting cell + Scene.environment.matrix[x][y] = {emptyElement} + -- Filling the matrix with the element + for i = x + 1, x + element.width do + for j = y + 1, y + element.height do + Scene.environment.matrix[i][j] = Scene.environment.matrix[x][y] + end + end +end + + +local checkTileCollision = function (x, y) + if (Scene.environment.matrix[x][y].Collision) then + return true + else + return false + end +end +-- !SECTION Methods + +local mainLoop = function () + Screen.waitVblankStart() -- Screen related stuff + Screen.refresh() -- Other Screen related stuff + Screen.clear(TOP_SCREEN) -- Clear top screen + Screen.clear(BOTTOM_SCREEN) -- Clear bottom screen + -- Keypress handling + keyEvents() -- Check for key events + -- Elements on screen + mainGUI() + Screen.flip() -- More screen related stuff +end + +-- START MAIN LOGIC +initialLoad() + +-- Continuously loop +while (true) do + mainLoop() -- Draw to screen +end + +