Compare commits

..

142 Commits

Author SHA1 Message Date
En Yi 8fff2edae5 Slightly increase water fill rate 2025-05-04 20:55:14 +08:00
En Yi fa1389a70a Fix incorrect solid tile generation 2025-05-04 20:55:06 +08:00
En Yi 523be15ead Fix boulder interaction on ladders
Fix also collision check for general entity overlapping a tile
2025-04-21 22:04:56 +08:00
En Yi 372a4da205 Adjust crate destuction time 2025-04-20 19:16:07 +08:00
En Yi f5d9e40298 'Fix' crate jump bug 2025-04-16 21:22:14 +08:00
En Yi 707ab692a1 Allow transition to ladder only on climbing up 2025-04-15 21:34:01 +08:00
En Yi b8fc099ced Remove 1frame jump after crate jump 2025-04-15 21:23:46 +08:00
En Yi bc0f7617d4 Slow down water filling rate 2025-04-13 20:30:36 +08:00
En Yi 7a4cf55c06 Allow only binary short hop on crates 2025-04-13 20:30:28 +08:00
En Yi ce84014668 Adjust Game UI + Add SFXs 2025-03-31 20:40:13 +08:00
En Yi cb22cac6c3 Update UI + Fix destroying ladder 2025-03-27 21:24:20 +08:00
En Yi 75bacdd59d Set initial camera position to player 2025-01-26 14:34:04 +08:00
En Yi 4e51f01cea Fix urchin sometimes not bouncing 2025-01-25 17:00:52 +08:00
En Yi b46097f735 Update preview rendering 2025-01-25 17:00:32 +08:00
En Yi bbf06c5655 Fix scroll area jittering 2025-01-25 16:59:57 +08:00
En Yi 96a6ccff39 Fix scroll bar when n_items < max 2025-01-22 22:54:55 +08:00
En Yi 451b099ec4 Update level select scene 2025-01-22 22:24:19 +08:00
En Yi d2e5ed82ad Change level preview based on selection
Changelog:
- Finish script to generate level preview altas
- Update render to pick preview based on scroll area selection
- Fix level order check in scripts
2025-01-21 22:13:05 +08:00
En Yi 9598fe7d35 Fix preview render size & add to level select 2025-01-21 21:31:03 +08:00
En Yi 925526199e Add water preview render 2025-01-20 22:09:41 +08:00
En Yi ee43a87f8a Add rendering for all tiles and entities 2025-01-20 21:47:09 +08:00
En Yi b51d505814 Remove solidness from ladders 2025-01-19 15:05:15 +08:00
En Yi 1c64b1cc27 Add halfrec rendering for spikes 2025-01-19 15:05:00 +08:00
En Yi 5b7ed2f3e5 Add prototype level map render 2025-01-18 15:51:13 +08:00
En Yi 1e584c1dc3 Add hover on select text 2025-01-18 10:23:23 +08:00
En Yi 15162c64e8 Add font loading and use font for menu 2025-01-18 09:39:36 +08:00
En Yi 1e0ec6edfb Speed up game scene rendering
Add a early exit for rendering water
2024-12-21 23:34:23 +08:00
En Yi 67e0e7dc08 Add resource packing script 2024-12-21 20:11:32 +08:00
En Yi f3defcf656 Significantly Update CMakeFile to reduce clutter
This is to reduce repetition and better integrate Tracy.

Some options are added as well.
2024-12-21 18:42:45 +08:00
En Yi ee65e3c974 Extend the render queue to tilemap
This is only applied to the main game
2024-12-21 16:53:47 +08:00
En Yi e24bb382f9 Update editor rendering function 2024-12-21 15:36:58 +08:00
En Yi 04d928c97f Add a rendering queue for sprite
Only applies for editor scene as a test
2024-12-21 14:37:18 +08:00
En Yi 34b60fb699 Restore boulder movement on wooden tile 2024-12-14 16:45:19 +08:00
En Yi f4a6d6c768 Fix crash on level loading 2024-12-12 21:19:58 +08:00
En Yi 3408fb8086 Add simple camera view indication 2024-12-10 22:28:39 +08:00
En Yi a6326a3944 Tweak crate fall delay 2024-12-09 21:51:55 +08:00
En Yi b5e31519a6 Add a direct level player for faster playtest 2024-12-08 15:39:22 +08:00
En Yi ee64133de7 Fix jump sfx incorrectly played 2024-12-08 15:39:06 +08:00
En Yi 518c2def93 Fix crawlspace death glitch 2024-12-08 15:38:51 +08:00
En Yi a123e59c82 Fix unable to bash box on the side underwater 2024-11-24 15:53:32 +08:00
En Yi 2de6eaa107 Add sprites for title and menu 2024-11-24 15:43:31 +08:00
En Yi 2d20b0c08a Add ldtk helper script 2024-11-16 11:05:37 +08:00
En Yi 6b4723b963 Fix jump water penalty not applied correctly 2024-11-16 11:05:26 +08:00
En Yi 0556318ca0 Fix urchin destroying spikes again 2024-11-15 21:31:33 +08:00
En Yi 5b29ad4ba4 Update swimming hit- and hurtbox proper 2024-11-13 23:06:26 +08:00
En Yi 6b59064772 Adjust player acceleration in water 2024-11-13 22:36:05 +08:00
En Yi c02eba9548 Add air pockets to level data 2024-11-11 22:59:32 +08:00
En Yi 9d5b54435b Increase water upthrust 2024-11-10 18:29:20 +08:00
En Yi 6509b33c5a Rework urchin collision with crates
Changelog:
- Fix error in urchin spawning in main game
- Reduce urchin's collision box
- Adjust crate destruction:
    - Delay with arrows and bombs, instant with anything else
2024-11-09 23:40:54 +08:00
En Yi 613d5642cc Add parsing for urchins 2024-11-09 16:15:59 +08:00
En Yi ab2928fab9 Update level data packing 2024-11-09 15:02:51 +08:00
En Yi 188c2e30a5 Add tileset info in level pack 2024-11-09 13:30:32 +08:00
En Yi abdf246382 Adjust explosion hitbox 2024-11-09 12:56:51 +08:00
En Yi c33f6f0df0 Adjust jump speed 2024-11-09 12:56:40 +08:00
En Yi 214025bc81 Adjust title image 2024-11-03 16:41:10 +08:00
En Yi 753cf7bbd7 Fix incorrect rendering height 2024-10-19 17:36:23 +08:00
En Yi 1f8c2b2151 Add dark top bar in game 2024-10-12 23:11:55 +08:00
En Yi 5eb7561340 Add offset to window scaling 2024-10-12 15:08:59 +08:00
En Yi 44911658d0 Add basic window resizing 2024-10-12 15:02:08 +08:00
En Yi f5fa46aeeb Settle the window size and UIs 2024-10-12 13:35:42 +08:00
En Yi b36592311a Add water splash to urchins 2024-10-05 20:32:11 +08:00
En Yi db8849a7de Add urchin particles and update menus 2024-10-05 20:22:16 +08:00
En Yi 1382cf1370 Deal with urchin's particle effects 2024-10-05 19:18:42 +08:00
En Yi e52c9445c9 Fix crash when player finish a level
This happens because the player exit entity is removed without checking
with the tilemap, causing it to linger
2024-10-05 19:07:55 +08:00
En Yi 8efa3015ad Update urchin hitbox and bbox
This is so that wooden can be destroyed by urchin. Urchin can still be
crushed by a wooden crate though.
2024-09-30 22:47:27 +08:00
En Yi 7851e05a12 Fix regression in one-tile collision 2024-09-30 21:37:48 +08:00
En Yi 3520715655 Allow urchin to be crushed
LIMITATION
- Urchin can be crushed by wooden crates.
2024-09-29 17:58:07 +08:00
En Yi d3db15a018 Add urchin sprite and fix its spike collision 2024-09-27 23:15:38 +08:00
En Yi 2ee1e38094 Remove unneeded code 2024-09-26 23:02:27 +08:00
En Yi 8bf1957e1e Fix incorrect velocity reflection on collision 2024-09-26 21:31:24 +08:00
En Yi 9854654d61 Slightly resize UI for spawning urchin 2024-09-26 20:46:58 +08:00
En Yi ce0ca96c67 Allow value snapping for urchin spawn velocity
Also, update the UI
2024-09-24 22:30:17 +08:00
En Yi b275caf795 Add spawning urchin in editor 2024-09-23 21:55:06 +08:00
En Yi 8a731637f7 Merge branch 'main' into profiling 2024-09-08 23:06:26 +08:00
En Yi 4abe996640 Add urchin enemy
Changelog:
- Ctransform component now has a bounce coeffcient which determines
  velocity reflection on contact.
2024-09-08 23:05:12 +08:00
En Yi f6847f1ffd Merge branch 'main' into profiling 2024-09-02 22:45:47 +08:00
En Yi 066ea5e080 Accumulate collided side before check 2024-09-02 22:45:16 +08:00
En Yi 3041f5df54 Merge branch 'remove_edge_check' into profiling 2024-09-02 22:02:37 +08:00
En Yi 41585f6938 Simplify edge velocity check
Internal Changelog:
- Use tile collision to determine which side has collided and perform
  the velocity zeroing, instead of separating out.
2024-09-02 21:58:11 +08:00
En Yi c8059300ac Fix freecam bug on game restart
Changelog:
- Lock player if freecam is active
2024-08-27 16:02:55 +08:00
En Yi e00e1ff8d2 Remove player on finish level 2024-08-27 15:49:21 +08:00
En Yi d3ea551024 Tweak engine config to allow web build
Internal Changelog:
- Reduce particles number
- Use webGL2
2024-08-27 15:49:05 +08:00
En Yi ca5c653b9d Test integrate tracy into scenes 2024-08-24 21:44:23 +08:00
En Yi b2beaea248 Experiment with Tracy profiler 2024-08-24 19:14:53 +08:00
En Yi ef04e4ce42 Fix name mistake in README 2024-08-24 15:45:33 +08:00
En Yi bbe3a22cc5 Replace ringbuffer with sc_queue
It is more flexible and uses less heap.
2024-08-24 15:30:32 +08:00
En Yi cb9508a4b6 Update README 2024-08-24 14:43:52 +08:00
En Yi 10d48c1d70 Allow optional inits for scene
Particle system and entity management are now optional during scene
init.
2024-08-24 14:42:11 +08:00
En Yi 8645bbd963 Use heaptrack for memory profiling on main 2024-08-24 14:41:10 +08:00
En Yi add592afd2 Refactor out entity removal function
This allows a singular entity removal method for the game
2024-08-24 12:43:49 +08:00
En Yi fb0f16d984 Add addr sanitisation for main game 2024-08-24 00:26:17 +08:00
En Yi 24b407fec2 Free level select scene on exit 2024-08-24 00:26:05 +08:00
En Yi 03a4240c6d Simplify and unify level restarting
Changelog:
- Add function to clear all game entities
- This functions is called in both editor and main game
2024-08-24 00:25:33 +08:00
En Yi 2fe3faf08f Update exit rendering 2024-08-24 00:23:17 +08:00
En Yi 16714159bb Tweak air point check depending on player stance 2024-08-21 16:28:41 +08:00
En Yi 9f496b4bee Tweak player's swimming hitbox and hurtbox 2024-08-21 16:26:16 +08:00
En Yi 34041a68bb Fix particle emitter not unloading on player death 2024-08-21 16:25:30 +08:00
En Yi 89176142b6 Readjust arrows and bombs spawning
Changelog:
- Make use of anchor point function
- Readjust offsets and anchor points of sprites
- Readjust hitboxes offset and sizes
2024-08-21 12:52:19 +08:00
En Yi d45937da6a Recenter player on ladders 2024-08-21 10:35:23 +08:00
En Yi f4167c6270 Add chest count in main game 2024-08-20 16:38:07 +08:00
En Yi bae17e8a87 Add level ending in main game 2024-08-20 14:52:26 +08:00
En Yi f208964e7f Fix rendering for finishing the level 2024-08-20 14:03:59 +08:00
En Yi 2be80ea6bf Add sprites for player finishing the stage
Also add cave exit sprite

They are all placeholder for now.
2024-08-20 14:03:41 +08:00
En Yi bf655daf8d Add mempool macros to simplify definition 2024-08-19 20:29:49 +08:00
En Yi 056b50c431 Separate out component memory pools definition
Changelog:
- Refactor the EC header files to put struct in more sensible locations:
    - Sprite and Particle Emitter + Configs are now assets, as they
      should be
    - Components type are now unsigned int, thus it is not sensitive to
      any component enums
    - Components enums are now pure index store. There are two sets of
      components: Basic for engine use, Extended for game-specific ones
- Component memory pools are now defined on outside of engine. Majority
  of the components will be game-specific, so it should be on the game
  to define the components and the mempools for all of them.
2024-08-19 18:26:08 +08:00
En Yi d2af974b29 Experiment with extern component mempool
Internal Changelog:
- Split assets from engine so that rres_packer do not need to implement
  mempools
- The idea is that mempool is incomplete and requires implementation of
  the mempool in scenes.
    - Components are usually game-specific, so this is the first step to
      decouple it from the engine.
2024-08-19 17:33:05 +08:00
En Yi e4b5695a15 Allow level end to be deleted 2024-08-19 14:24:33 +08:00
En Yi e37e89505a Experiment with timer-based transition 2024-08-19 14:24:22 +08:00
En Yi 019f39f84c Implement basic level state management
Internal Changelog:
- Remove spawn position for entity
    - Not used. If spawning is needed, use a spawning entity
- Put in state machine callback function
    - Player spawning system is part of callback function
    - Remove player spawning system
- Incorporate state transition in systems
2024-08-19 11:55:48 +08:00
En Yi f6f6d54ecf Put back the air meter
Render it on the game canvas layer
2024-08-17 20:37:20 +08:00
En Yi c20401c457 Rework free camera toggling behaviour for editor
Changelog:
- Reorder render layers
- Seperate out player related actions from others
- Add indication for free camera mode
2024-08-17 16:59:50 +08:00
En Yi acb1b7858c Add lookahead feature into main game 2024-08-17 14:59:17 +08:00
En Yi 90bbad09ae Add free-movement to camera in editor scene 2024-08-17 14:49:51 +08:00
En Yi 5d8385f9ff Refactor camera update system 2024-08-17 14:10:59 +08:00
En Yi 9fdb8296ff Add key to toggle player movement
This key will be used for looking ahead
2024-08-17 13:58:06 +08:00
En Yi e762f62f40 Decouple player dead and respawn system 2024-08-15 11:55:22 +08:00
En Yi 2461244f6b Add chest into ldtk packing 2024-08-15 11:08:56 +08:00
En Yi 9410075207 Add placeholder images 2024-08-15 11:08:39 +08:00
En Yi c1207ec5b4 Properly reset a level scene 2024-08-15 11:08:20 +08:00
En Yi 13d8aafec3 Increase engine limits 2024-08-15 11:00:16 +08:00
En Yi 259fa114ca Fix water filling rate 2024-08-13 18:17:52 +08:00
En Yi c3924c862b Adjust player swimming behaviour
Internal Changelog:
- Slightly increase upthrust
- Slightly reduce air time
2024-08-13 18:17:33 +08:00
En Yi 979533092e Update main game render 2024-08-13 15:34:31 +08:00
En Yi 639aed69db Reposition level select UI 2024-08-13 15:33:38 +08:00
En Yi 0e9ba5795c Center menu screen 2024-08-13 15:33:18 +08:00
En Yi 65b04523ce Update game rendering
Internal Changelog:
- Port editor scene rendering improvements
- Make game scene ~640x480 for testing
2024-08-12 16:06:36 +08:00
En Yi 9f6c37c1fd Fix bubbling particles issue out-of-water 2024-08-11 15:50:11 +08:00
En Yi 5d1784dad8 Attempt to fix BG scrolling 2024-08-10 17:45:09 +08:00
En Yi 2c908ffd46 Update rendering for editor scene
Internal Changelog:
- Add background
- Update grid and bbox drawing
2024-08-10 17:21:10 +08:00
En Yi f20daa9cce Update main game sprite rendering 2024-08-06 21:43:40 +08:00
En Yi 4a54bfe84f Incorporate anchor point in player sprite info
Changelog:
- src anchorpoint is also an enum. The offset needs to be computed
  dynamically due to flip_x
- Re-add back the offset field.
- Add symbol parsing for anchor point.
2024-08-06 21:36:17 +08:00
En Yi 1215746e05 Add x-flipping for anchoring 2024-08-06 20:36:23 +08:00
En Yi 657110a66d Implement sprite rendering anchor 2024-08-05 21:50:01 +08:00
En Yi 2dbc1f19ab Add additional sprites for swimming 2024-08-03 17:50:08 +08:00
En Yi 8207558be7 Add title to level select scene 2024-07-08 21:08:55 +08:00
En Yi 9b6c364269 Add mouse support for scroll area
Internal Changelog:
- Refactor scroll area refocus function
    - This only triggers when selection is made
- Add mouse function process in test scene
- Add action for mouse selection
2024-07-08 19:16:10 +08:00
En Yi eff3d090df Add proper level selection actions
Internal Changelog:
- Scroll area will auto scroll to make selection completely visible
2024-07-08 18:42:29 +08:00
En Yi 41f3656ba1 Integrate Level Selection scene transition
Internal Changelog:
- Changing scene now return the scene to change into
- Set the level pack and selected level
2024-07-08 18:18:48 +08:00
En Yi 98b957a8ff Encapsulate scroll area as UI component 2024-07-08 18:02:56 +08:00
En Yi 6dd185b6cd Integrate scroll bar to level select 2024-07-08 12:40:27 +08:00
En Yi 33de816841 Replace hardcoded scene values to enum 2024-07-07 16:48:55 +08:00
En Yi 0c461d3167 Integrate level select screen to main
Not complete tho
2024-07-07 15:45:17 +08:00
56 changed files with 3816 additions and 1612 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ release/
web/
compile_commands.json
.gdb_history
heaptrack.*

3
.gitmodules vendored 100644
View File

@ -0,0 +1,3 @@
[submodule "tracy"]
path = tracy
url = https://github.com/wolfpld/tracy.git

View File

@ -2,18 +2,26 @@ set(PROJECT_NAME HATPC_remake)
set(CMAKE_C_COMPILER clang)
set(CMAKE_C_FLAGS "-Wall -Wextra")
cmake_minimum_required(VERSION 3.22.1)
project(${PROJECT_NAME} C)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project(${PROJECT_NAME} C CXX)
set(CMAKE_C_STANDARD 99)
set(RAYLIB_DIR /usr/local/lib CACHE FILEPATH "directory to Raylib")
set(LIBZSTD_DIR /usr/local/lib CACHE FILEPATH "directory to zstd")
option(RUN_PROFILER OFF)
option(INCLUDE_ASAN ON)
option(EXPORT_MMAP OFF)
option(BUILD_EXTRAS OFF)
# If you want to use Heaptrack to profile the memory
# Do not compile in ASAN
if (EMSCRIPTEN)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPLATFORM_WEB")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s USE_GLFW=3 -s ASSERTIONS=1 -s WASM=1 -s ASYNCIFY -s TOTAL_MEMORY=16777216 -s TOTAL_STACK=1048576 --preload-file ./res ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPLATFORM_WEB")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s USE_GLFW=3 -s ASSERTIONS=1 -s WASM=1 -s ASYNCIFY -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s TOTAL_MEMORY=16777216 -s TOTAL_STACK=1048576 --preload-file ./res ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_EXECUTABLE_SUFFIX ".html")
set(CMAKE_EXECUTABLE_SUFFIX ".html")
endif ()
if (${CMAKE_BUILD_TYPE} STREQUAL tile16)
@ -24,164 +32,71 @@ set(GAME_LIBS
lib_scenes
)
if (${RUN_PROFILER})
set(GAME_LIBS
lib_scenes
pthread
dl
)
endif()
add_subdirectory(engine)
add_subdirectory(scenes)
if (NOT EMSCRIPTEN)
add_subdirectory(res)
endif ()
add_executable(${PROJECT_NAME}
main.c
)
target_include_directories(${PROJECT_NAME}
macro(add_target_exe name)
add_executable(${name}
${name}.c
tracy/public/TracyClient.cpp
)
target_include_directories(${name}
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
PUBLIC
tracy/public/
)
if (RUN_PROFILER)
target_compile_definitions(${name}
PUBLIC
TRACY_ENABLE
TRACY_ON_DEMAND
)
endif()
target_link_libraries(${PROJECT_NAME}
if (NOT EMSCRIPTEN)
if (INCLUDE_ASAN)
target_compile_options(${name} PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(${name} PRIVATE -fsanitize=address -gdwarf-4)
endif ()
if (EXPORT_MMAP)
target_link_options(${name} PRIVATE -Xlinker -Map=scene_test.map)
endif()
endif ()
target_link_libraries(${name}
PUBLIC
${GAME_LIBS}
)
add_executable(scene_test
scene_test.c
)
)
endmacro()
target_include_directories(scene_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
add_target_exe(main)
add_target_exe(level_test)
add_target_exe(scene_test)
if (NOT EMSCRIPTEN)
target_compile_options(scene_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(scene_test PRIVATE -fsanitize=address -gdwarf-4)
endif ()
target_link_libraries(scene_test
${GAME_LIBS}
)
if (NOT EMSCRIPTEN)
add_executable(EntManager_test
entManager_test.c
)
target_compile_options(EntManager_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(EntManager_test PRIVATE -fsanitize=address -gdwarf-4)
target_include_directories(EntManager_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(EntManager_test
${GAME_LIBS}
)
add_executable(scene_test_mem
scene_test.c
)
target_include_directories(scene_test_mem
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_options(scene_test_mem PRIVATE -Xlinker -Map=scene_test.map)
target_link_libraries(scene_test_mem
${GAME_LIBS}
)
add_executable(water_test
water_test.c
)
target_include_directories(water_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(water_test
${GAME_LIBS}
)
target_compile_options(water_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(water_test PRIVATE -fsanitize=address -gdwarf-4)
add_executable(level_load_test
level_load_test.c
)
target_include_directories(level_load_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(level_load_test
${GAME_LIBS}
)
target_compile_options(level_load_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(level_load_test PRIVATE -fsanitize=address -gdwarf-4)
add_executable(menu_test
menu_test.c
)
target_include_directories(menu_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_compile_options(menu_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(menu_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_libraries(menu_test
${GAME_LIBS}
)
add_executable(assets_test
assets_test.c
)
target_include_directories(assets_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_compile_options(assets_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(assets_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_libraries(assets_test
${GAME_LIBS}
)
add_executable(particle_test
particle_test.c
)
target_include_directories(particle_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_compile_options(particle_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(particle_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_libraries(particle_test
${GAME_LIBS}
)
add_executable(scene_man_test
scene_man_test.c
)
target_include_directories(scene_man_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_compile_options(scene_man_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(scene_man_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_libraries(scene_man_test
${GAME_LIBS}
)
add_executable(level_select_test
level_select_test.c
)
target_include_directories(level_select_test
PRIVATE
${CMAKE_CURRENT_LIST_DIR}
)
target_compile_options(level_select_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_options(level_select_test PRIVATE -fsanitize=address -gdwarf-4)
target_link_libraries(level_select_test
${GAME_LIBS}
)
if (BUILD_TESTING)
if (BUILD_EXTRAS AND NOT RUN_PROFILER)
add_target_exe(entManager_test)
add_target_exe(water_test)
add_target_exe(level_load_test)
add_target_exe(menu_test)
add_target_exe(assets_test)
add_target_exe(particle_test)
add_target_exe(scene_man_test)
add_target_exe(level_select_test)
endif()
if (BUILD_TESTING)
find_package(cmocka 1.1.0 REQUIRED)
add_subdirectory(tests)
endif()
endif()
endif()

View File

@ -11,13 +11,13 @@ The goal of the project is to make a game similar to Hannah and The Pirate Caves
## Implementation Notes
As mentioned, this is mostly a learning experience, so certain things are implemented for the sake of learning. However, there are certain things that I didn't bother with because dealing with them will open up another can of worms. Either that or I find it too large of a feature to implement on my own without hating my existance. For these, I will use libraries.
As this point of time (23/11/2023), these are the things I won't bother for now:
As this point of time (24/08/2024), these are the things I won't bother for now:
- Game rendering: I remember dealing with OpenGL for a little bit and it being a not-so-pleasant experience. This is also a large topic on its own too.
- Camera System: This is an interesting one. I would really like to implement this. However, this is sort of tied to game rendering, so no.
- Windows and raw input handling: Not keen on this.
- GUI: I'm not about to roll my own GUI library for this.
- GUI: I'm not about to roll my own GUI library for this. I'll do a lite version, but that's about it.
- Data structures: Will only do it for specific reasons. Otherwise, use a library.
- Level editor: ... no. This is more towards GUI design which I'm not currently keen on.
- Level editor: ... maybe. I already have a sandbox, so maybe I can turn that into an editor lite??? Won't think too much about it for now.
Libraries/Tools used:
- _raylib_ \[[link](https://github.com/raysan5/raylib)\] + _raygui_ \[[link](https://github.com/raysan5/raygui)\]: MVP of this project. Basically the backbone of the game. I've use it for some past projects and it's always a pleasant experience. Can recommend!
@ -29,14 +29,16 @@ Libraries/Tools used:
- _LDtk_ \[[link](https://ldtk.io/)\]: A nice level editor. I haven't use it to its fullest extent, but definitely having a good experience so far.
- _Aseprite_ \[[link](https://www.aseprite.org/)\]: Used it to create sprites. Good tool!
- _Emscripten_ \[[link](https://emscripten.org/)\]: For web build. It's really a marvel of technology!
- _heaptrack_ \[[link](https://github.com/KDE/heaptrack)\]: For heap profiling. Simple and straightforward to use.
## Progress
The engine features:
- An Entity-Component framework with an Entity Manager + memory pool that is specific for this project
- AABB collision system + Grid-based Broad phase collision detection
- Scene management and transition
- Scene tree management and transition
- Assets management and sprite transition
- Simple level loading
- Simple Particle effects management
Current progress:
- Simple main menu + sandbox scene
@ -47,7 +49,6 @@ Current progress:
- Chest and Level Ending
- Arrows and dynamites
- Water filling
- Simple Particle Effects
- Sound Effects
- Simple Camera Update System
- Demo level pack loading
@ -68,5 +69,12 @@ You may also turn off `BUILD_TESTING` to avoid building the tests.
There are also other binaries generated for testing purposes. Feel free to try them out if you manage to build them.
## Debugging && Profiling
All binaries except the _main_ one are built with ASAN, which helps to detect memory leakage.
_Heaptrack_ is used to do so for the main program to keep for memory leakage + heap usage. From my experience, it doesn't work with ASAN.
I'm looking for runtime profiler. Current candidates are: _Orbit_ and _Tracy_. For small-ish gameplay, the program still runs fine.
## Note on assets
This repository will not contain the assets used in the game, such as fonts, art, and sfx. However, the program should still run without those. Assets are placed in the _res/_ directory.

View File

@ -1,34 +1,43 @@
add_subdirectory(sc)
add_library(lib_engine STATIC
add_library(lib_assets STATIC
assets.c
AABB.c
gui.c
engine.c
collisions.c
rres.c
mempool.c
entManager.c
particle_sys.c
)
target_include_directories(lib_engine
target_include_directories(lib_assets
PRIVATE
${LIBZSTD_DIR}/include
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
${RAYLIB_DIR}/include
)
target_link_directories(lib_engine
target_link_directories(lib_assets
PUBLIC
${RAYLIB_DIR}/lib
${LIBZSTD_DIR}/lib
)
target_link_libraries(lib_engine
target_link_libraries(lib_assets
PUBLIC
zstd
raylib
sc_queue
sc_heap
sc_map
m
)
add_library(lib_engine OBJECT
AABB.c
gui.c
engine.c
collisions.c
mempool.c
entManager.c
render_queue.c
)
target_link_libraries(lib_engine
PUBLIC
lib_assets
sc_heap
sc_array
m
)

View File

@ -11,36 +11,39 @@
typedef struct EntityManager EntityManager_t;
typedef struct Entity Entity_t;
typedef enum ComponentEnum {
CBBOX_COMP_T = 0,
CTRANSFORM_COMP_T,
CTILECOORD_COMP_T,
CMOVEMENTSTATE_T,
CJUMP_COMP_T,
CPLAYERSTATE_T,
CCONTAINER_T,
CHITBOXES_T,
CHURTBOX_T,
CSPRITE_T,
CMOVEABLE_T,
CLIFETIMER_T,
CWATERRUNNER_T,
CAIRTIMER_T,
CEMITTER_T,
} ComponentEnum_t;
typedef enum AnchorPoint {
AP_TOP_LEFT,
AP_TOP_CENTER,
AP_TOP_RIGHT,
AP_MID_LEFT,
AP_MID_CENTER,
AP_MID_RIGHT,
AP_BOT_LEFT,
AP_BOT_CENTER,
AP_BOT_RIGHT,
} AnchorPoint_t;
typedef enum MovementMode {
REGULAR_MOVEMENT = 0,
KINEMATIC_MOVEMENT,
}MovementMode_t;
typedef struct _CBBox_t {
Vector2 size;
Vector2 offset;
Vector2 half_size;
bool solid;
bool fragile;
} CBBox_t;
/** Fundamental components:
// - Transform
// - BBox
// - TileCoords
**/
/** The component enums are purely a index store. Here, there are 3 basic component, that is necessary for the engine to function (mostly collisions)
* To extend the component list, define another set of enums as pure integer store and begin the enum at N_BASIC_COMPS
* These integers are also used by the component mempool as indices. Thus, the mempool must have the first 3 components as the basic components
**/
#define N_BASIC_COMPS 3
enum BasicComponentEnum {
CBBOX_COMP_T = 0,
CTRANSFORM_COMP_T,
CTILECOORD_COMP_T,
};
typedef struct _CTransform_t {
Vector2 prev_position;
@ -51,182 +54,18 @@ typedef struct _CTransform_t {
Vector2 shape_factor;
float grav_delay;
float grav_timer;
float bounce_coeff;
MovementMode_t movement_mode;
bool active;
} CTransform_t;
typedef struct _CMovementState_t {
uint8_t ground_state;
uint8_t water_state;
uint8_t x_dir;
float water_overlap;
} CMovementState_t;
// This is to store the occupying tiles
// Limits to store 4 tiles at a tile,
// Thus the size of the entity cannot be larger than the tile
typedef struct _CTileCoord_t {
unsigned int tiles[8];
unsigned int n_tiles;
} CTileCoord_t;
typedef struct _CJump_t {
int jump_speed;
uint8_t jumps;
uint8_t max_jumps;
uint8_t coyote_timer;
bool jumped;
bool jump_ready;
bool short_hop;
bool jump_released;
} CJump_t;
typedef enum PlayerState {
GROUNDED,
AIR,
} PlayerState_t;
typedef struct _CPlayerState_t {
Vector2 player_dir;
uint8_t jump_pressed;
uint8_t is_crouch;
bool ladder_state;
} CPlayerState_t;
typedef enum ContainerItem {
CONTAINER_EMPTY,
CONTAINER_LEFT_ARROW,
CONTAINER_RIGHT_ARROW,
CONTAINER_UP_ARROW,
CONTAINER_DOWN_ARROW,
CONTAINER_COIN,
CONTAINER_BOMB,
CONTAINER_EXPLOSION,
} ContainerItem_t;
typedef enum ContainerMaterial {
WOODEN_CONTAINER,
METAL_CONTAINER,
} ContainerMaterial_t;
typedef struct _CContainer_t {
ContainerMaterial_t material;
ContainerItem_t item;
} CContainer_t;
typedef struct _CHitBoxes_t {
Rectangle boxes[2];
uint8_t n_boxes;
uint8_t atk;
bool one_hit;
} CHitBoxes_t;
typedef struct _CHurtbox_t {
Vector2 offset;
typedef struct _CBBox_t {
Vector2 size;
uint8_t def;
unsigned int damage_src;
} CHurtbox_t;
typedef struct _CLifeTimer_t {
float life_time;
} CLifeTimer_t;
typedef struct _CAirTimer_t {
float max_ftimer;
float curr_ftimer;
float decay_rate;
uint8_t max_count;
uint8_t curr_count;
} CAirTimer_t;
typedef struct _BFSTile {
int32_t to;
int32_t from;
bool reachable;
}BFSTile_t;
typedef struct _BFSTileMap {
BFSTile_t* tilemap;
int32_t width;
int32_t height;
int32_t len;
}BFSTileMap_t;
typedef enum _WaterRunnerState
{
BFS_RESET = 0,
BFS_START,
LOWEST_POINT_SEARCH,
LOWEST_POINT_MOVEMENT,
REACHABILITY_SEARCH,
SCANLINE_FILL,
FILL_COMPLETE,
}WaterRunerState_t;
typedef struct _CWaterRunner {
BFSTileMap_t bfs_tilemap;
WaterRunerState_t state;
struct sc_queue_32 bfs_queue;
bool* visited;
int32_t current_tile;
int32_t target_tile;
int32_t fill_idx;
int16_t fill_range[2];
uint8_t movement_delay;
int8_t movement_speed;
int16_t counter;
float fractional;
}CWaterRunner_t;
// Credits to bedroomcoders.co.uk for this
typedef struct Sprite {
Texture2D* texture;
Vector2 frame_size;
Vector2 origin; // TL of the frame
Vector2 anchor; // Where transformation anchors on
uint8_t frame_per_row;
int frame_count;
int speed;
char* name;
} Sprite_t;
typedef unsigned int (*sprite_transition_func_t)(Entity_t *ent); // Transition requires knowledge of the entity
typedef struct _SpriteRenderInfo
{
Sprite_t* sprite;
Vector2 offset;
} SpriteRenderInfo_t;
typedef struct _CSprite_t {
SpriteRenderInfo_t* sprites;
sprite_transition_func_t transition_func;
unsigned int current_idx;
bool flip_x;
bool flip_y;
bool pause;
int current_frame;
float fractional;
float rotation; // Degree
float rotation_speed; // Degree / s
int elapsed;
Vector2 offset;
Color colour;
} CSprite_t;
typedef struct _CMoveable_t {
uint16_t move_speed;
Vector2 prev_pos;
Vector2 target_pos;
bool gridmove;
} CMoveable_t;
typedef uint16_t EmitterHandle;
typedef struct _CEmitter_t
{
EmitterHandle handle;
Vector2 offset;
} CEmitter_t;
Vector2 half_size;
bool solid;
bool fragile;
} CBBox_t;
static inline void set_bbox(CBBox_t* p_bbox, unsigned int x, unsigned int y)
{
@ -235,9 +74,15 @@ static inline void set_bbox(CBBox_t* p_bbox, unsigned int x, unsigned int y)
p_bbox->half_size.x = (unsigned int)(x / 2);
p_bbox->half_size.y = (unsigned int)(y / 2);
}
// This is to store the occupying tiles
// Limits to store 4 tiles at a tile,
// Thus the size of the entity cannot be larger than the tile
typedef struct _CTileCoord_t {
unsigned int tiles[8];
unsigned int n_tiles;
} CTileCoord_t;
struct Entity {
Vector2 spawn_pos;
Vector2 position;
unsigned long m_id;
unsigned int m_tag;
@ -256,7 +101,7 @@ enum EntityUpdateEvent
struct EntityUpdateEventInfo
{
unsigned long e_id;
ComponentEnum_t comp_type;
unsigned int comp_type;
unsigned long c_id;
enum EntityUpdateEvent evt_type;
};
@ -284,8 +129,8 @@ Entity_t* add_entity(EntityManager_t* p_manager, unsigned int tag);
void remove_entity(EntityManager_t* p_manager, unsigned long id);
Entity_t *get_entity(EntityManager_t* p_manager, unsigned long id);
void* add_component(Entity_t *entity, ComponentEnum_t comp_type);
void* get_component(Entity_t *entity, ComponentEnum_t comp_type);
void remove_component(Entity_t* entity, ComponentEnum_t comp_type);
void* add_component(Entity_t *entity, unsigned int comp_type);
void* get_component(Entity_t *entity, unsigned int comp_type);
void remove_component(Entity_t* entity, unsigned int comp_type);
#endif // __ENTITY_H

View File

@ -23,5 +23,6 @@ typedef enum ActionType
ACTION_SPAWN_TILE,
ACTION_REMOVE_TILE,
ACTION_SWITCH_TILESET,
ACTION_LOOKAHEAD,
}ActionType_t;
#endif // __ACTIONS_H

View File

@ -1,6 +1,7 @@
#include "assets.h"
#include "assert.h"
#include "engine_conf.h"
#include "raymath.h"
#define RRES_RAYLIB_IMPLEMENTATION
#include "rres.h"
@ -274,7 +275,7 @@ static LevelPack_t* add_level_pack_zst(Assets_t* assets, const char* name, const
for (lvls = 0; lvls < n_levels; ++lvls)
{
printf("Parsing level %u\n", lvls);
output.size = 36;
output.size = 40;
output.pos = 0;
do
@ -309,6 +310,8 @@ static LevelPack_t* add_level_pack_zst(Assets_t* assets, const char* name, const
memcpy(pack_info->pack.levels[lvls].level_name, level_decompressor.out_buffer, 32);
memcpy(&pack_info->pack.levels[lvls].width, level_decompressor.out_buffer + 32, 2);
memcpy(&pack_info->pack.levels[lvls].height, level_decompressor.out_buffer + 34, 2);
memcpy(&pack_info->pack.levels[lvls].n_chests, level_decompressor.out_buffer + 36, 2);
memcpy(&pack_info->pack.levels[lvls].flags, level_decompressor.out_buffer + 38, 2);
pack_info->pack.levels[lvls].level_name[31] = '\0';
printf("Level name: %s\n", pack_info->pack.levels[lvls].level_name);
printf("WxH: %u %u\n", pack_info->pack.levels[lvls].width, pack_info->pack.levels[lvls].height);
@ -551,15 +554,20 @@ void draw_sprite_pro(Sprite_t* spr, int frame_num, Vector2 pos, float rotation,
spr->frame_size.x * ((flip & 1) ? -1 : 1),
spr->frame_size.y * ((flip & 2) ? -1 : 1),
};
Rectangle dest = {
.x = pos.x,
.y = pos.y,
.width = spr->frame_size.x * scale.x,
.height = spr->frame_size.y * scale.y
};
// The anchor here is only for rotation and scaling.
// Translational anchor is expected to be accounted for
// So need to offset render position with anchor position
Vector2 anchor = spr->anchor;
anchor.x *= scale.x;
anchor.y *= scale.y;
Rectangle dest = {
.x = pos.x + anchor.x,
.y = pos.y + anchor.y,
.width = spr->frame_size.x * scale.x,
.height = spr->frame_size.y * scale.y
};
DrawTexturePro(
*spr->texture,
rec,
@ -568,3 +576,73 @@ void draw_sprite_pro(Sprite_t* spr, int frame_num, Vector2 pos, float rotation,
rotation, colour
);
}
static Vector2 internal_get_anchor_offset(Vector2 bbox, AnchorPoint_t anchor)
{
Vector2 offset = {0};
switch (anchor)
{
case AP_TOP_LEFT:
break;
case AP_TOP_CENTER:
offset.x = bbox.x / 2;
break;
case AP_TOP_RIGHT:
offset.x = bbox.x;
break;
case AP_MID_LEFT:
offset.x = 0;
offset.y = bbox.y / 2;
break;
case AP_MID_CENTER:
offset.x = bbox.x / 2;
offset.y = bbox.y / 2;
break;
case AP_MID_RIGHT:
offset.x = bbox.x;
offset.y = bbox.y / 2;
break;
case AP_BOT_LEFT:
offset.x = 0;
offset.y = bbox.y;
break;
case AP_BOT_CENTER:
offset.x = bbox.x / 2;
offset.y = bbox.y;
break;
case AP_BOT_RIGHT:
offset.x = bbox.x;
offset.y = bbox.y;
break;
}
return offset;
}
Vector2 shift_bbox(Vector2 bbox, Vector2 new_bbox, AnchorPoint_t anchor)
{
Vector2 p1 = internal_get_anchor_offset(bbox, anchor);
Vector2 p2 = internal_get_anchor_offset(new_bbox, anchor);
return Vector2Subtract(p1, p2);
}
Vector2 get_anchor_offset(Vector2 bbox, AnchorPoint_t anchor, bool flip_x)
{
if (flip_x)
{
switch(anchor)
{
case AP_TOP_LEFT: anchor = AP_TOP_RIGHT; break;
case AP_TOP_RIGHT: anchor = AP_TOP_LEFT; break;
case AP_MID_LEFT: anchor = AP_MID_RIGHT; break;
case AP_MID_RIGHT: anchor = AP_MID_LEFT; break;
case AP_BOT_LEFT: anchor = AP_BOT_RIGHT; break;
case AP_BOT_RIGHT: anchor = AP_BOT_LEFT; break;
default:
break;
}
}
return internal_get_anchor_offset(bbox, anchor);
}

View File

@ -4,7 +4,7 @@
#include "EC.h"
#include "raylib.h"
#include "rres.h"
#include "particle_sys.h"
#define N_ASSETS_TYPE 6
typedef enum AssetType
{
@ -16,6 +16,24 @@ typedef enum AssetType
AST_EMITTER_CONF,
}AssetType_t;
typedef enum PartEmitterType
{
EMITTER_UNKNOWN = 0,
EMITTER_BURST,
EMITTER_STREAM,
} PartEmitterType_t;
typedef struct EmitterConfig
{
float launch_range[2];
float speed_range[2];
float angle_range[2];
float rotation_range[2];
float particle_lifetime[2];
float initial_spawn_delay;
PartEmitterType_t type;
bool one_shot;
}EmitterConfig_t;
typedef struct Assets
{
@ -40,6 +58,8 @@ typedef struct LevelMap
char level_name[32];
uint16_t width;
uint16_t height;
uint16_t n_chests;
uint16_t flags; // In case of extras
LevelTileInfo_t* tiles;
}LevelMap_t;
@ -49,6 +69,18 @@ typedef struct LevelPack
LevelMap_t* levels;
}LevelPack_t;
// Credits to bedroomcoders.co.uk for this
typedef struct Sprite {
Texture2D* texture;
Vector2 frame_size;
Vector2 origin; // TL of the frame
Vector2 anchor; // Where transformation anchors on
uint8_t frame_per_row;
int frame_count;
int speed;
char* name;
} Sprite_t;
typedef struct RresFileInfo
{
rresCentralDir dir;
@ -83,6 +115,8 @@ LevelPack_t* get_level_pack(Assets_t* assets, const char* name);
void draw_sprite(Sprite_t* spr, int frame_num, Vector2 pos, float rotation, bool flip_x);
void draw_sprite_pro(Sprite_t* spr, int frame_num, Vector2 pos, float rotation, uint8_t flip, Vector2 scale, Color colour);
Vector2 get_anchor_offset(Vector2 bbox, AnchorPoint_t anchor, bool flip_x);
Vector2 shift_bbox(Vector2 bbox, Vector2 new_bbox, AnchorPoint_t anchor);
typedef struct SFX
{

View File

@ -160,7 +160,7 @@ uint8_t check_collision_at(Entity_t* p_ent, Vector2 pos, Vector2 bbox_sz, TileGr
CollideEntity_t ent = {
.p_ent = p_ent,
.bbox = (Rectangle){pos.x, pos.y, bbox_sz.x, bbox_sz.y},
.prev_bbox = (Rectangle){pos.x, pos.y, bbox_sz.x, bbox_sz.y},
.prev_bbox = (Rectangle){p_ent->position.x, p_ent->position.y, bbox_sz.x, bbox_sz.y},
.area = (TileArea_t){
.tile_x1 = (pos.x) / grid->tile_size,
.tile_y1 = (pos.y) / grid->tile_size,
@ -169,7 +169,7 @@ uint8_t check_collision_at(Entity_t* p_ent, Vector2 pos, Vector2 bbox_sz, TileGr
}
};
return check_collision(&ent, grid, false);
return check_collision(&ent, grid, true);
}
bool check_on_ground(Entity_t* p_ent, Vector2 prev_pos, Vector2 bbox_sz, TileGrid_t* grid)

View File

@ -1,6 +1,7 @@
#ifndef __COLLISION_FUNCS_H
#define __COLLISION_FUNCS_H
#include "EC.h"
#include "render_queue.h"
typedef enum SolidType
{
@ -42,6 +43,7 @@ typedef struct TileGrid
unsigned int max_tiles;
unsigned int tile_size;
Tile_t* tiles;
RenderInfoNode* render_nodes;
}TileGrid_t;
typedef struct TileArea {

View File

@ -15,6 +15,7 @@ void init_engine(GameEngine_t* engine, Vector2 starting_win_size)
init_assets(&engine->assets);
engine->intended_window_size = starting_win_size;
InitWindow(starting_win_size.x, starting_win_size.y, "raylib");
engine->base_canvas = LoadRenderTexture(starting_win_size.x, starting_win_size.y);
}
void deinit_engine(GameEngine_t* engine)
@ -24,6 +25,7 @@ void deinit_engine(GameEngine_t* engine)
sc_queue_term(&engine->key_buffer);
sc_queue_term(&engine->scene_stack);
sc_heap_term(&engine->scenes_render_order);
UnloadRenderTexture(engine->base_canvas);
CloseAudioDevice();
CloseWindow();
}
@ -89,10 +91,10 @@ void process_inputs(GameEngine_t* engine, Scene_t* scene)
}
}
void change_scene(GameEngine_t* engine, unsigned int idx)
Scene_t* change_scene(GameEngine_t* engine, unsigned int idx)
{
// Backwards compat
change_active_scene(engine, idx);
return change_active_scene(engine, idx);
}
bool load_sfx(GameEngine_t* engine, const char* snd_name, uint32_t tag_idx)
@ -116,7 +118,7 @@ void play_sfx_pitched(GameEngine_t* engine, unsigned int tag_idx, float pitch)
//if (sfx->snd != NULL)
{
PlaySound(*sfx->snd);
sfx->plays++;
//sfx->plays++;
}
//SetSoundPitch(*sfx->snd, 0.0f);
}
@ -150,13 +152,20 @@ void update_sfx_list(GameEngine_t* engine)
engine->sfx_list.played_sfx = 0;
}
void init_scene(Scene_t* scene, action_func_t action_func)
void init_scene(Scene_t* scene, action_func_t action_func, uint32_t subsystem_init)
{
sc_map_init_64(&scene->action_map, 32, 0);
sc_array_init(&scene->systems);
if (subsystem_init & ENABLE_ENTITY_MANAGEMENT_SYSTEM)
{
init_entity_manager(&scene->ent_manager);
}
if (subsystem_init & ENABLE_PARTICLE_SYSTEM)
{
init_particle_system(&scene->part_sys);
}
scene->subsystem_init = subsystem_init;
//scene->scene_type = scene_type;
scene->layers.n_layers = 0;
scene->bg_colour = WHITE;
@ -184,8 +193,16 @@ void free_scene(Scene_t* scene)
{
UnloadRenderTexture(scene->layers.render_layers[i].layer_tex);
}
if (scene->subsystem_init & ENABLE_ENTITY_MANAGEMENT_SYSTEM)
{
free_entity_manager(&scene->ent_manager);
}
if (scene->subsystem_init & ENABLE_PARTICLE_SYSTEM)
{
deinit_particle_system(&scene->part_sys);
}
}
inline void update_scene(Scene_t* scene, float delta_time)
@ -198,7 +215,10 @@ inline void update_scene(Scene_t* scene, float delta_time)
{
sys(scene);
}
if (scene->subsystem_init & ENABLE_PARTICLE_SYSTEM)
{
update_particle_system(&scene->part_sys, scene->delta_time);
}
}
static void _internal_render_scene(Scene_t* scene)
@ -233,8 +253,43 @@ static void _internal_render_scene(Scene_t* scene)
inline void render_scene(Scene_t* scene)
{
BeginDrawing();
BeginTextureMode(scene->engine->base_canvas);
_internal_render_scene(scene);
EndTextureMode();
int curr_width = GetRenderWidth();
int curr_height = GetRenderHeight();
Vector2 original_size = scene->engine->intended_window_size;
float wscale = (curr_width / original_size.x);
float hscale = (curr_height / original_size.y);
float min_dim = (wscale > hscale) ? hscale : wscale;
Vector2 offset = {
scene->engine->intended_window_size.x * (wscale - min_dim) / 2,
scene->engine->intended_window_size.y * (hscale - min_dim) / 2
};
wscale = min_dim;
hscale = min_dim;
Rectangle draw_rec = {
0,0,
scene->engine->intended_window_size.x,
scene->engine->intended_window_size.y
};
Rectangle draw_pos = {
draw_rec.x * wscale + offset.x, draw_rec.y * hscale + offset.y,
draw_rec.width * wscale, draw_rec.height * hscale
};
draw_rec.height *= -1;
BeginDrawing();
ClearBackground((Color){0,0,0,255});
DrawTexturePro(
scene->engine->base_canvas.texture,
draw_rec,
draw_pos,
(Vector2){0,0}, 0.0, WHITE
);
EndDrawing();
}
@ -361,11 +416,13 @@ void remove_child_scene(GameEngine_t* engine, unsigned int idx)
child->parent_scene = NULL;
}
void change_active_scene(GameEngine_t* engine, unsigned int idx)
Scene_t* change_active_scene(GameEngine_t* engine, unsigned int idx)
{
engine->scenes[engine->curr_scene]->state = 0;
engine->curr_scene = idx;
engine->scenes[engine->curr_scene]->state = SCENE_COMPLETE_ACTIVE;
sc_queue_clear(&engine->key_buffer);
return engine->scenes[engine->curr_scene];
}
void change_focused_scene(GameEngine_t* engine, unsigned int idx)

View File

@ -6,6 +6,7 @@
#include "sc/heap/sc_heap.h"
#include "assets.h"
#include "particle_sys.h"
#include "render_queue.h"
typedef struct Scene Scene_t;
@ -37,6 +38,7 @@ typedef struct GameEngine {
// This is in case of window scaling, where there needs to be
// an absolute reference
Vector2 intended_window_size;
RenderTexture2D base_canvas;
} GameEngine_t;
#define SCENE_ACTIVE_BIT (1 << 0) // Systems Active
@ -60,6 +62,7 @@ typedef struct SceneRenderLayers {
struct Scene {
// Not all scene needs an entity manager
// but too late to change this
uint32_t subsystem_init;
EntityManager_t ent_manager;
Scene_t* parent_scene;
struct sc_map_64 action_map; // key -> actions
@ -86,8 +89,8 @@ void process_active_scene_inputs(GameEngine_t* engine);
void update_curr_scene(GameEngine_t* engine);
void render_curr_scene(GameEngine_t* engine);
void change_scene(GameEngine_t* engine, unsigned int idx);
void change_active_scene(GameEngine_t* engine, unsigned int idx);
Scene_t* change_scene(GameEngine_t* engine, unsigned int idx);
Scene_t* change_active_scene(GameEngine_t* engine, unsigned int idx);
void change_focused_scene(GameEngine_t* engine, unsigned int idx);
bool load_sfx(GameEngine_t* engine, const char* snd_name, uint32_t tag_idx);
void play_sfx(GameEngine_t* engine, unsigned int tag_idx);
@ -99,7 +102,10 @@ extern void update_scene(Scene_t* scene, float delta_time);
extern void render_scene(Scene_t* scene);
extern void do_action(Scene_t* scene, ActionType_t action, bool pressed);
void init_scene(Scene_t* scene, action_func_t action_func);
//void init_scene(Scene_t* scene, action_func_t action_func);
#define ENABLE_ENTITY_MANAGEMENT_SYSTEM (1)
#define ENABLE_PARTICLE_SYSTEM (1 << 1)
void init_scene(Scene_t* scene, action_func_t action_func, uint32_t subsystem_init);
bool add_scene_layer(Scene_t* scene, int width, int height, Rectangle render_area);
void free_scene(Scene_t* scene);
void add_child_scene(GameEngine_t* engine, unsigned int child_idx, unsigned int parent_idx);

View File

@ -1,21 +1,24 @@
#ifndef _ENGINE_CONF_H
#define _ENGINE_CONF_H
#define MAX_SCENES_TO_RENDER 16
// Take care tuning these params. Web build doesn't work
// if memory used too high
#define MAX_SCENES_TO_RENDER 8
#define MAX_RENDER_LAYERS 4
#define MAX_ENTITIES 2048
#define MAX_RENDERMANAGER_DEPTH 4
#define MAX_ENTITIES 2047
#define MAX_TEXTURES 16
#define MAX_SPRITES 64
#define MAX_SPRITES 127
#define MAX_SOUNDS 32
#define MAX_FONTS 4
#define MAX_N_TILES 4096
#define MAX_N_TILES 16384
#define MAX_NAME_LEN 32
#define MAX_LEVEL_PACK 4
#define N_SFX 32
#define MAX_EMITTER_CONF 8
//#define MAX_PARTICLE_EMITTER 8
#define MAX_ACTIVE_PARTICLE_EMITTER 512
#define MAX_PARTICLES 64
#define MAX_ACTIVE_PARTICLE_EMITTER 255
#define MAX_PARTICLES 32
#define MAX_TILE_TYPES 16
#define N_TAGS 10

View File

@ -115,7 +115,6 @@ Entity_t *add_entity(EntityManager_t* p_manager, unsigned int tag)
Entity_t* p_ent = new_entity_from_mempool(&e_idx);
if (p_ent == NULL) return NULL;
p_ent->spawn_pos = (Vector2){0, 0};
p_ent->m_tag = tag;
sc_queue_add_last(&p_manager->to_add, e_idx);
p_ent->manager = p_manager;
@ -145,7 +144,7 @@ Entity_t* get_entity(EntityManager_t* p_manager, unsigned long id)
return p_entity;
}
void* add_component(Entity_t* p_entity, ComponentEnum_t comp_type)
void* add_component(Entity_t* p_entity, unsigned int comp_type)
{
if (p_entity->components[comp_type] == MAX_COMP_POOL_SIZE)
{
@ -164,7 +163,7 @@ void* add_component(Entity_t* p_entity, ComponentEnum_t comp_type)
return get_component(p_entity, comp_type);
}
void* get_component(Entity_t *p_entity, ComponentEnum_t comp_type)
void* get_component(Entity_t *p_entity, unsigned int comp_type)
{
unsigned long comp_type_idx = (unsigned long)comp_type;
unsigned long c_idx = p_entity->components[comp_type_idx];
@ -172,7 +171,7 @@ void* get_component(Entity_t *p_entity, ComponentEnum_t comp_type)
return get_component_wtih_id(comp_type, c_idx);
}
void remove_component(Entity_t *p_entity, ComponentEnum_t comp_type)
void remove_component(Entity_t *p_entity, unsigned int comp_type)
{
if (p_entity->components[comp_type] == MAX_COMP_POOL_SIZE) return;
struct EntityUpdateEventInfo evt = (struct EntityUpdateEventInfo){p_entity->m_id, comp_type, p_entity->components[comp_type] , COMP_DELETION};

View File

@ -1,5 +1,7 @@
#include "gui.h"
#include "raylib.h"
#include <string.h>
#include <stdio.h>
#define RAYGUI_MAX_CONTROLS 16 // Maximum number of standard controls
#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of standard properties
@ -9,6 +11,11 @@
static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 };
static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization
static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib)
//
// Check if two rectangles are equal, used to validate a slider bounds as an id
#ifndef CHECK_BOUNDS_ID
#define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height))
#endif
typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement;
@ -369,6 +376,403 @@ void UI_button(const UIComp_t* comp, const char* text)
}
void hover_text(const UIComp_t* comp, Font font, const char* text, Vector2 pos, int font_size, int spacing, Color colour) {
if (comp->state == STATE_FOCUSED) {
DrawTextEx(font, text, pos, font_size, spacing, Fade(colour, 0.1));
pos.y -= font_size >> 2;
}
DrawTextEx(font, text, pos, font_size, spacing, colour);
}
// Slider control with pro parameters
// NOTE: Other GuiSlider*() controls use this one
int GuiSliderPro(const UIComp_t* comp, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth)
{
Rectangle bounds = comp->bbox;
int result = 0;
GuiState state = comp->state;
float temp = (maxValue - minValue)/2.0f;
if (value == NULL) value = &temp;
int sliderValue = (int)(((*value - minValue)/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH)));
Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING),
0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) };
if (sliderWidth > 0) // Slider
{
slider.x += (sliderValue - (sliderWidth >> 1));
slider.width = (float)sliderWidth;
}
else if (sliderWidth == 0) // SliderBar
{
slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH);
slider.width = (float)sliderValue;
}
// Update control
//--------------------------------------------------------------------
//if ((state != STATE_DISABLED) && !guiLocked)
if (state != STATE_DISABLED)
{
Vector2 mousePoint = GetMousePosition();
if (comp->pressed) // Keep dragging outside of bounds
{
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON))
{
//if (CHECK_BOUNDS_ID(bounds, guiSliderActive))
{
state = STATE_PRESSED;
// Get equivalent value and slider position from mousePosition.x
*value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + sliderWidth/2)))/(float)(bounds.width - sliderWidth) + minValue;
}
}
//else
//{
// guiSliderDragging = false;
// guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 };
//}
}
else if (CheckCollisionPointRec(mousePoint, bounds))
{
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON))
{
state = STATE_PRESSED;
//guiSliderDragging = true;
//guiSliderActive = bounds; // Store bounds as an identifier when dragging starts
if (!CheckCollisionPointRec(mousePoint, slider))
{
// Get equivalent value and slider position from mousePosition.x
*value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + (sliderWidth >> 1) )))/(float)(bounds.width - sliderWidth) + minValue;
if (sliderWidth > 0) slider.x = mousePoint.x - slider.width/2; // Slider
else if (sliderWidth == 0) slider.width = (float)sliderValue; // SliderBar
}
}
else state = STATE_FOCUSED;
}
if (*value > maxValue) *value = maxValue;
else if (*value < minValue) *value = minValue;
}
// Bar limits check
if (sliderWidth > 0) // Slider
{
if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH);
else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH);
}
else if (sliderWidth == 0) // SliderBar
{
if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH);
}
//--------------------------------------------------------------------
// Draw control
//--------------------------------------------------------------------
GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED)));
// Draw slider internal bar (depends on state)
if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED)));
else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED)));
else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED)));
// Draw left/right text if provided
if (textLeft != NULL)
{
Rectangle textBounds = { 0 };
textBounds.width = (float)GetTextWidth(textLeft);
textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE);
textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING);
textBounds.y = bounds.y + bounds.height/2 - (GuiGetStyle(DEFAULT, TEXT_SIZE) >> 1);
GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3))));
}
if (textRight != NULL)
{
Rectangle textBounds = { 0 };
textBounds.width = (float)GetTextWidth(textRight);
textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE);
textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING);
textBounds.y = bounds.y + bounds.height/2 - (GuiGetStyle(DEFAULT, TEXT_SIZE) >> 1);
GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3))));
}
//--------------------------------------------------------------------
return result;
}
//------------------------------------------------------------------------------------
// Controls Functions Definitions (local)
//------------------------------------------------------------------------------------
float GuiVerticalSliderPro(const UIComp_t* comp, const char *textTop, const char *textBottom, float* in_value, float minValue, float maxValue, int sliderHeight)
{
float value = *in_value;
GuiState state = comp->state;
Rectangle bounds = comp->bbox;
int sliderValue = (int)(((value - minValue)/(maxValue - minValue)) * (bounds.height - 2 * GuiGetStyle(SLIDER, BORDER_WIDTH)));
Rectangle slider = {
bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING),
bounds.y + bounds.height - sliderValue,
bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING),
0.0f,
};
if (sliderHeight > 0) // Slider
{
slider.y -= sliderHeight >> 1;
slider.height = (float)sliderHeight;
}
else if (sliderHeight == 0) // SliderBar
{
slider.y -= GuiGetStyle(SLIDER, BORDER_WIDTH);
slider.height = (float)sliderValue;
}
// Update control
//--------------------------------------------------------------------
//if ((state != STATE_DISABLED) && !guiLocked)
if (state != STATE_DISABLED)
{
Vector2 mousePoint = GetMousePosition();
if (CheckCollisionPointRec(mousePoint, bounds))
{
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON))
{
state = STATE_PRESSED;
// Get equivalent value and slider position from mousePoint.x
float normalizedValue = (bounds.y + bounds.height - mousePoint.y - (float)(sliderHeight >> 1)) / (bounds.height - (float)sliderHeight);
value = (maxValue - minValue) * normalizedValue + minValue;
if (sliderHeight > 0) slider.y = mousePoint.y - slider.height / 2; // Slider
else if (sliderHeight == 0) // SliderBar
{
slider.y = mousePoint.y;
slider.height = bounds.y + bounds.height - slider.y - GuiGetStyle(SLIDER, BORDER_WIDTH);
}
}
else state = STATE_FOCUSED;
}
if (value > maxValue) value = maxValue;
else if (value < minValue) value = minValue;
}
// Bar limits check
if (sliderHeight > 0) // Slider
{
if (slider.y < (bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.y = bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH);
else if ((slider.y + slider.height) >= (bounds.y + bounds.height)) slider.y = bounds.y + bounds.height - slider.height - GuiGetStyle(SLIDER, BORDER_WIDTH);
}
else if (sliderHeight == 0) // SliderBar
{
if (slider.y < (bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH)))
{
slider.y = bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH);
slider.height = bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH);
}
}
//--------------------------------------------------------------------
// Draw control
//--------------------------------------------------------------------
GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), comp->alpha), Fade(GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED)), comp->alpha));
// Draw slider internal bar (depends on state)
if ((state == STATE_NORMAL) || (state == STATE_PRESSED)) GuiDrawRectangle(slider, 0, BLANK, Fade(GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED)), comp->alpha));
else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, Fade(GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED)), comp->alpha));
// Draw top/bottom text if provided
if (textTop != NULL)
{
Rectangle textBounds = { 0 };
textBounds.width = (float)GetTextWidth(textTop);
textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE);
textBounds.x = bounds.x + bounds.width/2 - textBounds.width/2;
textBounds.y = bounds.y - textBounds.height - GuiGetStyle(SLIDER, TEXT_PADDING);
GuiDrawText(textTop, textBounds, TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(SLIDER, TEXT + (state*3))), comp->alpha));
}
if (textBottom != NULL)
{
Rectangle textBounds = { 0 };
textBounds.width = (float)GetTextWidth(textBottom);
textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE);
textBounds.x = bounds.x + bounds.width/2 - textBounds.width/2;
textBounds.y = bounds.y + bounds.height + GuiGetStyle(SLIDER, TEXT_PADDING);
GuiDrawText(textBottom, textBounds, TEXT_ALIGN_LEFT, Fade(GetColor(GuiGetStyle(SLIDER, TEXT + (state*3))), comp->alpha));
}
//--------------------------------------------------------------------
*in_value = value;
return value;
}
float UI_vert_slider(const UIComp_t* comp, const char *textTop, const char *textBottom, float* value, float minValue, float maxValue)
{
return GuiVerticalSliderPro(comp, textTop, textBottom, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH));
}
// Slider control extended, returns selected value and has text
float UI_slider(const UIComp_t* comp, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue)
{
return GuiSliderPro(comp, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH));
}
void vert_scrollarea_init(VertScrollArea_t* scroll_area, Rectangle display_area, Vector2 canvas_dims)
{
scroll_area->comp.font = GetFontDefault();
scroll_area->canvas = LoadRenderTexture(canvas_dims.x, canvas_dims.y);
scroll_area->scroll_pos = canvas_dims.y - display_area.height;
scroll_area->scroll_bounds = (Vector2){
0, scroll_area->scroll_pos
};
vert_scrollarea_set_item_dims(scroll_area, 12, 3);
scroll_area->curr_selection = 0;
scroll_area->display_area = display_area;
scroll_area->scroll_bar.alpha = 1;
scroll_area->scroll_bar.bbox = (Rectangle){
display_area.width + display_area.x, display_area.y,
0.1f * display_area.width, display_area.height,
};
scroll_area->scroll_bar.state = STATE_NORMAL;
scroll_area->comp.alpha = 1;
scroll_area->comp.bbox = display_area;
scroll_area->comp.bbox.width *= 1.1f;
scroll_area->scroll_bar.state = STATE_NORMAL;
}
void vert_scrollarea_set_item_dims(VertScrollArea_t* scroll_area, unsigned int item_height, unsigned int item_padding)
{
scroll_area->item_height = item_height;
scroll_area->item_padding = item_padding;
scroll_area->max_items = (scroll_area->canvas.texture.height - scroll_area->item_padding) / (scroll_area->item_height + scroll_area->item_padding);
}
bool vert_scrollarea_n_items(VertScrollArea_t* scroll_area, unsigned int n_items) {
if (n_items >= scroll_area->max_items) return false;
// Due to OpenGL convention where y is -1 downwards,
// The scroll bar is set to decreases the scroll_pos when going down
// This value is used as the offset when rendering, which results in correctly
// give the illusion of 'scrolling down'
// So, adjust the minimum bound which is the height of the items that are not shown
scroll_area->n_items = n_items;
scroll_area->scroll_bounds.x =
(scroll_area->max_items - n_items)* (scroll_area->item_height + scroll_area->item_padding) + scroll_area->item_padding;
if (scroll_area->scroll_bounds.x > scroll_area->scroll_bounds.y) {
scroll_area->scroll_bounds.x = scroll_area->scroll_bounds.y;
}
return true;
}
void vert_scrollarea_insert_item(VertScrollArea_t* scroll_area, char* str, unsigned int item_idx)
{
if (item_idx >= scroll_area->max_items) return;
DrawTextEx(
scroll_area->comp.font,
str,
(Vector2){0, scroll_area->item_padding + (scroll_area->item_height + scroll_area->item_padding) * item_idx}
, scroll_area->item_height, 0, BLACK);
}
unsigned int vert_scrollarea_set_pos(VertScrollArea_t* scroll_area, Vector2 pos)
{
float x = pos.x - scroll_area->display_area.x;
float y = pos.y - scroll_area->display_area.y;
if (x >= scroll_area->display_area.width || x < 0) return scroll_area->max_items;
if (y >= scroll_area->display_area.height || y < 0) return scroll_area->max_items;
// How much have scroll down
float canvas_offset = (scroll_area->scroll_bounds.y - scroll_area->scroll_pos);
scroll_area->curr_selection = (y + canvas_offset - scroll_area->item_padding) / (scroll_area->item_height + scroll_area->item_padding);
return scroll_area->curr_selection;
}
void vert_scrollarea_refocus(VertScrollArea_t* scroll_area)
{
float canvas_offset = (scroll_area->scroll_bounds.y - scroll_area->scroll_pos);
// Reverse from item selection to y in display area
float selection_y = scroll_area->curr_selection * (scroll_area->item_height + scroll_area->item_padding) + scroll_area->item_padding - canvas_offset;
// Auto adjust scroll based on selection
if (selection_y < 0)
{
scroll_area->scroll_pos -= selection_y;
}
else if (selection_y + scroll_area->item_height + scroll_area->item_padding > scroll_area->display_area.height)
{
scroll_area->scroll_pos -= selection_y + scroll_area->item_height + scroll_area->item_padding - scroll_area->display_area.height;
}
}
void vert_scrollarea_render(VertScrollArea_t* scroll_area)
{
BeginScissorMode(scroll_area->comp.bbox.x, scroll_area->comp.bbox.y, scroll_area->comp.bbox.width, scroll_area->comp.bbox.height);
UI_vert_slider(&scroll_area->scroll_bar,
NULL,
NULL,
&scroll_area->scroll_pos,
scroll_area->scroll_bounds.x,
scroll_area->scroll_bounds.y
);
Rectangle draw_rec = (Rectangle){
0, scroll_area->scroll_pos,
scroll_area->display_area.width,
scroll_area->display_area.height
};
float canvas_offset = (scroll_area->scroll_bounds.y - scroll_area->scroll_pos);
float selection_y = scroll_area->curr_selection * (scroll_area->item_height + scroll_area->item_padding) + scroll_area->item_padding - canvas_offset;
DrawRectangle(
scroll_area->display_area.x,
scroll_area->display_area.y + selection_y,
scroll_area->canvas.texture.width, scroll_area->item_height,
Fade(BLUE, 0.7)
);
Vector2 draw_pos = {scroll_area->display_area.x, scroll_area->display_area.y};
draw_rec.height *= -1;
DrawTextureRec(
scroll_area->canvas.texture,
draw_rec,
draw_pos,
WHITE
);
EndScissorMode();
}
void vert_scrollarea_free(VertScrollArea_t* scroll_area)
{
UnloadRenderTexture(scroll_area->canvas);
}
void init_UI(void)
{
GuiLoadStyleDefault();

View File

@ -3,13 +3,52 @@
#include "raylib.h"
#include "raygui.h"
// Generic Component
typedef struct UIComp {
Rectangle bbox;
GuiState state;
float alpha;
bool pressed;
Font font;
} UIComp_t;
typedef struct VertScrollArea {
UIComp_t comp; // Display area + scrollbar
Rectangle display_area; // Display dimension
RenderTexture2D canvas; // Complete canvas
unsigned int n_items;
unsigned int max_items;
unsigned int curr_selection;
unsigned int item_height;
unsigned int item_padding;
float scroll_pos;
Vector2 scroll_bounds;
UIComp_t scroll_bar;
} VertScrollArea_t;
void init_UI(void);
void vert_scrollarea_init(VertScrollArea_t* scroll_area, Rectangle display_area, Vector2 canvas_dims);
void vert_scrollarea_set_item_dims(VertScrollArea_t* scroll_area, unsigned int item_height, unsigned int item_padding);
bool vert_scrollarea_n_items(VertScrollArea_t* scroll_area, unsigned int n_items);
void vert_scrollarea_insert_item(VertScrollArea_t* scroll_area, char* str, unsigned int item_idx);
unsigned int vert_scrollarea_set_pos(VertScrollArea_t* scroll_area, Vector2 pos);
void vert_scrollarea_refocus(VertScrollArea_t* scroll_area);
void vert_scrollarea_render(VertScrollArea_t* scroll_area);
void vert_scrollarea_free(VertScrollArea_t* scroll_area);
static inline void ScrollAreaRenderBegin(VertScrollArea_t* scroll)
{
BeginTextureMode(scroll->canvas);
}
#define ScrollAreaRenderEnd() EndTextureMode()
void UI_button(const UIComp_t* bbox, const char* text);
float UI_slider(const UIComp_t* comp, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue);
float UI_vert_slider(const UIComp_t* comp, const char *textTop, const char *textBottom, float* value, float minValue, float maxValue);
void hover_text(const UIComp_t* comp, Font font, const char* text, Vector2 pos, int font_size, int spacing, Color colour);
#endif

View File

@ -1,102 +1,11 @@
#include "mempool.h"
//#include "sc/queue/sc_queue.h"
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
typedef struct ULongCircBuffer {
unsigned long* buffer; // data buffer
unsigned long* buffer_end; // end of data buffer
uint32_t capacity; // maximum number of items in the buffer
uint32_t count; // number of items in the buffer
unsigned long* head; // pointer to head
unsigned long* tail; // pointer to tail
}ULongCircBuffer_t;
static void cb_init(ULongCircBuffer_t* cb, size_t capacity)
{
cb->buffer = (unsigned long*)malloc(capacity * sizeof(unsigned long));
assert(cb->buffer != NULL);
cb->buffer_end = cb->buffer + capacity;
cb->capacity = capacity;
cb->count = 0;
cb->head = cb->buffer;
cb->tail = cb->buffer;
}
static void cb_free(ULongCircBuffer_t* cb)
{
free(cb->buffer);
// clear out other fields too, just to be safe
}
static bool cb_pop_front(ULongCircBuffer_t* cb, unsigned long* item)
{
if (cb->count == 0) return false;
*item = *cb->tail;
cb->tail++;
if(cb->tail == cb->buffer_end) cb->tail = cb->buffer;
cb->count--;
return true;
}
static bool cb_push_back(ULongCircBuffer_t* cb, unsigned long item)
{
if(cb->count == cb->capacity) return false;
*(cb->head) = item;
cb->head++;
if(cb->head == cb->buffer_end) cb->head = cb->buffer;
cb->count++;
return true;
}
typedef struct MemPool {
void * const buffer;
const unsigned long max_size;
const unsigned long elem_size;
bool *use_list;
ULongCircBuffer_t free_list;
} MemPool_t;
// Static allocate buffers
static Entity_t entity_buffer[MAX_COMP_POOL_SIZE];
static CBBox_t bbox_buffer[MAX_COMP_POOL_SIZE];
static CTransform_t ctransform_buffer[MAX_COMP_POOL_SIZE];
static CTileCoord_t ctilecoord_buffer[MAX_COMP_POOL_SIZE];
static CMovementState_t cmstate_buffer[MAX_COMP_POOL_SIZE];
static CJump_t cjump_buffer[8]; // Only player is expected to have this
static CPlayerState_t cplayerstate_buffer[8]; // Only player is expected to have this
static CContainer_t ccontainer_buffer[MAX_COMP_POOL_SIZE];
static CHitBoxes_t chitboxes_buffer[MAX_COMP_POOL_SIZE];
static CHurtbox_t churtbox_buffer[MAX_COMP_POOL_SIZE];
static CSprite_t csprite_buffer[MAX_COMP_POOL_SIZE];
static CMoveable_t cmoveable_buffer[MAX_COMP_POOL_SIZE];
static CLifeTimer_t clifetimer_buffer[MAX_COMP_POOL_SIZE];
static CWaterRunner_t cwaterrunner_buffer[32];
static CAirTimer_t cairtimer_buffer[8]; // Only player is expected to have this
static CEmitter_t cemitter_buffer[MAX_COMP_POOL_SIZE]; // Only player is expected to have this
// Static allocate mempools
static MemPool_t comp_mempools[N_COMPONENTS] = {
{bbox_buffer, MAX_COMP_POOL_SIZE, sizeof(CBBox_t), NULL, {0}},
{ctransform_buffer, MAX_COMP_POOL_SIZE, sizeof(CTransform_t), NULL, {0}},
{ctilecoord_buffer, MAX_COMP_POOL_SIZE, sizeof(CTileCoord_t), NULL, {0}},
{cmstate_buffer, MAX_COMP_POOL_SIZE, sizeof(CMovementState_t), NULL, {0}},
{cjump_buffer, 8, sizeof(CJump_t), NULL, {0}},
{cplayerstate_buffer, 8, sizeof(CPlayerState_t), NULL, {0}},
{ccontainer_buffer, MAX_COMP_POOL_SIZE, sizeof(CContainer_t), NULL, {0}},
{chitboxes_buffer, MAX_COMP_POOL_SIZE, sizeof(CHitBoxes_t), NULL, {0}},
{churtbox_buffer, MAX_COMP_POOL_SIZE, sizeof(CHurtbox_t), NULL, {0}},
{csprite_buffer, MAX_COMP_POOL_SIZE, sizeof(CSprite_t), NULL, {0}},
{cmoveable_buffer, MAX_COMP_POOL_SIZE, sizeof(CMoveable_t), NULL, {0}},
{clifetimer_buffer, MAX_COMP_POOL_SIZE, sizeof(CLifeTimer_t), NULL, {0}},
{cwaterrunner_buffer, 32, sizeof(CWaterRunner_t), NULL, {0}},
{cairtimer_buffer, 8, sizeof(CAirTimer_t), NULL, {0}},
{cemitter_buffer, MAX_COMP_POOL_SIZE, sizeof(CEmitter_t), NULL, {0}},
};
static MemPool_t ent_mempool = {
.buffer = entity_buffer,
.max_size = MAX_COMP_POOL_SIZE,
@ -115,23 +24,21 @@ void init_memory_pools(void)
memset(comp_mempools[i].buffer, 0, comp_mempools[i].elem_size * comp_mempools[i].max_size);
comp_mempools[i].use_list = (bool*)calloc(comp_mempools[i].max_size, sizeof(bool));
assert(comp_mempools[i].use_list != NULL);
cb_init(&comp_mempools[i].free_list, comp_mempools[i].max_size);
sc_queue_init(&comp_mempools[i].free_list);
for (size_t j = 0; j < comp_mempools[i].max_size; ++j)
{
comp_mempools[i].free_list.buffer[j] = j;
sc_queue_add_last(&comp_mempools[i].free_list, j);
}
comp_mempools[i].free_list.count = comp_mempools[i].max_size;
}
memset(ent_mempool.buffer, 0, ent_mempool.elem_size*ent_mempool.max_size);
cb_init(&ent_mempool.free_list, ent_mempool.max_size);
sc_queue_init(&ent_mempool.free_list);
ent_mempool.use_list = (bool *)calloc(ent_mempool.max_size, sizeof(bool));
for (size_t i = 0; i < ent_mempool.max_size; ++i)
{
entity_buffer[i].m_id = i;
ent_mempool.free_list.buffer[i] = i;
sc_queue_add_last(&ent_mempool.free_list, i);
}
ent_mempool.free_list.count = ent_mempool.max_size;
pool_inited = true;
}
}
@ -143,10 +50,10 @@ void free_memory_pools(void)
for (size_t i = 0; i < N_COMPONENTS; ++i)
{
free(comp_mempools[i].use_list);
cb_free(&comp_mempools[i].free_list);
sc_queue_term(&comp_mempools[i].free_list);
}
free(ent_mempool.use_list);
cb_free(&ent_mempool.free_list);
sc_queue_term(&ent_mempool.free_list);
pool_inited = false;
}
@ -154,8 +61,9 @@ void free_memory_pools(void)
Entity_t* new_entity_from_mempool(unsigned long* e_idx_ptr)
{
unsigned long e_idx;
if (!cb_pop_front(&ent_mempool.free_list, &e_idx)) return NULL;
if (sc_queue_empty(&ent_mempool.free_list)) return NULL;
unsigned long e_idx = sc_queue_del_first(&ent_mempool.free_list);
*e_idx_ptr = e_idx;
ent_mempool.use_list[e_idx] = true;
@ -180,24 +88,25 @@ void free_entity_to_mempool(unsigned long idx)
if (ent_mempool.use_list[idx])
{
ent_mempool.use_list[idx] = false;
cb_push_back(&ent_mempool.free_list, idx);
sc_queue_add_first(&ent_mempool.free_list, idx);
}
}
void* new_component_from_mempool(ComponentEnum_t comp_type, unsigned long* idx)
void* new_component_from_mempool(unsigned int comp_type, unsigned long* idx)
{
void* comp = NULL;
assert(comp_type < N_COMPONENTS);
if (cb_pop_front(&comp_mempools[comp_type].free_list, idx))
{
if (sc_queue_empty(&comp_mempools[comp_type].free_list)) return NULL;
*idx = sc_queue_del_first(&comp_mempools[comp_type].free_list);
comp_mempools[comp_type].use_list[*idx] = true;
comp = comp_mempools[comp_type].buffer + (*idx * comp_mempools[comp_type].elem_size);
void* comp = comp_mempools[comp_type].buffer + (*idx * comp_mempools[comp_type].elem_size);
memset(comp, 0, comp_mempools[comp_type].elem_size);
}
return comp;
}
void* get_component_wtih_id(ComponentEnum_t comp_type, unsigned long idx)
void* get_component_wtih_id(unsigned int comp_type, unsigned long idx)
{
void * comp = NULL;
assert(comp_type < N_COMPONENTS);
@ -208,30 +117,30 @@ void* get_component_wtih_id(ComponentEnum_t comp_type, unsigned long idx)
return comp;
}
void free_component_to_mempool(ComponentEnum_t comp_type, unsigned long idx)
void free_component_to_mempool(unsigned int comp_type, unsigned long idx)
{
assert(comp_type < N_COMPONENTS);
// This just free the component from the memory pool
if (comp_mempools[comp_type].use_list[idx])
{
comp_mempools[comp_type].use_list[idx] = false;
cb_push_back(&comp_mempools[comp_type].free_list, idx);
sc_queue_add_first(&comp_mempools[comp_type].free_list, idx);
}
}
void print_mempool_stats(char* buffer)
{
buffer += sprintf(buffer, "Entity free: %u\n", ent_mempool.free_list.count);
buffer += sprintf(buffer, "Entity free: %lu\n", sc_queue_size(&ent_mempool.free_list));
for (size_t i = 0; i < N_COMPONENTS; ++i)
{
buffer += sprintf(
buffer, "%lu: %u/%u\n",
i, comp_mempools[i].free_list.count, comp_mempools[i].free_list.capacity
buffer, "%lu: %lu/%lu\n",
i, sc_queue_size(&comp_mempools[i].free_list), comp_mempools[i].free_list.cap
);
}
}
uint32_t get_num_of_free_entities(void)
{
return ent_mempool.free_list.count;
return sc_queue_size(&ent_mempool.free_list);
}

View File

@ -1,17 +1,48 @@
#ifndef __MEMPOOL_H
#define __MEMPOOL_H
#include "EC.h"
#include "sc/queue/sc_queue.h"
void init_memory_pools(void);
void free_memory_pools(void);
typedef struct MemPool {
void * const buffer;
const unsigned long max_size;
const unsigned long elem_size;
bool *use_list;
struct sc_queue_32 free_list;
} MemPool_t;
// Game needs to implement this somewhere
extern MemPool_t comp_mempools[N_COMPONENTS];
Entity_t* new_entity_from_mempool(unsigned long* e_idx_ptr);
Entity_t* get_entity_wtih_id(unsigned long idx);
void free_entity_to_mempool(unsigned long idx);
void* new_component_from_mempool(ComponentEnum_t comp_type, unsigned long* idx);
void* get_component_wtih_id(ComponentEnum_t comp_type, unsigned long idx);
void free_component_to_mempool(ComponentEnum_t comp_type, unsigned long idx);
void* new_component_from_mempool(unsigned int comp_type, unsigned long* idx);
void* get_component_wtih_id(unsigned int comp_type, unsigned long idx);
void free_component_to_mempool(unsigned int comp_type, unsigned long idx);
void print_mempool_stats(char* buffer);
uint32_t get_num_of_free_entities(void);
#define DEFINE_COMP_MEMPOOL_BUF(type, n) \
static type type##_buf[n]; \
const unsigned long type##_CNT = n; \
#define ADD_COMP_MEMPOOL(type) \
{type##_buf, type##_CNT, sizeof(type), NULL, {0}}, \
#define BEGIN_DEFINE_COMP_MEMPOOL \
DEFINE_COMP_MEMPOOL_BUF(CBBox_t, MAX_COMP_POOL_SIZE); \
DEFINE_COMP_MEMPOOL_BUF(CTransform_t, MAX_COMP_POOL_SIZE); \
DEFINE_COMP_MEMPOOL_BUF(CTileCoord_t, MAX_COMP_POOL_SIZE); \
MemPool_t comp_mempools[N_COMPONENTS] = { \
ADD_COMP_MEMPOOL(CBBox_t) \
ADD_COMP_MEMPOOL(CTransform_t) \
ADD_COMP_MEMPOOL(CTileCoord_t) \
#define END_DEFINE_COMP_MEMPOOL };
#endif //__MEMPOOL_H

View File

@ -3,16 +3,11 @@
#include "raylib.h"
#include "engine_conf.h"
#include "sc_queue.h"
#include "EC.h"
#include "assets.h"
#include <stdint.h>
#include <stdbool.h>
typedef enum PartEmitterType
{
EMITTER_UNKNOWN = 0,
EMITTER_BURST,
EMITTER_STREAM,
} PartEmitterType_t;
typedef uint16_t EmitterHandle;
typedef struct Particle
{
@ -32,18 +27,6 @@ typedef struct ParticleEmitter ParticleEmitter_t;
typedef void (*particle_update_func_t)(Particle_t* part, void* user_data, float delta_time);
typedef bool (*emitter_check_func_t)(const ParticleEmitter_t* emitter, float delta_time);
typedef struct EmitterConfig
{
float launch_range[2];
float speed_range[2];
float angle_range[2];
float rotation_range[2];
float particle_lifetime[2];
float initial_spawn_delay;
PartEmitterType_t type;
bool one_shot;
}EmitterConfig_t;
struct ParticleEmitter
{
const EmitterConfig_t* config;

View File

@ -539,7 +539,7 @@ RAYGUIAPI bool GuiSpinner(Rectangle bounds, const char *text, int *value, int mi
RAYGUIAPI bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers
RAYGUIAPI bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text
RAYGUIAPI bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control with multiple lines
RAYGUIAPI float GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Slider control, returns selected value
RAYGUIAPI float GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue);
RAYGUIAPI float GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Slider Bar control, returns selected value
RAYGUIAPI float GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Progress Bar control, shows current progress value
RAYGUIAPI void GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text

View File

@ -0,0 +1,42 @@
#include "render_queue.h"
#include "raylib.h"
void init_render_manager(RenderManager *manager)
{
memset(manager, 0, sizeof(*manager));
}
bool add_render_node(RenderManager *manager, RenderInfoNode *node, uint8_t layer_num)
{
if (node->next != NULL)
{
return false;
}
if (node->spr == NULL) {
return false;
}
layer_num = (layer_num >= MAX_RENDERMANAGER_DEPTH) ? MAX_RENDERMANAGER_DEPTH - 1 : layer_num;
node->next = manager->layers[layer_num];
manager->layers[layer_num] = node;
return true;
}
void execute_render(RenderManager *manager) {
for (uint8_t depth = 0; depth < MAX_RENDERMANAGER_DEPTH; ++depth)
{
RenderInfoNode* curr = manager->layers[depth];
while (curr != NULL)
{
draw_sprite_pro(
curr->spr, curr->frame_num, curr->pos,
curr->rotation, curr->flip, curr->scale,
curr->colour
);
RenderInfoNode* next = curr->next;
curr->next = NULL;
curr = next;
}
}
reset_render_manager(manager);
}

View File

@ -0,0 +1,29 @@
#ifndef RENDER_QUEUE_H
#define RENDER_QUEUE_H
#include "assets.h"
#include "engine_conf.h"
typedef struct RenderInfoNode RenderInfoNode;
struct RenderInfoNode {
// Intrusive Linked-list Node
RenderInfoNode* next;
Sprite_t* spr;
Vector2 pos;
int frame_num;
float rotation;
Vector2 scale;
Color colour;
uint8_t flip;
};
typedef struct RenderManager {
RenderInfoNode* layers[MAX_RENDERMANAGER_DEPTH];
} RenderManager;
void init_render_manager(RenderManager* manager);
#define reset_render_manager init_render_manager
bool add_render_node(RenderManager* manager, RenderInfoNode* node, uint8_t layer_num);
void execute_render(RenderManager* manager);
#endif

View File

@ -1,5 +1,6 @@
#include "mempool.h"
#include "scene_impl.h"
#include "gui.h"
#include <stdio.h>
#include <unistd.h>
#include <math.h>
@ -14,12 +15,15 @@ int main(void)
InitWindow(1280, 640, "raylib");
SetTargetFPS(60);
init_memory_pools();
init_UI();
LevelSelectScene_t scene;
init_level_select_scene(&scene);
scene.scene.bg_colour = RAYWHITE;
while(true)
{
Vector2 raw_mouse_pos = GetMousePosition();
scene.scene.mouse_pos = raw_mouse_pos;
// This entire key processing relies on the assumption that a pressed key will
// appear in the polling of raylib
@ -50,6 +54,18 @@ int main(void)
do_action(&scene.scene, action, true);
sc_queue_add_last(&key_buffer, button);
}
ActionType_t action = sc_map_get_64(&scene.scene.action_map, MOUSE_BUTTON_LEFT);
if (sc_map_found(&scene.scene.action_map))
{
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT))
{
do_action(&scene.scene, action, true);
}
else if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT))
{
do_action(&scene.scene, action, false);
}
}
float frame_time = GetFrameTime();
float delta_time = fminf(frame_time, DT);

128
level_test.c 100644
View File

@ -0,0 +1,128 @@
#include "raylib.h"
#include "assets_loader.h"
#include "scene_impl.h"
#include "ent_impl.h"
#include "mempool.h"
#include "constants.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include "tracy/TracyC.h"
#define N_SCENES 1
Scene_t *scenes[N_SCENES];
static GameEngine_t engine = {
.scenes = scenes,
.max_scenes = 1,
.curr_scene = 0,
.assets = {0}
};
const int screenWidth = VIEWABLE_MAP_WIDTH * TILE_SIZE;
const int screenHeight = VIEWABLE_MAP_HEIGHT * TILE_SIZE;
// Maintain own queue to handle key presses
struct sc_queue_32 key_buffer;
int main(int argc, char** argv)
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
init_engine(&engine, (Vector2){screenWidth, screenHeight});
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
load_from_infofile("res/assets.info.raw", &engine.assets);
init_player_creation("res/player_spr.info", &engine.assets);
init_item_creation(&engine.assets);
load_sfx(&engine, "snd_jump", PLAYER_JMP_SFX);
load_sfx(&engine, "snd_land", PLAYER_LAND_SFX);
load_sfx(&engine, "snd_wdrop", WATER_IN_SFX);
load_sfx(&engine, "snd_bland", BOULDER_LAND_SFX);
load_sfx(&engine, "snd_bubble", BUBBLE_SFX);
load_sfx(&engine, "snd_step", PLAYER_STEP_SFX);
load_sfx(&engine, "snd_dead", PLAYER_DEAD_SFX);
load_sfx(&engine, "snd_drwg", PLAYER_DROWNING_SFX);
load_sfx(&engine, "snd_mdestroy", METAL_DESTROY_SFX);
load_sfx(&engine, "snd_wdestroy", WOOD_DESTROY_SFX);
load_sfx(&engine, "snd_cland", WOOD_LAND_SFX);
load_sfx(&engine, "snd_explsn", EXPLOSION_SFX);
load_sfx(&engine, "snd_coin", COIN_SFX);
load_sfx(&engine, "snd_arrhit", ARROW_DESTROY_SFX);
load_sfx(&engine, "snd_launch", ARROW_RELEASE_SFX);
load_sfx(&engine, "snd_launch", BOMB_RELEASE_SFX);
LevelScene_t level_scene;
level_scene.scene.engine = &engine;
init_game_scene(&level_scene);
level_scene.data.tile_sprites[ONEWAY_TILE] = get_sprite(&engine.assets, "tl_owp");
level_scene.data.tile_sprites[LADDER] = get_sprite(&engine.assets, "tl_ldr");
level_scene.data.tile_sprites[SPIKES] = get_sprite(&engine.assets, "d_spikes");
level_scene.data.tile_sprites[SPIKES + TILE_90CWROT] = get_sprite(&engine.assets, "l_spikes");
level_scene.data.tile_sprites[SPIKES + TILE_90CCWROT] = get_sprite(&engine.assets, "r_spikes");
level_scene.data.tile_sprites[SPIKES + TILE_180ROT] = get_sprite(&engine.assets, "u_spikes");
Texture2D* tex = get_texture(&engine.assets, "bg_tex");
SetTextureWrap(*tex, TEXTURE_WRAP_REPEAT);
sc_map_del_64(&level_scene.scene.action_map, ACTION_EXIT);
// Load level
LevelPack_t* pack = get_level_pack(&engine.assets, "DefLevels");
if (pack == NULL)
{
puts("Default level pack not found!");
return 1;
}
unsigned int selected_level = 0;
if (argc == 2) {
selected_level = strtoul(argv[1], NULL, 10);
printf("Selected level: %u", selected_level);
}
if (selected_level >= pack->n_levels) {
printf("Level numbers out of bound. Picking 0");
selected_level = 0;
}
level_scene.data.level_pack = pack;
level_scene.data.current_level = selected_level;
scenes[0] = &level_scene.scene;
reload_level_tilemap(&level_scene);
change_scene(&engine, 0);
const float DT = 1.0f/60.0f;
while (!WindowShouldClose())
{
TracyCFrameMark;
// This entire key processing relies on the assumption that a pressed key will
// appear in the polling of raylib
Scene_t* curr_scene = engine.scenes[engine.curr_scene];
if (curr_scene->state == 0 && engine.curr_scene == 0)
{
break;
}
process_inputs(&engine, curr_scene);
float frame_time = GetFrameTime();
float delta_time = fminf(frame_time, DT);
{
TracyCZoneN(ctx, "Update", true)
update_scene(curr_scene, delta_time);
update_entity_manager(&curr_scene->ent_manager);
update_sfx_list(&engine);
TracyCZoneEnd(ctx)
}
// This is needed to advance time delta
render_scene(curr_scene);
if (curr_scene->state != 0)
{
sc_queue_clear(&key_buffer);
}
}
free_game_scene(&level_scene);
deinit_engine(&engine);
}

31
main.c
View File

@ -3,6 +3,7 @@
#include "scene_impl.h"
#include "ent_impl.h"
#include "mempool.h"
#include "constants.h"
#include <stdio.h>
#include <math.h>
#define N_SCENES 4
@ -10,13 +11,13 @@
Scene_t *scenes[N_SCENES];
static GameEngine_t engine = {
.scenes = scenes,
.max_scenes = 3,
.max_scenes = 4,
.curr_scene = 0,
.assets = {0}
};
const int screenWidth = 1280;
const int screenHeight = 640;
const int screenWidth = VIEWABLE_MAP_WIDTH * TILE_SIZE;
const int screenHeight = VIEWABLE_MAP_HEIGHT * TILE_SIZE;
// Maintain own queue to handle key presses
struct sc_queue_32 key_buffer;
@ -25,6 +26,7 @@ int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
SetConfigFlags(FLAG_WINDOW_RESIZABLE);
init_engine(&engine, (Vector2){screenWidth, screenHeight});
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
#ifndef NDEBUG
@ -63,21 +65,30 @@ int main(void)
level_scene.data.tile_sprites[SPIKES + TILE_90CWROT] = get_sprite(&engine.assets, "l_spikes");
level_scene.data.tile_sprites[SPIKES + TILE_90CCWROT] = get_sprite(&engine.assets, "r_spikes");
level_scene.data.tile_sprites[SPIKES + TILE_180ROT] = get_sprite(&engine.assets, "u_spikes");
LevelPack_t* pack = get_level_pack(&engine.assets, "TestLevels");
Texture2D* tex = get_texture(&engine.assets, "bg_tex");
SetTextureWrap(*tex, TEXTURE_WRAP_REPEAT);
LevelPack_t* pack = get_level_pack(&engine.assets, "DefLevels");
if (pack != NULL)
{
level_scene.data.level_pack = pack;
level_scene.data.current_level = 0;
load_level_tilemap(&level_scene, 0);
}
MenuScene_t menu_scene;
menu_scene.scene.engine = &engine;
init_menu_scene(&menu_scene);
scenes[0] = &menu_scene.scene;
scenes[1] = &level_scene.scene;
scenes[2] = &sandbox_scene.scene;
change_scene(&engine, 0);
LevelSelectScene_t level_sel_scene;
level_sel_scene.scene.engine = &engine;
level_sel_scene.data.level_pack = pack;
init_level_select_scene(&level_sel_scene);
scenes[MAIN_MENU_SCENE] = &menu_scene.scene;
scenes[LEVEL_SELECT_SCENE] = &level_sel_scene.scene;
scenes[GAME_SCENE] = &level_scene.scene;
scenes[SANDBOX_SCENE] = &sandbox_scene.scene;
change_scene(&engine, MAIN_MENU_SCENE);
const float DT = 1.0f/60.0f;
while (!WindowShouldClose())
@ -107,10 +118,10 @@ int main(void)
{
sc_queue_clear(&key_buffer);
}
}
free_sandbox_scene(&sandbox_scene);
free_game_scene(&level_scene);
free_level_select_scene(&level_sel_scene);
free_menu_scene(&menu_scene);
deinit_engine(&engine);
}

5
res/.gitignore vendored
View File

@ -1,7 +1,10 @@
*
!.gitignore
!CMakeLists.txt
!ldtk_repacker.py
!*.py
!pack_ldtk.sh
!pack_resources.sh
!test_assets.info
!testLevels.lvldata.zst
!rres_packer.c
!requirements.txt

View File

@ -8,6 +8,6 @@ set_target_properties(rres_packer
target_link_libraries(rres_packer
PRIVATE
lib_engine
lib_assets
)

View File

@ -37,12 +37,11 @@ ENUMIDS_TILETYPE_MAPPING = {
'Boulder': 20,
'Runner': 21,
'Player': 22,
'Chest': 23,
'Exit': 24,
'Urchin': 25,
}
#ENTID_MAPPING = {
# 'Player': 1
#}
# First go to tilesets and find Simple_tiles identifier, then find enumTags to identifier which tile type is what tileid
ids_tiletype_map = {}
tileset_defs = level_pack_data["defs"]["tilesets"]
@ -59,8 +58,18 @@ if not ids_tiletype_map:
pprint.pprint(ids_tiletype_map)
def get_level_order(lvl) -> int:
order = 65535;
for data in lvl['fieldInstances']:
if data["__identifier"] == "Order" and data["realEditorValues"]:
order = data["__value"]
return order
all_levels = level_pack_data["levels"]
all_levels.sort(key=get_level_order)
# Number of levels is the length of the levels
n_levels = len(level_pack_data["levels"])
n_levels = len(all_levels)
print("Number of levels:", n_levels)
fileparts = args.filename.split('.')
@ -74,11 +83,20 @@ converted_filename = '.'.join(fileparts)
with open(converted_filename, 'wb+') as out_file:
out_file.write(struct.pack("<I", n_levels))
# Then loop the levels. Read the layerIndstances
for level in level_pack_data["levels"]:
for level in all_levels:
n_chests : int = 0
# Search for __identifier for the level layout
level_name = level["identifier"]
print("Parsing level", level_name)
level_name = ""
level_metadata = level['fieldInstances']
level_tileset = 0;
for data in level_metadata:
if data["__identifier"] == "TileSet":
level_tileset = data["__value"]
if data["__identifier"] == "Name":
level_name = data["__value"]
print("Parsing level", level_name)
level_layout = {}
entity_layout = {}
@ -94,13 +112,20 @@ with open(converted_filename, 'wb+') as out_file:
# Dimensions of each level is obtained via __cWid and __cHei. Get the __gridSize as well
width = level_layout["__cWid"]
height = level_layout["__cHei"]
print(f"Dim.: {width}x{height}")
print(f"Dim.: {width}x{height}. N Tiles: {width * height}")
# Create a W x H array of tile information
n_tiles = width * height
tiles_info = [[0,0,0] for _ in range(n_tiles)]
# Loop through gridTiles, get "d" as the index to fill the info
for tile in level_layout["gridTiles"]:
for i, tile in enumerate(level_layout["gridTiles"]):
try:
tiles_info[tile["d"][0]][0] = ids_tiletype_map[tile["t"]]
if tiles_info[tile["d"][0]][0] == ENUMIDS_TILETYPE_MAPPING ["Chest"]:
n_chests += 1
except Exception as e:
print("Error on tile", i, i % width, i // height)
print(e)
tiles_info[tile["d"][0]][0] = 0
for i, water_level in enumerate(water_layout["intGridCsv"]):
tiles_info[i][2] = water_level
@ -109,14 +134,23 @@ with open(converted_filename, 'wb+') as out_file:
for ent in entity_layout["entityInstances"]:
x,y = ent["__grid"]
tiles_info[y*width + x][0] = ENUMIDS_TILETYPE_MAPPING[ent["__identifier"]]
if ent["__identifier"] == "Urchin":
spd_encoding = 0
for urchin_data in ent['fieldInstances']:
if urchin_data["__identifier"] == "Direction":
spd_encoding |= urchin_data["__value"] << 2
elif urchin_data["__identifier"] == "SpeedLevel":
spd_encoding |= urchin_data["__value"]
out_file.write(struct.pack("<32s2H", level_name.encode('utf-8'), width, height))
tiles_info[y*width + x][0] += spd_encoding
out_file.write(struct.pack("<32s4H", level_name.encode('utf-8'), width, height, n_chests, level_tileset))
for tile in tiles_info:
out_file.write(struct.pack("<3Bx", *tile))
for y in range(height):
for x in range(width):
print(tiles_info[y*width + x], end=" ")
print()
#for y in range(height):
# for x in range(width):
# print(tiles_info[y*width + x], end=" ")
# print()

257
res/level_render.py 100644
View File

@ -0,0 +1,257 @@
import sys
import argparse
import pprint
import json
import struct
from PIL import Image, ImageDraw
parser = argparse.ArgumentParser()
parser.add_argument('filename')
args = parser.parse_args()
print("Rendering", args.filename)
with open(args.filename, 'r') as f:
level_pack_data = json.load(f)
#pprint.pprint(level_pack_data)
ENUMIDS_TILETYPE_MAPPING = {
'Solid': 1,
'WoodenPlat': 2,
'Ladder': 3,
'LSpike': 4,
'RSpike': 5,
'USpike': 6,
'DSpike': 7,
'EmptyWCrate': 8,
'LArrowWCrate': 9,
'RArrowWCrate': 10,
'UArrowWCrate': 11,
'DArrowWCrate': 12,
'BombWCrate': 13,
'EmptyMCrate': 14,
'LArrowMCrate': 15,
'RArrowMCrate': 16,
'UArrowMCrate': 17,
'DArrowMCrate': 18,
'BombMCrate': 19,
'Boulder': 20,
'Runner': 21,
'Player': 22,
'Chest': 23,
'Exit': 24,
'Urchin': 25,
}
DANGER_COLOUR = (239,79,81,255)
WCRATE_COLOUR = (160,117,48,255)
MCRATE_COLOUR = (110,110,110,255)
WATER_COLOUR = (0,0,128,64)
REC_DRAW_FUNCTION = lambda ctx,x,y,s,c : ctx.rectangle(((x,y), (x+s-1, y+s-1)), c)
RECLINE_DRAW_FUNCTION = lambda ctx,x,y,s,c : ctx.rectangle(((x,y), (x+s-1, y+s-1)), fill=None, outline=c)
UPHALFREC_DRAW_FUNCTION = lambda ctx,x,y,s,c : ctx.rectangle(((x,y), (x+s-1, y+s//2-1)), c)
DOWNHALFREC_DRAW_FUNCTION = lambda ctx,x,y,s,c : ctx.rectangle(((x, y+s//2), (x+s-1,y+s-1)), c)
LEFTHALFREC_DRAW_FUNCTION = lambda ctx,x,y,s,c : ctx.rectangle(((x,y), (x+s//2-1, y+s-1)), c)
RIGHTHALFREC_DRAW_FUNCTION = lambda ctx,x,y,s,c : ctx.rectangle(((x+s//2,y), (x+s-1, y+s-1)), c)
CIRCLE_DRAW_FUNCTION = lambda ctx,x,y,s,c : ctx.circle((x+s//2, y+s//2), s//2, c)
TRIANGLE_DRAW_FUNCTION = lambda ctx,x,y,s,c : ctx.regular_polygon(((x+s//2, y+s//2), s//2), 3, 0, c)
def draw_bomb_tile(ctx, x, y, s, c):
REC_DRAW_FUNCTION(ctx, x, y, s, c)
ctx.line((x,y,x+s-1,y+s-1), DANGER_COLOUR, 1)
ctx.line((x,y+s-1,x+s-1,y), DANGER_COLOUR, 1)
def draw_arrow_tile(ctx, x, y, s, c, d):
REC_DRAW_FUNCTION(ctx, x, y, s, c)
if d == 0:
ctx.line((x+s//2,y+s//2,x+s//2,y), DANGER_COLOUR, 1)
elif d == 1:
ctx.line((x+s//2,y+s//2,x+s-1,y+s//2), DANGER_COLOUR, 1)
elif d == 2:
ctx.line((x+s//2,y+s//2,x+s//2,y+s-1), DANGER_COLOUR, 1)
elif d == 3:
ctx.line((x+s//2,y+s//2,x,y+s//2), DANGER_COLOUR, 1)
def draw_urchin_tile(ctx, x, y, s, c):
ctx.line((x,y,x+s-1,y+s-1), c, 1)
ctx.line((x,y+s-1,x+s-1,y), c, 1)
ctx.line((x+(s-1)//2,y,x+(s-1)//2,y+s-1), c, 1)
ctx.line((x,y+(s-1)//2,x+s-1,y+(s-1)//2), c, 1)
TILETYPE_SHAPE_MAP = {
'Solid': (REC_DRAW_FUNCTION, (0,0,0,255)),
'WoodenPlat': (UPHALFREC_DRAW_FUNCTION, (128,64,0,255)),
'Ladder': (RECLINE_DRAW_FUNCTION, (214,141,64,255)),
'LSpike': (LEFTHALFREC_DRAW_FUNCTION, DANGER_COLOUR),
'RSpike': (RIGHTHALFREC_DRAW_FUNCTION, DANGER_COLOUR),
'USpike': (UPHALFREC_DRAW_FUNCTION, DANGER_COLOUR),
'DSpike': (DOWNHALFREC_DRAW_FUNCTION, DANGER_COLOUR),
'EmptyWCrate': (REC_DRAW_FUNCTION, WCRATE_COLOUR),
'LArrowWCrate': (lambda ctx,x,y,s,c : draw_arrow_tile(ctx,x,y,s,c,3), WCRATE_COLOUR),
'RArrowWCrate': (lambda ctx,x,y,s,c : draw_arrow_tile(ctx,x,y,s,c,1), WCRATE_COLOUR),
'UArrowWCrate': (lambda ctx,x,y,s,c : draw_arrow_tile(ctx,x,y,s,c,0), WCRATE_COLOUR),
'DArrowWCrate': (lambda ctx,x,y,s,c : draw_arrow_tile(ctx,x,y,s,c,2), WCRATE_COLOUR),
'BombWCrate': (draw_bomb_tile, WCRATE_COLOUR),
'EmptyMCrate': (REC_DRAW_FUNCTION, MCRATE_COLOUR),
'LArrowMCrate': (lambda ctx,x,y,s,c : draw_arrow_tile(ctx,x,y,s,c,3), MCRATE_COLOUR),
'RArrowMCrate': (lambda ctx,x,y,s,c : draw_arrow_tile(ctx,x,y,s,c,1), MCRATE_COLOUR),
'UArrowMCrate': (lambda ctx,x,y,s,c : draw_arrow_tile(ctx,x,y,s,c,0), MCRATE_COLOUR),
'DArrowMCrate': (lambda ctx,x,y,s,c : draw_arrow_tile(ctx,x,y,s,c,2), MCRATE_COLOUR),
'BombMCrate': (draw_bomb_tile, MCRATE_COLOUR),
'Boulder': (CIRCLE_DRAW_FUNCTION, (45,45,45,255)),
'Runner': (TRIANGLE_DRAW_FUNCTION, (0,0,128,255)),
'Player': (REC_DRAW_FUNCTION, (255,0,255,255)),
'Chest': (REC_DRAW_FUNCTION, (255,255,0,255)),
'Exit': (REC_DRAW_FUNCTION, (0,255,0,255)),
'Urchin': (draw_urchin_tile, DANGER_COLOUR),
}
# First go to tilesets and find Simple_tiles identifier, then find enumTags to identifier which tile type is what tileid
ids_tiletype_map = {}
tileset_defs = level_pack_data["defs"]["tilesets"]
for ts_def in tileset_defs:
if ts_def["identifier"] != "Items_spritesheet":
continue
for tag in ts_def["enumTags"]:
ids_tiletype_map[tag["tileIds"][0]] = tag["enumValueId"]
if not ids_tiletype_map:
print("No tileset definition")
sys.exit(1)
pprint.pprint(ids_tiletype_map)
def get_level_order(lvl) -> int:
order = 65535;
for data in lvl['fieldInstances']:
if data["__identifier"] == "Order" and data["realEditorValues"]:
order = data["__value"]
return order
all_levels = level_pack_data["levels"]
all_levels.sort(key=get_level_order)
# Number of levels is the length of the levels
n_levels = len(all_levels)
print("Number of levels:", n_levels)
fileparts = args.filename.split('.')
if len(fileparts) == 1:
fileparts.append("lvldat")
else:
fileparts[-1] = "lvldata"
converted_filename = '.'.join(fileparts)
# First run the entire level pack to figure out the largest dimensions required.
# tile size needs to be dynamic. Fix the output dimensions (and therefore aspect ratio)
# figure out width and height of level
# Get the tile size that fit within the dimensions
# Error out if tile size is zero
# Figure out the offset to center the render
LEVELS_PER_ROW = 5
N_ROWS = n_levels // LEVELS_PER_ROW
if n_levels % LEVELS_PER_ROW != 0:
N_ROWS += 1
IMG_WIDTH = 400
IMG_HEIGHT = 400
FULL_IMG_WIDTH = LEVELS_PER_ROW * IMG_WIDTH
FULL_IMG_HEIGHT = N_ROWS * IMG_HEIGHT
# Each level should be packed as: [width, 2 bytes][height, 2 bytes][tile_type,entity,water,padding 1,1,1,1 bytes][tile_type,entity,water,padding 1,1,1,1 bytes]...
# Then loop the levels. Read the layerIndstances
lvl_render = Image.new("RGBA", (FULL_IMG_WIDTH, FULL_IMG_HEIGHT), (255,255,255,0))
water_render = Image.new("RGBA", (FULL_IMG_WIDTH, FULL_IMG_HEIGHT), (255,255,255,0))
render_ctx = ImageDraw.Draw(lvl_render)
water_render_ctx = ImageDraw.Draw(water_render)
for l, level in enumerate(all_levels):
lx = (l % LEVELS_PER_ROW) * IMG_WIDTH
ly = (l // LEVELS_PER_ROW) * IMG_HEIGHT
n_chests : int = 0
# Search for __identifier for the level layout
level_name = ""
level_metadata = level['fieldInstances']
level_tileset = 0;
for data in level_metadata:
if data["__identifier"] == "TileSet":
level_tileset = data["__value"]
if data["__identifier"] == "Name":
level_name = data["__value"]
print("Parsing level", level_name)
level_layout = {}
entity_layout = {}
water_layout = {}
for layer in level['layerInstances']:
if layer["__identifier"] == "Tiles":
level_layout = layer
elif layer["__identifier"] == "Entities":
entity_layout = layer
elif layer["__identifier"] == "Water":
water_layout = layer
# Dimensions of each level is obtained via __cWid and __cHei. Get the __gridSize as well
width = level_layout["__cWid"]
height = level_layout["__cHei"]
print(f"Dim.: {width}x{height}. N Tiles: {width * height}")
TILE_SIZE = 400 // max(width, height)
TILE_SIZE = max(TILE_SIZE,1)
# Create a W x H array of tile information
n_tiles = width * height
LVL_SIZE = (width*TILE_SIZE-1, height*TILE_SIZE-1)
OFFSET = ((IMG_WIDTH - LVL_SIZE[0]) // 2 + lx, (IMG_HEIGHT - LVL_SIZE[1])//2 + ly)
render_ctx.rectangle((OFFSET, (OFFSET[0]+width*TILE_SIZE-1, OFFSET[1] + height*TILE_SIZE-1)), (64,64,64,255))
# Loop through gridTiles, get "d" as the index to fill the info
for i, tile in enumerate(level_layout["gridTiles"]):
x, y= (tile["d"][0] % width * TILE_SIZE + OFFSET[0], tile["d"][0] // width * TILE_SIZE + OFFSET[1])
try:
if ids_tiletype_map[tile["t"]] in TILETYPE_SHAPE_MAP:
draw_func, colour = TILETYPE_SHAPE_MAP[ids_tiletype_map[tile["t"]]]
draw_func(render_ctx, x, y, TILE_SIZE, colour)
else:
print(ids_tiletype_map[tile["t"]], "is not mapped");
except Exception as e:
print("Error on tile", x, y)
render_ctx.circle((x,y), 2,(255,0,0,255))
print(e)
## Subject to change
for ent in entity_layout["entityInstances"]:
x,y = ent["__grid"]
x *= TILE_SIZE
x += OFFSET[0]
y *= TILE_SIZE
y += OFFSET[1]
ent = ent["__identifier"]
try:
if ent in TILETYPE_SHAPE_MAP:
draw_func, colour = TILETYPE_SHAPE_MAP[ent]
draw_func(render_ctx, x, y, TILE_SIZE, colour)
else:
print(ent, "is not mapped");
except Exception as e:
print("Error on tile", x, y)
render_ctx.circle((x,y), 2,(255,0,0,255))
print(e)
for i, water_level in enumerate(water_layout["intGridCsv"]):
if water_level == 0:
continue
x, y = (i % width * TILE_SIZE + OFFSET[0], i // width * TILE_SIZE + OFFSET[1])
height = TILE_SIZE * water_level / 4
water_render_ctx.rectangle(((x,y+TILE_SIZE-height), (x+TILE_SIZE-1, y+TILE_SIZE-1)), WATER_COLOUR)
lvl_render = Image.alpha_composite(lvl_render, water_render)
lvl_render.save("preview.png")
lvl_render.show()

3
res/pack_ldtk.sh 100755
View File

@ -0,0 +1,3 @@
#!/bin/bash
source venv/bin/activate
python ldtk_repacker.py $1.ldtk && zstd $1.lvldata && python level_render.py $1.ldtk

View File

@ -0,0 +1,6 @@
#!/bin/bash
sed 's|res/||g' assets.info.raw > assets.info
./rres_packer
mv ../web/res/myresources.rres ../web/res/myresources.rres.old
cp ./myresources.rres ../web/res/

View File

@ -0,0 +1 @@
Pillow

View File

@ -109,7 +109,7 @@ int main(void)
for (uint8_t i = 0; i < 6; ++i)
{
scenes[i] = &dummy_scenes[i].scene;
init_scene(&dummy_scenes[i].scene, &level_do_action);
init_scene(&dummy_scenes[i].scene, &level_do_action, 0);
dummy_scenes[i].scene.engine = &engine;
dummy_scenes[i].number = i;
add_scene_layer(

View File

@ -1,3 +1,6 @@
#ifdef TRACY_ENABLE
#include "tracy/TracyC.h"
#endif
#include "scene_impl.h"
#include "ent_impl.h"
#include "assets_loader.h"
@ -61,6 +64,9 @@ int main(void)
load_sfx(&engine, "snd_cland", WOOD_LAND_SFX);
load_sfx(&engine, "snd_explsn", EXPLOSION_SFX);
load_sfx(&engine, "snd_coin", COIN_SFX);
load_sfx(&engine, "snd_step", PLAYER_STEP_SFX);
load_sfx(&engine, "snd_dead", PLAYER_DEAD_SFX);
load_sfx(&engine, "snd_drwg", PLAYER_DROWNING_SFX);
load_sfx(&engine, "snd_arrhit", ARROW_DESTROY_SFX);
load_sfx(&engine, "snd_launch", ARROW_RELEASE_SFX);
load_sfx(&engine, "snd_launch", BOMB_RELEASE_SFX);
@ -89,10 +95,22 @@ int main(void)
float frame_time = GetFrameTime();
float delta_time = fminf(frame_time, DT);
#ifdef TRACY_ENABLE
TracyCZoneN(ctx, "Overall", true)
#endif
#ifdef TRACY_ENABLE
TracyCZoneN(ctx1, "Update", true)
#endif
update_scene(&scene.scene, delta_time);
update_entity_manager(&scene.scene.ent_manager);
#ifdef TRACY_ENABLE
TracyCZoneEnd(ctx1)
#endif
// This is needed to advance time delta
render_scene(&scene.scene);
#ifdef TRACY_ENABLE
TracyCZoneEnd(ctx)
#endif
update_sfx_list(&engine);
if (WindowShouldClose()) break;
}

View File

@ -9,11 +9,23 @@ add_library(lib_scenes STATIC
game_scene.c
game_systems.c
scene_systems.c
camera_systems.c
engine_impl.c
)
target_include_directories(lib_scenes
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/../tracy/public/
)
if (${RUN_PROFILER})
target_compile_definitions(lib_scenes
PUBLIC
TRACY_ENABLE
TRACY_ON_DEMAND
)
endif()
target_link_libraries(lib_scenes
PUBLIC
lib_engine

View File

@ -7,6 +7,7 @@ typedef enum AssetInfoType
TEXTURE_INFO,
SPRITE_INFO,
SOUND_INFO,
FONT_INFO,
EMITTER_INFO,
LEVELPACK_INFO,
INVALID_INFO
@ -80,6 +81,8 @@ static inline AssetInfoType_t get_asset_type(const char* str)
if (strcmp(str, "LevelPack") == 0) return LEVELPACK_INFO;
if (strcmp(str, "Font") == 0) return FONT_INFO;
return INVALID_INFO;
}
@ -272,12 +275,22 @@ bool load_from_infofile(const char* file, Assets_t* assets)
{
if (add_sound(assets, name, info_str) == NULL)
{
printf("Unable to add texture at line %lu\n", line_num);
printf("Unable to add sound at line %lu\n", line_num);
break;
}
printf("Added sound %s as %s\n", info_str, name);
}
break;
case FONT_INFO:
{
if (add_font(assets, name, info_str) == NULL)
{
printf("Unable to add font at line %lu\n", line_num);
break;
}
printf("Added font %s as %s\n", info_str, name);
}
break;
case LEVELPACK_INFO:
{
//if (add_level_pack(assets, name, info_str) == NULL)

View File

@ -11,6 +11,7 @@ typedef enum EntityTag {
LEVEL_END_TAG,
DESTRUCTABLE_ENT_TAG,
DYNMEM_ENT_TAG,
URCHIN_ENT_TAG,
} EntityTag_t;
typedef enum SFXTag {
@ -19,6 +20,9 @@ typedef enum SFXTag {
PLAYER_RUN_SFX,
PLAYER_LADDER_SFX,
PLAYER_WATER_RUN_SFX,
PLAYER_STEP_SFX,
PLAYER_DEAD_SFX,
PLAYER_DROWNING_SFX,
WATER_IN_SFX,
WATER_OUT_SFX,
WOOD_LAND_SFX,
@ -35,9 +39,11 @@ typedef enum SFXTag {
COIN_SFX,
} SFXTag_t;
//typedef enum SceneType {
// LEVEL_SCENE = 0,
// MENU_SCENE,
//}SceneType_t;
typedef enum SceneType {
MAIN_MENU_SCENE = 0,
LEVEL_SELECT_SCENE,
GAME_SCENE,
SANDBOX_SCENE,
}SceneType_t;
#endif

View File

@ -0,0 +1,102 @@
#include "game_systems.h"
#include "ent_impl.h"
#include "raymath.h"
void camera_update_system(Scene_t* scene)
{
LevelSceneData_t* data = &CONTAINER_OF(scene, LevelScene_t, scene)->data;
Entity_t* p_player;
const int width = data->game_rec.width;
const int height =data->game_rec.height;
data->camera.cam.offset = (Vector2){ width/2.0f, height/2.0f };
if (data->camera.mode == CAMERA_FOLLOW_PLAYER)
{
Vector2 target_vel = {0};
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
{
CTransform_t* p_ctransform = get_component(p_player, CTRANSFORM_COMP_T);
data->camera.target_pos.x = p_player->position.x;
target_vel = p_ctransform->velocity;
CMovementState_t* p_movement = get_component(p_player, CMOVEMENTSTATE_T);
CPlayerState_t* p_pstate = get_component(p_player, CPLAYERSTATE_T);
data->camera.target_pos.x += (p_movement->x_dir == 1) ? width/6: -width/6;
if (p_movement->ground_state == 0b01
|| (p_movement->water_state & 1)
|| (p_pstate->ladder_state & 1)
)
{
data->camera.base_y = p_player->position.y;
}
if (p_player->position.y >= data->camera.base_y)
{
data->camera.target_pos.y = p_player->position.y;
data->camera.target_pos.y += p_ctransform->velocity.y * 0.2;
}
}
data->camera.target_pos.x = Clamp(data->camera.target_pos.x, data->game_rec.width / 2,
fmax(data->tilemap.width * data->tilemap.tile_size, data->game_rec.width) - data->game_rec.width / 2);
data->camera.target_pos.y = Clamp(data->camera.target_pos.y, data->game_rec.height / 2,
fmax(data->tilemap.height * data->tilemap.tile_size, data->game_rec.width) - data->game_rec.height / 2);
// Mass-Spring damper update in x direction
float x = data->camera.target_pos.x - data->camera.cam.target.x;
float v = data->camera.current_vel.x - target_vel.x;
float F = data->camera.k * x - data->camera.c * v;
// Kinematics update
const float dt = scene->delta_time;
float a_dt = F * dt / data->camera.mass;
data->camera.cam.target.x += (data->camera.current_vel.x + a_dt * 0.5) * dt;
data->camera.current_vel.x += a_dt;
// Simple lerp for y direction
float dy = (data->camera.target_pos.y - data->camera.cam.target.y);
data->camera.cam.target.y += dy * 0.1f;
}
else
{
//sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
//{
// break;
//}
}
// Bound camera within level limits
Vector2 max = GetWorldToScreen2D(
(Vector2){
fmax(data->tilemap.width * data->tilemap.tile_size, data->game_rec.width),
fmax(data->tilemap.height * data->tilemap.tile_size, data->game_rec.height)
},
data->camera.cam
);
Vector2 min = GetWorldToScreen2D((Vector2){0, 0}, data->camera.cam);
if (max.x < width)
{
//data->camera.cam.offset.x = width - (max.x - width/2.0f);
data->camera.cam.target.x = fmax(data->tilemap.width * data->tilemap.tile_size, data->game_rec.width) - (width >> 1);
data->camera.current_vel.x = 0;
}
if (min.x > 0)
{
//data->camera.cam.offset.x = (width >> 1) - min.x;
data->camera.cam.target.x = (width >> 1);
data->camera.current_vel.x = 0;
}
if (max.y < height)
{
//data->camera.cam.offset.y = height - (max.y - height/2.0f);
data->camera.cam.target.y = fmax(data->tilemap.height * data->tilemap.tile_size, data->game_rec.height) - (height >> 1);
data->camera.current_vel.y = 0;
}
if (min.y > 0)
{
//data->camera.cam.offset.y = (height >> 1) - min.y;
data->camera.cam.target.y = (height >> 1);
data->camera.current_vel.y = 0;
}
}

180
scenes/components.h 100644
View File

@ -0,0 +1,180 @@
#include "EC.h"
#include "engine.h"
#include "render_queue.h"
#include "particle_sys.h" // includes assets
enum ComponentEnum {
CMOVEMENTSTATE_T = N_BASIC_COMPS,
CJUMP_COMP_T,
CPLAYERSTATE_T,
CCONTAINER_T,
CHITBOXES_T,
CHURTBOX_T,
CSPRITE_T,
CMOVEABLE_T,
CLIFETIMER_T,
CWATERRUNNER_T,
CAIRTIMER_T,
CEMITTER_T,
CSQUISHABLE_T
};
typedef struct _CMovementState_t {
uint8_t ground_state;
uint8_t water_state;
uint8_t x_dir;
float water_overlap;
} CMovementState_t;
typedef struct _CJump_t {
int jump_speed;
uint8_t jumps;
uint8_t max_jumps;
uint8_t coyote_timer;
bool jumped;
bool jump_ready;
bool short_hop;
bool jump_released;
} CJump_t;
typedef enum PlayerState {
GROUNDED,
AIR,
} PlayerState_t;
typedef struct _CPlayerState_t {
Vector2 player_dir;
uint8_t jump_pressed;
uint8_t is_crouch;
bool ladder_state;
bool locked; // Whether to respond to inputs
} CPlayerState_t;
typedef enum ContainerItem {
CONTAINER_EMPTY,
CONTAINER_LEFT_ARROW,
CONTAINER_RIGHT_ARROW,
CONTAINER_UP_ARROW,
CONTAINER_DOWN_ARROW,
CONTAINER_COIN,
CONTAINER_BOMB,
CONTAINER_EXPLOSION,
} ContainerItem_t;
typedef enum ContainerMaterial {
WOODEN_CONTAINER,
METAL_CONTAINER,
} ContainerMaterial_t;
typedef struct _CContainer_t {
ContainerMaterial_t material;
ContainerItem_t item;
} CContainer_t;
typedef struct _CHitBoxes_t {
Rectangle boxes[2];
uint8_t n_boxes;
uint8_t atk;
bool one_hit;
} CHitBoxes_t;
typedef struct _CHurtbox_t {
Vector2 offset;
Vector2 size;
uint8_t def;
unsigned int damage_src;
} CHurtbox_t;
typedef struct _CLifeTimer_t {
float life_time;
} CLifeTimer_t;
typedef struct _CAirTimer_t {
float max_ftimer;
float curr_ftimer;
float decay_rate;
uint8_t max_count;
uint8_t curr_count;
} CAirTimer_t;
typedef struct _BFSTile {
int32_t to;
int32_t from;
bool reachable;
}BFSTile_t;
typedef struct _BFSTileMap {
BFSTile_t* tilemap;
int32_t width;
int32_t height;
int32_t len;
}BFSTileMap_t;
typedef enum _WaterRunnerState
{
BFS_RESET = 0,
BFS_START,
LOWEST_POINT_SEARCH,
LOWEST_POINT_MOVEMENT,
REACHABILITY_SEARCH,
SCANLINE_FILL,
FILL_COMPLETE,
}WaterRunerState_t;
typedef struct _CWaterRunner {
BFSTileMap_t bfs_tilemap;
WaterRunerState_t state;
struct sc_queue_32 bfs_queue;
bool* visited;
int32_t current_tile;
int32_t target_tile;
int32_t fill_idx;
int16_t fill_range[2];
uint8_t movement_delay;
int8_t movement_speed;
int16_t counter;
float fractional;
}CWaterRunner_t;
typedef unsigned int (*sprite_transition_func_t)(Entity_t *ent); // Transition requires knowledge of the entity
typedef void (*sprite_sfx_func_t)(GameEngine_t*, Entity_t *ent); // Transition requires knowledge of the entity
typedef struct _SpriteRenderInfo
{
Sprite_t* sprite;
Vector2 offset;
AnchorPoint_t src_anchor;
AnchorPoint_t dest_anchor;
} SpriteRenderInfo_t;
typedef struct _CSprite_t {
RenderInfoNode node;
SpriteRenderInfo_t* sprites;
sprite_transition_func_t transition_func;
sprite_sfx_func_t sfx_func;
unsigned int current_idx;
int current_frame;
float fractional;
float rotation_speed; // Degree / s
int elapsed;
Vector2 offset;
uint8_t depth;
bool pause;
} CSprite_t;
typedef struct _CMoveable_t {
uint16_t move_speed;
Vector2 prev_pos;
Vector2 target_pos;
bool gridmove;
} CMoveable_t;
typedef struct _CEmitter_t
{
EmitterHandle handle;
Vector2 offset;
} CEmitter_t;
typedef struct _CSquishable_t {
bool active; //< Dummy variable
} CSquishable_t;

View File

@ -1,12 +1,16 @@
// Constants to be used in game
#ifndef __CONSTANTS_H
#define __CONSTANTS_H
#include <stdint.h>
#ifndef TILE16_SIZE
#define TILE_SIZE 32
#define DEFAULT_MAP_WIDTH 48
#define DEFAULT_MAP_HEIGHT 32
#define VIEWABLE_MAP_WIDTH 32
#define VIEWABLE_MAP_HEIGHT 16
#define VIEWABLE_EDITOR_MAP_WIDTH 32
#define VIEWABLE_EDITOR_MAP_HEIGHT 16
#define VIEWABLE_MAP_WIDTH 25
#define VIEWABLE_MAP_HEIGHT 14
#else
#define TILE_SIZE 16
#define DEFAULT_MAP_WIDTH 64
@ -17,13 +21,13 @@
#define DELTA_T 0.017
#define GRAV_ACCEL 1500
#define JUMP_SPEED 70
#define JUMP_SPEED 600
#define MOVE_ACCEL 1300
#ifndef TILE16_SIZE
#define PLAYER_WIDTH 28
#define PLAYER_WIDTH 22
#define PLAYER_HEIGHT 42
#define PLAYER_C_WIDTH 28
#define PLAYER_C_WIDTH 30
#define PLAYER_C_HEIGHT 26
#else
#define PLAYER_WIDTH 14
@ -39,12 +43,17 @@
#define GROUND_X_FRICTION 6.1
#define GROUND_Y_FRICTION 1.0
#define ARROW_SPEED 350
#define ARROW_SPEED 400
#define MAX_WATER_LEVEL 4
#define WATER_BBOX_STEP (TILE_SIZE / MAX_WATER_LEVEL)
#define MAX_LEVEL_NUM 50
#define MAX_N_TILES 4096
static const uint8_t CONNECTIVITY_TILE_MAPPING[16] = {
0,3,15,14,
1,2,12,13,
7,6,11,10,
4,5,8 ,9 ,
};
#endif // __CONSTANTS_H

View File

@ -1,3 +1,4 @@
#include "EC.h"
#include "scene_impl.h"
#include "game_systems.h"
#include "water_flow.h"
@ -28,29 +29,34 @@ enum EntitySpawnSelection {
SPAWN_BOULDER,
SPAWN_WATER_RUNNER,
SPAWN_LEVEL_END,
SPAWN_URCHIN,
};
#define MAX_SPAWN_TYPE 16
#define MAX_SPAWN_TYPE 17
static unsigned int current_spawn_selection = 0;
static bool metal_toggle = false;
static bool crate_activation = false;
#define MAX_URCHIN_SPAWN_SPD 20
#define URCHIN_SPAWN_UI_RADIUS 52
#define URCHIN_SPAWN_UI_DIVISION 13
#define URCHIN_SPAWN_UI_N (URCHIN_SPAWN_UI_RADIUS / URCHIN_SPAWN_UI_DIVISION)
#define URCHIN_SPAWN_ANGLE_DIVISION 45
#define URCHIN_SPAWN_UI_ANGLE_N (360 / URCHIN_SPAWN_ANGLE_DIVISION)
#define URCHIN_VELOCITY_SCALE 8
static Vector2 urchin_spawn_vec = {0,0};
static Vector2 urchin_click_pos = {0,0};
#define SELECTION_TILE_SIZE 32
#define SELECTION_TILE_HALFSIZE (SELECTION_TILE_SIZE >> 1)
#define SELECTION_GAP 5
#define SELECTION_REGION_WIDTH (SELECTION_TILE_SIZE * MAX_SPAWN_TYPE)
#define SELECTION_REGION_HEIGHT SELECTION_TILE_SIZE
#define GAME_LAYER 0
#define SELECTION_LAYER 1
#define CONTROL_LAYER 2
static const uint8_t CONNECTIVITY_TILE_MAPPING[16] = {
0,3,15,14,
1,2,12,13,
7,6,11,10,
4,5,8 ,9 ,
};
#define GAME_LAYER 2
#define SELECTION_LAYER 0
#define CONTROL_LAYER 1
#define N_SOLID_TILESETS 3
static const char* SOLID_TILE_SELECTIONS[N_SOLID_TILESETS] = {
@ -77,6 +83,7 @@ static char* get_spawn_selection_string(enum EntitySpawnSelection sel)
case SPAWN_BOULDER: return "boulder";
case SPAWN_WATER_RUNNER: return "water runner";
case SPAWN_LEVEL_END: return "level end";
case SPAWN_URCHIN: return "urchin";
default: return "unknown";
}
}
@ -96,6 +103,20 @@ static inline unsigned int get_tile_idx(int x, int y, const TileGrid_t* tilemap)
return MAX_N_TILES;
}
static int round_to_nearest(int in_val, int divider)
{
if (divider == 0) return in_val;
int remainder = in_val % divider;
int dividend = in_val / divider;
if (remainder > (divider >> 1))
{
dividend++;
}
return dividend * divider;
}
// This means you might be able to have two editor scene without running into problems
#define SELECTION_RENDER_HEIGHT (SELECTION_REGION_HEIGHT * 3)
static void level_scene_render_func(Scene_t* scene)
@ -110,21 +131,95 @@ static void level_scene_render_func(Scene_t* scene)
Vector2 draw_pos = {game_rec.x, game_rec.y + game_rec.height + SELECTION_GAP};
BeginTextureMode(scene->layers.render_layers[CONTROL_LAYER].layer_tex);
ClearBackground(BLANK);
if (data->camera.mode == CAMERA_RANGED_MOVEMENT)
{
DrawRectangle(game_rec.x - 3, game_rec.y - 3, game_rec.width + 6, game_rec.height + 6, RED);
}
DrawRectangleLines(
selection_rec.x + current_spawn_selection * SELECTION_TILE_SIZE, selection_rec.y,
SELECTION_TILE_SIZE, SELECTION_TILE_SIZE, GREEN
);
DrawText("R to reset the map, Q/E to cycle the selection,\nF to toggle metal crates. T to toggle crate spawn behaviour\nB to toggle grid, V to set spawn point", selection_rec.x, selection_rec.y + selection_rec.height + 2, 14, BLACK);
DrawText("R to reset the map, Q/E to cycle the selection,\nF to toggle metal crates. T to toggle crate spawn behaviour\nZ to change tileset, X to toggle grid\nC to set spawn point, V to toggle free cam, B to toggle slowmo", selection_rec.x, selection_rec.y + selection_rec.height + 2, 14, BLACK);
draw_pos.x = game_rec.x + (MAX_SPAWN_TYPE + 1) * SELECTION_TILE_SIZE;
sprintf(buffer, "Selection: %s", get_spawn_selection_string(current_spawn_selection));
sprintf(buffer, "%s", get_spawn_selection_string(current_spawn_selection));
DrawText(buffer, draw_pos.x, draw_pos.y, 20, BLACK);
draw_pos.y += SELECTION_TILE_SIZE + 5;
draw_pos.y += 20 + 5;
sprintf(buffer, "Crate %s on spawn", crate_activation? "active" : "inactive");
DrawText(buffer, draw_pos.x, draw_pos.y, 20, BLACK);
draw_pos.y += SELECTION_TILE_SIZE + 5;
draw_pos.y += 20 + 5;
sprintf(buffer, "Time scale: %.2f", scene->time_scale);
DrawText(buffer, draw_pos.x, draw_pos.y, 20, BLACK);
draw_pos.y += 20 + 5;
sprintf(buffer, "Camera mode: %s", data->camera.mode == CAMERA_FOLLOW_PLAYER ? "FOLLOW" : "FREE");
DrawText(buffer, draw_pos.x, draw_pos.y, 20, BLACK);
draw_pos.x = game_rec.x + game_rec.width - 50 - URCHIN_SPAWN_UI_RADIUS;
draw_pos.y = game_rec.y + game_rec.height + SELECTION_GAP + URCHIN_SPAWN_UI_RADIUS;
// Draw Urchin spawn info
DrawCircleV(draw_pos, URCHIN_SPAWN_UI_RADIUS, RED);
for (unsigned int i = 0; i <= URCHIN_SPAWN_UI_N; ++i)
{
DrawCircleLinesV(draw_pos, i * URCHIN_SPAWN_UI_DIVISION, BLACK);
}
for (unsigned int i = 0; i < URCHIN_SPAWN_UI_ANGLE_N; ++i)
{
float angle = i * URCHIN_SPAWN_ANGLE_DIVISION * PI / 180;
Vector2 draw_end = Vector2Scale(
(Vector2){cosf(angle), sinf(angle)}, URCHIN_SPAWN_UI_RADIUS
);
draw_end = Vector2Add(draw_end, draw_pos);
DrawLineEx(draw_pos, draw_end, 1, BLACK);
}
// HACK: because this ui is not stored, need to perform the mouse check here
bool to_snap = false;
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT))
{
Vector2 tmp = Vector2Subtract(
scene->mouse_pos, draw_pos
);
float mag = Vector2Length(tmp);
if (mag <= URCHIN_SPAWN_UI_RADIUS)
{
urchin_click_pos = Vector2Scale(
Vector2Normalize(tmp), mag
);
}
else
{
to_snap = true;
}
}
else if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT))
{
to_snap = true;
}
if (to_snap)
{
float mag = Vector2Length(urchin_click_pos);
float dir = atan2f(urchin_click_pos.y, urchin_click_pos.x);
dir *= 180 / PI;
dir += dir < 0 ? 360 : 0;
mag = round_to_nearest((int)mag, URCHIN_SPAWN_UI_DIVISION);
dir = round_to_nearest((int)dir, URCHIN_SPAWN_ANGLE_DIVISION);
dir *= PI / 180;
urchin_spawn_vec = Vector2Scale(
(Vector2){cosf(dir), sinf(dir)}, mag
);
urchin_click_pos = urchin_spawn_vec;
}
DrawCircleV(
Vector2Add(urchin_click_pos, draw_pos),
4, BLUE
);
// For DEBUG
const int gui_x = game_rec.x + game_rec.width + 10;
@ -158,14 +253,6 @@ static void level_scene_render_func(Scene_t* scene)
gui_y += 30;
#endif
CAirTimer_t* p_air = get_component(p_ent, CAIRTIMER_T);
Vector2 air_pos = {game_rec.x + game_rec.width - 16, game_rec.y + game_rec.height - 16};
for (uint8_t i = 0; i < p_air->curr_count; i++)
{
DrawCircleV(air_pos, 16, BLUE);
air_pos.x -= 32;
}
}
//sprintf(buffer, "Spawn Entity: %s", get_spawn_selection_string(current_spawn_selection));
//DrawText(buffer, gui_x, 240, 12, BLACK);
@ -208,9 +295,76 @@ static void render_editor_game_scene(Scene_t* scene)
max.x = (int)fmin(tilemap.width, max.x + 1);
max.y = (int)fmin(tilemap.height, max.y + 1);
// Queue Sprite rendering
sc_map_foreach_value(&scene->ent_manager.entities, p_ent)
{
if (!p_ent->m_alive) continue;
CSprite_t* p_cspr = get_component(p_ent, CSPRITE_T);
if (p_cspr == NULL) continue;
const SpriteRenderInfo_t spr = p_cspr->sprites[p_cspr->current_idx];
if (spr.sprite == NULL) continue;
Vector2 pos = p_ent->position;
CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
if (p_bbox != NULL)
{
pos = Vector2Add(
pos,
get_anchor_offset(p_bbox->size, spr.dest_anchor, p_cspr->node.flip & 1)
);
}
pos = Vector2Subtract(
pos,
get_anchor_offset(spr.sprite->frame_size, spr.src_anchor, p_cspr->node.flip & 1)
);
Vector2 offset = spr.offset;
if (p_cspr->node.flip & 1) offset.x *= -1;
pos = Vector2Add(pos, offset);
// Entity culling
if (
pos.x + spr.sprite->frame_size.x < min.x * tilemap.tile_size
|| pos.x > max.x * tilemap.tile_size
|| pos.y + spr.sprite->frame_size.y < min.y * tilemap.tile_size
|| pos.y > max.y * tilemap.tile_size
)
{
continue;
}
if (p_ent->m_tag == LEVEL_END_TAG)
{
p_cspr->current_frame = 2 * data->selected_solid_tilemap + (
(data->coins.current < data->coins.total) ? 0 : 1);
}
p_cspr->node.spr = spr.sprite;
p_cspr->node.frame_num = p_cspr->current_frame;
p_cspr->node.pos = pos;
add_render_node(&data->render_manager, &p_cspr->node, p_cspr->depth);
}
Texture2D* bg = get_texture(&scene->engine->assets, "bg_tex");
BeginTextureMode(scene->layers.render_layers[GAME_LAYER].layer_tex);
ClearBackground(WHITE);
DrawTexturePro(*bg,
//(Rectangle){0,0,64,64},
(Rectangle){min.x,0,(tilemap.width+1)*tilemap.tile_size*2, (tilemap.height+1)*tilemap.tile_size*2},
(Rectangle){0,0,(tilemap.width+1)*tilemap.tile_size*2, (tilemap.height+1)*tilemap.tile_size*2},
//(Rectangle){0,0,game_rec.width, game_rec.height},
(Vector2){0,0}, 0.0f, WHITE
);
BeginMode2D(data->camera.cam);
if (point_in_AABB(data->player_spawn, (Rectangle){min.x * tilemap.tile_size, min.y * tilemap.tile_size, data->game_rec.width, data->game_rec.height}))
{
DrawCircleV(data->player_spawn, 6, PURPLE);
}
for (int tile_y = min.y; tile_y < max.y; tile_y++)
{
for (int tile_x = min.x; tile_x < max.x; tile_x++)
@ -219,91 +373,27 @@ static void render_editor_game_scene(Scene_t* scene)
int x = tile_x * TILE_SIZE;
int y = tile_y * TILE_SIZE;
//if (!tilemap.tiles[i].moveable)
//{
// // Draw water tile
// Color water_colour = ColorAlpha(RED, 0.2);
// DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, water_colour);
//}
uint8_t tile_sprite_idx = tilemap.tiles[i].tile_type + tilemap.tiles[i].rotation;
if (data->tile_sprites[tile_sprite_idx] != NULL)
if (tilemap.tiles[i].tile_type == LADDER)
{
draw_sprite(data->tile_sprites[tile_sprite_idx], 0, (Vector2){x,y}, 0.0f, false);
}
else if (tilemap.tiles[i].tile_type == SOLID_TILE)
if (data->tile_sprites[tilemap.tiles[i].tile_type] != NULL)
{
draw_sprite(
data->solid_tile_sprites, CONNECTIVITY_TILE_MAPPING[tilemap.tiles[i].connectivity],
(Vector2){x,y}, 0.0f, false
);
//sprintf(buffer, "%u", tilemap.tiles[i].connectivity);
//DrawText(buffer, x + tilemap.tile_size / 2, y + tilemap.tile_size / 2, 12, WHITE);
draw_sprite(data->tile_sprites[tilemap.tiles[i].tile_type], 0, (Vector2){x,y}, 0.0f, false);
}
else if (tilemap.tiles[i].tile_type == ONEWAY_TILE)
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, MAROON);
}
else if (tilemap.tiles[i].tile_type == LADDER)
else
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, ORANGE);
}
else if (tilemap.tiles[i].tile_type == SPIKES)
{
DrawRectangle(
x + tilemap.tiles[i].offset.x, y + tilemap.tiles[i].offset.y,
tilemap.tiles[i].size.x, tilemap.tiles[i].size.y, RED
);
}
if (tilemap.tiles[i].wet)
{
#define SURFACE_THICKNESS 4
int up = i - tilemap.width;
unsigned int bot = i + tilemap.width;
int right = i + 1;
int left = i - 1;
int bot_line = y + TILE_SIZE - tilemap.tiles[i].water_level * WATER_BBOX_STEP - SURFACE_THICKNESS / 2;
if (up >= 0 && tilemap.tiles[up].wet)
{
DrawLineEx((Vector2){x + TILE_SIZE / 2, y}, (Vector2){x + TILE_SIZE / 2, y + TILE_SIZE - tilemap.tiles[i].water_level * WATER_BBOX_STEP}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
if (
bot <= tilemap.n_tiles
&& tilemap.tiles[i].water_level == 0
)
{
if (i % tilemap.width != 0 && tilemap.tiles[left].wet && (tilemap.tiles[bot].solid == SOLID || tilemap.tiles[bot-1].solid == SOLID))
{
DrawLineEx((Vector2){x, bot_line}, (Vector2){x + TILE_SIZE / 2, bot_line}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
if (right % tilemap.width != 0 && tilemap.tiles[right].wet && (tilemap.tiles[bot].solid == SOLID || tilemap.tiles[bot+1].solid == SOLID))
{
DrawLineEx((Vector2){x + TILE_SIZE / 2, bot_line}, (Vector2){x + TILE_SIZE, bot_line}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
}
}
if (tilemap.tiles[i].max_water_level < MAX_WATER_LEVEL)
{
DrawRectangleLinesEx((Rectangle){x, y, TILE_SIZE, TILE_SIZE}, 2.0, ColorAlpha(BLUE, 0.5));
}
}
}
char buffer[64] = {0};
if (data->show_grid)
{
sc_map_foreach_value(&scene->ent_manager.entities, p_ent)
{
CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
// Draw the spawn point
if (p_ent->m_tag == PLAYER_ENT_TAG)
{
DrawCircleV(p_ent->spawn_pos, 6, PURPLE);
}
// Entity culling
Vector2 box_size = {0};
if (p_bbox != NULL) box_size = p_bbox->size;
@ -317,6 +407,7 @@ static void render_editor_game_scene(Scene_t* scene)
Color colour;
switch(p_ent->m_tag)
{
case NO_ENT_TAG:
case PLAYER_ENT_TAG:
colour = RED;
break;
@ -337,6 +428,21 @@ static void render_editor_game_scene(Scene_t* scene)
break;
}
if (p_ent->m_tag == LEVEL_END_TAG)
{
DrawCircleV(p_ent->position, tilemap.tile_size >> 1, (data->coins.current < data->coins.total)? RED : GREEN);
continue;
}
if (p_ent->m_tag == DYNMEM_ENT_TAG)
{
CWaterRunner_t* p_runner = get_component(p_ent, CWATERRUNNER_T);
unsigned int x = ((p_runner->current_tile) % tilemap.width) * tilemap.tile_size;
unsigned int y = ((p_runner->current_tile) / tilemap.width) * tilemap.tile_size;
DrawCircle(x+16, y+16, 8, ColorAlpha(BLUE, 0.6));
}
if (p_bbox != NULL)
{
if (p_ent->m_tag == BOULDER_ENT_TAG)
@ -426,21 +532,85 @@ static void render_editor_game_scene(Scene_t* scene)
};
DrawRectangleLinesEx(rec, 1.5, PURPLE);
}
CSprite_t* p_cspr = get_component(p_ent, CSPRITE_T);
if (p_cspr != NULL)
}
}
execute_render(&data->render_manager);
for (int tile_y = min.y; tile_y < max.y; tile_y++)
{
const SpriteRenderInfo_t spr = p_cspr->sprites[p_cspr->current_idx];
if (spr.sprite != NULL)
for (int tile_x = min.x; tile_x < max.x; tile_x++)
{
Vector2 pos = Vector2Add(p_ent->position, spr.offset);
draw_sprite(spr.sprite, p_cspr->current_frame, pos, 0.0f, p_cspr->flip_x);
int i = tile_x + tile_y * tilemap.width;
int x = tile_x * TILE_SIZE;
int y = tile_y * TILE_SIZE;
if (tilemap.tiles[i].tile_type != LADDER)
{
uint8_t tile_sprite_idx = tilemap.tiles[i].tile_type + tilemap.tiles[i].rotation;
if (data->tile_sprites[tile_sprite_idx] != NULL)
{
draw_sprite(data->tile_sprites[tile_sprite_idx], 0, (Vector2){x,y}, 0.0f, false);
}
else if (tilemap.tiles[i].tile_type == SOLID_TILE)
{
draw_sprite(
data->solid_tile_sprites, CONNECTIVITY_TILE_MAPPING[tilemap.tiles[i].connectivity],
(Vector2){x,y}, 0.0f, false
);
//sprintf(buffer, "%u", tilemap.tiles[i].connectivity);
//DrawText(buffer, x + tilemap.tile_size / 2, y + tilemap.tile_size / 2, 12, WHITE);
}
else if (tilemap.tiles[i].tile_type == ONEWAY_TILE)
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, MAROON);
}
else if (tilemap.tiles[i].tile_type == SPIKES)
{
DrawRectangle(
x + tilemap.tiles[i].offset.x, y + tilemap.tiles[i].offset.y,
tilemap.tiles[i].size.x, tilemap.tiles[i].size.y, RED
);
}
}
if (tilemap.tiles[i].wet)
{
#define SURFACE_THICKNESS 4
int up = i - tilemap.width;
unsigned int bot = i + tilemap.width;
int right = i + 1;
int left = i - 1;
int bot_line = y + TILE_SIZE - tilemap.tiles[i].water_level * WATER_BBOX_STEP - SURFACE_THICKNESS / 2;
if (up >= 0 && tilemap.tiles[up].wet)
{
DrawLineEx((Vector2){x + TILE_SIZE / 2, y}, (Vector2){x + TILE_SIZE / 2, y + TILE_SIZE - tilemap.tiles[i].water_level * WATER_BBOX_STEP}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
if (
bot <= tilemap.n_tiles
&& tilemap.tiles[i].water_level == 0
)
{
if (i % tilemap.width != 0 && tilemap.tiles[left].wet && (tilemap.tiles[bot].solid == SOLID || tilemap.tiles[bot-1].solid == SOLID))
{
DrawLineEx((Vector2){x, bot_line}, (Vector2){x + TILE_SIZE / 2, bot_line}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
if (right % tilemap.width != 0 && tilemap.tiles[right].wet && (tilemap.tiles[bot].solid == SOLID || tilemap.tiles[bot+1].solid == SOLID))
{
DrawLineEx((Vector2){x + TILE_SIZE / 2, bot_line}, (Vector2){x + TILE_SIZE, bot_line}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
}
}
sc_map_foreach_value(&scene->ent_manager.entities_map[LEVEL_END_TAG], p_ent)
if (tilemap.tiles[i].max_water_level < MAX_WATER_LEVEL)
{
DrawCircleV(p_ent->position, tilemap.tile_size >> 1, (data->coins.current < data->coins.total)? RED : GREEN);
DrawRectangleLinesEx((Rectangle){x, y, TILE_SIZE, TILE_SIZE}, 2.0, ColorAlpha(BLUE, 0.5));
}
}
}
draw_particle_system(&scene->part_sys);
@ -466,16 +636,6 @@ static void render_editor_game_scene(Scene_t* scene)
}
}
sc_map_foreach_value(&scene->ent_manager.entities_map[DYNMEM_ENT_TAG], p_ent)
{
CWaterRunner_t* p_runner = get_component(p_ent, CWATERRUNNER_T);
unsigned int x = ((p_runner->current_tile) % tilemap.width) * tilemap.tile_size;
unsigned int y = ((p_runner->current_tile) / tilemap.width) * tilemap.tile_size;
DrawCircle(x+16, y+16, 8, ColorAlpha(BLUE, 0.6));
}
if (data->show_grid)
{
for (int tile_y = min.y; tile_y < max.y; tile_y++)
@ -488,15 +648,15 @@ static void render_editor_game_scene(Scene_t* scene)
sprintf(buffer, "%u", sc_map_size_64v(&tilemap.tiles[i].entities_set));
if (tilemap.tiles[i].solid > 0)
//if (tilemap.tiles[i].solid > 0)
{
DrawText(buffer, x, y, 10, WHITE);
}
else
{
// Draw water tile
DrawText(buffer, x, y, 10, BLACK);
}
//else
//{
// // Draw water tile
// DrawText(buffer, x, y, 10, BLACK);
//}
}
}
@ -504,15 +664,28 @@ static void render_editor_game_scene(Scene_t* scene)
for (size_t i = min.x; i < max.x; ++i)
{
int x = (i+1)*TILE_SIZE;
DrawLine(x, 0, x, tilemap.height * TILE_SIZE, BLACK);
DrawLine(x, 0, x, tilemap.height * TILE_SIZE, WHITE);
}
for (size_t i = min.y; i < max.y;++i)
{
int y = (i+1)*TILE_SIZE;
DrawLine(0, y, tilemap.width * TILE_SIZE, y, BLACK);
DrawLine(0, y, tilemap.width * TILE_SIZE, y, WHITE);
}
}
EndMode2D();
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_ent)
{
CAirTimer_t* p_air = get_component(p_ent, CAIRTIMER_T);
Sprite_t* spr = get_sprite(&scene->engine->assets, "p_bigbubble");
Vector2 air_pos = {data->game_rec.width - 32, data->game_rec.height - 32};
for (uint8_t i = 0; i < p_air->curr_count; i++)
{
draw_sprite(spr, 0, air_pos, 0, false);
//DrawCircleV(air_pos, 16, BLUE);
air_pos.x -= 32;
}
}
EndTextureMode();
}
@ -658,7 +831,22 @@ static void toggle_block_system(Scene_t* scene, ActionType_t action, bool presse
if (p_ent != NULL)
{
p_ent->position.x = (tile_idx % tilemap.width) * tilemap.tile_size + (tilemap.tile_size >> 1);
p_ent->position.y = (tile_idx / tilemap.width) * tilemap.tile_size + (tilemap.tile_size >> 1);;
p_ent->position.y = (tile_idx / tilemap.width) * tilemap.tile_size + (tilemap.tile_size >> 1);
}
}
break;
case SPAWN_URCHIN:
{
Entity_t* p_ent = create_urchin(&scene->ent_manager);
if (p_ent != NULL)
{
CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
p_ent->position.x = (tile_idx % tilemap.width) * tilemap.tile_size
+ (tilemap.tile_size >> 1) - p_bbox->half_size.x;
p_ent->position.y = (tile_idx / tilemap.width) * tilemap.tile_size
+ (tilemap.tile_size >> 1) - p_bbox->half_size.y;
CTransform_t* p_ct = get_component(p_ent, CTRANSFORM_COMP_T);
p_ct->velocity = Vector2Scale(urchin_spawn_vec, URCHIN_VELOCITY_SCALE);
}
}
break;
@ -756,40 +944,19 @@ static void restart_editor_level(Scene_t* scene)
tilemap.tiles[i].water_level = 0;
tilemap.tiles[i].wet = false;
tilemap.tiles[i].size = (Vector2){TILE_SIZE, TILE_SIZE};
Entity_t* ent;
unsigned int m_id;
sc_map_foreach(&tilemap.tiles[i].entities_set, m_id, ent)
{
if (ent->m_tag == PLAYER_ENT_TAG) continue;
CTileCoord_t* p_tilecoord = get_component(
ent, CTILECOORD_COMP_T
);
for (size_t i = 0;i < p_tilecoord->n_tiles; ++i)
}
clear_all_game_entities(CONTAINER_OF(scene, LevelScene_t, scene));
Entity_t* p_player = create_player(&scene->ent_manager);
p_player->position = data->player_spawn;
CPlayerState_t* p_pstate = get_component(p_player, CPLAYERSTATE_T);
if (data->camera.mode == CAMERA_RANGED_MOVEMENT)
{
// Use previously store tile position
// Clear from those positions
unsigned int tile_idx = p_tilecoord->tiles[i];
sc_map_del_64v(&(tilemap.tiles[tile_idx].entities_set), m_id);
}
//remove_entity(&scene->ent_manager, m_id);
CWaterRunner_t* p_crunner = get_component(ent, CWATERRUNNER_T);
if (p_crunner == NULL)
{
remove_entity(&scene->ent_manager, m_id);
}
else
{
free_water_runner(ent, &scene->ent_manager);
}
}
}
Entity_t* p_ent;
sc_map_foreach_value(&scene->ent_manager.entities_map[LEVEL_END_TAG], p_ent)
{
remove_entity(&scene->ent_manager, p_ent->m_id);
p_pstate->locked = true;
}
update_entity_manager(&scene->ent_manager);
for (size_t i = 0; i < tilemap.width; ++i)
{
unsigned int tile_idx = (tilemap.height - 1) * tilemap.width + i;
@ -822,6 +989,43 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
case ACTION_JUMP:
p_playerstate->jump_pressed = pressed;
break;
case ACTION_LOOKAHEAD:
if (!pressed)
{
p_playerstate->locked = !p_playerstate->locked;
}
break;
case ACTION_SET_SPAWNPOINT:
data->player_spawn = p_player->position;
break;
default:
break;
}
}
if (data->camera.mode == CAMERA_RANGED_MOVEMENT)
{
switch(action)
{
case ACTION_UP:
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.y -= 20;
break;
case ACTION_DOWN:
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.y += 20;
break;
case ACTION_LEFT:
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.x -= 20;
break;
case ACTION_RIGHT:
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.x += 20;
break;
default:
break;
}
}
switch(action)
{
case ACTION_NEXT_SPAWN:
if (!pressed)
{
@ -961,7 +1165,10 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
break;
case ACTION_NEXTLEVEL:
case ACTION_RESTART:
if (!pressed)
{
restart_editor_level(scene);
}
break;
case ACTION_TOGGLE_GRID:
if (!pressed)
@ -969,9 +1176,6 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
data->show_grid = !data->show_grid;
}
break;
case ACTION_SET_SPAWNPOINT:
p_player->spawn_pos = p_player->position;
break;
case ACTION_TOGGLE_TIMESLOW:
if (!pressed)
{
@ -989,7 +1193,6 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
case ACTION_REMOVE_TILE:
toggle_block_system(scene, action, pressed);
update_entity_manager(&scene->ent_manager);
default:
break;
case ACTION_SWITCH_TILESET:
if (!pressed)
@ -1000,13 +1203,57 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
data->solid_tile_sprites = get_sprite(&scene->engine->assets, SOLID_TILE_SELECTIONS[data->selected_solid_tilemap]);
}
break;
case ACTION_LOOKAHEAD:
if (!pressed)
{
data->camera.mode = (data->camera.mode == CAMERA_FOLLOW_PLAYER) ? CAMERA_RANGED_MOVEMENT : CAMERA_FOLLOW_PLAYER;
}
break;
default:
break;
}
}
static void at_level_start(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
data->sm.state = LEVEL_STATE_RUNNING;
}
static void at_level_dead(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
Entity_t* p_player = create_player(&scene->ent_manager);
p_player->position = data->player_spawn;
CPlayerState_t* p_pstate = get_component(p_player, CPLAYERSTATE_T);
if (data->camera.mode == CAMERA_RANGED_MOVEMENT)
{
p_pstate->locked = true;
}
change_level_state(data, LEVEL_STATE_STARTING);
}
static void at_level_complete(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
data->sm.fractional += scene->delta_time;
if (data->sm.fractional > 1.0f)
{
data->sm.fractional -= 1.0f;
data->sm.counter++;
}
if (data->sm.counter >= 1)
{
data->sm.counter = 0;
data->sm.fractional = 0;
at_level_dead(scene);
}
}
void init_sandbox_scene(LevelScene_t* scene)
{
init_scene(&scene->scene, &level_do_action);
init_scene(&scene->scene, &level_do_action, ENABLE_ENTITY_MANAGEMENT_SYSTEM | ENABLE_PARTICLE_SYSTEM);
init_entity_tag_map(&scene->scene.ent_manager, PLAYER_ENT_TAG, 4);
init_entity_tag_map(&scene->scene.ent_manager, BOULDER_ENT_TAG, MAX_COMP_POOL_SIZE);
init_entity_tag_map(&scene->scene.ent_manager, LEVEL_END_TAG, 16);
@ -1015,9 +1262,13 @@ void init_sandbox_scene(LevelScene_t* scene)
scene->data.tilemap.tiles = all_tiles;
init_level_scene_data(
&scene->data, MAX_N_TILES, all_tiles,
(Rectangle){25, 25, VIEWABLE_MAP_WIDTH*TILE_SIZE, VIEWABLE_MAP_HEIGHT*TILE_SIZE}
(Rectangle){10, 10, VIEWABLE_EDITOR_MAP_WIDTH*TILE_SIZE, VIEWABLE_EDITOR_MAP_HEIGHT*TILE_SIZE}
);
scene->data.show_grid = true;
scene->data.sm.state_functions[LEVEL_STATE_STARTING] = at_level_start;
scene->data.sm.state_functions[LEVEL_STATE_RUNNING] = NULL;
scene->data.sm.state_functions[LEVEL_STATE_DEAD] = at_level_dead;
scene->data.sm.state_functions[LEVEL_STATE_COMPLETE] = at_level_complete;
scene->data.tile_sprites[ONEWAY_TILE] = get_sprite(&scene->scene.engine->assets, "tl_owp");
scene->data.tile_sprites[LADDER] = get_sprite(&scene->scene.engine->assets, "tl_ldr");
scene->data.tile_sprites[SPIKES] = get_sprite(&scene->scene.engine->assets, "d_spikes");
@ -1026,6 +1277,8 @@ void init_sandbox_scene(LevelScene_t* scene)
scene->data.tile_sprites[SPIKES + TILE_180ROT] = get_sprite(&scene->scene.engine->assets, "u_spikes");
scene->data.selected_solid_tilemap = 0;
scene->data.solid_tile_sprites = get_sprite(&scene->scene.engine->assets, SOLID_TILE_SELECTIONS[0]);
Texture2D* tex = get_texture(&scene->scene.engine->assets, "bg_tex");
SetTextureWrap(*tex, TEXTURE_WRAP_REPEAT);
for (size_t i = 0; i < scene->data.tilemap.width; ++i)
{
@ -1034,10 +1287,6 @@ void init_sandbox_scene(LevelScene_t* scene)
}
scene->scene.bg_colour = LIGHTGRAY;
add_scene_layer(
&scene->scene, scene->data.game_rec.width, scene->data.game_rec.height,
scene->data.game_rec
);
add_scene_layer(
&scene->scene, SELECTION_REGION_WIDTH, SELECTION_REGION_HEIGHT,
(Rectangle){
@ -1054,6 +1303,24 @@ void init_sandbox_scene(LevelScene_t* scene)
scene->scene.engine->intended_window_size.y
}
);
add_scene_layer(
&scene->scene, scene->data.game_rec.width, scene->data.game_rec.height,
scene->data.game_rec
);
{
Entity_t* p_player = create_player(&scene->scene.ent_manager);
CSprite_t* p_cspr = get_component(p_player, CSPRITE_T);
p_cspr->node.flip |= 1;
p_player->position.x = 100;
p_player->position.y = (scene->data.tilemap.height - 1) * scene->data.tilemap.tile_size - PLAYER_HEIGHT;
scene->data.player_spawn = p_player->position;
scene->data.camera.target_pos = p_player->position;
scene->data.camera.cam.target = p_player->position;
}
update_entity_manager(&scene->scene.ent_manager);
BeginTextureMode(scene->scene.layers.render_layers[SELECTION_LAYER].layer_tex);
ClearBackground(LIGHTGRAY);
@ -1221,6 +1488,9 @@ void init_sandbox_scene(LevelScene_t* scene)
case SPAWN_LEVEL_END:
DrawCircleV(Vector2Add(draw_pos, half_size), half_size.x, GREEN);
break;
case SPAWN_URCHIN:
DrawCircleV(Vector2Add(draw_pos, half_size), half_size.x, RED);
break;
}
draw_pos.x += SELECTION_TILE_SIZE;
@ -1230,11 +1500,8 @@ void init_sandbox_scene(LevelScene_t* scene)
EndTextureMode();
create_player(&scene->scene.ent_manager);
update_entity_manager(&scene->scene.ent_manager);
// insert level scene systems
sc_array_add(&scene->scene.systems, &update_tilemap_system);
sc_array_add(&scene->scene.systems, &player_movement_input_system);
sc_array_add(&scene->scene.systems, &player_bbox_update_system);
sc_array_add(&scene->scene.systems, &player_pushing_system);
@ -1246,11 +1513,11 @@ void init_sandbox_scene(LevelScene_t* scene)
sc_array_add(&scene->scene.systems, &boulder_destroy_wooden_tile_system);
sc_array_add(&scene->scene.systems, &update_tilemap_system);
sc_array_add(&scene->scene.systems, &tile_collision_system);
//sc_array_add(&scene->scene.systems, &update_tilemap_system);
sc_array_add(&scene->scene.systems, &update_tilemap_system);
sc_array_add(&scene->scene.systems, &hitbox_update_system);
sc_array_add(&scene->scene.systems, &player_crushing_system);
sc_array_add(&scene->scene.systems, &spike_collision_system);
sc_array_add(&scene->scene.systems, &edge_velocity_check_system);
//sc_array_add(&scene->scene.systems, &edge_velocity_check_system);
sc_array_add(&scene->scene.systems, &state_transition_update_system);
sc_array_add(&scene->scene.systems, &update_entity_emitter_system);
sc_array_add(&scene->scene.systems, &player_ground_air_transition_system);
@ -1261,8 +1528,9 @@ void init_sandbox_scene(LevelScene_t* scene)
sc_array_add(&scene->scene.systems, &camera_update_system);
sc_array_add(&scene->scene.systems, &player_dir_reset_system);
sc_array_add(&scene->scene.systems, &update_water_runner_system);
sc_array_add(&scene->scene.systems, &player_respawn_system);
sc_array_add(&scene->scene.systems, &check_player_dead_system);
sc_array_add(&scene->scene.systems, &level_end_detection_system);
sc_array_add(&scene->scene.systems, &level_state_management_system);
sc_array_add(&scene->scene.systems, &render_editor_game_scene);
sc_array_add(&scene->scene.systems, &level_scene_render_func);
@ -1285,10 +1553,11 @@ void init_sandbox_scene(LevelScene_t* scene)
sc_map_put_64(&scene->scene.action_map, KEY_T, ACTION_CRATE_ACTIVATION);
sc_map_put_64(&scene->scene.action_map, KEY_L, ACTION_EXIT);
sc_map_put_64(&scene->scene.action_map, KEY_R, ACTION_RESTART);
sc_map_put_64(&scene->scene.action_map, KEY_B, ACTION_TOGGLE_GRID);
sc_map_put_64(&scene->scene.action_map, KEY_Z, ACTION_SWITCH_TILESET);
sc_map_put_64(&scene->scene.action_map, KEY_V, ACTION_SET_SPAWNPOINT);
sc_map_put_64(&scene->scene.action_map, KEY_U, ACTION_TOGGLE_TIMESLOW);
sc_map_put_64(&scene->scene.action_map, KEY_X, ACTION_TOGGLE_GRID);
sc_map_put_64(&scene->scene.action_map, KEY_C, ACTION_SET_SPAWNPOINT);
sc_map_put_64(&scene->scene.action_map, KEY_V, ACTION_LOOKAHEAD);
sc_map_put_64(&scene->scene.action_map, KEY_B, ACTION_TOGGLE_TIMESLOW);
sc_map_put_64(&scene->scene.action_map, MOUSE_LEFT_BUTTON, ACTION_SPAWN_TILE);
sc_map_put_64(&scene->scene.action_map, MOUSE_RIGHT_BUTTON, ACTION_REMOVE_TILE);
@ -1296,6 +1565,7 @@ void init_sandbox_scene(LevelScene_t* scene)
void free_sandbox_scene(LevelScene_t* scene)
{
clear_all_game_entities(scene);
free_scene(&scene->scene);
term_level_scene_data(&scene->data);
}

View File

@ -0,0 +1,35 @@
#include "mempool.h"
#include "components.h"
/** This file is supposed to implement any required engine functions
*/
DEFINE_COMP_MEMPOOL_BUF(CMovementState_t, MAX_COMP_POOL_SIZE)
DEFINE_COMP_MEMPOOL_BUF(CJump_t, 8)
DEFINE_COMP_MEMPOOL_BUF(CPlayerState_t, 8)
DEFINE_COMP_MEMPOOL_BUF(CContainer_t, MAX_COMP_POOL_SIZE)
DEFINE_COMP_MEMPOOL_BUF(CHitBoxes_t, MAX_COMP_POOL_SIZE)
DEFINE_COMP_MEMPOOL_BUF(CHurtbox_t, MAX_COMP_POOL_SIZE)
DEFINE_COMP_MEMPOOL_BUF(CSprite_t, MAX_COMP_POOL_SIZE)
DEFINE_COMP_MEMPOOL_BUF(CMoveable_t, MAX_COMP_POOL_SIZE)
DEFINE_COMP_MEMPOOL_BUF(CLifeTimer_t, MAX_COMP_POOL_SIZE)
DEFINE_COMP_MEMPOOL_BUF(CWaterRunner_t, 32)
DEFINE_COMP_MEMPOOL_BUF(CAirTimer_t, 8)
DEFINE_COMP_MEMPOOL_BUF(CEmitter_t, 32)
DEFINE_COMP_MEMPOOL_BUF(CSquishable_t, MAX_COMP_POOL_SIZE)
// Component mempools are added in the order of the component enums
BEGIN_DEFINE_COMP_MEMPOOL
ADD_COMP_MEMPOOL(CMovementState_t)
ADD_COMP_MEMPOOL(CJump_t)
ADD_COMP_MEMPOOL(CPlayerState_t)
ADD_COMP_MEMPOOL(CContainer_t)
ADD_COMP_MEMPOOL(CHitBoxes_t)
ADD_COMP_MEMPOOL(CHurtbox_t)
ADD_COMP_MEMPOOL(CSprite_t)
ADD_COMP_MEMPOOL(CMoveable_t)
ADD_COMP_MEMPOOL(CLifeTimer_t)
ADD_COMP_MEMPOOL(CWaterRunner_t)
ADD_COMP_MEMPOOL(CAirTimer_t)
ADD_COMP_MEMPOOL(CEmitter_t)
ADD_COMP_MEMPOOL(CSquishable_t)
END_DEFINE_COMP_MEMPOOL

View File

@ -1,12 +1,13 @@
#ifndef __ENT_IMPL_H
#define __ENT_IMPL_H
#include "assets.h"
#include "assets_tag.h"
#include "components.h"
bool init_player_creation(const char* info_file, Assets_t* assets);
bool init_player_creation_rres(const char* rres_file, const char* file, Assets_t* assets);
Entity_t* create_player(EntityManager_t* ent_manager);
Entity_t* create_dead_player(EntityManager_t* ent_manager);
Entity_t* create_player_finish(EntityManager_t* ent_manager);
bool init_item_creation(Assets_t* assets);
@ -15,6 +16,7 @@ Entity_t* create_boulder(EntityManager_t* ent_manager);
Entity_t* create_arrow(EntityManager_t* ent_manager, uint8_t dir);
Entity_t* create_bomb(EntityManager_t* ent_manager, Vector2 launch_dir);
Entity_t* create_explosion(EntityManager_t* ent_manager);
Entity_t* create_urchin(EntityManager_t* ent_manager);
Entity_t* create_chest(EntityManager_t* ent_manager);
Entity_t* create_level_end(EntityManager_t* ent_manager);

View File

@ -1,3 +1,5 @@
#include "tracy/TracyC.h"
#include "scene_impl.h"
#include "game_systems.h"
#include "water_flow.h"
@ -9,6 +11,7 @@
#include <stdio.h>
static Tile_t all_tiles[MAX_N_TILES] = {0};
static RenderInfoNode all_tile_rendernodes[MAX_N_TILES] = {0};
#define GAME_LAYER 0
#define CONTROL_LAYER 1
@ -20,33 +23,77 @@ static void level_scene_render_func(Scene_t* scene)
BeginTextureMode(scene->layers.render_layers[CONTROL_LAYER].layer_tex);
ClearBackground(BLANK);
if (data->camera.mode == CAMERA_RANGED_MOVEMENT)
{
// TL
DrawLineEx((Vector2){32,32},
(Vector2){64,32}, 5, WHITE);
DrawLineEx((Vector2){32,32},
(Vector2){32,64}, 5, WHITE);
//TR
DrawLineEx((Vector2){data->game_rec.width - 64,32},
(Vector2){data->game_rec.width - 32, 32}, 5, WHITE);
DrawLineEx((Vector2){data->game_rec.width - 32,32},
(Vector2){data->game_rec.width - 32, 64}, 5, WHITE);
//BL
DrawLineEx((Vector2){32,data->game_rec.height-32},
(Vector2){64,data->game_rec.height - 32}, 5, WHITE);
DrawLineEx((Vector2){32,data->game_rec.height-64},
(Vector2){32, data->game_rec.height - 32}, 5, WHITE);
//BR
DrawLineEx((Vector2){data->game_rec.width - 64,data->game_rec.height-32},
(Vector2){data->game_rec.width - 32,data->game_rec.height - 32}, 5, WHITE);
DrawLineEx((Vector2){data->game_rec.width - 32,data->game_rec.height-64},
(Vector2){data->game_rec.width - 32, data->game_rec.height - 32}, 5, WHITE);
}
Entity_t* p_ent;
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_ent)
{
CAirTimer_t* p_air = get_component(p_ent, CAIRTIMER_T);
Vector2 air_pos = {data->game_rec.x + data->game_rec.width - 16, data->game_rec.y + data->game_rec.height - 16};
Sprite_t* spr = get_sprite(&scene->engine->assets, "p_bigbubble");
Vector2 air_pos = {data->game_rec.width - 32, data->game_rec.height - 32};
for (uint8_t i = 0; i < p_air->curr_count; i++)
{
DrawCircleV(air_pos, 16, BLUE);
draw_sprite(spr, 0, air_pos, 0, false);
//DrawCircleV(air_pos, 16, BLUE);
air_pos.x -= 32;
}
}
// For DEBUG
const int gui_x = data->game_rec.x + data->game_rec.width + 10;
//sprintf(buffer, "Spawn Entity: %s", get_spawn_selection_string(current_spawn_selection));
//DrawText(buffer, gui_x, 240, 12, BLACK);
sprintf(buffer, "Number of Entities: %u", sc_map_size_64v(&scene->ent_manager.entities));
DrawText(buffer, gui_x, 70, 12, BLACK);
sprintf(buffer, "FPS: %u", GetFPS());
DrawText(buffer, gui_x, 120, 12, BLACK);
int gui_x = 5;
sprintf(buffer, "%u %u", sc_map_size_64v(&scene->ent_manager.entities), GetFPS());
DrawText(buffer, gui_x, data->game_rec.height - 12, 12, WHITE);
print_mempool_stats(buffer);
DrawText(buffer, gui_x, 150, 12, BLACK);
DrawRectangle(0, 0, data->game_rec.width, 32, (Color){0,0,0,128});
{
DrawText("Z", 300, 5, 24, RED);
Sprite_t* spr = get_sprite(&scene->engine->assets, "eye");
if (data->camera.mode == CAMERA_RANGED_MOVEMENT)
{
draw_sprite(spr, 1, (Vector2){332, 0}, 0, false);
}
else
{
draw_sprite(spr, 0, (Vector2){332, 0}, 0, false);
}
}
DrawText(data->level_pack->levels[data->current_level].level_name, 5, 5, 24, WHITE);
{
sprintf(buffer, "%u / %u", data->coins.current, data->coins.total);
gui_x = data->game_rec.width - MeasureText(buffer, 24) - 5;
// TODO: Use the chest sprite
Sprite_t* spr = get_sprite(&scene->engine->assets, "chest");
draw_sprite_pro(spr, 0, (Vector2){gui_x-32, 8}, 0, 0, (Vector2){0.5,0.5}, WHITE);
DrawText(buffer, gui_x, 5, 24, RED);
}
EndTextureMode();
}
static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
CPlayerState_t* p_playerstate;
sc_map_foreach_value(&scene->ent_manager.component_map[CPLAYERSTATE_T], p_playerstate)
{
@ -54,19 +101,27 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
{
case ACTION_UP:
p_playerstate->player_dir.y = (pressed)? -1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.y -= 20;
break;
case ACTION_DOWN:
p_playerstate->player_dir.y = (pressed)? 1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.y += 20;
break;
case ACTION_LEFT:
p_playerstate->player_dir.x = (pressed)? -1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.x -= 20;
break;
case ACTION_RIGHT:
p_playerstate->player_dir.x = (pressed)? 1 : 0;
if (data->camera.mode == CAMERA_RANGED_MOVEMENT) data->camera.cam.target.x += 20;
break;
case ACTION_JUMP:
p_playerstate->jump_pressed = pressed;
break;
case ACTION_LOOKAHEAD:
p_playerstate->locked = pressed;
data->camera.mode = pressed ? CAMERA_RANGED_MOVEMENT : CAMERA_FOLLOW_PLAYER;
break;
default:
break;
}
@ -89,7 +144,7 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
case ACTION_EXIT:
if(scene->engine != NULL)
{
change_scene(scene->engine, 0);
change_scene(scene->engine, MAIN_MENU_SCENE);
}
break;
default:
@ -100,17 +155,18 @@ static void level_do_action(Scene_t* scene, ActionType_t action, bool pressed)
static void render_regular_game_scene(Scene_t* scene)
{
TracyCZoneN(ctx, "GameRender", true)
// This function will render the game scene outside of the intended draw function
// Just for clarity and separation of logic
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
TileGrid_t tilemap = data->tilemap;
Entity_t* p_ent;
Vector2 min = GetScreenToWorld2D((Vector2){data->game_rec.x, data->game_rec.y}, data->camera.cam);
Vector2 min = GetScreenToWorld2D((Vector2){0, 0}, data->camera.cam);
Vector2 max = GetScreenToWorld2D(
(Vector2){
data->game_rec.x + data->game_rec.width,
data->game_rec.y + data->game_rec.height
data->game_rec.width,
data->game_rec.height
},
data->camera.cam
);
@ -121,10 +177,95 @@ static void render_regular_game_scene(Scene_t* scene)
max.x = (int)fmin(tilemap.width, max.x + 1);
max.y = (int)fmin(tilemap.height, max.y + 1);
// Queue TileMap rendering
for (int tile_y = min.y; tile_y < max.y; tile_y++)
{
for (int tile_x = min.x; tile_x < max.x; tile_x++)
{
int i = tile_x + tile_y * tilemap.width;
// Tile render nodes is static on load and is not dynamically updated.
// We exploit the fact that all tiles can only change into an empty
// tile.
// This won't work if a tile can change into something else
if (tilemap.tiles[i].tile_type == EMPTY_TILE) continue;
uint8_t depth = 2;
if (tilemap.tiles[i].tile_type == LADDER)
{
depth = 0;
}
add_render_node(&data->render_manager, all_tile_rendernodes + i, depth);
}
}
// Queue Sprite rendering
unsigned int ent_idx;
CSprite_t* p_cspr;
sc_map_foreach(&scene->ent_manager.component_map[CSPRITE_T], ent_idx, p_cspr)
{
Entity_t* p_ent = get_entity(&scene->ent_manager, ent_idx);
if (!p_ent->m_alive) continue;
const SpriteRenderInfo_t spr = p_cspr->sprites[p_cspr->current_idx];
if (spr.sprite == NULL) continue;
Vector2 pos = p_ent->position;
CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
if (p_bbox != NULL)
{
pos = Vector2Add(
pos,
get_anchor_offset(p_bbox->size, spr.dest_anchor, p_cspr->node.flip & 1)
);
}
pos = Vector2Subtract(
pos,
get_anchor_offset(spr.sprite->frame_size, spr.src_anchor, p_cspr->node.flip & 1)
);
Vector2 offset = spr.offset;
if (p_cspr->node.flip & 1) offset.x *= -1;
pos = Vector2Add(pos, offset);
// Entity culling
if (
pos.x + spr.sprite->frame_size.x < min.x * tilemap.tile_size
|| pos.x > max.x * tilemap.tile_size
|| pos.y + spr.sprite->frame_size.y < min.y * tilemap.tile_size
|| pos.y > max.y * tilemap.tile_size
)
{
continue;
}
if (p_ent->m_tag == LEVEL_END_TAG)
{
p_cspr->current_frame = 2 * data->selected_solid_tilemap + (
(data->coins.current < data->coins.total) ? 0 : 1);
}
p_cspr->node.spr = spr.sprite;
p_cspr->node.frame_num = p_cspr->current_frame;
p_cspr->node.pos = pos;
add_render_node(&data->render_manager, &p_cspr->node, p_cspr->depth);
}
Texture2D* bg = get_texture(&scene->engine->assets, "bg_tex");
BeginTextureMode(scene->layers.render_layers[GAME_LAYER].layer_tex);
ClearBackground(WHITE);
DrawTexturePro(*bg,
//(Rectangle){0,0,64,64},
(Rectangle){min.x,0, data->game_rec.width, data->game_rec.height},
(Rectangle){0,0, data->game_rec.width, data->game_rec.height},
//(Rectangle){0,0,game_rec.width, game_rec.height},
(Vector2){0,0}, 0.0f, WHITE
);
BeginMode2D(data->camera.cam);
#ifdef ENABLE_RENDER_FALLBACK
for (int tile_y = min.y; tile_y < max.y; tile_y++)
{
for (int tile_x = min.x; tile_x < max.x; tile_x++)
@ -133,66 +274,15 @@ static void render_regular_game_scene(Scene_t* scene)
int x = tile_x * TILE_SIZE;
int y = tile_y * TILE_SIZE;
if (data->tile_sprites[tilemap.tiles[i].tile_type] != NULL)
if (tilemap.tiles[i].tile_type == LADDER)
{
draw_sprite(data->tile_sprites[tilemap.tiles[i].tile_type], 0, (Vector2){x,y}, 0.0f, false);
}
else if (tilemap.tiles[i].tile_type == SOLID_TILE)
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, BLACK);
}
else if (tilemap.tiles[i].tile_type == ONEWAY_TILE)
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, MAROON);
}
else if (tilemap.tiles[i].tile_type == LADDER)
if (data->tile_sprites[tilemap.tiles[i].tile_type] == NULL)
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, ORANGE);
}
else if (tilemap.tiles[i].tile_type == SPIKES)
{
DrawRectangle(
x + tilemap.tiles[i].offset.x, y + tilemap.tiles[i].offset.y,
tilemap.tiles[i].size.x, tilemap.tiles[i].size.y, RED
);
}
if (tilemap.tiles[i].wet)
{
#define SURFACE_THICKNESS 4
int up = i - tilemap.width;
unsigned int bot = i + tilemap.width;
int right = i + 1;
int left = i - 1;
int bot_line = y + TILE_SIZE - tilemap.tiles[i].water_level * WATER_BBOX_STEP - SURFACE_THICKNESS / 2;
if (up >= 0 && tilemap.tiles[up].wet)
{
DrawLineEx((Vector2){x + TILE_SIZE / 2, y}, (Vector2){x + TILE_SIZE / 2, y + TILE_SIZE - tilemap.tiles[i].water_level * WATER_BBOX_STEP}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
if (
bot <= tilemap.n_tiles
&& tilemap.tiles[i].water_level == 0
)
{
if (i % tilemap.width != 0 && tilemap.tiles[left].wet && (tilemap.tiles[bot].solid == SOLID || tilemap.tiles[bot-1].solid == SOLID))
{
DrawLineEx((Vector2){x, bot_line}, (Vector2){x + TILE_SIZE / 2, bot_line}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
if (right % tilemap.width != 0 && tilemap.tiles[right].wet && (tilemap.tiles[bot].solid == SOLID || tilemap.tiles[bot+1].solid == SOLID))
{
DrawLineEx((Vector2){x + TILE_SIZE / 2, bot_line}, (Vector2){x + TILE_SIZE, bot_line}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
}
if (tilemap.tiles[i].max_water_level < MAX_WATER_LEVEL)
{
DrawRectangleLinesEx((Rectangle){x, y, TILE_SIZE, TILE_SIZE}, 2.0, ColorAlpha(BLUE, 0.5));
}
}
}
}
sc_map_foreach_value(&scene->ent_manager.entities, p_ent)
{
CBBox_t* p_bbox = get_component(p_ent, CBBOX_COMP_T);
@ -207,18 +297,8 @@ static void render_regular_game_scene(Scene_t* scene)
|| p_ent->position.y > max.y * tilemap.tile_size
) continue;
// Render Sprite only
CSprite_t* p_cspr = get_component(p_ent, CSPRITE_T);
if (p_cspr != NULL)
{
const SpriteRenderInfo_t spr = p_cspr->sprites[p_cspr->current_idx];
if (spr.sprite != NULL)
{
Vector2 pos = Vector2Add(p_ent->position, spr.offset);
draw_sprite(spr.sprite, p_cspr->current_frame, pos, 0.0f, p_cspr->flip_x);
}
continue;
}
if (p_cspr != NULL) continue;
// Continue here only if no sprite
Color colour;
@ -238,6 +318,12 @@ static void render_regular_game_scene(Scene_t* scene)
break;
}
if (p_ent->m_tag == LEVEL_END_TAG)
{
DrawCircleV(p_ent->position, tilemap.tile_size >> 1, (data->coins.current < data->coins.total)? RED : GREEN);
continue;
}
if (p_bbox != NULL)
{
if (p_ent->m_tag == BOULDER_ENT_TAG)
@ -302,19 +388,6 @@ static void render_regular_game_scene(Scene_t* scene)
}
}
}
sc_map_foreach_value(&scene->ent_manager.entities_map[DYNMEM_ENT_TAG], p_ent)
{
CWaterRunner_t* p_runner = get_component(p_ent, CWATERRUNNER_T);
unsigned int x = ((p_runner->current_tile) % tilemap.width) * tilemap.tile_size;
unsigned int y = ((p_runner->current_tile) / tilemap.width) * tilemap.tile_size;
DrawCircle(x+16, y+16, 8, ColorAlpha(BLUE, 0.6));
}
draw_particle_system(&scene->part_sys);
// Draw water tile
for (int tile_y = min.y; tile_y < max.y; tile_y++)
{
for (int tile_x = min.x; tile_x < max.x; tile_x++)
@ -323,6 +396,84 @@ static void render_regular_game_scene(Scene_t* scene)
int x = tile_x * TILE_SIZE;
int y = tile_y * TILE_SIZE;
if (tilemap.tiles[i].tile_type != LADDER)
{
uint8_t tile_sprite_idx = tilemap.tiles[i].tile_type + tilemap.tiles[i].rotation;
if (tilemap.tiles[i].tile_type == SOLID_TILE)
{
if (data->solid_tile_sprites == NULL) {
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, BLACK);
}
}
else if (data->tile_sprites[tile_sprite_idx] == NULL)
{
if (tilemap.tiles[i].tile_type == ONEWAY_TILE)
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, MAROON);
}
else if (tilemap.tiles[i].tile_type == SPIKES)
{
DrawRectangle(
x + tilemap.tiles[i].offset.x, y + tilemap.tiles[i].offset.y,
tilemap.tiles[i].size.x, tilemap.tiles[i].size.y, RED
);
}
}
}
}
}
#endif //ENABLE_RENDER_FALLBACK
execute_render(&data->render_manager);
// Particle effect will always be in front of everything except water
draw_particle_system(&scene->part_sys);
// Render water
for (int tile_y = min.y; tile_y < max.y; tile_y++)
{
for (int tile_x = min.x; tile_x < max.x; tile_x++)
{
int i = tile_x + tile_y * tilemap.width;
if (!tilemap.tiles[i].wet && tilemap.tiles[i].water_level == 0) {
continue;
}
int x = tile_x * TILE_SIZE;
int y = tile_y * TILE_SIZE;
// Draw water flow
#define SURFACE_THICKNESS 4
int up = i - tilemap.width;
unsigned int bot = i + tilemap.width;
int right = i + 1;
int left = i - 1;
int bot_line = y + TILE_SIZE - tilemap.tiles[i].water_level * WATER_BBOX_STEP - SURFACE_THICKNESS / 2;
if (up >= 0 && tilemap.tiles[up].wet)
{
DrawLineEx((Vector2){x + TILE_SIZE / 2, y}, (Vector2){x + TILE_SIZE / 2, y + TILE_SIZE - tilemap.tiles[i].water_level * WATER_BBOX_STEP}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
if (
bot <= tilemap.n_tiles
&& tilemap.tiles[i].water_level == 0
)
{
if (i % tilemap.width != 0 && tilemap.tiles[left].wet && (tilemap.tiles[bot].solid == SOLID || tilemap.tiles[bot-1].solid == SOLID))
{
DrawLineEx((Vector2){x, bot_line}, (Vector2){x + TILE_SIZE / 2, bot_line}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
if (right % tilemap.width != 0 && tilemap.tiles[right].wet && (tilemap.tiles[bot].solid == SOLID || tilemap.tiles[bot+1].solid == SOLID))
{
DrawLineEx((Vector2){x + TILE_SIZE / 2, bot_line}, (Vector2){x + TILE_SIZE, bot_line}, SURFACE_THICKNESS, ColorAlpha(BLUE, 0.7));
}
}
//if (tilemap.tiles[i].max_water_level < MAX_WATER_LEVEL)
//{
// DrawRectangleLinesEx((Rectangle){x, y, TILE_SIZE, TILE_SIZE}, 2.0, ColorAlpha(BLUE, 0.5));
//}
uint32_t water_height = tilemap.tiles[i].water_level * WATER_BBOX_STEP;
Color water_colour = ColorAlpha(BLUE, 0.5);
DrawRectangle(
@ -334,31 +485,64 @@ static void render_regular_game_scene(Scene_t* scene)
);
}
}
// Draw Border
DrawLine(0, 0, 0, tilemap.height * tilemap.tile_size, BLACK);
DrawLine(0, 0, tilemap.width * TILE_SIZE, 0, BLACK);
int val = (tilemap.width) * tilemap.tile_size;
DrawLine(val, 0, val, tilemap.height * tilemap.tile_size, BLACK);
val = (tilemap.height) * tilemap.tile_size;
DrawLine(0, val, tilemap.width * TILE_SIZE, val, BLACK);
EndMode2D();
EndTextureMode();
TracyCZoneEnd(ctx)
}
static void at_level_start(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
data->sm.state = LEVEL_STATE_RUNNING;
}
static void at_level_complete(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
data->sm.fractional += scene->delta_time;
if (data->sm.fractional > 1.0f)
{
data->sm.fractional -= 1.0f;
data->sm.counter++;
}
if (data->sm.counter >= 1)
{
do_action(scene, ACTION_NEXTLEVEL, false);
data->sm.state = LEVEL_STATE_STARTING;
}
}
void init_game_scene(LevelScene_t* scene)
{
init_scene(&scene->scene, &level_do_action);
init_scene(&scene->scene, &level_do_action, ENABLE_ENTITY_MANAGEMENT_SYSTEM | ENABLE_PARTICLE_SYSTEM);
init_entity_tag_map(&scene->scene.ent_manager, PLAYER_ENT_TAG, 4);
init_entity_tag_map(&scene->scene.ent_manager, BOULDER_ENT_TAG, MAX_COMP_POOL_SIZE);
init_entity_tag_map(&scene->scene.ent_manager, LEVEL_END_TAG, 16);
init_entity_tag_map(&scene->scene.ent_manager, DYNMEM_ENT_TAG, 16);
scene->data.tilemap.tiles = all_tiles;
scene->data.tilemap.render_nodes = all_tile_rendernodes;
init_level_scene_data(
&scene->data, MAX_N_TILES, all_tiles,
(Rectangle){25, 25, 32*TILE_SIZE, 18*TILE_SIZE}
(Rectangle){
0,0,
VIEWABLE_MAP_WIDTH*TILE_SIZE, VIEWABLE_MAP_HEIGHT*TILE_SIZE
}
);
for (size_t i = 0; i < MAX_N_TILES; i++)
{
memset(all_tile_rendernodes + i, 0, sizeof(RenderInfoNode));
all_tile_rendernodes[i].pos = (Vector2){
(i % scene->data.tilemap.width) * TILE_SIZE,
(i / scene->data.tilemap.width) * TILE_SIZE,
};
all_tile_rendernodes[i].scale = (Vector2){1,1};
all_tile_rendernodes[i].colour = WHITE;
}
scene->data.sm.state_functions[LEVEL_STATE_STARTING] = at_level_start;
scene->data.sm.state_functions[LEVEL_STATE_RUNNING] = NULL;
scene->data.sm.state_functions[LEVEL_STATE_DEAD] = NULL;
scene->data.sm.state_functions[LEVEL_STATE_COMPLETE] = at_level_complete;
scene->scene.bg_colour = LIGHTGRAY;
add_scene_layer(
@ -366,18 +550,17 @@ void init_game_scene(LevelScene_t* scene)
scene->data.game_rec
);
add_scene_layer(
&scene->scene, scene->scene.engine->intended_window_size.x,
scene->scene.engine->intended_window_size.y,
(Rectangle){
0, 0,
scene->scene.engine->intended_window_size.x,
scene->scene.engine->intended_window_size.y
}
&scene->scene,
scene->data.game_rec.width, scene->data.game_rec.height,
scene->data.game_rec
);
create_player(&scene->scene.ent_manager);
update_entity_manager(&scene->scene.ent_manager);
// Set up textures
scene->data.solid_tile_sprites = get_sprite(&scene->scene.engine->assets, "stile0");
sc_array_add(&scene->scene.systems, &update_tilemap_system);
sc_array_add(&scene->scene.systems, &player_movement_input_system);
sc_array_add(&scene->scene.systems, &player_bbox_update_system);
@ -393,7 +576,6 @@ void init_game_scene(LevelScene_t* scene)
sc_array_add(&scene->scene.systems, &hitbox_update_system);
sc_array_add(&scene->scene.systems, &player_crushing_system);
sc_array_add(&scene->scene.systems, &spike_collision_system);
sc_array_add(&scene->scene.systems, &edge_velocity_check_system);
sc_array_add(&scene->scene.systems, &state_transition_update_system);
sc_array_add(&scene->scene.systems, &update_entity_emitter_system);
sc_array_add(&scene->scene.systems, &player_ground_air_transition_system);
@ -403,8 +585,10 @@ void init_game_scene(LevelScene_t* scene)
sc_array_add(&scene->scene.systems, &sprite_animation_system);
sc_array_add(&scene->scene.systems, &camera_update_system);
sc_array_add(&scene->scene.systems, &player_dir_reset_system);
sc_array_add(&scene->scene.systems, &player_respawn_system);
sc_array_add(&scene->scene.systems, &update_water_runner_system);
sc_array_add(&scene->scene.systems, &check_player_dead_system);
sc_array_add(&scene->scene.systems, &level_end_detection_system);
sc_array_add(&scene->scene.systems, &level_state_management_system);
sc_array_add(&scene->scene.systems, &render_regular_game_scene);
sc_array_add(&scene->scene.systems, &level_scene_render_func);
// This avoid graphical glitch, not essential
@ -419,11 +603,13 @@ void init_game_scene(LevelScene_t* scene)
sc_map_put_64(&scene->scene.action_map, KEY_R, ACTION_RESTART);
sc_map_put_64(&scene->scene.action_map, KEY_RIGHT_BRACKET, ACTION_NEXTLEVEL);
sc_map_put_64(&scene->scene.action_map, KEY_LEFT_BRACKET, ACTION_PREVLEVEL);
sc_map_put_64(&scene->scene.action_map, KEY_Z, ACTION_LOOKAHEAD);
}
void free_game_scene(LevelScene_t* scene)
{
clear_all_game_entities(scene);
free_scene(&scene->scene);
term_level_scene_data(&scene->data);
}

File diff suppressed because it is too large Load Diff

View File

@ -16,16 +16,16 @@ void state_transition_update_system(Scene_t* scene);
void update_entity_emitter_system(Scene_t* scene);
void update_tilemap_system(Scene_t* scene);
void hitbox_update_system(Scene_t* scene);
void edge_velocity_check_system(Scene_t* scene);
void sprite_animation_system(Scene_t* scene);
void boulder_destroy_wooden_tile_system(Scene_t* scene);
void camera_update_system(Scene_t* scene);
void container_destroy_system(Scene_t* scene);
void player_dir_reset_system(Scene_t* scene);
void player_respawn_system(Scene_t* scene);
void check_player_dead_system(Scene_t* scene);
void lifetimer_update_system(Scene_t* scene);
void airtimer_update_system(Scene_t* scene);
void spike_collision_system(Scene_t* scene);
void level_end_detection_system(Scene_t* scene);
void level_state_management_system(Scene_t* scene);
#endif // __GAME_SYSTEMS_H

View File

@ -1,20 +1,31 @@
#include "EC.h"
#include "assets_tag.h"
#include "ent_impl.h"
#include "constants.h"
#include "raymath.h"
static SpriteRenderInfo_t item_sprite_map[20] = {0};
static SpriteRenderInfo_t item_sprite_map[22] = {0};
#define URCHIN_OFFSET 6
bool init_item_creation(Assets_t* assets)
{
item_sprite_map[0].sprite = get_sprite(assets, "w_crate");
item_sprite_map[1].sprite = get_sprite(assets, "m_crate");
item_sprite_map[2].sprite = get_sprite(assets, "r_arrow");
item_sprite_map[2].offset = (Vector2){-8, 4};
//item_sprite_map[2].offset = (Vector2){-8, 6};
item_sprite_map[2].src_anchor = AP_MID_CENTER;
item_sprite_map[2].src_anchor = AP_MID_CENTER;
item_sprite_map[3].sprite = get_sprite(assets, "u_arrow");
item_sprite_map[3].src_anchor = AP_MID_CENTER;
item_sprite_map[3].src_anchor = AP_MID_CENTER;
item_sprite_map[4].sprite = get_sprite(assets, "l_arrow");
item_sprite_map[4].src_anchor = AP_MID_CENTER;
item_sprite_map[4].src_anchor = AP_MID_CENTER;
item_sprite_map[5].sprite = get_sprite(assets, "d_arrow");
item_sprite_map[5].src_anchor = AP_MID_CENTER;
item_sprite_map[5].src_anchor = AP_MID_CENTER;
item_sprite_map[6].sprite = get_sprite(assets, "bomb");
item_sprite_map[6].offset = (Vector2){0, -4};
item_sprite_map[6].src_anchor = AP_MID_CENTER;
item_sprite_map[6].src_anchor = AP_MID_CENTER;
item_sprite_map[7].sprite = get_sprite(assets, "w_ra_crate");
item_sprite_map[8].sprite = get_sprite(assets, "m_ra_crate");
item_sprite_map[9].sprite = get_sprite(assets, "w_ua_crate");
@ -26,9 +37,16 @@ bool init_item_creation(Assets_t* assets)
item_sprite_map[15].sprite = get_sprite(assets, "w_b_crate");
item_sprite_map[16].sprite = get_sprite(assets, "m_b_crate");
item_sprite_map[17].sprite = get_sprite(assets, "explode");
item_sprite_map[17].offset = (Vector2){-12, -12};
item_sprite_map[17].src_anchor = AP_MID_CENTER;
item_sprite_map[17].src_anchor = AP_MID_CENTER;
item_sprite_map[18].sprite = get_sprite(assets, "chest");
item_sprite_map[19].sprite = get_sprite(assets, "boulder");
item_sprite_map[20].sprite = get_sprite(assets, "exit");
item_sprite_map[20].src_anchor = AP_BOT_CENTER;
item_sprite_map[20].src_anchor = AP_BOT_CENTER;
item_sprite_map[20].offset = (Vector2){0, TILE_SIZE >> 1};
item_sprite_map[21].sprite = get_sprite(assets, "urchin");
item_sprite_map[21].offset = (Vector2){-URCHIN_OFFSET, -URCHIN_OFFSET};
return true;
}
@ -44,7 +62,7 @@ Entity_t* create_crate(EntityManager_t* ent_manager, bool metal, ContainerItem_t
p_bbox->fragile = false;
CTransform_t* p_ctransform = add_component(p_crate, CTRANSFORM_COMP_T);
p_ctransform->grav_delay = 0.20f;
p_ctransform->grav_delay = 0.10f;
p_ctransform->shape_factor = metal ? (Vector2){0.7,0.7} : (Vector2){0.8,0.8} ;
add_component(p_crate, CMOVEMENTSTATE_T);
@ -56,6 +74,9 @@ Entity_t* create_crate(EntityManager_t* ent_manager, bool metal, ContainerItem_t
CSprite_t* p_cspr = add_component(p_crate, CSPRITE_T);
p_cspr->sprites = item_sprite_map;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->node.colour = WHITE;
{
CContainer_t* p_container = add_component(p_crate, CCONTAINER_T);
p_container->material = metal? METAL_CONTAINER : WOODEN_CONTAINER;
@ -104,6 +125,9 @@ Entity_t* create_boulder(EntityManager_t* ent_manager)
CSprite_t* p_cspr = add_component(p_boulder, CSPRITE_T);
p_cspr->sprites = item_sprite_map;
p_cspr->current_idx = 19;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->node.colour = WHITE;
p_cspr->depth = 2;
return p_boulder;
}
@ -127,26 +151,32 @@ Entity_t* create_arrow(EntityManager_t* ent_manager, uint8_t dir)
CSprite_t* p_cspr = add_component(p_arrow, CSPRITE_T);
p_cspr->sprites = item_sprite_map;
p_cspr->current_idx = 2;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->node.colour = WHITE;
//p_hitbox->boxes[0] = (Rectangle){TILE_SIZE - 5, TILE_SIZE / 2 - 5, 5, 5};
const int HITBOX_LONG_SIDE = 10;
const int HITBOX_SHORT_SIDE = 4;
const int CENTER_POSITION = (TILE_SIZE - HITBOX_SHORT_SIDE) >> 1;
const int HITBOX_CENTER = (HITBOX_SHORT_SIDE >> 1);
switch(dir)
{
case 0:
p_hitbox->boxes[0] = (Rectangle){10, TILE_SIZE / 2 - 5, 10, 5};
p_ctransform->velocity.x = -ARROW_SPEED;
p_cspr->current_idx += 2;
p_hitbox->boxes[0] = (Rectangle){-CENTER_POSITION, -HITBOX_CENTER, HITBOX_LONG_SIDE, HITBOX_SHORT_SIDE};
break;
case 2:
p_hitbox->boxes[0] = (Rectangle){TILE_SIZE / 2 - 5, 10, 5, 10};
p_hitbox->boxes[0] = (Rectangle){-HITBOX_CENTER, -CENTER_POSITION, HITBOX_SHORT_SIDE, HITBOX_LONG_SIDE};
p_ctransform->velocity.y = -ARROW_SPEED;
p_cspr->current_idx += 1;
break;
case 3:
p_hitbox->boxes[0] = (Rectangle){TILE_SIZE / 2 - 5, 10, 5, 10};
p_hitbox->boxes[0] = (Rectangle){-HITBOX_CENTER, CENTER_POSITION - HITBOX_LONG_SIDE, HITBOX_SHORT_SIDE, HITBOX_LONG_SIDE};
p_ctransform->velocity.y = ARROW_SPEED;
p_cspr->current_idx += 3;
break;
default:
p_hitbox->boxes[0] = (Rectangle){10, TILE_SIZE / 2 - 5, 10, 5};
p_hitbox->boxes[0] = (Rectangle){CENTER_POSITION - HITBOX_LONG_SIDE, -HITBOX_CENTER, HITBOX_LONG_SIDE, HITBOX_SHORT_SIDE};
p_ctransform->velocity.x = ARROW_SPEED;
break;
}
@ -159,22 +189,12 @@ Entity_t* create_bomb(EntityManager_t* ent_manager, Vector2 launch_dir)
Entity_t* p_bomb = add_entity(ent_manager, DESTRUCTABLE_ENT_TAG);
if (p_bomb == NULL) return NULL;
p_bomb->position.x += (TILE_SIZE - 25) / 2;
p_bomb->position.y += (TILE_SIZE - 25) / 2;
if (launch_dir.x > 0)
{
p_bomb->position.x += TILE_SIZE/ 2;
}
else if (launch_dir.x < 0)
{
p_bomb->position.x -= TILE_SIZE / 2;
}
add_component(p_bomb, CTILECOORD_COMP_T);
add_component(p_bomb, CMOVEMENTSTATE_T);
CHitBoxes_t* p_hitbox = add_component(p_bomb, CHITBOXES_T);
p_hitbox->n_boxes = 1;
p_hitbox->boxes[0] = (Rectangle){0, 0, 25, 25};
p_hitbox->boxes[0] = (Rectangle){-13, -13, 26, 26};
p_hitbox->atk = 0;
p_hitbox->one_hit = true;
@ -185,6 +205,8 @@ Entity_t* create_bomb(EntityManager_t* ent_manager, Vector2 launch_dir)
CSprite_t* p_cspr = add_component(p_bomb, CSPRITE_T);
p_cspr->sprites = item_sprite_map;
p_cspr->current_idx = 6;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->node.colour = WHITE;
CTransform_t* p_ctransform = add_component(p_bomb, CTRANSFORM_COMP_T);
p_ctransform->active = true;
@ -201,8 +223,8 @@ Entity_t* create_explosion(EntityManager_t* ent_manager)
Entity_t* p_explosion = add_entity(ent_manager, DESTRUCTABLE_ENT_TAG);
if (p_explosion == NULL) return NULL;
p_explosion->position.x -= 16;
p_explosion->position.y -= 16;
//p_explosion->position.x -= 16;
//p_explosion->position.y -= 16;
add_component(p_explosion, CTILECOORD_COMP_T);
CHitBoxes_t* p_hitbox = add_component(p_explosion, CHITBOXES_T);
p_hitbox->n_boxes = 1;
@ -212,17 +234,63 @@ Entity_t* create_explosion(EntityManager_t* ent_manager)
CTransform_t* p_ctransform = add_component(p_explosion, CTRANSFORM_COMP_T);
p_ctransform->movement_mode = KINEMATIC_MOVEMENT;
p_ctransform->active = true;
p_hitbox->boxes[0] = (Rectangle){0, 0, TILE_SIZE + 32, TILE_SIZE + 32};
const int hitbox_sz = (TILE_SIZE << 1) + (TILE_SIZE >> 1);
p_hitbox->boxes[0] = (Rectangle){-(hitbox_sz >> 1), -(hitbox_sz >> 1), hitbox_sz, hitbox_sz};
CSprite_t* p_cspr = add_component(p_explosion, CSPRITE_T);
p_cspr->sprites = item_sprite_map;
p_cspr->current_idx = 17;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->node.colour = WHITE;
p_cspr->depth = 3;
CLifeTimer_t* p_clifetimer = add_component(p_explosion, CLIFETIMER_T);
p_clifetimer->life_time = 0.05f;
return p_explosion;
}
Entity_t* create_urchin(EntityManager_t* ent_manager)
{
// The hit box is larger than the bbox
// Unfortunately, it's too late to incorporate the offset for the bbox component
// So, offset the hitbox instead and external reposition it.
Entity_t* p_urchin = add_entity(ent_manager, URCHIN_ENT_TAG);
if (p_urchin == NULL) return NULL;
CBBox_t* p_bbox = add_component(p_urchin, CBBOX_COMP_T);
set_bbox(p_bbox, TILE_SIZE-(URCHIN_OFFSET<<1), TILE_SIZE-(URCHIN_OFFSET<<1));
CTransform_t* p_ctransform = add_component(p_urchin, CTRANSFORM_COMP_T);
p_ctransform->movement_mode = KINEMATIC_MOVEMENT;
p_ctransform->bounce_coeff = 1.0f;
p_ctransform->velocity.x = -100;
p_ctransform->active = true;
add_component(p_urchin, CTILECOORD_COMP_T);
add_component(p_urchin, CMOVEMENTSTATE_T);
CHurtbox_t* p_hurtbox = add_component(p_urchin, CHURTBOX_T);
p_hurtbox->size = p_bbox->size;
p_hurtbox->def = 2;
p_hurtbox->damage_src = -1;
CHitBoxes_t* p_hitbox = add_component(p_urchin, CHITBOXES_T);
p_hitbox->n_boxes = 1;
p_hitbox->boxes[0] = (Rectangle) {-(URCHIN_OFFSET>>1),-(URCHIN_OFFSET>>1),TILE_SIZE-URCHIN_OFFSET,TILE_SIZE-URCHIN_OFFSET};
p_hitbox->atk = 2;
CSprite_t* p_cspr = add_component(p_urchin, CSPRITE_T);
p_cspr->sprites = item_sprite_map;
p_cspr->current_idx = 21;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->node.colour = WHITE;
p_cspr->depth = 2;
add_component(p_urchin, CSQUISHABLE_T);
return p_urchin;
}
Entity_t* create_chest(EntityManager_t* ent_manager)
{
Entity_t* p_chest = add_entity(ent_manager, CHEST_ENT_TAG);
@ -231,7 +299,7 @@ Entity_t* create_chest(EntityManager_t* ent_manager)
CBBox_t* p_bbox = add_component(p_chest, CBBOX_COMP_T);
set_bbox(p_bbox, TILE_SIZE, TILE_SIZE);
p_bbox->solid = true;
p_bbox->fragile = true;
p_bbox->fragile = false;
CTransform_t* p_ctransform = add_component(p_chest, CTRANSFORM_COMP_T);
p_ctransform->grav_delay = 0.3f;
@ -246,6 +314,9 @@ Entity_t* create_chest(EntityManager_t* ent_manager)
CSprite_t* p_cspr = add_component(p_chest, CSPRITE_T);
p_cspr->sprites = item_sprite_map;
p_cspr->current_idx = 18;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->node.colour = WHITE;
p_cspr->depth = 2;
return p_chest;
@ -256,6 +327,12 @@ Entity_t* create_level_end(EntityManager_t* ent_manager)
Entity_t* p_flag = add_entity(ent_manager, LEVEL_END_TAG);
if (p_flag == NULL) return NULL;
add_component(p_flag, CTRANSFORM_COMP_T);
CSprite_t* p_cspr = add_component(p_flag, CSPRITE_T);
p_cspr->sprites = item_sprite_map;
p_cspr->current_idx = 20;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->node.colour = WHITE;
add_component(p_flag, CTILECOORD_COMP_T);
return p_flag;
}

View File

@ -1,25 +1,27 @@
#include "scene_impl.h"
#include "assets_tag.h"
#include "gui.h"
#include "raymath.h"
#include <stdio.h>
static void level_select_render_func(Scene_t* scene)
{
LevelSelectSceneData_t* data = &(CONTAINER_OF(scene, LevelSelectScene_t, scene)->data);
Sprite_t* level_board = get_sprite(&scene->engine->assets, "lvl_board");
Sprite_t* level_select = get_sprite(&scene->engine->assets, "lvl_select");
Sprite_t* preview = get_sprite(&scene->engine->assets, "lvlprvw");
Font* menu_font = get_font(&scene->engine->assets, "MenuFont");
BeginTextureMode(scene->layers.render_layers[0].layer_tex);
ClearBackground(RAYWHITE);
Rectangle draw_rec = (Rectangle){
0, data->scroll,
scene->layers.render_layers[0].render_area.width,
scene->layers.render_layers[0].render_area.height
};
Vector2 draw_pos = {50, 50};
draw_rec.height *= -1;
DrawTextureRec(
data->level_display.texture,
draw_rec,
draw_pos,
WHITE
);
ClearBackground(BLANK);
draw_sprite(level_select, 0, (Vector2){0,0},0, false);
draw_sprite(level_board, 0, (Vector2){level_select->frame_size.x,0},0, false);
draw_sprite(preview, data->scroll_area.curr_selection, (Vector2){
level_select->frame_size.x + (level_board->frame_size.x - preview->frame_size.x) / 2,
(level_board->frame_size.y - preview->frame_size.y) / 2,
},0, false);
DrawTextEx(*menu_font, "Level Select", (Vector2){60, 20}, 40, 4, BLACK);
vert_scrollarea_render(&data->scroll_area);
EndTextureMode();
}
@ -29,17 +31,70 @@ static void level_select_do_action(Scene_t* scene, ActionType_t action, bool pre
switch(action)
{
case ACTION_UP:
if (pressed)
if (!pressed)
{
data->scroll += 3;
data->scroll = Clamp(data->scroll, 0, 400);
if (data->scroll_area.curr_selection > 0)
{
data->scroll_area.curr_selection--;
vert_scrollarea_refocus(&data->scroll_area);
}
}
break;
case ACTION_DOWN:
if (pressed)
if (!pressed)
{
data->scroll -= 3;
data->scroll = Clamp(data->scroll, 0, 400);
if (data->scroll_area.curr_selection < data->level_pack->n_levels - 1)
{
data->scroll_area.curr_selection++;
vert_scrollarea_refocus(&data->scroll_area);
}
}
break;
case ACTION_EXIT:
if (!pressed)
{
if(scene->engine != NULL)
{
change_scene(scene->engine, MAIN_MENU_SCENE);
}
}
break;
case ACTION_NEXT_SPAWN:
if (!pressed)
{
unsigned int prev_sel = data->scroll_area.curr_selection;
// TODO: Add scene offset to scroll area calculation
if (vert_scrollarea_set_pos(&data->scroll_area, scene->mouse_pos) != data->scroll_area.max_items)
{
vert_scrollarea_refocus(&data->scroll_area);
if (prev_sel == data->scroll_area.curr_selection)
{
if (data->level_pack != NULL && data->scroll_area.curr_selection < data->level_pack->n_levels)
{
// TODO: Need to load the current level
LevelScene_t* level_scene = (LevelScene_t*)change_scene(scene->engine, GAME_SCENE);
level_scene->data.level_pack = data->level_pack;
level_scene->data.current_level = data->scroll_area.curr_selection;
reload_level_tilemap(level_scene);
}
}
}
}
break;
case ACTION_CONFIRM:
if (!pressed)
{
if (data->level_pack != NULL && data->scroll_area.curr_selection < data->level_pack->n_levels)
{
// TODO: Need to load the current level
LevelScene_t* level_scene = (LevelScene_t*)change_scene(scene->engine, GAME_SCENE);
level_scene->data.level_pack = data->level_pack;
level_scene->data.current_level = data->scroll_area.curr_selection;
reload_level_tilemap(level_scene);
}
}
break;
default:
@ -47,31 +102,64 @@ static void level_select_do_action(Scene_t* scene, ActionType_t action, bool pre
}
}
#define FONT_SIZE 22
#define TEXT_PADDING 3
#define SCROLL_TOTAL_HEIGHT 800
void init_level_select_scene(LevelSelectScene_t* scene)
{
init_scene(&scene->scene, &level_select_do_action);
init_scene(&scene->scene, &level_select_do_action, 0);
add_scene_layer(
&scene->scene, 300, 400,
(Rectangle){0, 0, 300, 400}
);
scene->data.scroll = 400;
scene->data.level_display = LoadRenderTexture(300, 800);
const unsigned int n_elems = 800 / (12+3);
BeginTextureMode(scene->data.level_display);
ClearBackground(GRAY);
for (unsigned int i = 0; i < n_elems; ++i)
{
char buf[32];
sprintf(buf, "Level %u", i);
DrawText(buf, 0, (12+3) * i, 12, BLACK);
&scene->scene, scene->scene.engine->intended_window_size.x,
scene->scene.engine->intended_window_size.y,
(Rectangle){
0, 0,
scene->scene.engine->intended_window_size.x,
scene->scene.engine->intended_window_size.y
}
EndTextureMode();
);
scene->scene.bg_colour = BLACK;
Sprite_t* level_select = get_sprite(&scene->scene.engine->assets, "lvl_select");
vert_scrollarea_init(&scene->data.scroll_area, (Rectangle){50, 75, level_select->frame_size.x * 0.6, level_select->frame_size.y * 3 / 4}, (Vector2){level_select->frame_size.x * 0.6, SCROLL_TOTAL_HEIGHT});
vert_scrollarea_set_item_dims(&scene->data.scroll_area, FONT_SIZE, TEXT_PADDING);
Font* menu_font = get_font(&scene->scene.engine->assets, "MenuFont");
if (menu_font != NULL) {
scene->data.scroll_area.comp.font = *menu_font;
}
char buf[32];
ScrollAreaRenderBegin(&scene->data.scroll_area);
ClearBackground(BLANK);
if (scene->data.level_pack != NULL)
{
vert_scrollarea_n_items(&scene->data.scroll_area, scene->data.level_pack->n_levels);
for (unsigned int i = 0; i < scene->data.level_pack->n_levels; ++i)
{
vert_scrollarea_insert_item(&scene->data.scroll_area, scene->data.level_pack->levels[i].level_name, i);
}
for (unsigned int i = scene->data.level_pack->n_levels; i < scene->data.scroll_area.max_items; ++i)
{
vert_scrollarea_insert_item(&scene->data.scroll_area, "---", i);
}
}
else
{
for (unsigned int i = 0; i < scene->data.scroll_area.max_items; ++i)
{
sprintf(buf, "Level %u", i);
vert_scrollarea_insert_item(&scene->data.scroll_area, buf, i);
}
}
ScrollAreaRenderEnd();
sc_array_add(&scene->scene.systems, &level_select_render_func);
sc_map_put_64(&scene->scene.action_map, KEY_UP, ACTION_UP);
sc_map_put_64(&scene->scene.action_map, KEY_DOWN, ACTION_DOWN);
sc_map_put_64(&scene->scene.action_map, KEY_Q, ACTION_EXIT);
sc_map_put_64(&scene->scene.action_map, KEY_ENTER, ACTION_CONFIRM);
sc_map_put_64(&scene->scene.action_map, MOUSE_LEFT_BUTTON, ACTION_NEXT_SPAWN); // Abuse an unused action
}
void free_level_select_scene(LevelSelectScene_t* scene)
{
vert_scrollarea_free(&scene->data.scroll_area);
free_scene(&scene->scene);
}

View File

@ -1,18 +1,50 @@
#include "scene_impl.h"
#include "assets_tag.h"
#include "raymath.h"
#include <stdio.h>
static void menu_scene_render_func(Scene_t* scene)
{
MenuSceneData_t* data = &(CONTAINER_OF(scene, MenuScene_t, scene)->data);
Sprite_t* spr = get_sprite(&scene->engine->assets, "title_spr");
Sprite_t* title_spr = get_sprite(&scene->engine->assets, "title_board");
Sprite_t* title_select = get_sprite(&scene->engine->assets, "title_select");
Rectangle render_rec = scene->layers.render_layers[0].render_area;
Font* menu_font = get_font(&scene->engine->assets, "MenuFont");
BeginTextureMode(scene->layers.render_layers[0].layer_tex);
ClearBackground(RAYWHITE);
DrawText("This is a game", 25, 220, 12, BLACK);
UI_button(data->buttons, "Start");
UI_button(data->buttons + 1, "Sandbox");
UI_button(data->buttons + 2, "Continue");
UI_button(data->buttons + 3, "Exit");
draw_sprite(spr, 0, (Vector2){0, 0}, 0, false);
draw_sprite(title_spr, 0, (Vector2){32, 10}, 0, false);
Vector2 font_sz = MeasureTextEx(*menu_font, "Bunny's Spelunking Adventure", 56, 0);
Vector2 title_pos = {
.x = (render_rec.width - font_sz.x) / 2,
.y = 32 + (title_spr->frame_size.y - font_sz.y) / 2
};
DrawTextEx(*menu_font, "Bunny's Spelunking Adventure", title_pos, 56, 0, BLACK);
const char* OPTIONS[3] = {"Start", "Credits", "Exit"};
for (uint8_t i = 0; i < 3; ++i)
{
Vector2 pos = (Vector2){data->buttons[i].bbox.x, data->buttons[i].bbox.y};
pos = Vector2Add(
pos,
shift_bbox(
(Vector2){
data->buttons[i].bbox.width,data->buttons[i].bbox.height
},
title_select->frame_size,
AP_MID_CENTER
)
);
draw_sprite(title_select, 0, pos, 0, false);
font_sz = MeasureTextEx(*menu_font, OPTIONS[i], 32, 6);
Vector2 title_pos = {
.x = pos.x + (title_select->frame_size.x - font_sz.x) / 2,
.y = pos.y + (title_spr->frame_size.y) / 2 - font_sz.y
};
hover_text(data->buttons + i, *menu_font, OPTIONS[i], title_pos, 32, 6, BLACK);
}
EndTextureMode();
}
@ -21,12 +53,9 @@ static void exec_component_function(Scene_t* scene, int sel)
switch(sel)
{
case 0:
change_scene(scene->engine, 1);
change_scene(scene->engine, LEVEL_SELECT_SCENE);
break;
case 1:
change_scene(scene->engine, 2);
break;
case 3:
case 2:
scene->state = 0;
break;
default:
@ -132,28 +161,31 @@ static void gui_loop(Scene_t* scene)
void init_menu_scene(MenuScene_t* scene)
{
init_scene(&scene->scene, &menu_do_action);
init_scene(&scene->scene, &menu_do_action, 0);
sc_array_add(&scene->scene.systems, &gui_loop);
sc_array_add(&scene->scene.systems, &menu_scene_render_func);
int button_x = scene->scene.engine->intended_window_size.x / 8;
int button_y = scene->scene.engine->intended_window_size.y / 3;
int spacing = 100;
scene->data.buttons[0] = (UIComp_t) {
.bbox = {25,255,125,30},
.bbox = {button_x,button_y,125,30},
.state = STATE_NORMAL,
.alpha = 1.0
};
scene->data.buttons[1] = (UIComp_t) {
.bbox = {25,300,125,30},
.bbox = {button_x,button_y+spacing,125,30},
.state = STATE_NORMAL,
.alpha = 1.0
};
scene->data.buttons[2] = (UIComp_t) {
.bbox = {25,345,125,30},
.bbox = {button_x,button_y+spacing * 2,125,30},
.state = STATE_NORMAL,
.alpha = 1.0
};
scene->data.buttons[3] = (UIComp_t) {
.bbox = {25,390,125,30},
.bbox = {button_x,button_y+spacing * 3,125,30},
.state = STATE_NORMAL,
.alpha = 1.0
};

View File

@ -1,10 +1,11 @@
#include "engine.h"
#include "ent_impl.h"
#include "constants.h"
#include <stdio.h>
#include <string.h>
#include "raymath.h"
#define N_PLAYER_SPRITES 9
#define N_PLAYER_SPRITES 12
enum PlayerSpriteEnum
{
SPR_PLAYER_STAND = 0,
@ -15,7 +16,10 @@ enum PlayerSpriteEnum
SPR_PLAYER_CROUCH,
SPR_PLAYER_CRMOVE,
SPR_PLAYER_SWIM,
SPR_PLAYER_USWIM,
SPR_PLAYER_DSWIM,
SPR_PLAYER_DEAD,
SPR_PLAYER_ENTER,
};
static SpriteRenderInfo_t player_sprite_map[N_PLAYER_SPRITES] = {0};
@ -27,8 +31,13 @@ static unsigned int player_sprite_transition_func(Entity_t* ent)
CSprite_t* p_spr = get_component(ent, CSPRITE_T);
CPlayerState_t* p_plr = get_component(ent, CPLAYERSTATE_T);
if (p_ctrans->velocity.x > 0) p_spr->flip_x = true;
else if (p_ctrans->velocity.x < 0) p_spr->flip_x = false;
if (p_ctrans->velocity.x > 0) {
p_spr->node.flip |= 1;
}
else if (p_ctrans->velocity.x < 0)
{
p_spr->node.flip &= ~1;
}
p_spr->pause = false;
@ -50,11 +59,29 @@ static unsigned int player_sprite_transition_func(Entity_t* ent)
}
else if (p_move->water_state & 1)
{
if (p_ctrans->velocity.y > 50) return SPR_PLAYER_DSWIM;
if (p_ctrans->velocity.y < -50) return SPR_PLAYER_USWIM;
return SPR_PLAYER_SWIM;
}
return (p_ctrans->velocity.y < 0) ? SPR_PLAYER_JUMP : SPR_PLAYER_FALL;
}
static void player_sfx_func(GameEngine_t* engine, Entity_t* ent) {
CSprite_t* p_spr = get_component(ent, CSPRITE_T);
if (p_spr->current_idx == SPR_PLAYER_RUN) {
if (p_spr->current_frame % 3 == 0) {
play_sfx_pitched(engine, PLAYER_STEP_SFX, p_spr->current_frame == 0?1.0f : 0.75f );
}
}
if (p_spr->current_idx == SPR_PLAYER_CRMOVE) {
if (p_spr->current_frame % 2 == 0) {
play_sfx(engine, PLAYER_STEP_SFX);
}
}
}
Entity_t* create_player(EntityManager_t* ent_manager)
{
Entity_t* p_ent = add_entity(ent_manager, PLAYER_ENT_TAG);
@ -68,7 +95,7 @@ Entity_t* create_player(EntityManager_t* ent_manager)
p_ct->shape_factor = (Vector2){1, 1};
CJump_t* p_cjump = add_component(p_ent, CJUMP_COMP_T);
p_cjump->jump_speed = 680;
p_cjump->jump_speed = JUMP_SPEED;
p_cjump->jumps = 1;
p_cjump->max_jumps = 1;
p_cjump->jump_ready = true;
@ -98,16 +125,22 @@ Entity_t* create_player(EntityManager_t* ent_manager)
CAirTimer_t* p_air = add_component(p_ent, CAIRTIMER_T);
p_air->max_count = 10;
p_air->curr_count = 10;
p_air->max_ftimer = 1.0f;
p_air->max_ftimer = 0.9f;
p_air->decay_rate = 1.0f;
CSprite_t* p_cspr = add_component(p_ent, CSPRITE_T);
p_cspr->sprites = player_sprite_map;
p_cspr->transition_func = &player_sprite_transition_func;
p_cspr->sfx_func = &player_sfx_func;
p_cspr->node.colour = WHITE;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->depth = 1;
CEmitter_t* p_emitter = add_component(p_ent, CEMITTER_T);
p_emitter->offset = (Vector2){7,0};
add_component(p_ent, CSQUISHABLE_T);
return p_ent;
}
@ -124,12 +157,69 @@ Entity_t* create_dead_player(EntityManager_t* ent_manager)
CSprite_t* p_cspr = add_component(p_ent, CSPRITE_T);
p_cspr->sprites = player_sprite_map;
p_cspr->current_idx = SPR_PLAYER_DEAD;
p_cspr->node.colour = WHITE;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->depth = 3;
add_component(p_ent, CMOVEMENTSTATE_T);
return p_ent;
}
static unsigned int player_finish_transition_func(Entity_t* ent)
{
CSprite_t* p_spr = get_component(ent, CSPRITE_T);
// Due to index-from-0
if (p_spr->current_frame == p_spr->sprites[p_spr->current_idx].sprite->frame_count - 1)
{
p_spr->pause = true;
//remove_entity(ent->manager, ent->m_id);
}
return p_spr->current_idx;
}
Entity_t* create_player_finish(EntityManager_t* ent_manager)
{
Entity_t* p_ent = add_entity(ent_manager, NO_ENT_TAG);
if (p_ent == NULL) return NULL;
CSprite_t* p_cspr = add_component(p_ent, CSPRITE_T);
p_cspr->sprites = player_sprite_map;
p_cspr->current_idx = SPR_PLAYER_ENTER;
p_cspr->transition_func = &player_finish_transition_func;
p_cspr->node.colour = WHITE;
p_cspr->node.scale = (Vector2){1, 1};
p_cspr->depth = 1;
CLifeTimer_t* p_clifetimer = add_component(p_ent, CLIFETIMER_T);
p_clifetimer->life_time = 0.9f;
return p_ent;
}
static AnchorPoint_t parse_anchor_symbol(const char symbol[2])
{
if (symbol[0] == 't')
{
if (symbol[1] == 'l') return AP_TOP_LEFT;
if (symbol[1] == 'c') return AP_TOP_CENTER;
if (symbol[1] == 'r') return AP_TOP_RIGHT;
}
else if (symbol[0] == 'm')
{
if (symbol[1] == 'l') return AP_MID_LEFT;
if (symbol[1] == 'c') return AP_MID_CENTER;
if (symbol[1] == 'r') return AP_MID_RIGHT;
}
else if (symbol[0] == 'b')
{
if (symbol[1] == 'l') return AP_BOT_LEFT;
if (symbol[1] == 'c') return AP_BOT_CENTER;
if (symbol[1] == 'r') return AP_BOT_RIGHT;
}
return AP_TOP_LEFT;
}
static bool init_player_file(FILE* in_file, Assets_t* assets)
{
static bool already_init = false;
@ -156,18 +246,23 @@ static bool init_player_file(FILE* in_file, Assets_t* assets)
while(*info_str == ' ' || *info_str == '\t') info_str++;
Vector2 offset;
char src_ap_symbol[3];
char dest_ap_symbol[3];
int data_count = sscanf(
info_str, "%f,%f",
&offset.x, &offset.y
info_str, "%f,%f,%2s,%2s",
&offset.x, &offset.y, src_ap_symbol, dest_ap_symbol
);
if (data_count !=2)
if (data_count != 4)
{
printf("Unable to parse info for player at line %lu\n", line_num);
return false;
}
Sprite_t* spr = get_sprite(assets, name);
spr->anchor = Vector2Scale(spr->frame_size, 0.5f);
player_sprite_map[i].sprite = spr;
player_sprite_map[i].offset = offset;
player_sprite_map[i].src_anchor = parse_anchor_symbol(src_ap_symbol);
player_sprite_map[i].dest_anchor = parse_anchor_symbol(dest_ap_symbol);
i++;
}
already_init = true;

View File

@ -27,8 +27,14 @@ typedef struct CoinCounter
uint16_t total;
}CoinCounter_t;
typedef enum CameraMode {
CAMERA_FOLLOW_PLAYER = 0,
CAMERA_RANGED_MOVEMENT,
} CameraMode_t;
typedef struct LevelCamera {
Camera2D cam;
CameraMode_t mode;
Vector2 target_pos;
float base_y;
//Vector2 prev_pos;
@ -36,8 +42,25 @@ typedef struct LevelCamera {
float mass;
float c; // damping factor
float k; // spring constant
float range_limit;
}LevelCamera_t;
typedef enum LevelSceneState {
LEVEL_STATE_STARTING = 0,
LEVEL_STATE_RUNNING,
LEVEL_STATE_DEAD,
LEVEL_STATE_COMPLETE,
} LevelSceneState_t;
typedef struct LevelSceneStateMachine {
LevelSceneState_t state;
system_func_t state_functions[4];
// Engine has no timeline support, so make a pseudo timeline implementation
float fractional;
uint32_t counter;
} LevelSceneStateMachine_t;
typedef struct LevelSceneData {
TileGrid_t tilemap;
// TODO: game_rec is actually obsolete since this is in the scene game layer
@ -50,8 +73,18 @@ typedef struct LevelSceneData {
unsigned int current_level;
CoinCounter_t coins;
bool show_grid;
Vector2 player_spawn;
LevelSceneStateMachine_t sm;
RenderManager render_manager;
}LevelSceneData_t;
static inline void change_level_state(LevelSceneData_t* data, LevelSceneState_t state)
{
data->sm.state = state;
data->sm.counter = 0;
data->sm.fractional = 0;
}
typedef struct LevelScene {
Scene_t scene;
LevelSceneData_t data;
@ -62,6 +95,8 @@ void free_game_scene(LevelScene_t* scene);
void init_sandbox_scene(LevelScene_t* scene);
void free_sandbox_scene(LevelScene_t* scene);
void init_level_scene_data(LevelSceneData_t* data, uint32_t max_tiles, Tile_t* tiles, Rectangle view_zone);
void clear_an_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent);
void clear_all_game_entities(LevelScene_t* scene);
void term_level_scene_data(LevelSceneData_t* data);
void reload_level_tilemap(LevelScene_t* scene);
void load_next_level_tilemap(LevelScene_t* scene);
@ -87,8 +122,8 @@ typedef struct MenuScene {
} MenuScene_t;
typedef struct LevelSelectSceneData {
RenderTexture2D level_display;
float scroll;
VertScrollArea_t scroll_area;
LevelPack_t* level_pack;
} LevelSelectSceneData_t;
typedef struct LevelSelectScene {

View File

@ -1,10 +1,15 @@
#include "collisions.h"
#include "scene_impl.h"
#include "water_flow.h"
#include "ent_impl.h"
#include "constants.h"
#include "raymath.h"
void init_level_scene_data(LevelSceneData_t* data, uint32_t max_tiles, Tile_t* tiles, Rectangle view_zone)
{
init_render_manager(&data->render_manager);
data->game_rec = view_zone;
memset(&data->camera, 0, sizeof(LevelCamera_t));
data->camera.cam.rotation = 0.0f;
@ -12,6 +17,7 @@ void init_level_scene_data(LevelSceneData_t* data, uint32_t max_tiles, Tile_t* t
data->camera.mass = 0.2f;
data->camera.k = 6.2f;
data->camera.c = 2.2f;
data->camera.range_limit = 200.0f;
data->tilemap.max_tiles = max_tiles;
if (tiles != NULL)
@ -40,6 +46,9 @@ void init_level_scene_data(LevelSceneData_t* data, uint32_t max_tiles, Tile_t* t
}
memset(&data->coins, 0, sizeof(data->coins));
data->show_grid = false;
memset(&data->sm, 0, sizeof(data->sm));
}
void term_level_scene_data(LevelSceneData_t* data)
@ -50,6 +59,44 @@ void term_level_scene_data(LevelSceneData_t* data)
}
}
void clear_an_entity(Scene_t* scene, TileGrid_t* tilemap, Entity_t* p_ent)
{
/* Use the helper function to remove any entity
* This is because some components may have deinit steps
* This function will also take care of the tilemap collision handling
* */
CEmitter_t* p_emitter = get_component(p_ent, CEMITTER_T);
if (p_emitter != NULL)
{
unload_emitter_handle(&scene->part_sys, p_emitter->handle);
}
if (get_component(p_ent, CWATERRUNNER_T)!= NULL)
{
free_water_runner(p_ent, &scene->ent_manager);
}
remove_entity_from_tilemap(&scene->ent_manager, tilemap, p_ent);
}
void clear_all_game_entities(LevelScene_t* scene)
{
Entity_t* ent;
sc_map_foreach_value(&scene->scene.ent_manager.entities, ent)
{
clear_an_entity(&scene->scene, &scene->data.tilemap, ent);
}
// This is unnecessary as the first pass should clear everything
// For now, leave it in. This is not expected to call all the time
// so not too bad
clear_entity_manager(&scene->scene.ent_manager);
for (size_t i = 0; i < scene->data.tilemap.n_tiles;i++)
{
sc_map_clear_64v(&scene->data.tilemap.tiles[i].entities_set);
}
}
bool load_level_tilemap(LevelScene_t* scene, unsigned int level_num)
{
if (level_num >= scene->data.level_pack->n_levels) return false;
@ -62,27 +109,61 @@ bool load_level_tilemap(LevelScene_t* scene, unsigned int level_num)
scene->data.tilemap.width = lvl_map.width;
scene->data.tilemap.height = lvl_map.height;
scene->data.tilemap.n_tiles = n_tiles;
scene->data.coins.current = 0;
scene->data.coins.total = lvl_map.n_chests;
#define N_SOLID_TILESETS 3
static const char* SOLID_TILE_SELECTIONS[N_SOLID_TILESETS] = {
"stile0", "stile1", "stile2"
};
scene->data.selected_solid_tilemap = lvl_map.flags;
scene->data.solid_tile_sprites = get_sprite(&scene->scene.engine->assets, SOLID_TILE_SELECTIONS[lvl_map.flags]);
clear_all_game_entities(scene);
clear_entity_manager(&scene->scene.ent_manager);
for (size_t i = 0; i < scene->data.tilemap.n_tiles;i++)
{
scene->data.tilemap.tiles[i].max_water_level = 4;
scene->data.tilemap.tiles[i].solid = NOT_SOLID;
scene->data.tilemap.tiles[i].tile_type = EMPTY_TILE;
scene->data.tilemap.tiles[i].rotation = TILE_NOROTATE;
scene->data.tilemap.tiles[i].connectivity = 0;
scene->data.tilemap.tiles[i].moveable = true;
scene->data.tilemap.tiles[i].offset = (Vector2){0, 0};
scene->data.tilemap.tiles[i].size = (Vector2){TILE_SIZE, TILE_SIZE};
sc_map_clear_64v(&scene->data.tilemap.tiles[i].entities_set);
scene->data.tilemap.tiles[i].def = 0;
if (lvl_map.tiles[i].tile_type == 1)
memset(scene->data.tilemap.render_nodes + i, 0, sizeof(RenderInfoNode));
scene->data.tilemap.render_nodes[i].pos = (Vector2){
(i % scene->data.tilemap.width) * TILE_SIZE,
(i / scene->data.tilemap.width) * TILE_SIZE,
};
scene->data.tilemap.render_nodes[i].scale = (Vector2){1,1};
scene->data.tilemap.render_nodes[i].colour = WHITE;
if (lvl_map.tiles[i].tile_type == SOLID_TILE)
{
change_a_tile(&scene->data.tilemap, i, SOLID_TILE);
}
scene->data.tilemap.tiles[i].water_level = lvl_map.tiles[i].water;
if (lvl_map.tiles[i].water > MAX_WATER_LEVEL) {
scene->data.tilemap.tiles[i].max_water_level = 0;
}
// Two pass
else
{
scene->data.tilemap.tiles[i].max_water_level = 4;
scene->data.tilemap.tiles[i].water_level = lvl_map.tiles[i].water;
scene->data.tilemap.tiles[i].wet = scene->data.tilemap.tiles[i].water_level > 0;
}
}
// Two pass, because some tile depends on the solidity of the tiles
for (size_t i = 0; i < scene->data.tilemap.n_tiles;i++)
{
if (lvl_map.tiles[i].tile_type == SOLID_TILE)
{
change_a_tile(&scene->data.tilemap, i, SOLID_TILE);
}
if (lvl_map.tiles[i].tile_type >= 8 && lvl_map.tiles[i].tile_type < 20)
{
uint32_t tmp_idx = lvl_map.tiles[i].tile_type - 8;
@ -136,12 +217,74 @@ bool load_level_tilemap(LevelScene_t* scene, unsigned int level_num)
Entity_t* ent = create_player(&scene->scene.ent_manager);
ent->position.x = (i % scene->data.tilemap.width) * scene->data.tilemap.tile_size;
ent->position.y = (i / scene->data.tilemap.width) * scene->data.tilemap.tile_size;
ent->spawn_pos = ent->position;
scene->data.camera.target_pos.x = ent->position.x;
scene->data.camera.target_pos.y = ent->position.y;
scene->data.camera.cam.target.x = ent->position.x;
scene->data.camera.cam.target.y = ent->position.y;
}
break;
case 23:
{
Entity_t* ent = create_chest(&scene->scene.ent_manager);
ent->position.x = (i % scene->data.tilemap.width) * scene->data.tilemap.tile_size;
ent->position.y = (i / scene->data.tilemap.width) * scene->data.tilemap.tile_size;
CTransform_t* p_ctransform = get_component(ent, CTRANSFORM_COMP_T);
p_ctransform->active = true;
}
break;
case 24:
{
Entity_t* ent = create_level_end(&scene->scene.ent_manager);
if (ent != NULL)
{
ent->position.x = (i % scene->data.tilemap.width) * scene->data.tilemap.tile_size;
ent->position.y = (i / scene->data.tilemap.width) * scene->data.tilemap.tile_size + (scene->data.tilemap.tile_size >> 1);
}
}
break;
default:
break;
}
if (lvl_map.tiles[i].tile_type >= 25)
{
Entity_t* ent = create_urchin(&scene->scene.ent_manager);
if (ent != NULL)
{
CBBox_t* p_bbox = get_component(ent, CBBOX_COMP_T);
ent->position.x = (i % scene->data.tilemap.width) * scene->data.tilemap.tile_size + (scene->data.tilemap.tile_size >> 1) - p_bbox->half_size.x;
ent->position.y = (int)(i / scene->data.tilemap.width) * scene->data.tilemap.tile_size + (scene->data.tilemap.tile_size >> 1) - p_bbox->half_size.y;
uint8_t spd_encoding = lvl_map.tiles[i].tile_type - 25;
float angle = 45.0f / 180.0f * PI * ((spd_encoding >> 2) & 7);
float mag = 75 * (spd_encoding & 3);
CTransform_t* p_ct = get_component(ent, CTRANSFORM_COMP_T);
p_ct->velocity = Vector2Scale(
(Vector2){cosf(angle), sinf(angle)}, mag
);
}
}
}
// This only works for static loading.
// If a tilemap change change to some other arbitrary tile.
// Then this should be done while changing a tile.
if (lvl_map.tiles[i].tile_type == SOLID_TILE)
{
scene->data.tilemap.render_nodes[i].spr = scene->data.solid_tile_sprites;
scene->data.tilemap.render_nodes[i].frame_num = CONNECTIVITY_TILE_MAPPING[
scene->data.tilemap.tiles[i].connectivity
];
}
else
{
uint8_t tile_sprite_idx = scene->data.tilemap.tiles[i].tile_type + scene->data.tilemap.tiles[i].rotation;
scene->data.tilemap.render_nodes[i].spr = scene->data.tile_sprites[
tile_sprite_idx
];
}
}
@ -188,20 +331,20 @@ void change_a_tile(TileGrid_t* tilemap, unsigned int tile_idx, TileType_t new_ty
break;
case LADDER:
{
int up_tile = tile_idx - tilemap->width;
if (up_tile > 0 && tilemap->tiles[up_tile].tile_type != LADDER)
{
tilemap->tiles[tile_idx].solid = ONE_WAY;
}
else
//int up_tile = tile_idx - tilemap->width;
//if (up_tile > 0 && tilemap->tiles[up_tile].tile_type != LADDER)
//{
// tilemap->tiles[tile_idx].solid = ONE_WAY;
//}
//else
{
tilemap->tiles[tile_idx].solid = NOT_SOLID;
}
unsigned int down_tile = tile_idx + tilemap->width;
if (down_tile < tilemap->n_tiles && tilemap->tiles[down_tile].tile_type == LADDER)
{
tilemap->tiles[down_tile].solid = (tilemap->tiles[tile_idx].tile_type != LADDER)? ONE_WAY : NOT_SOLID;
}
//unsigned int down_tile = tile_idx + tilemap->width;
//if (down_tile < tilemap->n_tiles && tilemap->tiles[down_tile].tile_type == LADDER)
//{
// tilemap->tiles[down_tile].solid = (tilemap->tiles[tile_idx].tile_type != LADDER)? ONE_WAY : NOT_SOLID;
//}
}
break;
case SPIKES:
@ -217,37 +360,39 @@ void change_a_tile(TileGrid_t* tilemap, unsigned int tile_idx, TileType_t new_ty
unsigned int down_tile = tile_idx + tilemap->width;
if (down_tile < tilemap->n_tiles && tilemap->tiles[down_tile].tile_type == LADDER)
{
tilemap->tiles[down_tile].solid = ONE_WAY;
tilemap->tiles[down_tile].solid = NOT_SOLID;
}
}
tilemap->tiles[tile_idx].rotation = TILE_NOROTATE;
const int SPIKE_HITBOX_LONGSIDE = 30;
const int SPIKE_HITBOX_SHORTSIDE = 12;
if (new_type == SPIKES)
{
// Priority: Down, Up, Left, Right
if (tile_idx + tilemap->width < tilemap->n_tiles && tilemap->tiles[tile_idx + tilemap->width].tile_type == SOLID_TILE)
{
tilemap->tiles[tile_idx].offset = (Vector2){0,16};
tilemap->tiles[tile_idx].size = (Vector2){32,16};
tilemap->tiles[tile_idx].offset = (Vector2){0,tilemap->tile_size - SPIKE_HITBOX_SHORTSIDE};
tilemap->tiles[tile_idx].size = (Vector2){SPIKE_HITBOX_LONGSIDE, SPIKE_HITBOX_SHORTSIDE};
}
else if (tile_idx - tilemap->width >= 0 && tilemap->tiles[tile_idx - tilemap->width].tile_type == SOLID_TILE)
else if (tile_idx >= tilemap->width && tilemap->tiles[tile_idx - tilemap->width].tile_type == SOLID_TILE)
{
tilemap->tiles[tile_idx].offset = (Vector2){0,0};
tilemap->tiles[tile_idx].size = (Vector2){32,16};
tilemap->tiles[tile_idx].size = (Vector2){SPIKE_HITBOX_LONGSIDE, SPIKE_HITBOX_SHORTSIDE};
tilemap->tiles[tile_idx].rotation = TILE_180ROT;
}
else if (tile_idx % tilemap->width != 0 && tilemap->tiles[tile_idx - 1].tile_type == SOLID_TILE)
{
tilemap->tiles[tile_idx].offset = (Vector2){0,0};
tilemap->tiles[tile_idx].size = (Vector2){16,32};
tilemap->tiles[tile_idx].size = (Vector2){SPIKE_HITBOX_SHORTSIDE, SPIKE_HITBOX_LONGSIDE};
tilemap->tiles[tile_idx].rotation = TILE_90CWROT;
}
else if ((tile_idx + 1) % tilemap->width != 0 && tilemap->tiles[tile_idx + 1].tile_type == SOLID_TILE)
{
tilemap->tiles[tile_idx].offset = (Vector2){16,0};
tilemap->tiles[tile_idx].size = (Vector2){16,32};
tilemap->tiles[tile_idx].offset = (Vector2){tilemap->tile_size - SPIKE_HITBOX_SHORTSIDE,0};
tilemap->tiles[tile_idx].size = (Vector2){SPIKE_HITBOX_SHORTSIDE, SPIKE_HITBOX_LONGSIDE};
tilemap->tiles[tile_idx].rotation = TILE_90CCWROT;
}
else
@ -270,6 +415,7 @@ void change_a_tile(TileGrid_t* tilemap, unsigned int tile_idx, TileType_t new_ty
tilemap->tiles[tile_idx].moveable = (
tilemap->tiles[tile_idx].tile_type == EMPTY_TILE
|| tilemap->tiles[tile_idx].tile_type == SPIKES
|| tilemap->tiles[tile_idx].tile_type == LADDER
);
tilemap->tiles[tile_idx].def = (tilemap->tiles[tile_idx].tile_type == SOLID_TILE) ? 5: 2;

View File

@ -289,11 +289,10 @@ void update_water_runner_system(Scene_t* scene)
break;
case SCANLINE_FILL:
{
const float FILL_RATE = 1.0f/24;
const float FILL_RATE = 1.0f/22;
p_crunner->fractional += scene->delta_time;
if (p_crunner->fractional < FILL_RATE) break;
p_crunner->fractional -= FILL_RATE;
// Unsigned usage here is okay
unsigned int start_tile =
(p_crunner->current_tile / p_crunner->bfs_tilemap.width) * p_crunner->bfs_tilemap.width;
@ -308,6 +307,7 @@ void update_water_runner_system(Scene_t* scene)
if (curr_tile->water_level < curr_tile->max_water_level)
{
curr_tile->water_level++;
p_crunner->fractional -= FILL_RATE;
}
if (curr_tile->water_level < curr_tile->max_water_level)
{

1
tracy 160000

@ -0,0 +1 @@
Subproject commit 5d542dc09f3d9378d005092a4ad446bd405f819a

View File

@ -110,7 +110,7 @@ static void level_scene_render_func(Scene_t* scene)
if (spr.sprite != NULL)
{
Vector2 pos = Vector2Add(p_ent->position, spr.offset);
draw_sprite(spr.sprite, p_cspr->current_frame, pos, 0.0f, p_cspr->flip_x);
draw_sprite(spr.sprite, p_cspr->current_frame, pos, 0.0f, p_cspr->node.flip & 1);
}
}
}
@ -400,7 +400,7 @@ int main(void)
LevelScene_t scene;
scene.scene.engine = &engine;
init_scene(&scene.scene, &level_do_action);
init_scene(&scene.scene, &level_do_action, ENABLE_ENTITY_MANAGEMENT_SYSTEM);
init_entity_tag_map(&scene.scene.ent_manager, PLAYER_ENT_TAG, 4);
init_entity_tag_map(&scene.scene.ent_manager, DYNMEM_ENT_TAG, 16);
init_level_scene_data(