0

I'm trying to create a rigid body like Item using only Physics2DServer and VisualServer like this:

extends Node2D

var _body:RID
var canvasItem:RID

func _enter_tree():
    _body=Physics2DServer.body_create();
    Physics2DServer.body_set_space(_body, get_world_2d().space);
    
    var shape= RectangleShape2D.new();
    shape.extents=Vector2(30,30);
    
    var ci_rid = VisualServer.canvas_item_create() # ci= Canvas Item
    VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
    
    Physics2DServer.body_add_shape(_body, shape.get_rid(), self.global_transform, false);
    Physics2DServer.body_set_force_integration_callback(_body, self, "_body_moved",ci_rid);

    var image=Image.new();
    image.load("res://icon.png")

    var texture_rid := VisualServer.texture_create_from_image(image)
    VisualServer.texture_set_flags (texture_rid,VisualServer.TEXTURE_FLAG_ANISOTROPIC_FILTER)
    
    VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
    VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(image.get_size() * -0.5, image.get_size()), texture_rid);
    VisualServer.canvas_item_set_transform(ci_rid, self.transform)

func _body_moved(state:Physics2DDirectBodyState,ci_rid):
    VisualServer.canvas_item_set_transform(ci_rid,state.transform)

but for some reason the collision is not working

Edit:

I think the main problem is the var shape= RectangleShape2D.new();

because when I added a export(Shape2D) var shape; instead and added a RectangleShape2D manually, then the collision worked properly

Edit for transform based problem:

extends Node2D

var _body:RID
var _shape:RID
var canvasItem:RID

func _enter_tree():
    _body=Physics2DServer.body_create();
    Physics2DServer.body_set_space(_body, get_world_2d().space);
    
    var ci_rid = VisualServer.canvas_item_create() # ci= Canvas Item
    VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
    
    _shape = Physics2DServer.rectangle_shape_create()
    Physics2DServer.shape_set_data(_shape, Vector2(30,30))
    
    Physics2DServer.body_add_shape(_body,_shape);
    Physics2DServer.body_set_force_integration_callback(_body, self, "_body_moved",ci_rid);

    var texture:Texture = load("res://icon.png")
    var image:Image = texture.get_data()

    var texture_rid := VisualServer.texture_create_from_image(image)
    VisualServer.texture_set_flags(texture_rid,VisualServer.TEXTURE_FLAG_ANISOTROPIC_FILTER)
    
    VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
    VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(image.get_size() * -0.5, image.get_size()), texture_rid);
    VisualServer.canvas_item_set_transform(ci_rid, Transform2D.IDENTITY)
    
    set_notify_transform(true)

func _exit_tree():
    if(_body.get_id()!=0):
        Physics2DServer.free_rid(_body)
    if(_shape.get_id()!=0):
        Physics2DServer.free_rid(_shape)

func _body_moved(state:Physics2DDirectBodyState,ci_rid):
    VisualServer.canvas_item_set_transform(ci_rid,state.transform)

func _notification(what: int) -> void:
    if what == NOTIFICATION_TRANSFORM_CHANGED:
        if _body.get_id() != 0:
            Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_TRANSFORM, transform)
cak3_lover
  • 1,440
  • 5
  • 26

1 Answers1

0

Missing Shape

The problem with the shape is that it is getting freed.

If you declare a field with the shape at the top of the script, and then initialize it _enter_tree, it works correctly. But declared inside _enter_tree it lives until the end of the method when it goes out of scope.


The lesson is that a RID is not a reference.

The alternative is to create the shape and the texture with the Physics2DServer:

var shape_rid := Physics2DServer.rectangle_shape_create()
Physics2DServer.shape_set_data(shape_rid, Vector2(30,30))

And since that is a RID created with the Physics2DServer you would free it by calling the free_rid method of the Physics2DServer.


By the way, I'm getting a warning about the texture. You can load it as a Texture instead:

var texture:Texture = load("res://icon.png")
var image:Image = texture.get_data()

Which will the warning go away.

And then we are tempted to do this:

var texture_rid := texture.get_rid()

But we get errors… The issue is the same, the texture is getting freed when the method ends because the variable is the only reference and it is going out of scope. Declaring the texture at the top of the file fixes the error spam.

And, yes, we could create the texture with the VisualServer. But since you are loading it from a resource, it is not practical.


Transform

First of all, the canvas item is positioned relative to the node where the code is running:

VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
VisualServer.canvas_item_set_transform(ci_rid, Transform2D.IDENTITY)

Second, the body is placed in global coordinates. So we should do this:

Physics2DServer.body_set_state(_body, Physics2DServer.BODY_STATE_TRANSFORM, global_transform)

Notice global_transform instead of transform.


And that brings us to this:

  • state.transform is global coordinates.
  • canvas_item_set_transform wants coordinates relative to the parent (the node where the code is running).

This a way to deal with it:

VisualServer.canvas_item_set_transform(ci_rid, global_transform.inverse() * state.transform)

Alternatively, you could move the node to follow the body:

func _body_moved(state:Physics2DDirectBodyState, _user_data) -> void:
    global_transform = state.transform

And that would move the canvas item too, because, remember that it is parented (with canvas_item_set_parent).

Since you are moving the body when the node moves (in _notification), I believe moving the node to follow the body is the correct solution. This way, they stay in sync all the time.


About the shape: it is placed relative to the body. Both the shape and the body can move. The physics engine will move the body (and the shape follows because it is positioned relative to the body), but the physics engine will not move the shape directly. You, however, could move the shape directly if you wanted. This is the same relationship that a RigidBody2D and its CollisionShape2D would have.

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • could you take a look at the `VisualServer.canvas_item_set_transform(ci_rid,state.transform)` & `Physics2DServer.body_add_shape(_body, shape.get_rid(), self.global_transform, false);` lines of code? I'm struggling with body transformation globally, The body and texture isn't being placed and moved the way it's suppose to – cak3_lover Apr 20 '22 at 17:46
  • @cakelover We were using `Transform2D.IDENTITY` in `body_add_shape` before, but now you are using `self.global_transform`. You don't need to pass it, since it is optional, but in [FauxBody](https://stackoverflow.com/a/71622366/402022) I was setting disabled, and I cannot skip parameters (GDScript does not have named parameters). As far as I can tell shape transform is not global, but relative to the body. By the way, be aware that `state.transform` is the transform of the body, not the shape. I also notice is that you use `self.transform` in `_enter_tree`, which is not global. – Theraot Apr 20 '22 at 17:59
  • ah I got the `Transform2D.IDENTITY` one but how exactly do I set the transform of visual along with the body movement? also I'm getting confused is to how to visualise body and shape? Also which is the moving component, the body or the shape? I thought the shape was fixed and moved along with the body, I made an edit to the question to deal with the transformation part of the problem – cak3_lover Apr 20 '22 at 18:17
  • 1
    @cakelover I have expanded the answer. – Theraot Apr 20 '22 at 19:53
  • could you explain this line `global_transform.inverse() * state.transform`? as far as I understand you took the `state.transform` and converted it into a global transform by matrix multiplication but why did you take `.inverse()`? – cak3_lover Apr 20 '22 at 20:21
  • 1
    @cakelover I didn't covert `state.transform` to global coordinates. It already is in global coordinates. But I want it relative to the node. I have the global coordinates of the node: `global_transform`, and the body: `state.transform`. And I'm looking for a transform `x` such that `global_transform * x = state.transform`, solve for `x` (remember that transform multiplication is not commutative): `global_transform.inverse() * global_transform * x = global_transform.inverse() * state.transform` and thus `x = global_transform.inverse() * state.transform`. – Theraot Apr 20 '22 at 20:30