commit 4cd9745438cfb5dccbf68bf0be6231706c5eb919 Author: me Date: Tue Dec 17 10:48:09 2024 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61298fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.swp +game +wasm/ +raylib/ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c8cb50e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 alloca.space + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aab7fa8 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Blobby Beachball + +Don't drop the ball! + +__Keys__: WASD + Arrows + +## Build + +Requires [raylib](https://raylib.com) and a C compiler. + +Can be build locally using `run.sh`, or for WebAssembly using `wasm.sh`. + +### WASM + +Install [emcc](https://emscripten.org/docs/getting_started/downloads.html), +extract [`raylib-5.5_webassembly.zip`](https://github.com/raysan5/raylib/releases) +in this directory as `raylib/`, then run `wasm.sh`. + +## Play + +[At alloca.space](https://alloca.space/creativity/games/blobbybeach/index.html). diff --git a/assets/background.png b/assets/background.png new file mode 100644 index 0000000..354ed18 Binary files /dev/null and b/assets/background.png differ diff --git a/assets/ball.png b/assets/ball.png new file mode 100644 index 0000000..089a94b Binary files /dev/null and b/assets/ball.png differ diff --git a/assets/blobbybeach.mp3 b/assets/blobbybeach.mp3 new file mode 100644 index 0000000..e6135c9 Binary files /dev/null and b/assets/blobbybeach.mp3 differ diff --git a/assets/blue_player.png b/assets/blue_player.png new file mode 100644 index 0000000..d5e76e1 Binary files /dev/null and b/assets/blue_player.png differ diff --git a/assets/bop.wav b/assets/bop.wav new file mode 100644 index 0000000..71c1ef5 Binary files /dev/null and b/assets/bop.wav differ diff --git a/assets/floor.wav b/assets/floor.wav new file mode 100644 index 0000000..54a154d Binary files /dev/null and b/assets/floor.wav differ diff --git a/assets/jump.wav b/assets/jump.wav new file mode 100644 index 0000000..7fb6505 Binary files /dev/null and b/assets/jump.wav differ diff --git a/assets/pink_player.png b/assets/pink_player.png new file mode 100644 index 0000000..416c811 Binary files /dev/null and b/assets/pink_player.png differ diff --git a/main.c b/main.c new file mode 100644 index 0000000..8254b70 --- /dev/null +++ b/main.c @@ -0,0 +1,379 @@ +#include "math.h" +#include "raylib/include/raylib.h" + +#define FPS 60 +#define FRAME_SPEED 8 +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 +#define SPRITE_SIZE 64 +#define BOTTOM_POSITION ((float)SCREEN_HEIGHT - (float)SPRITE_SIZE) + +/* Player */ + +typedef struct { + Texture2D texture; + Rectangle frame; + int counter; + int speed; + int current; + int total_frames; +} Graphics; + +void updateGraphics(Graphics* graphics) { + // update animation + graphics->counter++; + + if (graphics->counter >= (FPS/graphics->speed)) { + graphics->counter = 0; + graphics->current++; + + if (graphics->current >= graphics->total_frames) graphics->current = 0; + + graphics->frame.y = (float)graphics->current * (float)graphics->texture.height/graphics->total_frames; + } +} + +typedef struct { + int up; + int left; + int right; +} Keys; + +typedef struct { + Vector2 position; + Vector2 acceleration; + Graphics graphics; + Keys keys; +} Player; + +Player newPlayer(const char* path, Keys keys, float position) { + Texture2D texture = LoadTexture(path); + Graphics graphics = { + .texture = texture, + .frame = { 0.0f, 0.0f, (float)SPRITE_SIZE, (float)SPRITE_SIZE }, + .counter = 0, + .speed = 8, + .current = 0, + .total_frames = 20, + }; + + + Player player = { + .position = { position - (float)SPRITE_SIZE / 2, BOTTOM_POSITION }, + .acceleration = { (float)0, (float)0 }, + .graphics = graphics, + .keys = keys + }; + return player; +} + +void updatePlayer(Player* player, Sound* jump) { + // update animation + updateGraphics(&(player->graphics)); + + // handle inputs X + if (IsKeyDown(player->keys.right)) { player->acceleration.x += 0.25f; } + if (IsKeyDown(player->keys.left)) { player->acceleration.x -= 0.25f; } + if (!IsKeyDown(player->keys.left) && !IsKeyDown(player->keys.right)) { + if (player->acceleration.x > 0) { + player->acceleration.x -= 0.2f; + } + else if (player->acceleration.x < 0) { + player->acceleration.x += 0.2f; + } + } + // handle inputs Y + if (IsKeyDown(player->keys.up) && player->position.y == BOTTOM_POSITION) { + player->acceleration.y -= 7.0f; + PlaySound(*jump); + } + if (player->position.y < BOTTOM_POSITION) { + player->acceleration.y += 0.28f; + } + + // fix acceleration + if (player->acceleration.x > 6.0f) { + player->acceleration.x = 6.0f; + } + else if (player->acceleration.x < -6.0f) { + player->acceleration.x = -6.0f; + } + else if (-0.15f < player->acceleration.x && player->acceleration.x < 0.15f) { + player->acceleration.x = 0.0f; + } + + // update position + player->position.y += player->acceleration.y; + if (player->position.y > BOTTOM_POSITION) { + player->position.y = BOTTOM_POSITION; + player->acceleration.y = 0.0f; + } + + player->position.x += player->acceleration.x; + if (player->position.x > SCREEN_WIDTH - SPRITE_SIZE) { + player->position.x = SCREEN_WIDTH - SPRITE_SIZE; + player->acceleration.x = 0.0f; + } + else if (player->position.x < 0) { + player->position.x = 0; + player->acceleration.x = 0.0f; + } +} + +Rectangle getPlayerBox(const Player* player) { + Rectangle rectangle = { + player->position.x, + player->position.y, + SPRITE_SIZE, + SPRITE_SIZE, + }; + return rectangle; +} + +/* Ball */ + +typedef struct { + Vector2 position; + Vector2 acceleration; + Vector2 origin; + float rotation; + Texture2D texture; + Rectangle frame; +} Ball; + +Ball newBall(const char* path) { + Texture2D texture = LoadTexture(path); + + Ball ball = { + .position = { (float)SCREEN_WIDTH/2 - (float)SPRITE_SIZE / 2, SCREEN_HEIGHT/2 }, + .acceleration = { (float)-3.0f, (float)-8.0f }, + .origin = { (float)SPRITE_SIZE / 2, (float)SPRITE_SIZE / 2 }, + .rotation = (float)0.0f, + .texture = texture, + .frame = { 0.0f, 0.0f, (float)SPRITE_SIZE, (float)SPRITE_SIZE }, + }; + return ball; +} + +Rectangle getBallBox(const Ball* ball) { + Rectangle rectangle = { + ball->position.x, + ball->position.y, + SPRITE_SIZE, + SPRITE_SIZE, + }; + return rectangle; +} + +float min(float a, float b) { + return a < b ? a : b; +} + +typedef enum { + Air, + Dropped, + Saved, +} BallStatus; + +BallStatus updateBall(Ball* ball, const Player* player1, const Player* player2) { + BallStatus status = Air; + + // collision + bool collision1 = CheckCollisionRecs(getBallBox(ball), getPlayerBox(player1)); + bool collision2 = CheckCollisionRecs(getBallBox(ball), getPlayerBox(player2)); + + if (collision1 && collision2) { + status = Saved; + ball->acceleration.y = -8.0f + min(min(0, player1->acceleration.y), player2->acceleration.y); + ball->acceleration.x = 1.3f + player1->acceleration.x; + if (fabsf(player1->acceleration.x) > fabsf(player1->acceleration.x)) { + ball->acceleration.x = 1.3f + player1->acceleration.x; + } else { + ball->acceleration.x = 1.2f + player2->acceleration.x; + } + } + else if (collision1) { + status = Saved; + ball->acceleration.y = -8.0f + min(0, player1->acceleration.y); + ball->acceleration.x += 1.0f * player1->acceleration.x; + } + else if (collision2) { + status = Saved; + ball->acceleration.y = -7.0f + min(0, player2->acceleration.y); + ball->acceleration.x += 1.0f * player2->acceleration.x; + } + // physics + // y + else if (ball->position.y >= BOTTOM_POSITION) { + status = Dropped; + ball->position.y = BOTTOM_POSITION; + + if (-0.5f < ball->acceleration.y && ball->acceleration.y < 0.5f) { + ball->acceleration.y = 0; + } + else { + ball->acceleration.y *= -0.9f; + } + } + if (ball->position.y < BOTTOM_POSITION) { + ball->acceleration.y += 0.28f; + } + + ball->position.y += ball->acceleration.y; + + // x + if (ball->position.x > SCREEN_WIDTH - SPRITE_SIZE) { + ball->position.x = SCREEN_WIDTH - SPRITE_SIZE; + ball->acceleration.x *= -0.9f; + } + else if (ball->position.x < 0) { + ball->position.x = 0; + ball->acceleration.x *= -0.9f; + } + if (ball->acceleration.x < -10.0f) { + ball->acceleration.x = -10.0f; + } + if (ball->acceleration.x > 10.0f) { + ball->acceleration.x = 10.0f; + } + ball->position.x += ball->acceleration.x; + + ball->rotation += ball->acceleration.x; + + return status; +} + +typedef struct { + Graphics graphics; + Vector2 position; +} Background; + +Background newBackground(const char* path) { + Texture2D texture = LoadTexture(path); + Graphics graphics = { + .texture = texture, + .frame = { 0.0f, 0.0f, (float)SCREEN_WIDTH, (float)SCREEN_HEIGHT }, + .counter = 0, + .speed = 4, + .current = 0, + .total_frames = 24, + }; + + + Background background = { + .graphics = graphics, + .position = { 0, 0 }, + }; + return background; +} + +void updateBackground(Background* background) { + updateGraphics(&background->graphics); +} + +/* Main */ +int main(void) { + InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Volleyball"); + InitAudioDevice(); + + // sounds + Music music = LoadMusicStream("assets/blobbybeach.mp3"); + Sound jump = LoadSound("assets/jump.wav"); + Sound floor = LoadSound("assets/floor.wav"); + Sound bop = LoadSound("assets/bop.wav"); + + Background background = newBackground("assets/background.png"); + + Keys pink_keys = { .up = KEY_W, .left = KEY_A, .right = KEY_D }; + Player pink_player = newPlayer("assets/pink_player.png", pink_keys, (float)SCREEN_WIDTH/4); + Keys blue_keys = { .up = KEY_UP, .left = KEY_LEFT, .right = KEY_RIGHT }; + Player blue_player = newPlayer("assets/blue_player.png", blue_keys, (float)SCREEN_WIDTH/4 * 3); + Ball ball = newBall("assets/ball.png"); + + unsigned current_score = 0; + unsigned best_record = 0; + unsigned cooloff = 0; + bool pause_music = false; + + PlayMusicStream(music); + + SetTargetFPS(FPS); + + // Game loop + while (!WindowShouldClose()) + { + UpdateMusicStream(music); + + // Restart music playing (stop and play) + if (IsKeyPressed(KEY_M)) + { + if (pause_music) { + PlayMusicStream(music); + pause_music = false; + } + else { + StopMusicStream(music); + pause_music = true; + } + } + + // Update + if (cooloff > 0) { cooloff--; } + updateBackground(&background); + updatePlayer(&pink_player, &jump); + updatePlayer(&blue_player, &jump); + BallStatus ball_status = updateBall(&ball, &pink_player, &blue_player); + + switch (ball_status) { + case Saved: + if (cooloff == 0) { + cooloff = 10; + current_score++; + if (current_score > best_record) { + best_record = current_score; + } + PlaySound(bop); + } + break; + case Dropped: + current_score = 0; + PlaySound(floor); + break; + default: + break; + } + + // Draw + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawTextureRec(background.graphics.texture, background.graphics.frame, background.position, WHITE); + + DrawTextureRec(pink_player.graphics.texture, pink_player.graphics.frame, pink_player.position, WHITE); + DrawTextureRec(blue_player.graphics.texture, blue_player.graphics.frame, blue_player.position, WHITE); + //DrawTextureRec(ball.texture, ball.frame, ball.position, WHITE); + Rectangle dest = { ball.position.x + SPRITE_SIZE/2, ball.position.y + SPRITE_SIZE/2, ball.origin.x * 2, ball.origin.y * 2 }; + DrawTexturePro(ball.texture, ball.frame, dest, ball.origin, ball.rotation, WHITE); + + DrawRectangle(0, 0, 240, 90, (Color){ 0, 10, 60, 120 }); + + if (current_score == best_record) { + DrawText(TextFormat("Best Record: %i", best_record), 10, 10, 24 + cooloff / 2, PINK); + DrawText(TextFormat("Score: %i", current_score), 10, 50, 24 + cooloff / 2, PINK); + } else { + DrawText(TextFormat("Best Record: %i", best_record), 10, 10, 24, BLACK); + DrawText(TextFormat("Score: %i", current_score), 10, 50, 24 + cooloff / 2, WHITE); + } + + EndDrawing(); + } + + // De-Initialization + UnloadTexture(pink_player.graphics.texture); + UnloadTexture(blue_player.graphics.texture); + UnloadTexture(ball.texture); + CloseWindow(); + + return 0; +} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..7686d65 --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +cc main.c -lraylib -lGL -lm -lpthread -ldl -lrt -lX11 -o game && ./game diff --git a/wasm.sh b/wasm.sh new file mode 100755 index 0000000..4eddd54 --- /dev/null +++ b/wasm.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +mkdir -p wasm + +emcc -o wasm/game.html main.c -Os -Wall ./raylib/lib/libraylib.a -I. -I./raylib.h -L. -L./raylib/lib/librarylib.a -s USE_GLFW=3 -s ASYNCIFY -DPLATFORM_WEB --preload-file assets -s -s TOTAL_MEMORY=67108864 -s ASSERTIONS=1