This is the testing Godot forums! All forum posts unique to this forum will be deleted! Please use the main forums here for any posts you want to keep. All forum rules still apply.

How to I get these subdividing squares into the correct position?

PopeRigbyPopeRigby Posts: 39Member
in 2D

I have this bit of code for a subdivision function that is applied on a square:

extends CollisionShape2D

func _process(_delta):
    if Input.is_action_just_pressed("subdivide"):
        var oldExtents = shape.get_extents()
        var oldPosition =  get_position()

        queue_free() # removes the original block

        var topLeft = duplicate()
        get_parent().add_child(topLeft) # adds topLeft as a child of StaticBody2D
        topLeft.shape.extents = Vector2(oldExtents.x/2,oldExtents.y/2)
        topLeft.get_node("Sprite").scale = topLeft.shape.extents * 0.0316
        topLeft.set_position(oldPosition + Vector2(-oldExtents.x*2,-oldExtents.y*2))
        # set_pos(get_pos() + Vector2(-new_size/2, -new_size/2))

        var topRight = duplicate()
        get_parent().add_child(topRight) 
        topRight.shape.extents = Vector2(oldExtents.x/2,oldExtents.y/2)
        topRight.get_node("Sprite").scale = topRight.shape.extents * 0.0316
        topRight.set_position(oldPosition + Vector2(oldExtents.x*2,-oldExtents.y*2))

        var bottomLeft = duplicate()
        get_parent().add_child(bottomLeft) 
        bottomLeft.shape.extents = Vector2(oldExtents.x/2,oldExtents.y/2)
        bottomLeft.get_node("Sprite").scale = bottomLeft.shape.extents * 0.0316
        bottomLeft.set_position(oldPosition + Vector2(-oldExtents.x*2,oldExtents.y*2))

        var bottomRight = duplicate()
        get_parent().add_child(bottomRight) 
        bottomRight.shape.extents = Vector2(oldExtents.x/2,oldExtents.y/2)
        bottomRight.get_node("Sprite").scale = bottomRight.shape.extents * 0.0316
        bottomRight.set_position(oldPosition + Vector2(oldExtents.x*2,oldExtents.y*2))

        print(get_position())

Here's what currently happens when I click subdivide:
https://watch.haddock.cc/videos/watch/b4cd3847-ac42-4cee-916e-e32bbe446523

As you can see, it works fine the first division, but on the second division it only works on the top left corner. All the other corners get gradually smaller. On the third division I have no idea what's happening. So what can I do to get this to subdivide nicely?

Best Answer

  • HaledireHaledire Posts: 23
    edited June 10 Accepted Answer

    As you keep cleaning out redundancies, you get closer to the code that I've made here in the earlier post:
    https://godotforums.org/discussion/comment/38972/#Comment_38972
    The difference compared to the code you are writing now is that I have a different node order, and the code is executing from what in your case would be the parent of your StaticBody2D. The hierarchy is different but the methods are the same.

    get_children() gets a list of nodes that are a child of your StaticBody2D. Again, currently you are referencing $CollisionShape2D directly, you do NOT want that.

    CollisionShape2D is a child of StaticBody2D. get_children() gets the node reference of any child node (ie: CollisionShape2D on the first iteration). At the end of subdivide, you need to remove this node, as it is no longer necessary (and in terms of iterating over children - detrimental as it's useless baggage - you'll be creating the same nodes endlessly if you don't remove the original: node.queue_free()).

    for child in get_children():
        subdivide(child)
    

    On the second iteration, you want the new nodes created by Subdivide. This means subdivide(child) needs to create new children of the StaticBody2D (ie: add_child(), not get_parent().add_child() - you need more children of StaticBody2D, not more children of your Viewport in the case of running this code by itself). Using for child in get_children() means that you can run this same code regardless if you have 1, 4, 8, 16, 32, 64, etc children and it will run subdivide on each child - provided they are nodes created "within" the static body, not outside. Currently you're trying to make an object inside the node appear outside the node, and then you lack nodes for the code to work from.

«1

Answers

  • HaledireHaledire Posts: 23Member

    From what it looks like, you are basing the subdivision off of the same shape every time.
    When you duplicate the Collision2D, the shape in the duplicate is the same shape as the original.
    Because all of your calculations are based off of the extents of a shared resource, each iteration of the code shrinks more and more.

    1. You obtain old_extents from the original's extents and begin the first subdivision.
    2. You set the shape to be old_extents / 2 (btw you don't need to multiply both parts of a Vector2 separately when using a single value). The shape is now shared between 4 objects.
    3. You subdivide again - sending the now halved extents to the next object, which makes 4 more copies of the same shape, a shape whose extents then gets halved, which sends the half of the half of the original extents to the third object, etc.

    You need to duplicate the Collision2D.shape resource with shape.duplicate(true) to sever the link between each new Collision2D.

  • PopeRigbyPopeRigby Posts: 39Member

    I refactored it a bit but is this what it should look like?

    extends CollisionShape2D
    
    func _process(_delta):
        if Input.is_action_just_pressed("subdivide"):
            var oldExtents = shape.get_extents()
            var oldPosition =  get_position()
            var newExtents = Vector2(shape.get_extents()/2)
    
            queue_free() # removes the original block
    
            var topLeft = shape.duplicate()
            get_parent().add_child(topLeft) # adds topLeft as a child of StaticBody2D
            topLeft.shape.extents = newExtents
            topLeft.get_node("Sprite").scale = topLeft.shape.extents * 0.0316
            topLeft.set_position(oldPosition + Vector2(-oldExtents.x*2,-oldExtents.y*2))
    
            var topRight = shape.duplicate()
            get_parent().add_child(topRight) 
            topRight.shape.extents = newExtents
            topRight.get_node("Sprite").scale = topRight.shape.extents * 0.0316
            topRight.set_position(oldPosition + Vector2(oldExtents.x*2,-oldExtents.y*2))
    
            var bottomLeft = shape.duplicate()
            get_parent().add_child(bottomLeft) 
            bottomLeft.shape.extents = newExtents
            bottomLeft.get_node("Sprite").scale = bottomLeft.shape.extents * 0.0316
            bottomLeft.set_position(oldPosition + Vector2(-oldExtents.x*2,oldExtents.y*2))
    
            var bottomRight = shape.duplicate()
            get_parent().add_child(bottomRight) 
            bottomRight.shape.extents = newExtents
            bottomRight.get_node("Sprite").scale = bottomRight.shape.extents * 0.0316
            bottomRight.set_position(oldPosition + Vector2(oldExtents.x*2,oldExtents.y*2))
    

    That doesn't seem to work though, and I get this when I click:

    Invalid type in function 'add_child' in base 'StaticBody2D'. The Object-derived class of argument 1 (RectangleShape2D) is not a subclass of the expected argument class.
    
  • HaledireHaledire Posts: 23Member

    I'd set up code that works similarly but from a parent outside of the object dividing:

    extends Node2D
    
    var point_array = [Vector2(-.5,-.5), Vector2(.5, -.5),Vector2(-.5, .5),Vector2(.5, .5)]
    
    func subdivide(original):
        var clone = original.duplicate()
        var clone_shape = original.get_node("CollisionShape2D").shape
        original.queue_free()
        clone.get_node("Sprite").scale *= .5
        clone.get_node("CollisionShape2D").shape = clone_shape.duplicate(true)
        clone.get_node("CollisionShape2D").shape.extents = clone_shape.extents / 2
        for positioned in point_array:
            var placed_clone = clone.duplicate()
            placed_clone.position += positioned * clone_shape.extents
            add_child(placed_clone)
    
    func _input(event):
        if event.is_action_pressed("ui_accept"):
            for child in get_children():
                subdivide(child)
    

    The important part here is that you clone the node, AND clone the shape inside the node.
    Your direction = duplicate() functions were fine before, but they also needed to duplicate their shape.
    As an example:
    var topLeft = duplicate()
    topLeft.shape = topLeft.shape.duplicate(true)

  • PopeRigbyPopeRigby Posts: 39Member

    Ok I moved the script to my StaticBody2D and set it up like this (I'm only doing one corner for testing purposes)

    extends StaticBody2D
    
    func _process(_delta):
        if Input.is_action_just_pressed("subdivide"):
            var original = $CollisionShape2D
            var oldExtents = $CollisionShape2D.shape.get_extents()
    
            queue_free() # removes the original block
    
            var topLeft = original.duplicate()
            var topLeftShape = original.shape.duplicate()
            add_child(topLeft) # adds topLeft as a child of StaticBody2D
            topLeftShape.extents = Vector2(topLeftShape.get_extents()/2)
            topLeft.get_node("Sprite").scale = topLeftShape.extents * 0.0316
            topLeft.set_position(original.get_position() + Vector2(-oldExtents.x*2,-oldExtents.y*2))
    

    Unfortunately that just makes my square disappear when I click.

  • HaledireHaledire Posts: 23Member
    edited June 7

    Bear in mind my code was working from the entity above the one being divided - right now you're copying a child of the object and adding another child - but you've set the object to queue_free() so the entire hierarchy you're starting to build vanishes as soon as your code completes.

    Maybe you mean to be doing $CollisionShape2D.queue_free()?
    Before you were doing get_parent().add_child(), now transitioning to the new node, you 'are' the parent in this case.

  • PopeRigbyPopeRigby Posts: 39Member

    Doing $CollisionShape2D.queue_free() just gives me this when I subdivide twice.

    get_node: Node not found: CollisionShape2D
    
  • HaledireHaledire Posts: 23Member
    edited June 8

    The issue I see going forward is that - you won't always have $CollisionShape2D. When you add a duplicate of $CollisionShape2D, the new node will not have the same name. 2 Nodes in the same parent cannot have the same name. Even if you turn on legibile names with add_child(), the full name of the node will not match in each iteration of the code. You'll need to iterate over the children of the StaticBody2D rather than find a specific node path.

    The question here is if the StaticBody2D is intended to stay as a constant node and are you only making new CollisionShape2D's? If you're only making children, you're going to need to make a function that each child can run to perform this set of actions in the way that I made my code. Right now you're making code that will only support 4 objects that will slowly separate from one another, as you're only defining 4 objects when you need them to constantly split.

    Your previous code was fine and even if you move it up a level you would still need to perform the same actions with the node paths changed to reflect the change in level. What the original code was lacking was simply the separation of the duplicate's shapes from the original shape, which you are still lacking here as you aren't setting the new shape to the new instances, but making a variable that won't be called on future iterations (at least from what you have posted).

    The way you perform the actions is dependent upon which direction you're working the code. Is the node telling itself to perform a set of actions (bottom to top), or is the node telling something else to perform that same set of actions. (top to bottom)

  • PopeRigbyPopeRigby Posts: 39Member

    So do you think I should just scrap what I currently have? Or maybe you could give me an example of what I can add to make it work. I'm a little confused.

  • HaledireHaledire Posts: 23Member
    edited June 9

    Your original code was only missing this to pair with lines 10, 17, 23, and 29:

    var topLeft = duplicate()
    #create a new resource by duplicating the current
    topLeft.shape = topLeft.shape.duplicate(true)
    
    var topRight = duplicate()
    #create a new resource by duplicating the current
    topRight.shape = topRight.shape.duplicate(true)
    
    var bottomLeft = duplicate()
    #create a new resource by duplicating the current
    bottomRight.shape = bottomRight.shape.duplicate(true)
    
    var bottomRight = duplicate()
    #create a new resource by duplicating the current
    bottomRight.shape = bottomRight.shape.duplicate(true)
    

    Because all your code relied on a shape that was being shared each time the code was run by multiple objects, you got an unexpected result. You need to use duplicate(true) to create a new Resource that is unique to that object.
    Godot Docs: Resource : Method - duplicate()

    If you were to move this code up from the CollisionShape2D while keeping the same node Hierarchy, you'd simply update your paths to...

    old_extents = shape.extents
    becomes
    old_extents = $CollisionShape2D.shape.extents

    bottomRight.shape = bottomRight.shape.duplicate(true)
    becomes
    bottomRight.get_node("CollisionShape2D").shape = bottomRight.get_node("CollisionShape2D").shape.duplicate(true)

    If you separate the sprite from the CollisionShape2D like your newer code implies, you'd just repeat the same positioning, duplicating and scaling code for it.

    An optimization to the code would be to not make topLeft, topRight, bottomLeft, and bottomRight from scratch.
    All 4 of these objects are the same. Make 1 new object (I called mine Clone) to make your edits (new shape, new shape dimensions), and duplicate that object 3 more times.

    This example is going off of the original code:

    #make the first clone
    var clone1 = duplicate()
    clone.shape = clone.shape.duplicate(true)
    clone.shape.extents = clone.shape.extents / 2
    
    #make the rest of the clones
    var clone2 = clone1.duplicate()
    
    var clone3 = clone1.duplicate()
    
    var clone4 = clone1.duplicate() 
    

    To optimize further - don't even make 4 variables, make 2.
    1. Make Stamp - define it's parameters:
    var Stamp = duplicate()
    Stamp.shape = Stamp.shape.duplicate(true)
    Stamp.shape.extents = Stamp.shape.extents / 2
    2. Make Clone from Stamp: var Clone = Stamp.duplicate()
    3. Give it the top left position (which your code uses completely indepentent code for). get_parent().add_child(Clone)
    4. Clone = Stamp.duplicate()
    5. Give clone (which is now a new duplicate of Stamp) the top right position
    6. get_parent().add_child(Clone)
    7. repeat steps again for bottom left
    8. for bottom Right - you no longer need Stamp, so set Stamp to bottom right position
    9. get_parent().add_child(Stamp)

    Now you have 4 children, and you used 2 variables to hold instances, rather than 4.

    These 4 objects can share the same shape, because the necessary method to make your code work is to not edit the original shape, but to make a new shape and use the previous shape's dimensions as a reference for positioning. If each individual object needs to be able to subdivide only itself, then each object would need to create a new shape for itself.

  • PopeRigbyPopeRigby Posts: 39Member
    edited June 9

    Well, I thought I understood what you were saying and made this, but it doesn't do anything.

    extends StaticBody2D
    
    func _input(event):
        if event.is_action_pressed("subdivide"):
            subdivide()
    
    func subdivide():
        var Stamp = $CollisionShape2D.duplicate()
        Stamp.shape = Stamp.shape.duplicate(true)
        var oldExtents = Stamp.shape.extents
        Stamp.shape.extents = Stamp.shape.extents / 2
    
    
        var Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x*2,-oldExtents.y*2)) # top left
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(oldExtents.x*2,-oldExtents.y*2)) # top right
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x*2,oldExtents.y*2)) # bottom left
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Stamp.set_position(Stamp.get_position() + Vector2(-oldExtents.x*2,oldExtents.y*2)) # bottom right
        Stamp.get_node("Sprite").scale = Stamp.shape.extents * 0.0316
        get_parent().add_child(Stamp)
    

    If you separate the sprite from the CollisionShape2D like your newer code implies, you'd just repeat the same positioning, duplicating and scaling code for it.

    I actually didn't do that. Should I?

    P.S. for reference, my scene tree looks like this:

  • HaledireHaledire Posts: 23Member
    edited June 9

    You may need to throw in a yield for an idle frame after making the original duplicate. I'm noticing problems trying to duplicate() and then doing get_parent().add_child(). It's fine if I duplicate the node, yield for a frame, then make multiple duplicates of said node.

    var Stamp = $CollisionShape2D.duplicate()
    var Stamp.shape = Stamp.shape.duplicate(true)
    yield(get_tree(),"idle_frame")
    

    And I may have mistook reading the code before. I thought I saw a get_node("Sprite") without a variable name in front of it. I was writing some of that right before heading to work.

  • PopeRigbyPopeRigby Posts: 39Member

    Like this?

    extends StaticBody2D
    
    func _input(event):
        if event.is_action_pressed("subdivide"):
            subdivide()
    
    func subdivide():
        var Stamp = $CollisionShape2D.duplicate()
        Stamp.shape = Stamp.shape.duplicate(true)
        yield(get_tree(),"idle_frame") # <<<<<<<<<<<<<<<<<<<<<
        var oldExtents = Stamp.shape.extents
        Stamp.shape.extents = Stamp.shape.extents / 2
    
    
        var Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x*2,-oldExtents.y*2)) # top left
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(oldExtents.x*2,-oldExtents.y*2)) # top right
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x*2,oldExtents.y*2)) # bottom left
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Stamp.set_position(Stamp.get_position() + Vector2(-oldExtents.x*2,oldExtents.y*2)) # bottom right
        Stamp.get_node("Sprite").scale = Stamp.shape.extents * 0.0316
        get_parent().add_child(Stamp)
    

    That still doesn't work. No errors though

  • HaledireHaledire Posts: 23Member

    3 Things here -

    1. Your bottom right code is doing the same as your top left code. (position -x, position +y) instead of (position +x, position +y). You see nothing happening because the only one that would've been visible is in the wrong spot. If you check the remote tree of your running project you can find this information out.

    2. You are adding a child to your parent. Meaning, you are no longer positioning within the same relative positional space. If you run this scene by itself, you would only see the bottom right child.

    3. Your positioning distance is off.

    4. You are dividing a child but adding it to your parent - You should be either iterating over children to make more children, or having the node duplicate itself in it's parent to make more siblings. Go one way or the other, not halfway in both directions.

    You are writing code as if you expect to only run it on 1 object (ie: You're still directly accessing $CollisionShape2D by name, if you start removing the original node, it's not going to be there later). You can't start to subdivide beyond 1 iteration because you always reference that same node.

    subdivide() should be getting a node passed into it in order to not need a hard coded reference. the code that calls subdivide has to iterate over get_children() in order to be able to repeat beyond the first level of subdivision.

  • PopeRigbyPopeRigby Posts: 39Member

    Sorry, I'm getting a little confused. Could you maybe take a look at this and tell me what I should add? I tried basing it off of what you just said. It's hard for me to figure out what I'm supposed to do without an example.

    extends StaticBody2D
    
    func _input(event):
        if event.is_action_pressed("subdivide"):
            for child in get_children(): 
                subdivide($CollisionShape2D)
    
    func subdivide(node):
        var Stamp = node.duplicate()
        Stamp.shape = Stamp.shape.duplicate(true)
    #    yield(get_tree(),"idle_frame")
        var oldExtents = Stamp.shape.extents
        Stamp.shape.extents = Stamp.shape.extents / 2
    
    
        var Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x/2,-oldExtents.y/2)) # top left
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(oldExtents.x/2,-oldExtents.y/2)) # top right
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x/2,oldExtents.y/2)) # bottom left
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Stamp.set_position(Stamp.get_position() + Vector2(oldExtents.x/2,oldExtents.y/2)) # bottom right
        Stamp.get_node("Sprite").scale = Stamp.shape.extents * 0.0316
        get_parent().add_child(Stamp)
    

    You are adding a child to your parent. Meaning, you are no longer positioning within the same relative positional space. If you run this scene by itself, you would only see the bottom right child.

    So where should I add the child?

  • HaledireHaledire Posts: 23Member
    edited June 10 Accepted Answer

    As you keep cleaning out redundancies, you get closer to the code that I've made here in the earlier post:
    https://godotforums.org/discussion/comment/38972/#Comment_38972
    The difference compared to the code you are writing now is that I have a different node order, and the code is executing from what in your case would be the parent of your StaticBody2D. The hierarchy is different but the methods are the same.

    get_children() gets a list of nodes that are a child of your StaticBody2D. Again, currently you are referencing $CollisionShape2D directly, you do NOT want that.

    CollisionShape2D is a child of StaticBody2D. get_children() gets the node reference of any child node (ie: CollisionShape2D on the first iteration). At the end of subdivide, you need to remove this node, as it is no longer necessary (and in terms of iterating over children - detrimental as it's useless baggage - you'll be creating the same nodes endlessly if you don't remove the original: node.queue_free()).

    for child in get_children():
        subdivide(child)
    

    On the second iteration, you want the new nodes created by Subdivide. This means subdivide(child) needs to create new children of the StaticBody2D (ie: add_child(), not get_parent().add_child() - you need more children of StaticBody2D, not more children of your Viewport in the case of running this code by itself). Using for child in get_children() means that you can run this same code regardless if you have 1, 4, 8, 16, 32, 64, etc children and it will run subdivide on each child - provided they are nodes created "within" the static body, not outside. Currently you're trying to make an object inside the node appear outside the node, and then you lack nodes for the code to work from.

  • PopeRigbyPopeRigby Posts: 39Member
    edited June 13

    Ok, cool. Here it is now:

    extends StaticBody2D
    
    func _input(event):
        if event.is_action_pressed("subdivide"):
            for child in get_children(): 
                subdivide(child)
    
    func subdivide(node):
        var Stamp = node.duplicate()
        Stamp.shape = Stamp.shape.duplicate(true)
    #    yield(get_tree(),"idle_frame")
        var oldExtents = Stamp.shape.extents
        Stamp.shape.extents = Stamp.shape.extents / 2
    
    
        var Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x/2,-oldExtents.y/2)) # top left
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(oldExtents.x/2,-oldExtents.y/2)) # top right
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x/2,oldExtents.y/2)) # bottom left
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        add_child(Clone)
    
        Stamp.set_position(Stamp.get_position() + Vector2(oldExtents.x/2,oldExtents.y/2)) # bottom right
        Stamp.get_node("Sprite").scale = Stamp.shape.extents * 0.0316
        add_child(Stamp)
    

    Unfortunately it does this though:
    https://watch.haddock.cc/videos/watch/bf34b89b-582a-4a67-a234-824f3bb2fe1f

  • PopeRigbyPopeRigby Posts: 39Member
    edited June 13

    Wait. I just had to change the divide by 2 to a multiply by 2. I did it! Now I just need to set a limit so it can't subdivide into infinity and break the game.

    Edit: I did that too. Yay!

  • PopeRigbyPopeRigby Posts: 39Member
    edited June 14

    Hey @Haledire, I know this isn't in the scope of this question, but do you think you could help me make it so it will subdivide wherever my mouse goes? Like this:


    (TwistedTwigleg edit: Fixed image link)

  • HaledireHaledire Posts: 23Member
    edited June 14

    That one I'm not quite clear on. You could connect the CollisionShape2D signal mouse_entered to the subdivide function and pass in the node reference.

    How this would work I'm not fully clear. You would need to send the node as a bind with the signal connection (as mouse_entered has no parameters).

    func _ready():
        $GollisionShape2D.connect("mouse_entered", self, "subdivide". [$CollisionShape2D])
    

    in subdivide, for each clone and eventually the stamp:

        var Clone = Stamp.duplicate()
        Clone.connect("mouse_entered", self, "subdivide", [Clone])
        Clone.shape = Clone.shape.duplicate(true)
    

    The shapes will all have to be unique here (instead of just doing the stamp as you need each individual node using it's own unique shape). The thing I'm not clear on here would be handling just how fast things would start subdividing due to the signal.

    (pay no mind to the mouse cursor at the end, that's just how LICECap reacts when I try to stop recording)

    In your case, you're using the CollisionShape2D directly, which doesn't have this signal as far as I can see. You'd need to go up one more level and put your CollisionShape2D into either a KinematicBody2D, StaticBody2D, Area2D, or RigidBody2D (any physics body that takes a CollisionShape2D). My sample image is from me using an Area2D as the node being fed into subdivide()

  • HaledireHaledire Posts: 23Member

    Fun with RigidBodies

    Just to show a slight issue with the fact that this subdivides so fast.

  • MegalomaniakMegalomaniak Posts: 2,580Admin

    Could just implement a limit to how far it can divide things.

  • HaledireHaledire Posts: 23Member
    edited June 15

    I'm just playing around with this at this point. I took a look at a few approaches:

    1. A Timer to delay the connect
    2. call_deferred() to avoid immediate duplication
    3. Limiting the division level

    This is doing call_deferred() and limiting duplication to 5 levels. If I move slowly, call_deferred() slows down the duplication a bit.
    If I use a timer I can get this to divide while remaining mostly stable.

    Though I'm assuming the gaps that appear are because the rigidbodies are exactly end to end here.
    (also slowly realizing this may not fit in "GUI" at this point)

  • MegalomaniakMegalomaniak Posts: 2,580Admin

    Good point, moved to 2D. Though I suspect much of this can apply to 3D as well.

  • PopeRigbyPopeRigby Posts: 39Member
    edited June 16

    I honestly can't remember why I categorized it under GUI.

    Accidentally commented twice

  • PopeRigbyPopeRigby Posts: 39Member

    I honestly can't remember why I categorized it under GUI. Also, mind sharing your code @Haledire?

  • HaledireHaledire Posts: 23Member
    edited June 16

    not much different than what i've already stated about it

    in terms of limiting the division - I set the initial object with some metadata that will be processed with each iteration:
    $RigidBody2D.set_meta("level", 0)

    This data is then tested in subdivide at the very beginning, if it exceeds the limit, the object is removed rather than divided, and the function is ended:

    func subdivide(node):
        var division = node.get_meta("level")
        if division > 4:
                node.queue_free()
                return
        var stamp = node.duplicate()
        stamp.set_meta("level", division + 1)
    

    in the call deferred method, i simply added a step to generating each chunk of the subdivision:

    clone = stamp.duplicate()
    ...#clone preparation
    add_child(clone)
    clone.call_deferred("connect", "mouse_entered", self, "subdivide", [clone], CONNECT_ONESHOT)
    

    The oneshot is just for peace of mind, but the gist here is call deferred delays the connect ever so slightly, so that instead of the new chunk appearing and immediately dividing as it immediately detects the mouse entered, it delays by a frame so that when the connection happens, the mouse is already "in" the shape, so "mouse_entered" won't fire. I suspect due to the fact the rigidbodies shift at times because of the spilt, this can still cause some quick duplication, but doesn't create "dust" size duplicates right away.

    The timer method required me to create an array of clones to iterate over after they were all added to the tree:

    var clone_array = []
    
    clone = stamp.duplicate()
    ...#clone preparation
    add_child(clone)
    clone_array.append(clone)
    

    After all of the clones are generated and still within subdivide, i then set a SceneTreeTimer to delay the connection:

    yield(get_tree().create_timer(.5), "timeout")
    for clone in clone_array:
        clone.connect("mouse_entered", self, "subdivide", [clone], CONNECT_ONESHOT)
    
  • PopeRigbyPopeRigby Posts: 39Member
    edited June 16

    Why are you calling set_meta() on RigidBody2D? Isn't CollisionShape2D the node that's being passed into subdivide()

    for child in get_children():
            subdivide(child)
    

    When I try to use set_meta() on my RigidBody2D - which is my root node - I get Invalid operands 'Nil' and 'int' in operator '>'.

    Here's how I'm calling it, because I haven't gotten to the connecting up the mouse entered signal yet:

    func _on_DestructibleBlock_mouse_entered():
        for child in get_children():
            set_meta("level", 0)
            subdivide(child)
    

    This is my current version of subdivide():

    func subdivide(node):
        var division = node.get_meta("level")
        if division > 4:
                node.queue_free()
                return
        var Stamp = node.duplicate()
        Stamp.set_meta("level", division + 1)
        Stamp.shape = Stamp.shape.duplicate(true)
        var oldExtents = Stamp.shape.extents
        Stamp.shape.extents = Stamp.shape.extents / 2
    
        var Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x*2,-oldExtents.y*2))
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316 # TODO: get sprite scale instead of hardcoding
        add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(oldExtents.x*2,-oldExtents.y*2))
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x*2,oldExtents.y*2))
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        add_child(Clone)
    
        Stamp.set_position(Stamp.get_position() + Vector2(oldExtents.x*2,oldExtents.y*2))
        Stamp.get_node("Sprite").scale = Stamp.shape.extents * 0.0316
        add_child(Stamp)
    
  • HaledireHaledire Posts: 23Member

    what im dividing is the rigidbody2d that contains a sprite and a collisionshape2d. the object im running this code from is the parent of the rigidbody2d.

    meaning when i feed in a child to subdivide, i feed a rigidbody2d. collisionshape2d by itself doesnt have the mouse entered signal im using trigger division, which I mentioned before.

  • PopeRigbyPopeRigby Posts: 39Member

    Ok that makes sense. I made the root node a Node2D like this:

    extends Node2D
    
    var min_extents = Vector2(1.25,1.25)
    var extents = Vector2(100,100)
    
    func _ready():
        $RigidBody2D.set_meta("level", 0)
    
    func _on_DestructibleBlock_mouse_entered():
        for child in get_children():
            subdivide(child)
    
    func subdivide(node):
        var division = node.get_meta("level")
        if division > 4:
                node.queue_free()
                return
        var Stamp = node.get_child(0).duplicate()
        Stamp.set_meta("level", division + 1)
        Stamp.shape = Stamp.shape.duplicate(true)
        var oldExtents = Stamp.shape.extents
        Stamp.shape.extents = Stamp.shape.extents / 2
    
        var Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x*2,-oldExtents.y*2))
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316 # TODO: get sprite scale instead of hardcoding
        get_parent().add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(oldExtents.x*2,-oldExtents.y*2))
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Clone = Stamp.duplicate()
        Clone.set_position(Clone.get_position() + Vector2(-oldExtents.x*2,oldExtents.y*2))
        Clone.get_node("Sprite").scale = Clone.shape.extents * 0.0316
        get_parent().add_child(Clone)
    
        Stamp.set_position(Stamp.get_position() + Vector2(oldExtents.x*2,oldExtents.y*2))
        Stamp.get_node("Sprite").scale = Stamp.shape.extents * 0.0316
        get_parent().add_child(Stamp)
    

    This isn't working though. I doesn't subdivide and a square gets put up into the top left corner:
    https://watch.haddock.cc/videos/watch/610cf280-fb3a-44e4-b6e9-050ebd60d1f3

    Also, the CollisionShape2Ds are getting added as children of root instead of the RigidBody2D:

  • HaledireHaledire Posts: 23Member

    Thing to keep in mind: A RigidBody2D is the Physics body object with the motion logic. A CollisionShape2D is the space that said object takes up. CollisionShape2Ds don't know "how" to collide with other shapes, they only know "where".

    You wouldn't create new CollisionShape2D nodes to break apart the RigidBody2D body - you need more RigidBody2D bodies to separate and make chunks from the original body. The change in code to move up from subdividing the shape to the physics body is only in how you reference the collision shape in order to duplicate the resource and how you reference the sprite in order to change it's scale.

    An analogy for this: An apple. You don't in reality cut the body of an apple and move "the space of the apple" from the "body of the apple". You cut an apple and separate a physical chunk of the body of the apple, said chunk having it's own body and space.

    The reason you're seeing a box show up in the top corner is because you are transferring objects of different coordinate space. There's no code needed to fix this other than to go back to the Stamp duplicating the node going into Subdivide. The reason this is happening is the Child CollisionShape2D has a position of 0 in relation to RigidBody2D. You are now moving that relative position from being inside the RigidBody's space to the Viewport's space.

    This is going back to the earlier problem of having code act on a node's child but placing that child in the node's parent, but acting on the hierarchy from a different direction. Earlier you were telling a node to get_parent().add_child(). Now you're acting from the other direction - add_child(get_child())

Leave a Comment

Rich Text Editor. To edit a paragraph's style, hit tab to get to the paragraph menu. From there you will be able to pick one style. Nothing defaults to paragraph. An inline formatting menu will show up when you select text. Hit tab to get into that menu. Some elements, such as rich link embeds, images, loading indicators, and error messages may get inserted into the editor. You may navigate to these using the arrow keys inside of the editor and delete them with the delete or backspace key.