Integrate the SFXR UI into RGM, and the SFXR audio generation routines as a library in ENIGMA.
SFXR is DrPetter's sound-effect generator; it's used quite often in competitions such as the Ludum Dare as it is an extremely useful tool for prototyping. You can find a copy of its source code both on its homepage and at this Git mirror, with work towards a Qt version already having happened in this repository.
Integrating SFXR into ENIGMA brings a couple of benefits:
Because there are lots of versions of SFXR available, including a Qt version, it's up to the proposer of this project to determine the best starting point and path forward. The two primary options are as follows:
To extend this project with machine learning, a contributor would need to propose a plan to obtain label data. Examples of this plan:
Mentors: Josh, Greg
Difficulty: Small (depending on proposed scope)
Expected size: 90h (can be grown in scope to fit 175h or even 350h with ML)
Skills required: C++ fundamentals, Qt (for IDE integration)
ENIGMA currently lacks third party integration with various digital distribution platforms such as Steam, Xbox, or Origin. First class support for these services would make it easier for Indie developers to publish ENIGMA created games. Developers would like for it to be easier to monetize their games and enable them to release bigger and better updates to their users. Compatibility with existing GameMaker API would be a secondary priority, as GameMaker only supports Steam officially.
This part will allow developers to create a multiplayer games that can be saved on cloud using Steam features.
This is the second part of #1881, where you will complete the implementation of Steamworks extension to give ENIGMA's users full integration of Steamworks SDK into their games. Main PRs: #2343 and #2350 (check the work done in there).
Note that the contributor will need this previous work for Dhruv Chawla (here) and this PR #2361. The buffer work is merged into AST-Generation
, which means most of the work will be in there. Also, you will have to refactor the whole demo game as it's not created for AST-Generation
branch but for master.
The contributor should build ENIGMA on Linux first. You can then build the extension after following instructions inside this README.md file. Currently, no other OS is supported as the binder only supports Linux. DON'T forget to work on top of this PR #2363, until it's merged.
The contributor will need to work on RGM to implement the required UI for the extension. Example: the app_id
textEdit widget.
Mentors: Robert, Josh
Difficulty: Medium - Hard
Expected size: 350h
Skills required: C++ fundamentals, Web/HTTP API, JSON, testing fundamentals, and Qt.
Skills preferred: Knowledge of batch API requests and Steam Workshop profile is ideal.
Mentors: Greg, Robert, Josh
Difficulty: Medium
Expected size: 350h
Preferred Skills:
Mentors: Greg, Robert, Josh
Difficulty: Medium
Expected size: 350h
Preferred Skills:
Last year's project, titled "Data Buffers/Serialization", led to a complete
rewrite of the EDL (ENIGMA Development Language) parser. As it stands, the
parser can parse most of the EDL language, with some features which are
incomplete marked as "Unimplemented".
Mentors: Josh, Greg
Difficulty: Medium/Hard
Expected size: 350h
Skills required: C++ fundamentals, compiler fundamentals
We would also like to support resource groups so that a user may load and unload specific resources at certain times, i.e, load_resource_group(level1)
loading all of the resources contained in the level1 group. The groups would be specified by full or relative tree paths from the root of their project. Relative paths would make sense in the current executable context (e.g, if the function is called from a script, object, or room, etc). Groups are primarily created and organized through a frontend editor, like RadialGM, and the metadata for them is included in the tree node protocol buffer used by the compiler.
Milestones
Mentors: Josh, Greg
Difficulty: Hard
Expected size: 350h
Skills required: C++ fundamentals
Skills preferred: Experience with resource management in video game engines
Closes #1881. This is the second part of my first PR #2343.
This PR is part of the Google Summer of Code 2023
program.
I will be updating my Logs inside my domain.
LeaderboardEntry
data typeLeaderboardDisplayType
constantLeaderboardSortOrder
constant✅ Adding support for other platforms in the Makefile file.
✅ Switching the Default
property in the Steamworks & Json extensions YAML file to false
.
✅ Switching the cxxflags
property in Compilers/Linux/gcc.ey
to -fdiagnostics-color=always
.
⬜ Reviewing all error messages and documentation in newly added files.
✅ Adding the license to all newly added files.
✅ Providing installation instructions for all platforms.
✅ Modifying the extension to call steam_init();
automatically when enabled.
✅ Match all naming conventions with GMS's Steamworks extension.
⬜ Testing the APIs with the latest version of Steamworks SDK, Steam, OS version, etc.
⬜ Must test all EDL scripts in the example game provided.
⬜ Providing SOG/unit tests for all functions written (this need to be studied carefully as in order to test the C++ wrapper tester must have steam installed and running or I may use mocks).
✅ Clearing garbage files.
✅ DEBUG_MESSAGE()
must exist only in APIs files. Try to remove it from wrapper files. Try to make the wrapper independent on ENIGMA.
✅ Fix example game background design.
⬜ Update the example game to match the latest version of GMS Steamworks extension.
✅ The compiler must write a file next to the exe.
⬜ Review includes.
✅ Commit the example game again and remove it from .gitignore
file.
⬜ The extension and the example must be compatible with GameMaker.
⬜ Set the title for Steamworks demo to Created with ENIGMA
.
⬜ Monitor GMS's terminal behavior.
⬜ Test the changes with RGM.
⬜ Solve all build warnings.
Aside from these, I also made the following pull requests which got merged:
libEGM
Also, I made the following pull requests which did not get merged:
MSVC x86_64
CII tracked my progress approximately weekly on my personal blog: https://dc03.github.io/
My GSoC project spans into three PRs in three different projects of two separate organizations. The two organizations involved are Enigma-dev and Tiled. fundies and Josh are my mentors from Enigma-dev and bjorn is my mentor from Tiled.
Requirement: You have to use Arch linux OS specifically, as Enigma-dev's RadialGM only compiles in Arch linux at the time of writing these steps. Compiling RadialGM can be tough, so feel free to ping in enigma-dev discord server.
Steps to test the project:
Short demo video of the final outcome: https://www.youtube.com/watch?v=ZUJd5VhqQo8
Weekly project work logs: https://kartikshrivastava.github.io/
Tiled TMX and TSX importer support status in RadialGM and enigma-dev:
Enigma's EGM importer support status in Tiled:
SHADER FIX
In task 1 I started with some bug fixes like Refactoring for GLES build
which included:
This fix was needed because OpenGL-Common has shared code between GLES and all OpenGL and some functions were only available in OpenGL, not GLES because of which they were giving errors.
After this the next bug I fixed was the Scaling issue and presence of garbage in the android build which included :
For fixing the scaling issue I had to modify few functions such as window_get_width() & window_get_height() with the idea of using SDL_GL_GetDrawableSize() instead of SDL_GetWindowSize().
Since resizing window is not possible in android I modified initGameWindow() as well by removing the macro since we do not need it anymore.
For fixing the garbage issue I added some new functions like graphics_remove_garbage() in Platforms\SDL\event.cpp and d3d_enable_scissor_test() in OpenGL-Common\d3d.cpp.
static void graphics_remove_garbage(float x, float y, float width, float height) {
graphics_set_viewport(x,y,width,height);
enigma_user::d3d_enable_scissor_test(false);
enigma_user::draw_clear(enigma_user::window_get_color());
}
void d3d_enable_scissor_test(bool enable) {
if(enable==true)
glEnable(GL_SCISSOR_TEST);
else
glDisable(GL_SCISSOR_TEST);
}
I modified the windowsResized() function in event.cpp used the graphics_remove_garbage() function inside it and the d3d_enable_scissor_test() was simply used to enable and disable the scissor test.
A scissor test is what tells the GPU to stay within a particular rectangle when filling. It's an optimization (which may or may not matter when already specifying a viewport). Once enabled, pixels outside the scissor box(drawing area) will be discarded, removing the garbage around our game.
Below is the comparison of what it looked like before and after adding these changes and building the game again:
game screenshots:
BEFORE | AFTER |
---|---|
Both the scaling and garbage issues were fixed.
After this, I started my first task for which I created a sample game with some sprites and circles and added different colors to them just to see the error in our shader.
Below is the difference between Android and Windows before the shader fix :
Windows
:
After this, my mentors suggested that I should learn GLSL so I learned few concepts of GLSL like swizzling, samplers, etc and I got to know that we can access all the 4 components of a vec4 in any order you want.
My fix for the Shader issue was using "TexColor = texture( en_TexSampler, v_TextureCoord.st ) * v_Color;" for OpenGL and "TexColor.bgra = texture( en_TexSampler, v_TextureCoord.st ).rgba * v_Color;" for GLES. I did this by storing these strings in a common variable texColorString which I added in OpenGL-Common/shader.h and I used this variable in the raw string which is returned in getDefaultFragmentShader function.
@RobertBColton tested these changes and below are the test results:
Shader fix commits
: b8ec0bf
Task 1 completed.
ADDING A NEW AUDIO SYSTEM
We needed a new audio system that was able to work for android, so I created an SDL audio system with the help of SDL_mixer
I used SDL_mixer docs to get all the functions I need for this audio system like :
First I created SoundResource.h which has the sound structure and some functions like update, destroy etc. Then I created SDLsystem which has audiosystem_initialize() which is responsible for initializing the SDL mixer api using Mix_OpenAudio and loading support for different audio sample formats using Mix_Init.
After this I added sound functions in SDLbasic.cpp like sound_exists, sound_add, sound_play, sound_delete, sound_stop, sound_loop and few more. These functions are common for all the audio systems. I used AssetArray for sounds because it is easy to access sound objects using the id.
For example below is my sound_play function which uses an SDL_mixer function,
bool sound_play(int sound) {
const Sound& snd = sounds.get(sound);
if (Mix_PlayChannel(-1,snd.mc, 0) == -1) { return false; }
return true;
}
In the function above, sounds is the AssetArray of Sound objects and I use the id passed as a parameter to get the Sound. The sound structure has a chunk pointer(mc) and Mix_PlayChannel is the mixer function for playing the chunk on a channel. The other basic functions are made using the same approach.
Once I finished the basic functions I started the audio functions. For audio functions, I added SoundChanel.h which had the SoundChannel structure which looked like
struct SoundChannel {
Mix_Chunk *mchunk;
int soundIndex;
double priority;
};
SoundChannel is being used because most of the audio functions for the SDL audio system use channel id instead of sound id.
Just like sound functions the audio functions are common for all the audio systems. I created SDLadvanced.cpp
and added audio functions like audio_exists, audio_add, audio_play_sound, audio_stop_sound and few more.
The audio functions are made using the logic I used for sound functions. For example:
sound_ispaused:
bool sound_ispaused(int sound) {
const Sound& snd = sounds.get(sound);
int channel_count = Mix_AllocateChannels(-1);
for(int i = 0;i <= channel_count ; i++) {
if (Mix_GetChunk(i) == snd.mc && Mix_Paused(i) == 1) { return true; }
}
return false;
}
audio_is_paused:
bool audio_is_paused(int index) {
if (index >= AUDIO_CHANNEL_OFFSET) {
return Mix_Paused(index - AUDIO_CHANNEL_OFFSET);
}
const Sound& snd = sounds.get(index);
for(size_t i = 0; i < sound_channels.size() ; i++) {
if (sound_channels[i]->mchunk == snd.mc && Mix_Paused(i) == 1) { return true; }
}
return false;
}
logic is the same but one uses only sound while the other uses both sound and channel structures.
This is because sound basic is not intended to be multichannel, but advanced audio is intended to take full advantage of SDL_mixer. Audio functions are positional multichannel audio (much more powerful) & sound functions are kept for backwards compatibility. The audio_play_sound function returns the channel id, that way the user can store the id of the channel in a variable then all the volume/pan/stop/pause functions will take both sound id and sound channel id. Using the advanced audio functions, the user gets full control over each channel.
After finishing the required functions for both SDLbasic.cpp and SDLadvanced.cpp I started testing for both windows and android and I was able to hear the audio for both windows and android builds.
The tests were successful for FLAC
, MP3
, OGG
, WAV
audio file formats.
Audio system commits
: c87994f, 0d55208
Task 2 completed.
ADDING THE TILT FEATURE
@fundies provided some useful links for this task like:
SDL_android.c & SDLActivity.java
after going through these files we decided to use Android_JNI_GetAccelerometerValues.
I started task 3 by first adding android.cpp in Platforms\SDL\Android and added 3 functions device_get_tilt_x, device_get_tilt_y & device_get_tilt_z and included SDL_android.h which is a requirement for Android_JNI_GetAccelerometerValues.
Then I added override CXXFLAGS += -I$(ENIGMA_ROOT)/android/external/SDL2/src/core/android/ & SOURCES += $(wildcard Platforms/SDL/Android/*.cpp) to the makefile present in Platforms\SDL. Below is the implementation of device_get_tilt_x
float device_get_tilt_x()
{
float Values[3],tilt_x;
Android_JNI_GetAccelerometerValues(Values);
tilt_x = Values[0];
return tilt_x;
}
The other 2 functions are similar to this and the only change is that tilt_y returns Values[1] and tilt_z returns Values[2].
I added the tilt feature in the first audio system commit: c87994f
Task 3 completed.
This pull request contributes to the collision detection system (#1882) in the ENIGMA, adding a completely new feature of using polygons for detecting collisions instead of the other two options available, BBOX, and Precise. It uses simple or complex polygons to mask objects for collisions while extending the interface for the Collision Detection System that is used by PRECISE and BBOX collision Systems and also provides Polygons as separate resources that can be used for Collisions and Drawing.
I added a new class of Engine resources to use, Polygons, which are closed shapes defined by a set of points that can be assigned to objects for detecting collisions.
polygon_internal.h
in the UniversalSystems/Resources/
.Diagonals
that store the internal diagonal information of a polygon, and is used for triangulation (see below).MinMaxProjection
to store projection data used for collision detection (see below).Polygon
class stores the following information in it:
std::vector<glm::vec2> points
are the main polygon points that are stored in a clockwise fashion, with the assumption that the polygon formed from the points is a close region.std::vector<glm::vec2> offsetPoints
are the polygon points that are offset, stored to improve time performance by caching them once.std::vector<Diagonal> diagonals
are the internal diagonals of the polygons after triangulation, stored to improve time performance by caching them once.int height, width
are the dimensions of the bounding box of the original polygonglm::vec2 offset
is the offset point inside the polygon that is considered the origin inside ENIGMA and LGM. This is the point where the x
and y
of an instance will reside over.bool concave
indicates whether a polygon is concave (true) or convex (false).Polygon
class includes the following noteworthy methods:
void addPoint(point)
adds a polygon point, assuming that the function is used to add points in a clockwise manner and that the point added closes the polygon with the first point. This function is overloaded to work with both glm::vec2
and a tuple of double
void removePoints(point)
remove the polygon point, assuming that the point before and after this point will form a line segment and complete the polygon.Polygon
and the Diagonal
class, the file polygon_internal.h
also has some important mathematical functions that are used for computation throughout the codebase:
rotateVector(vector, angle, pivot)
rotates the vector from the given pivot to the angle specified (radians), and returns the rotated vector.computeLeftNormal(vector)
computes the left side normal of a vector (see image)angleBetweenVectors
computes the angle between vectors using the x-axis as the origin vector. The angle is computed in radians.Added the file polygon.h
which complete Interface for Creating, Editing, and Manipulating Polygons inside GML Scripts, with functions that can be extended to IDE as well like LGM or RGM. These functions are the main interface for ENIGMA users to use the new system. Some of the noteworthy functions in this are:
polygon_add(height, width)
adds a new polygon resource inside the AssetArray<Polygon> polygons
. The AssetArray
is an external resource list that can be accessed by both enigma
and enigma_user
namespaces. This function returns int polygon_id
which is the AssetArray
assigned ID to the polygon resource and must be provided to use subsequent interface functionspolygon_add_point(polygonID, point)
Adds the point according to the above-mentioned assumptions in the polygon with the ID specified.polygon_remove_point(polygonID, point)
removes the point according to the above-mentioned assumptions in the polygon with the ID specified.polygon_set_offset(polygonID, x, y)
sets the offset of the polygon resource with the ID specified.polygon_decompose()
calls the triangulation of the concave polygon, to decompose into smaller subpolygons (see Triangulation for details).Finally, it includes a complete API interface for Attaching and Transforming Polygons with Objects and Instances.
int polygon_index
in the Universal_System/Object_Tiers/collisions_object.h
. This ensures that each instance of the collisions_object
has a polygon attached to it. By default, this value is -1
to signal that no polygons are attached.gs_scalar polygon_xscale, polygon_yscale, polygon_angle
inside Universal_System/Object_Tiers/collisions_object.h
.
Universal_System\Resources\polygon_internal.h
. In each case, points are sent by reference to reduce the redundant copying of vectors.:
offsetPoints(points, x, y)
to add the offset into the list of points.rotatePoints(points, angle, origin)
to rotate the list of points about the origin and the angle specified in radians.scalePoints(points, scale_x, scale_y)
to scale the list of points on the x
and the y
axes.transformPoints(points, x, y, angle, origin, scale_x, scale_y)
combines the above 3 functions into a single operation, for efficient computation that is at a needed per-frame basis.The main collision detection function works in 6 layers inside the ENIGMA's engine and is listed down from core to interface:
Projection Layer computes the MinMax projection of vectors and points in polygon and ellipse cases.
Separating Axis Theorem Layer computes the actual collision detection between the polygon shapes (convex only) using the MinMax projections. This handles cases for both elliptical and simple polygon shapes
Polygon-case Layer abstracts Concave collision detection from the interface functions, by providing a single point for any polygon collision.
Collision-Shape Layer is the first layer to abstract from mathematics. It computes collision checks for different shapes and also incorporates Bounding Box collision checks
Main Layer computes collision detection between instances and handles all the instance, transformation, and object-related logic. This is the layer that can benefit from a Broadphase collision system (see Future Works).
Interface Layer is the existing GML Layer that falls in the enigma_namespace
, and is ENIGMA's API. This layer acts as an adapter to multiple different collision systems and is therefore implemented by each system independently.
The block diagram illustrates the architecture that is implemented, and then the functions are detailed.
Projection Layer
Collision_Systems\Polygon\polygon_collision_util.h
getMinMaxProjection(points, axis)
computes the project of all the points in the list on the specified axis. This is done by computing the dot product of the point with the axes and finding minimum and maximums. It returns the structure MinMaxProjection
with the min and max values of the project as well as the indices of the points that correspond to those. getEllipseProjectionPoints(angle, x, y, rx, ry)
computes the projection of the ellipse formed at (x, y) with rx and ry being the radii on x and y-axis respectively. This assumes that the ellipse is perfectly horizontal (aligned with the x-axis) and computes its projection at the specified angle.SAT Layer
Collision_Systems\Polygon\polygon_collision_util.h
get_polygon_polygon_collision(poly1, poly2)
function checks whether or not two simple polygon points are colliding or not. It first computed the normals of the edges of the points provided, and iterate over the MinMax Projection of all the points on those axes one by one. If there is a single axis without overlap then there is no collision happening and it returns false. Otherwise, we have a collision and it returns true.get_polygon_ellipse_collision(poly1, x, y, rx, ry)
function works similarly but computes collision between a simple polygon and an ellipse. The method is the same as above, It just computes the ellipse projections.Polygon-case Layer
Collision_Systems\Polygon\polygon_collision_util.h
get_complex_polygon_collision()
has a lot of parameters. The function acts as a bridge between instances and collision functions. It does that by
polygon_angle, polygon_xscale, polygon_yscale, x, y
.Polygon::getOffset()
function.subpolygons
from the polygon
resource, iterates over all subpolygons, and applies transformations. It then computes collision using SAT Layer with each subpolygon.get_complex_ellipse_collision()
works similarly as above, the difference being the collision check between polygon and an ellipse. The function takes all the parameters needed to compute the ellipse, and also runs through the concave collision case.Collision-Shape Layer
Collision_Systems\Polygon\polygon_collision_util.h
get_polygon_inst_collision(inst1, inst2, x, y)
computes instance instance collision that both have polygons attached to them. The optional argument of x and y can be used for the offset.get_polygon_point_collision(inst, x, y)
computes collision with a point by creating a small Box around the point and using that shape for collision checks.get_polygon_bbox_collisions(inst, inst, x, y)
computes collision between an instance with a polygon and an instance with a BBOX. BBOX of polygons is computed using their minimum and maximum Points.Main Layer
Collision_Systems\Polygon\PolygonImpl.h
file.solid
, notme
, and the existence of a collision mask, either sprite_index
or polygon_index
.object
that is given in the parameterobject_collisions*
with the colliding instance.collide_inst_inst()
collide_inst_rect()
collide_inst_line()
collide_inst_point()
collide_inst_ellipse()
collide_inst_circle()
get_colliding_bbox_instances(x1, y1, object, solid_only)
returns a vector of all the instances that are colliding with their BBOX on the given point. This function is used to query the collision system once and is used by:
destroy_inst_point()
to destroy all instances on the pointchange_inst_point
to change all instances into another one on a point.Interface Layer
Collision_Systems\Polygon\Polygonfuncs.cpp
.For the collision system to support concave polygons, they need to be first decomposed into smaller convex polygons that can be used by the SAT layer. Concave to Convex Polygon Decomposition using simple Triangulation methods, to handle collision checks for Concave Polygons.
Universal_System\Resources\polygon_internal.h
file.areaOfTriangle(a, b, c)
computes the area bounded by the three pointspointLeft(a, b, c)
checks if point c
is on the left of the line segment formed by a
and b
.pointLeftOn(a, b, c)
checks if point c
is on the left or on the line segment formed by a
and b
.pointCollinear(a, b, c)
checks if point c
is collinear to the line segment a
and b
.pointBetween(a, b, c)
checks if point c
is in between the line segment a
and b
diagonal(points, i, j)
checks is the line segment (points[i], points[j])
a diagonaldiagonalInCone(points, i, j)
checks is the line segment (points[i], points[j])
a diagonal and in coneinternalDiagonal(points, i, j)
checks is the line segment (points[i], points[j])
an internal diagonal of the closed polygon.Polygon::subpolygons
Polygon::decomposeConcave()
to perform triangulation on the Polygon
and cache the subpolygons
and the diagonals
formed after it. If a polygon is flagged as concave
then:
addPoint(point)
and removePoint(point)
to recompute triangulations.concave
to true
polygon_decompose_concave(polygon_id)
I added general methods for Drawing Polygons, with options for colors, outline, and their Axis Aligned Bounding Boxes (AABB). All code added in the file Graphics_Systems\General\GSpolygon.h
. These functions are interface and can be directly called from GML scripts inside ENIGMA. All the functions work by first fetching the polygon points and computing the transformed points from the transformation that the callee provides.
draw_polygon(id)
draws the outline of the polygon.draw_polygon_bbox(id)
draws the outline of the polygon along with its Bounding Boxdraw_polygon_sub(id)
draws the outline of the polygon along with its internal diagonals for subpolygon.draw_polygon_color(id)
colored version of the functiondraw_polygon_bbox_color(id)
colored version of the functiondraw_polygon_sub_color(id)
colored version of the functionAdded Testcases Game inside the Tests
directory, which encompasses Polygon collision checks for all major functions. The game file is found at CommandLine/testing/Tests/polygon_collision.gmx
. The test cases test for all of the interface functions mentioned above for three different sets of polygons:
CommandLine/testing/Tests/polygon_collision.cpp
int polygonID
inside CompilerSource/backend/resources/GmObject.h
to store polygonID attached by an object for LGM.CompilerSource/compiler/components/write_object_data.cpp
file to add polygonID
inside the instance creation code.objectdata
in CompilerSource/compiler/components/write_object_data.cpp
.get_bbox_border
function inside precImpl.h
, bbox_impl.h
and polygon_impl.h
into a single static function inside the file collisions_general.h
The collision system PR won't be complete without some stress tests and benchmarking against other collision systems.
collision_rectangle
benchmarkBranch | Time | CPU RAM (MB) | GPU RAM (MB) |
---|---|---|---|
Polygon | ➖ 113 | ➖ 11.2 | ➖ 176.3 |
Precise | 🔺 119 | ➖ 11.3 (-/+ 2) | 🔺 412.3 |
BBOX | 🔻 080 | ➖ 10.6 (-/+ 2) | 🔻 139.5 |
The results shows that the Polygon collision system performs almost the same in terms of time complexity against the Precise system, providing the same level of accuracy in collision detection while using fewer GPU resources. |
place_free
benchmarkBranch | Time | CPU RAM (MB) | GPU RAM (MB) |
---|---|---|---|
Polygon | ➖ 131 | ➖ 12.0 | ➖ 80.1 |
Precise | 🔻 084 | ➖ 13.5 (-/+ 2) | 🔺 98.8 |
BBOX | 🔻 043 | ➖ 13.2 (-/+ 2) | 🔻 90.2 |
The test performs adequate in the Polygon collision system, give great accuracy and a reduce amount of GPU usage as compared to Precise System. |
Some of the work that was not covered in the time period of GSOC, and is left are as below