From 05aba527dbfb90dd3204dea649729cf213d66cb4 Mon Sep 17 00:00:00 2001 From: Tomas Horsky Date: Wed, 6 May 2026 13:15:17 +0900 Subject: [PATCH] updated readme --- README.md | 71 ++++++++++++++++++++++++++------------ public/assets/p5play.webp | Bin 0 -> 13938 bytes 2 files changed, 48 insertions(+), 23 deletions(-) create mode 100644 public/assets/p5play.webp diff --git a/README.md b/README.md index 730350a..783f7b9 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,73 @@ # Nubzuki Jump -- Name: Tomas Horsky -- ID: 20256426 -- Email: tomashorsky@kaist.ac.kr -- Git Repository: https://git.prototyping.id/20256426/NubzukiJump -- Demo Video: +- Name: Tomas Horsky +- ID: 20256426 +- Email: tomashorsky@kaist.ac.kr +- Git Repository: https://git.prototyping.id/20256426/NubzukiJump +- Demo Video: https://www.youtube.com/watch?v=E3_a1b5RLrk -## How to use +## How to use and play ```bash npm install npm run dev ``` +Then play the game either by using the arrow keys (smoother), or enable camera controls and move by moving your head. + + ## Description of the game -Nubzuki Jump is a simple 2D platformer game where the player controls a character named Nubzuki. The goal is to jump on platforms and reach as high as possible without falling down. The game features four different types of platforms, which are introduced as the player progresses, the types of platforms are: +Nubzuki Jump is a simple 2D platformer game where the player controls Nubzuki. The goal is to jump on platforms and reach as high as possible without falling down. The game features four different types of platforms, which are introduced as the player progresses: 1. **Basic platform** -2. **Moving platform** – a platform that moves horizontally +2. **Moving platform** – moves horizontally 3. **Spring platform** – boosts the player's jump 4. **One-time platform** – disappears after one jump -The player can move only left and right, the jumping is done automatically whenever player lands on platform. Game is controlled either by using the **arrow keys**, or by enabling **camera control** and moving head left or right. +The player can only move left and right; jumping happens automatically whenever the player lands on a platform. The game can be controlled either using the **arrow keys** or by enabling **camera control** and moving the head left or right. + ## Code Organization -The code is divided into two main parts, the Svelte app and the game logic inside of `src/game`. The Svelte app is responsible for layout of the page, displaying the leaderboard and handling the game state (start, end, restart). It also contains one component `CameraControl.svelte` which is responsible for enabling camera control and showing reading of head movement. -The game logic is divided into several files: -1. `game.js` - the main file which initializes the game and handles the game loop -2. `player.js` - contains the logic related to the player character, such as movement and player state -3. `platforms.js` - contains logic for creating and managing platforms -4. `platformTypes.js` - handles the creation and behavior of of platforms by type -5. `cameraControl.js` - contains the logic for enabling camera control and reading head movement -6. `constants.js` - contains constants for the game +The code is divided into two main parts: the Svelte app (`App.svelte` and `CameraControl.svelte`) and the game logic inside `src/game`. `App.svelte` is responsible for the page layout, displaying the leaderboard, and handling the game state (start, playing, game over). +The `CameraControl.svelte` component handles enabling camera control and visualizing the detected head movement. + +The game logic is split into several files: +1. `game.js` – initializes the game and handles the main game loop +2. `player.js` – contains logic related to the player character (movement, state) +3. `platforms.js` – handles creation and management of platforms +4. `platformTypes.js` – defines behavior and visuals of different platform types +5. `cameraControl.js` – handles camera input and head movement detection +6. `constants.js` – stores game constants and configuration + +The way different parts of the application are connected and communicate can be seen in the following diagram: -How that different parts of the code are conected and are communicating can be seen in the following diagram:
Organization diagram
-## Issues -The issue I was not able to solve is that when using camera control, the game becomes slower and a little bit laggy. I improved it by lowering the camera resolution and reducing the detection frequency, however its still not perfect but playeble. Other then that, the colisons between player and edges of platform are not always perfect, but nothing terrible. + +## Implementation + + +I used **Svelte 5** with runes in combination with **p5** and **p5play**. At first, I thought I would use Svelte a lot, but since the game is basically just a p5 canvas, I used Svelte only for the layout outside of the game. I used **p5play** to help me with the physics, the player and platforms are **Sprites** (game objects from the **p5play** library) which simplifies collision handling and jumping. The world movement could also have been implemented p5play (utiliazing camera movement feature), but I decided to implement it on my own. + +I implemented the game basically on my own, without prompting AI to generate code for me, but I used Copilot to help me code faster. However, I used AI for the Svelte layout part and the camera control. Making the camera control work was quite a long process, because the head movement detection is quite performance-heavy and difficult to make smooth in real time. I tried a few different libraries and landed on MediaPipe FaceLandmarker, which, in combination with lowering the video quality and detection frequency, works quite well. + +The main functions of the game are: +- `updateGame()` – handles the game loop by updating the player (`movePlayer()`), moving the world (`moveWorld()`), and checking if the player has fallen +- `movePlayer()` – processes player movement and triggers jumps when landing on platforms +- `moveWorld()` – gradually moves the world upward and generates new platforms when needed (`generateNewPlatforms()`) +- `generateNewPlatforms()` – fills empty space with platforms, types are chosen based on player elevation and randomness +- `updateCameraControl()` – processes camera input by detecting head (nose) position (left, center, right) + + +## Issues +The main issue I was not able to fully solve is that when using camera control, the game becomes slower and slightly laggy. I improved it by lowering the camera resolution and reducing the detection frequency, but it is still not perfect. Additionally, I encountered circular dependency issue from Svelte’s internal modules which I was not able to resolve, but it does not affect the game in anyway. + ## Resources and Acknowledgements -- [P5play tutorial](https://p5play.org/learn/sprite) and [P5play documentation](https://p5play.org/docs) - to help me with p5play -- **Github copilot** - to help me write code faster -- **ChatGPT** - mainly for camera controls and for finding bugs in my code \ No newline at end of file +- [P5play tutorial](https://p5play.org/learn/sprite) and [P5play documentation](https://p5play.org/docs) – helped me learn and use the P5play library +- **GitHub Copilot** – helped me code faster +- **ChatGPT** – helped me with implementing camera controls, svelte layout and with debugging + \ No newline at end of file diff --git a/public/assets/p5play.webp b/public/assets/p5play.webp new file mode 100644 index 0000000000000000000000000000000000000000..f1cfe2b971c18d5f6a415fa4c0562988b89f7a26 GIT binary patch literal 13938 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 literal 0 HcmV?d00001