Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In-Game: Add support for raycasting within the physics/Box2D functionality #2762

Closed
mistletoe opened this issue Dec 8, 2023 · 13 comments
Closed
Assignees
Labels
documentation Improvements or additions to documentation are required by this issue feature request New feature (or a request for one)
Milestone

Comments

@mistletoe
Copy link

Is your feature request related to a problem?

Problem: Box2D has had raycasts (simple tests of line segments vs. Fixtures) for ages. Having this available would massively impact game design with GameMaker and hugely optimize a lot of game designs. For example, it's almost certainly a lot cheaper to raycast in most situations than to run Precise collisions, but makers can build their physics via Chain etc. to get a "close-enough" approximation of the Sprite.
This is one of those big things that GameMaker has been missing for a decade, and it's a big deal... but it's not hard to implement.

Describe the solution you'd like

Essentially, implement a call to this: https://www.iforce2d.net/b2dtut/raycasting

In GML, this would be something like this:

physics_raycast(xStart,yStart,xEnd,yEnd, <instance id, object id, or array, same as line_collision(), ds_list_to_use)

Would return a ds_list or a struct, whichever is faster.

If returning a ds_list, the first value of list should be either noone (nothing hit- fraction value returned by Box2D > 1) or closest hit Instance. Second and third values should be the XY position of the hit (the normal in Box2D), and fourth value should be the fraction value.

If returning a struct, then it'd be:

testStruct = physics_raycast(xStart,yStart,xEnd,yEnd, <instance id, object id, or array, same as line_collision());

testStruct.hitID would be the id, or noone.
testStruct.normalX would be the normal's X.
testStruct.normalY would be the normal's Y.
testStruct.fraction would be the fraction value.

Personally, I'd prefer the struct version, because then you could extend this into physics_raycast_list() which would return a ds_list of structs with all the data a developer needs, rather than how it works with line_collision_list(), where it merely returns a list of Instance IDs, which typically requires further processing to arrive at a good result.

Describe alternatives you've considered

The only real alternative is using scr_rangeFinderExact(), which is a well-known, brute-force script for getting these kinds of collisions in GameMaker. While it works... it's slow and inefficient, it doesn't work on anything in Physics World that doesn't have a sprite, it can't deal with something whose Physics World presence is considerably different than the sprite, etc.

We cannot write good alternatives, because we cannot get access to the Physics Body's polylines in GameMaker, etc., etc.

About the only way a workaround might be built is storing the polylines of a Physics Fixture as point data, doing rotations during testing, etc., etc. But that defeats the whole purpose- the data is already there... we just need a GML function.

Additional context

It's literally a feature that would transform a lot of my project and hugely impact a lot of others' as well. This is one of those mundane-looking things that's very important in real-world game-dev.

@mistletoe mistletoe added the feature request New feature (or a request for one) label Dec 8, 2023
@Callsign54
Copy link

Plus, the binding system of Box2D fixtures to Game Maker objects is extremely performance killing as each fixtures carries the burden of unnecessary variables along with them (see, image_index, sprite_index, image_xscale etc.)

@mistletoe
Copy link
Author

Plus, the binding system of Box2D fixtures to Game Maker objects is extremely performance killing as each fixtures carries the burden of unnecessary variables along with them (see, image_index, sprite_index, image_xscale etc.)

Is it including all of that when the Fixture is destroyed via physics_delete_fixture()?

After defining any Fixture and using physics_fixture_bind(id), I always ensure it's deleted immediately afterwards, because that's how it's done in every single example and I've always presumed (given the warnings about memleaks in the Manual) it was the Right Way.

I presume that, in Physics World, however, there's still very much a Fixture present, in the Box2D sense.

Anyhow, yes... IDK why Physics World and Box2D continue to feel a bit half-finished, when really, all GMS games should be made with it and Raycasts and Physics collisions should probably replace Precise collisions for 99% of projects.

@KeeVeeGames
Copy link

KeeVeeGames commented Dec 10, 2023

Yes!
And other Box2D functionality also. Collision flags, filtering, additional joints, multiple physics worlds, etc.
We basically need a full port of Box2D API in GameMaker and not a stub like it is now. It may seem that the physics engine is not enough popular among GameMaker developers so it doesn't deserve to have YoYo developers' attention and to be completed. But that's because in its current state it is virtually impossible to make a somewhat complex game with Box2D as it just isn't fully usable. It is a vicious circle: nobody uses Box2D because YoYo didn't fully complete it, and YoYo is not completing Box2D because nobody uses it.

@mistletoe
Copy link
Author

Thanks for all of the support for this!

I've been using GMS's Box2D for several years now. It transformed my project in a lot of ways. When I started with GMS, I thought (based on GMS marketing at the time) that adding physical interaction between actors would be simple and easy, like Unity. Turned out... not so much, but I've struggle-bussed anyhow.

I think it's great... other than all the missing features. It's light-years better in terms of collision detection for a given clock cycle, and it just borks less in general.

I 100% understand why GMS has, and will continue to have, the core pixel-perfect engine, but for everyday "I want X to collide with Y and I don't want to think about how it works much" and other basic tasks in a game with a lot going on, Box2D is just plain better, other than the end-user experience, newbie-friendliness... and the major missing features.

For example, Fixtor is what the engine needs, in terms of practical Fixture-editing functionality. GMS doesn't even support Chain (which is super-duper important, as it allows non-convex geometry) and, more importantly, doesn't output code that the end-user can manipulate. This is a big deal. What if your Instance is larger, or smaller, than the fixture you created? What if you need to make changes?

Ideally, that would get fixed; creating a Fixture would automagically create a script in the Create Event like this, rather than how it works now, where it's black-box:

		//Odd L-shaped tables.
		case obj_Office_Table_L_Brown:
	
			fix[0] = physics_fixture_create();
			physics_fixture_set_chain_shape(fix[0], 1);

			physics_fixture_add_point(fix[0], 30*image_xscale, 25*image_yscale);
			physics_fixture_add_point(fix[0], 29*image_xscale, -26*image_yscale);
			physics_fixture_add_point(fix[0], -32*image_xscale, -26*image_yscale);
			physics_fixture_add_point(fix[0], -32*image_xscale, -11*image_yscale);
			physics_fixture_add_point(fix[0], 15*image_xscale, -11*image_yscale);
			physics_fixture_add_point(fix[0], 15*image_xscale, 25*image_yscale);

			for (var i = 0; i < 1; i += 1) {
				//Pushable Chain!
				physics_fixture_set_collision_group(fix[i],-1);
				physics_fixture_set_density(fix[i], 0);
				physics_fixture_set_friction(fix[i], 0.20);
				physics_fixture_set_linear_damping(fix[i], 4);
				physics_fixture_set_angular_damping(fix[i], 0.97);
				physics_fixture_set_restitution(fix[i], 0.10);
				physics_fixture_set_sensor(fix[i], 0);
				physics_fixture_set_awake(fix[i], 1);
				physics_fixture_bind_ext(fix[i], id, 0-offsetx, 0-offsety);
				physics_fixture_delete(fix[i]);
			}
			physics_mass_properties(300, phy_com_x, phy_com_y, phy_inertia);//Manual mass setting!
			break;

I have about a hundred things like that in my game atm. It's output from Fixtor that I've had to edit to automatically deal w/ image_scale problems (things in my game often generate w/ reversed coordinates). I haven't tested the current version to see if it's finally fixed that issue.

Anyhow, ideally, we'd have all of the Box2D features, including being able to alter the boundaries after creation (like, IDK why we're encourage to delete Fixtures as soon as possible; surely Fixtures with bad Instances can get garbage-collected now). I'd just settle for raycasts; it would literally double my framerate in most scenarios, lol. I think if raycasts are in, and somebody makes a halfway-decent tutorial for How To Use Physics that is ... more newbie-friendly... usage would take off.

@mistletoe
Copy link
Author

I've downloaded the current Fixtor, and it's not only fixed the issues w/ scaling, it's basically what GMS should have.

YYG, please contact the maintainer and ask if you can buy their source and integrate it in GMS directly. There's no point in reinventing this wheel; that this automatically cuts Fixtures into convex shapes now is pretty amazing. It shouldn't be a third-party product.

It also puts paid to "but nobody uses this feature", imo. The developer wouldn't have kept going on this otherwise.

This is how nice the output is now:
`
function () {

///@func (flipH, flipV, OffsetX, OffsetY, removePreviousFixture)
///@desc This script is generated by Fixtor. You can use this script in any event you want, to create/reassign a new fixture for the calling instance.
///@param {bool}{optional} flipH Whether to horizontally flip this fixture.
///@param {bool}{optional} flipV Whether to vertically flip this fixture.
///@param {bool}{optional} OffsetX The x distance to offset from the sprite origin.
///@param {bool}{optional} OffsetY The y distance to offset from the sprite origin.
///@param {bool}{optional} removePreviousFixture Whether to remove the previously assigned fixture for the calling instance.
///@param {bool}{optional} applySpriteOrigin Whether to make this fixture's origin point the same as the sprite.


/// Preparing some temporary variables ///
var _flipH = 0, _flipV = 0;
var _offsetX = 0, _offsetY = 0;
var _removePrevFixt = 1;
var _applySpriteOrigin = 1;

if argument_count > 0 _flipH = argument[0];
if argument_count > 1 _flipV = argument[1];
if _flipH == 0 {_flipH = 1;} else {_flipH = -1};
if _flipV == 0 {_flipV = 1;} else {_flipV = -1};
if argument_count > 2 _offsetX = argument[2];
if argument_count > 3 _offsetY = argument[3];
if argument_count > 4 _removePrevFixt = argument[4];
if argument_count > 5 _applySpriteOrigin = argument[5];
if _applySpriteOrigin {
	var _sprOffsetX = -(sprite_width/2-sprite_xoffset), _sprOffsetY = -(sprite_height/2-sprite_yoffset);
} else {
	var _sprOffsetX = 0, _sprOffsetY = 0;
};


/// Begin creating polygon fixtures ///
var _fix, _vertexGroup, _vertices, _ind2use;
var _finalOffsetX = _offsetX + _sprOffsetX, _finalOffsetY = _offsetY + _sprOffsetY;

//Polygon piece #0
_vertexGroup[0,0] = 46.94;
_vertexGroup[0,1] = 25.29;
_vertexGroup[1,0] = 26.98;
_vertexGroup[1,1] = 15.09;
_vertexGroup[2,0] = 36.85;
_vertexGroup[2,1] = 10.21;
_vertexGroup[3,0] = 47.05;
_vertexGroup[3,1] = 14.29;
_vertices = 4;
_fix[0] = physics_fixture_create();
physics_fixture_set_polygon_shape(_fix[0]);
for (var i = 0; i < _vertices; ++i) {
	if _flipH+_flipV == 0 { _ind2use = _vertices-1-i; } else { _ind2use = i; };
	physics_fixture_add_point(_fix[0], (_vertexGroup[_ind2use, 0] + _finalOffsetX)*_flipH, (_vertexGroup[_ind2use, 1] + _finalOffsetY)*_flipV);
};


//Polygon piece #1
_vertexGroup[0,0] = 46.94;
_vertexGroup[0,1] = 25.29;
_vertexGroup[1,0] = 46.16;
_vertexGroup[1,1] = 100.14;
_vertexGroup[2,0] = 4.85;
_vertexGroup[2,1] = 100.29;
_vertexGroup[3,0] = 2.18;
_vertexGroup[3,1] = 76.93;
_vertexGroup[4,0] = 34.21;
_vertexGroup[4,1] = 18.79;
_vertices = 5;
_fix[1] = physics_fixture_create();
physics_fixture_set_polygon_shape(_fix[1]);
for (var i = 0; i < _vertices; ++i) {
	if _flipH+_flipV == 0 { _ind2use = _vertices-1-i; } else { _ind2use = i; };
	physics_fixture_add_point(_fix[1], (_vertexGroup[_ind2use, 0] + _finalOffsetX)*_flipH, (_vertexGroup[_ind2use, 1] + _finalOffsetY)*_flipV);
};


//Polygon piece #2
_vertexGroup[0,0] = 14.69;
_vertexGroup[0,1] = 8.81;
_vertexGroup[1,0] = 34.21;
_vertexGroup[1,1] = 18.79;
_vertexGroup[2,0] = 12.98;
_vertexGroup[2,1] = 57.32;
_vertexGroup[3,0] = 12.37;
_vertexGroup[3,1] = 9.84;
_vertices = 4;
_fix[2] = physics_fixture_create();
physics_fixture_set_polygon_shape(_fix[2]);
for (var i = 0; i < _vertices; ++i) {
	if _flipH+_flipV == 0 { _ind2use = _vertices-1-i; } else { _ind2use = i; };
	physics_fixture_add_point(_fix[2], (_vertexGroup[_ind2use, 0] + _finalOffsetX)*_flipH, (_vertexGroup[_ind2use, 1] + _finalOffsetY)*_flipV);
};


//Polygon piece YoYoGames/GameMaker-Feature-Requests#3
_vertexGroup[0,0] = 12.37;
_vertexGroup[0,1] = 9.84;
_vertexGroup[1,0] = 12.60;
_vertexGroup[1,1] = 27.39;
_vertexGroup[2,0] = 0.87;
_vertexGroup[2,1] = 23.19;
_vertexGroup[3,0] = 1.29;
_vertexGroup[3,1] = 14.74;
_vertices = 4;
_fix[3] = physics_fixture_create();
physics_fixture_set_polygon_shape(_fix[3]);
for (var i = 0; i < _vertices; ++i) {
	if _flipH+_flipV == 0 { _ind2use = _vertices-1-i; } else { _ind2use = i; };
	physics_fixture_add_point(_fix[3], (_vertexGroup[_ind2use, 0] + _finalOffsetX)*_flipH, (_vertexGroup[_ind2use, 1] + _finalOffsetY)*_flipV);
};


/// Remove the previous fixture (if needed) ///
if _removePrevFixt && variable_instance_exists(id,"current_fix") {
	var _arrn = array_length(current_fix);
	for (var i = 0; i < _arrn; ++i) {
		physics_remove_fixture(id,current_fix[i]);
	};
};


/// Begin binding the fixture ///
var _arrn = array_length(_fix);
for (var i = 0; i < _arrn; ++i) {
	physics_fixture_set_density(_fix[i], 0.50);
	physics_fixture_set_restitution(_fix[i], 0.10);
	physics_fixture_set_collision_group(_fix[i], 0);
	physics_fixture_set_linear_damping(_fix[i], 0.10);
	physics_fixture_set_angular_damping(_fix[i], 0.10);
	physics_fixture_set_friction(_fix[i], 0.20);
	physics_fixture_set_sensor(_fix[i], 0);
	physics_fixture_set_awake(_fix[i], 1);
	current_fix[i] = physics_fixture_bind_ext(_fix[i], id, 0, 0);
	physics_fixture_delete(_fix[i]);
};

};
`

@Callsign54
Copy link

Yes! And other Box2D functionality also. Collision flags, filtering, additional joints, multiple physics worlds, etc. We basically need a full port of Box2D API in GameMaker and not a stub like it is now. It may seem that the physics engine is not enough popular among GameMaker developers so it doesn't deserve to have YoYo developers' attention and to be completed. But that's because in its current state it is virtually impossible to make a somewhat complex game with Box2D as it just isn't fully usable. It is a vicious circle: nobody uses Box2D because YoYo didn't fully complete it, and YoYo is not completing Box2D because nobody uses it.

I know a person that made sandbox simulation game(over 1m downloads on Google Play). I also have one, that has 100k downloads. Both games are quite popular.

Unfortunately, the physics system hasn't been updated properly since GMS 1.4. We always have to do some workarounds to achieve some cool things.

@yevhenii-sir
Copy link

yevhenii-sir commented Jan 7, 2024

I was also affected by the issue of not having a proper way to "cast a ray" and find collisions.

I had a task to create a laser that would interact with roads, bridges, and obstacles. I had to create a separate object with a line fixture and perform a physics_test_overlap check, moving the check in the desired direction. When there are loads of objects, it severely impacts performance. Also, the accuracy of collision detection suffers - the higher the accuracy, the greater the load.

Example:

var xx = phy_position_x;
var yy = phy_position_y;

for (var i = 0; i < line_check_iterations; i++) {
	var next_xx = xx + lengthdir_x(line_distance, line_direction);
	var next_yy = yy + lengthdir_y(line_distance, line_direction);
	
	if (physics_test_overlap(xx, yy, 0, obj_collision_parent)) {
		collide_on_car = physics_test_overlap(xx, yy, 0, obj_car_);
		if (collide_on_car) {
			set_damage_car(0.013);
		}

		break;
	}
	
	xx = next_xx;
	yy = next_yy;
}

laser_xx = xx;
laser_yy = yy;

If your suggestion were implemented, it would be AMAZING!

@iampremo iampremo transferred this issue from another repository Jan 9, 2024
@rwkay rwkay self-assigned this Mar 22, 2024
@Glim888
Copy link

Glim888 commented Mar 23, 2024

@rwkay You also could provide us a pointer to the Box2D, LiquidFun variable. So we could implement some missing features ourself.

Moe Box2D and LiquidFun features would be great, but pls keep compatibility. Thanks

@rwkay
Copy link

rwkay commented Apr 20, 2024

Project for testing new function PhysicsRaycast.zip

@Glim888
Copy link

Glim888 commented Apr 20, 2024

@rwkay In case of updating box2d, I would like to help you with testing, discussion of features etc.

I have pushed GMS Box2D and liquidFun features to the limits. I have done multiple games with over 8 mio downloads based on GMS Box2D. Feel free to contact me. Thanks =)

@rwkay
Copy link

rwkay commented Apr 22, 2024

Feature Added in 2024.6 -

physics_raycast(xStart,yStart,xEnd,yEnd,ids,[all_hits],[max_fraction],...)

This function checks the physics fixtures of the objects given against the ray specified, it will return an array of structures that will give the hitPointX, hitPointY (the room coordinates of the intersection), normalX, normalY (the normal of the intersection), fraction (the normalised distance down the ray of the intersection)

  • xStart - x position of the start of the ray.
  • yStart - y position of the start of the ray.
  • xEnd - x position of the start of the ray.
  • yEnd - y position of the start of the ray.
  • ids - instance or object id (or an array of instance or object ids) to check the ray against, these should have physics fixtures
  • all_hits - set to true if you want all hits returned, false if you want the closest hit only - defaults to false
  • max_fraction - proportion of the ray to check, defaults to 1.0, can be set to be smaller or larger

@rwkay rwkay closed this as completed Apr 22, 2024
@YYDan YYDan added this to the 2024.6 milestone Apr 22, 2024
@gurpreetsinghmatharoo gurpreetsinghmatharoo added the documentation Improvements or additions to documentation are required by this issue label Apr 23, 2024
@YYBartT YYBartT self-assigned this Apr 29, 2024
YYBartT added a commit to YoYoGames/GameMaker-Manual that referenced this issue May 8, 2024
YoYoGames/GameMaker-Bugs#2762

* Added function reference page, updated links & added to TOC
* Added example video
* Added three code examples, the first two can be copy-pasted into a new project
gurpreetsinghmatharoo added a commit to YoYoGames/GameMaker-Manual that referenced this issue May 9, 2024
* develop.bart:
  docs(feature): added documentation for physics_raycast YoYoGames/GameMaker-Bugs#2762
  docs(feature): documented gamepad_enumerate YoYoGames/GameMaker-Bugs#5329
  docs(feature): skeleton_animation_clear optional parameters YoYoGames/GameMaker-Bugs#1570
  docs(feature): documented dbg_view_exists and dbg_section_exists YoYoGames/GameMaker-Bugs#4931
  docs(feature): documented dbg_view_exists and dbg_section_exists YoYoGames/GameMaker-Bugs#4931
  docs(feature): YoYoGames/GameMaker-Bugs#1570
  docs(general): mentioned that any attached files are uploaded privately
  docs(feature): smaller custom format screenshot YoYoGames/GameMaker-Bugs#5495
  docs(feature): documented the new dbg_text_separator function YoYoGames/GameMaker-Bugs#4998
  docs(feature): Switch to the docking branch of imgui YoYoGames/GameMaker-Bugs#4905
  docs(general): document OS and audio device limitations when using audio YoYoGames/GameMaker-Bugs#5580
  docs(feature): Add an option to specify "step" in the ImGUI slider control YoYoGames/GameMaker-Bugs#2971

# Conflicts:
#	Manual/contents/GameMaker_Language/GML_Reference/Asset_Management/Audio/audio_sound_length.htm
#	Manual/contents/GameMaker_Language/GML_Reference/Debugging/dbg_slider.htm
#	Manual/contents/GameMaker_Language/GML_Reference/Debugging/dbg_slider_int.htm
@YYBartT
Copy link

YYBartT commented May 9, 2024

Added function reference page for physics_raycast() to the manual.

@cameron-home
Copy link

Verified in IDE v2024.600.0.563 Runtime v2024.600.0.580

@cameron-home cameron-home moved this from Ready for QA to Verified in Team Workload Jun 5, 2024
@YYDan YYDan changed the title Allow Box2D Raycasts, finally. In-Game: Add support for raycasting within the physics/Box2D functionality Jun 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation are required by this issue feature request New feature (or a request for one)
Projects
Status: Verified
Development

No branches or pull requests

10 participants