T
TwoFox

  • Feb 18, 2024
  • Joined Apr 5, 2023
  • 1 best answer
  • Solved with the help of jagh201 on the discord:

    # SCsub
    import os
    
    Import('env')
    
    env.add_source_files(env.modules_sources, "/home/teehee/git/tree-sitter/lib/src/lib.c")
    env.Append(CPPPATH=["/home/teehee/git/tree-sitter/lib/include"
                         , "/home/teehee/git/tree-sitter/lib/src"])
    
    env.StaticLibrary("/home/teehee/godot/modules/simple/ll/tree-sitter", "/home/teehee/git/tree-sitter/lib/src/lib.c")
    
    env.Append(LIBPATH="/home/teehee/godot/modules/simple/ll")
    env.Append(LIBS="tree-sitter")
    
    menv = env.Clone()
    menv.add_source_files(env.modules_sources, "*.cpp")

    I had to let scons build the library itself. The call to StaticLibrary produced an object called libtree-sitter.windows.x86-64.a I renamed it libtree-sitter.a, commented out StaticLibrary, and it worked from there.

  • I'm trying to write a module using tree-sitter with some custom grammars. Here's a minimal example that works outside of Godot:

    /* main.cpp */
    #include <tree_sitter/api.h>
    
    int main() {
      TSParser* p = ts_parser_new();
      return 0;
    }
    # SConstruct
    env = Environment()
    
    env.Append(LIBPATH="/home/teehee/git/tree-sitter")
    env.Append(LIBS="tree-sitter")
    
    env.Program("out", "main.cpp")

    For the test in Godot, I made a trivial module and pasted in the above code:

    /* printer_of_2s.cpp */
    #include "printer_of_2s.h"
    #include <tree_sitter/api.h>
    
    int tt_fn() {
      TSParser* p = ts_parser_new();
      return 0;
    }
    
    
    void PrinterOf2s::print_two() {
      print_line(two);
    }
    
    void PrinterOf2s::_bind_methods() {
      ClassDB::bind_method(D_METHOD("print_two"), &PrinterOf2s::print_two);
    }
    
    
    PrinterOf2s::PrinterOf2s() {
      two = 2;
    }
    # SCsub
    Import('env')
    
    menv = env.Clone()
    menv.add_source_files(env.modules_sources, "*.cpp")
    menv.Append(CPPPATH="/home/teehee/git/tree-sitter/lib/include")
    
    env.Append(LIBPATH="/home/teehee/git/tree-sitter")
    env.Append(LIBS="tree-sitter")

    After compiling with scons p=windows custom_modules=~/godot/modules/ -j 3 (I'm using WSL), I get a linker error:

    /usr/bin/x86_64-w64-mingw32-ld: modules/libmodule_simple.windows.editor.x86_64.a(printer_of_2s.windows.editor.x86_64.o):printer_of_2s.:(.text+0x125): undefined reference to `ts_parser_new'

    I know that the function is defined, because otherwise the first program wouldn't compile. I also know that scons can find the library, because that would give me a different error. I'm not sure what I could be doing differently.

    • Solved with the help of jagh201 on the discord:

      # SCsub
      import os
      
      Import('env')
      
      env.add_source_files(env.modules_sources, "/home/teehee/git/tree-sitter/lib/src/lib.c")
      env.Append(CPPPATH=["/home/teehee/git/tree-sitter/lib/include"
                           , "/home/teehee/git/tree-sitter/lib/src"])
      
      env.StaticLibrary("/home/teehee/godot/modules/simple/ll/tree-sitter", "/home/teehee/git/tree-sitter/lib/src/lib.c")
      
      env.Append(LIBPATH="/home/teehee/godot/modules/simple/ll")
      env.Append(LIBS="tree-sitter")
      
      menv = env.Clone()
      menv.add_source_files(env.modules_sources, "*.cpp")

      I had to let scons build the library itself. The call to StaticLibrary produced an object called libtree-sitter.windows.x86-64.a I renamed it libtree-sitter.a, commented out StaticLibrary, and it worked from there.

  • The question is whether or not attacking should be possible independent of movement. If no (e.g. in a fighting game), I would add a displacement component to the Attack state and maybe rename Move to MovementInput. And when the player enters the Attack state, it takes control of all movement until it finishes. The Attack-Move is then just a normal Attack with a modified movement control scheme.

    If you want the 2 to be independent (e.g. in a twin-stick shooter), then I would define a 2 separate state machines: (Hold, Walk, Run) and (Wait, Attack, Deflect). Both run in parallel at the same time, but they can call each other without modifying their internal state. E.g. if I'm currently in Walk, I can press M2 to initiate Deflect, but this doesn't take me out of Walk. The meta-state machine would then consist of tuples: ((Hold, Wait), (Hold, Attack), (Hold, Deflect), ... , (Run, Attack), (Run, Deflect)).
    Although, I would really try to avoid implementing it explicitly, unless you have a very complex control scheme.

    Both approaches can scale with well-defined states. The 2nd one perhaps less, because of the amount possible connections between a large number of FSMs, but that's a problem with every modular system. It really depends on the type of behaviour your want to model.

  • DaveTheCoder Thank you for the link. I found a solution in one of the issues, but I still don't understand that constructor.

  • And if it's impossible, then what is the purpose of this constructor:

    ● Array Array(base: Array, type: int, class_name: StringName, script: Variant)

    Edit: Solution from github

    var reports: Array[RefCounted]
    reports.assign(Array())
  • rayner9718 Try moving the node with your fall zone above the node with your character. It could be that the signal is being emitted before the function connects to it.

  • Haystack

    ┖╴Node2D
        ┠╴TabContainer
        ┃  ┠╴Label
        ┃  ┖╴Label2
        ┠╴HBoxContainer
        ┃  ┠╴ItemList
        ┃  ┠╴TextEdit
        ┃  ┖╴ItemList2
        ┖╴PanelContainer

    For the top-level containers, calculate the sizes from a script attached to Node2D or just set them manually. Set Horizontal Expand on TextEdit and set min sizes for its siblings (again, a script lets you calculate these). This is just with default Controls but the same logic applies to your custom classes: Containers on top, Controls at the bottom of the tree. And for dysproportionate layouts, expand on the biggest sibling and min sizes on the rest.

    Controls shouldn't tile their children. That's the Containers' job. If you want custom tiling, you should extend Container, not Control, and put your tiling function _notification. There's a code example in the gui docs

  • TwoFox Ah, silly me, I just restated your question. The reason why it doesn't work is that pure Controls don't
    tile their children. You have to code that behaviour yourself

    • You need to order the nodes like this:

       
       ┖╴Node2D
          ┖╴VBoxContainer
             ┠╴HBoxContainer
             ┃  ┠╴Label
             ┃  ┖╴Label2
             ┖╴HBoxContainer2
                ┠╴Label3
                ┖╴Label4

      And then if you want to move the entire grid around, adjust only the top-level container (in this case VBoxContainer). The idea is that Containers automatically tile their direct children, and you build the UI by nesting Containers with different tiling behaviours.

      • I could help out with writing and a bit of code

      • When I was in school, I spent a lot of time reading about lectureship and experimenting with techniques to make my learning more efficient (cause I hated school and wanted to spend as little time studying as possible). What I came up with is a 3-step process. Ideally, you'd have both the docs and a solution on-hand.

        If not, then you can go back to the scientific method: formulate a theory, test that theory and nothing else, formulate a new theory, repeat. In the context of game dev, this would usually mean putting print statements everywhere or watching vars in the debugger. It helps to have some sort of automated testing system for your game. This method is more general but also slower.

        Anyway, if I have ideal conditions, then here's what I do:

        1. Solve the problem, while following the solution to a T.
        2. Solve the problem, only looking at the solution for hints.
        3. Solve the problem on my own without any help.
        4. If at any point, you see something you don't understand, look it up in the docs.

        Also, spam asserts everywhere. Every time you make any assumption about your code, assert. You have no idea how often I've had things crash and burn because I've had values that weren't what I've thought. In the same vein, only develop one feature at a time and pay close attention to your APIs, e.g. Instead of trying to make 'enemy AI', make a path finding algorithm first, and then a state machine that responds to the path finding. And lastly, regularly rebuild small portions of your game from scratch. You'll get better over time, and better code is easier to maintain.

        • xyz I completely forgot about styleboxes and overrides. Thank you

        • xyz That should be impossible. Every character that gets created is added to _chars. And that part of the function is called only if _chars.get() returns non-null. Also, the full error says:

          Invalid set index 'texture' (on base: 'Panel') with value of type 'CompressedTexture2D'

          even if I use x.get_child(0), instead of x.get_node(), so it looks to me like the game can find the right node.

          • xyz replied to this.
          • I'm working on a HUD for a visual novel. I have a cache for characters that have been shown so far (independent of the characters actually on-screen), a function to hide them and another one to show them, move them around, change expressions, etc.

            func show_char(who: String, model: String, lock_chars := true) -> void:
            	assert(who != "")
            	assert(who.is_valid_filename(), "'%s'" % who)
            
            	# Default to base_model if called with an empty string
            	if model == "":
            		model = base_model
            	assert(model.is_valid_filename(), "'%s'" % model)
            
            	# Determine side
            	var side: Container
            	if _next_side:
            		side = %Characters/Right
            	else:
            		side = %Characters/Left
            	assert(side != null)
            
            	# If this character has been shown before, simply reuse the node
            	var x: AspectRatioContainer = _chars.get(who)
            	if x:
            		print(x.get_node("model"))
            		var model = char_path + who + "/" + model
            		x.get_node("%model").texture = load(model)
            		# If the character is shown on the correct side or lock_chars
            		# is true, do nothing
            		if x.get_parent() == side or lock_chars:
            			return
            		# If the character is shown on a different side, hide them
            		if x.get_parent() != null:
            			x.get_parent().remove_child(x)
            		# Show character on the correct side
            		side.add_child(x)
            		x.set_owner(side)
            		# Toggle side
            		_next_side = not _next_side
            		return
            
            	# Character not shown before. Build a new branch
            	assert(_chars.get(who) == null, 
            			"Tried to create an existing character '%s'" % who)
            	# Prepare the branch
            	var img := StyleBoxTexture.new()
            	var img_panel := Panel.new()
            	var img_container := AspectRatioContainer.new()
            	# Load texture
            	model = char_path + who + "/" + model
            	assert(ResourceLoader.exists(model),"%s" % model)
            	img.texture = load(model)
            	# Adjust min size
            	img_container.set_custom_minimum_size(img.texture.get_size())
            	# Build the new panel
            	img_panel.add_theme_stylebox_override("panel", img)
            	img_panel.set_name("model")
            	img_panel.set_unique_name_in_owner(true)
            	# Add panel to container
            	img_container.add_child(img_panel)
            	img_panel.set_owner(img_container)
            	# Name container
            	img_container.set_name(who)
            	img_container.set_unique_name_in_owner(true)
            	# Record it in _chars
            	_chars[who] = img_container
            	# Add it to SceneTree
            	side.add_child(img_container)
            	img_container.set_owner(side)
            	# Toggle side
            	_next_side = not _next_side
            
            
            func hide_char(who: String) -> void:
            	assert(_chars.get(who) != null, "%s" % who)
            	assert(_chars.get(who).get_parent() != null, "%s" % who)
            
            	# Search CharacterRow for the given character and hide them.
            	var x: AspectRatioContainer = _chars.get(who)
            	x.get_parent().remove_child(x)
            	x.set_owner(null)

            The character row is just a bunch of HBoxContainers inside of a MarginContainer.

            The problem comes from hiding and then showing the same character model. The first call to load(), in the 2nd part of show_char(), returns a Texture2D, and life is good. The 2nd call then tries to load the same image but returns a CompressedTexture2D, which raises an Invalid set index error. How can I force load() to always return a Texture2D?

            • TwoFox Well the error message says it all. Panel node doesn't have the texture property so you can't assign to it.

              If you want to change the texture on the Panel's stylebox that you previously overriden via Panel::add_theme_stylebox_override(), you'll need to retrieve Panel's stylebox object using Panel::get_theme_stylebox(), and assign the texture to that object.

              To put it more simply you're trying to do this:

              Panel.texture = some_texture

              While you need to do this:

              Panel.stylebox.texture = some_texture

              But you cannot do it via dot syntax like that (for reasons we won't get into now). Instead you need to call Panel::get_theme_stylebox() to get the reference to the stylebox object.

              So instead of:

              x.get_node("%model").texture = load(model)

              You'll have to do:

              x.get_node("%model").get_theme_stylebox("panel").texture = load(mode)

              Assuming that "panel" is the name of an existing stylebox override you assigned earlier.

          • You could use signals for this kind of thing.

            extends Node2D
            
            # Declare signals
            signal next_phase(day: bool)
            signal next_hr(hr: int, day: bool)
            
            # Declare vars to save day/night cycle state
            var hour := 0
            var max_hrs := 12
            var day := false
            
            
            # Called when the node enters the scene tree for the first time.
            func _ready():
            	# 1 hr in s. THIS IS IN REAL TIME
            	$DayNightCycle.start(3600)
            	# Every time the timer reaches 0,
            	# Advance the current hour
            	$DayNightCycle.timeout.connect(advance_hr)
            	# Toggle day/night if necessary
            	$DayNightCycle.timeout.connect(reset_hr)
            	# Check if it's time to sleep
            	next_hr.connect(sleep)
            
            
            func advance_hr() -> void:
            	hour += 1 # Advance hour
            	next_hr.emit(hour, day) # Notify all connected functions
            
            
            func reset_hr() -> void:
            	if hour == max_hrs: # Check if the day/night is over
            		hour = 0
            		day = not day
            		next_phase.emit(day) # Notify all connected functions
            
            
            func sleep(time: int, time_of_day: bool) -> void:
            	if time == 8 and not time_of_day: # Check if it's time to sleep
            		print("sleep")

            advance_hr() and reset_hr() will be called every time the timer reaches 0, and sleep() will be called every time that advance_hr() runs; however, the if-statements will make sure that it only runs when it's supposed to (i.e. at the 8th hr of the night). This example uses a Timer node, which counts real-world time, but it's trivial to adapt it to your own system: just declare the signals (along with any relevant state info) and emit them when you want things to happen. Then all you have to do is connect the functions from your other scripts.

            If you have a ton of different villagers, you might consider making an array of Callables and using the hour as an index, e.g.

            var night_tasks = [rest, rest, rest, sleep, rest, rest]
            
            
            func rest():
            	pass
            
            
            func do_night_tasks(hr: int):
            	var fn: Callable = night_tasks[hr]
            	if fn:
            		fn.call()
            
            
            next_hr.connect(do_night_tasks)

            This is easier to manipulate programmatically.