Coder Social home page Coder Social logo

Comments (9)

RangerDev1 avatar RangerDev1 commented on July 20, 2024

Hey bro! As I said on twitter, i'v coded the node to make an array by path. Well, I don't know if it's the best way, and I see several optimization points, but I think it should contribute to the plugin :)

The path positioning and rotation function I got a long time ago in a post inside godot's github, the engine has a problem with PathFollow in the orientation that still doesn't work correctly, some user posted this code as a solution until it is corrected (it's not the original is modified) but I couldn't find the post anymore.

I'm putting the code here because I won't be able to fork to give the PR at the moment.

array_modifier.gd:

tool
extends EditorPlugin


func _enter_tree():
	add_custom_type(
		"ArrayModifier",
		"Spatial",
		preload("ArrayModifier.gd"),
		preload("array_modifier_icon.png")
	)
	add_custom_type(
		"ArrayModifierPath",
		"Spatial",
		preload("ArrayModifierPath.gd"),
		preload("array_modifier_icon.png")
	)
	add_custom_type(
		"ArrayModifier2D",
		"Node2D",
		preload("ArrayModifier2D.gd"),
		preload("array_modifier_icon_2d.png")
	)


func _exit_tree():
	remove_custom_type("ArrayModifier")
	remove_custom_type("ArrayModifier2D")
	remove_custom_type("ArrayModifierPath")

ArrayModifierPath.gd:

extends Spatial
tool

export(NodePath) var pathTrack setget set_path
#export(float) var pathOffsetStart
export(float) var repeatOffset setget set_repeat_offset

export (int) var repeat_count = 1 setget set_repeat_count
export (bool) var force_refresh = false setget set_force_refresh

var _hooks = Dictionary()
var p = null

func _ready():
	if p != null:
		_adjust_copies()

func _get_copy_position_rotation(_instance_number):
	var position = _instance_number * repeatOffset
	var plen = p.curve.get_baked_length()
	var absOffs = fmod(plen*position,plen)
	var offs1 = max(absOffs-0.01,0)
	var offs2 = min(absOffs+0.01,plen)
	var p1 = p.curve.interpolate_baked(offs1,true)
	var p2 = p.curve.interpolate_baked(offs2,true)
	var newpos = p1+((p2-p1)/2.0)

	return [Vector3(newpos.x,newpos.y,newpos.z), Vector3(p2.x,p2.y,p2.z)]

func set_path(value):
	if get_node(value) is Path:
		p = get_node(value)
		pathTrack = value
		
		_adjust_copies()
	else:
		p = null
		pathTrack = null

func set_repeat_count(value):
	# Make sure the value will be at least 1
	
	repeat_count = max(value,1)
	if pathTrack != null:
		_adjust_copies()

func set_repeat_offset(value):
	# Make sure there is always the same number of offsets and levels
	if value == repeatOffset:
		return
	
	repeatOffset = value
	
	if p != null:
		_adjust_position_of_copies()

func set_force_refresh(value):
	if p != null:
		_adjust_copies()


func _adjust_copies():
	# Clean up existing hooks
	for child in get_children():
		if child.name.begins_with(_get_hook_name_prefix()):
			remove_child(child)
			child.queue_free()
	
	for orig_child in get_children():
		# Ignore hooks
		if orig_child.name.begins_with(_get_hook_name_prefix()):
			continue
		
		# Create a "hook" for each actual child, to hold the copies in
		var hook = Spatial.new()
		hook.name = _get_hook_name(orig_child)
		add_child(hook)
		_hooks[hook.name] = hook
		
		# Index 0 would be the original, so start with 1
		for index in range(0, repeat_count):
			var _pr = _get_copy_position_rotation(index)
			if index == 0:
				orig_child.transform.origin = _pr[0]
				orig_child.transform.looking_at(_pr[1],Vector3.UP)
			else:
				var temp_index = index
				var copy = orig_child.duplicate()
				copy.transform.origin = _pr[0]
				copy.transform = copy.transform.looking_at(_pr[1],Vector3.UP)
				hook.add_child(copy)


func _adjust_position_of_copies():	
	for orig_child in get_children():
		# Ignore hooks
		if orig_child.name.begins_with(_get_hook_name_prefix()):
			continue
		
		# Find the corresponding hook
		var hook = _hooks.get(_get_hook_name(orig_child))
		if hook == null:
			printerr("Corresponding hook not found: ", orig_child.name)
			return
		
		# Index 0 would be the original, so start with 1
		for index in range(0, repeat_count):
			var temp_index = index
			var _pr = _get_copy_position_rotation(index)
			if index == 0:
				orig_child.transform.origin = _pr[0]
				orig_child.global_transform = orig_child.global_transform.looking_at(_pr[1],Vector3.UP)
			else:
				# Find the copy that should be moved
				var copy = hook.get_children()[index - 1]
				copy.transform.origin = _pr[0]
				copy.global_transform = copy.global_transform.looking_at(_pr[1],Vector3.UP)


func _get_hook_name_prefix():
	return "_array_modifier_hook_" + str(get_instance_id())


func _get_hook_name(child):
	return _get_hook_name_prefix() + "_" + str(child.get_instance_id())

The code is very simple and I think it's a nice start for this feature!

Hope this helps!

from godot-array-modifier.

miskatonicstudio avatar miskatonicstudio commented on July 20, 2024

I think this would be a great first version! Thanks a lot for the contribution :)

Do you want me to copy this code to the plugin, test, and commit? Or do you prefer to create a PR on your own? The second way makes your contribution in this repo more visible, but I'm ok with both ;)

from godot-array-modifier.

RangerDev1 avatar RangerDev1 commented on July 20, 2024

You can do without any problems for me. I won't be able to access my VSCODE anytime soon. I'm glad I helped! :D

from godot-array-modifier.

RangerDev1 avatar RangerDev1 commented on July 20, 2024

I made some updates. I am committed to this because I will use it in my game! I already had this solution but not in plugin, as the community like you always help I want to help too, These adjustments put offset (instanceOffset) that I use a lot here and also the function to apply copies, which you can use without problems on others scripts of this plugin.

extends Spatial
tool

var _hooks = Dictionary()
var p = null
var h = null
var plen = 0
var _os = 0
var offsetSize = 1

export(NodePath) var pathTrack setget set_path
export(float, 0, 1, 0.001) var pathOffsetStart setget set_pathOffsetStart
export(float) var repeatOffset setget set_repeat_offset
export(Vector3) var instanceOffset = Vector3.ZERO

export (int) var repeat_count = 1 setget set_repeat_count
export (bool) var force_refresh = false setget set_force_refresh
export(NodePath) var hostNode setget set_hostNode
export (bool) var applyCopies = false setget set_apply_copies

func _ready():
	if p != null:
		_adjust_copies()

func _get_copy_position_rotation(_instance_number):
	var position = _os + _instance_number * repeatOffset 
	var absOffs = fmod(plen*position,plen)
	var offs1 = max(absOffs-0.01,0)
	var offs2 = min(absOffs+0.01,plen)
	var p1 = p.curve.interpolate_baked(offs1,true)
	var p2 = p.curve.interpolate_baked(offs2,true)
	var newpos = p1+((p2-p1)/2.0)

	return [Vector3(newpos.x,newpos.y,newpos.z), Vector3(p2.x,p2.y,p2.z)]

func set_path(value):
	if get_node(value) is Path:
		p = get_node(value)
		plen = p.curve.get_baked_length()
		pathTrack = value
		
		_adjust_copies()
	else:
		p = null
		pathTrack = null

func set_pathOffsetStart(value):
	pathOffsetStart = value
	_os = pathOffsetStart
	
	if pathTrack != null:
		_adjust_position_of_copies()

func set_repeat_count(value):
	# Make sure the value will be at least 1
	
	repeat_count = max(value,1)
	if pathTrack != null:
		_adjust_copies()

func set_repeat_offset(value):
	# Make sure there is always the same number of offsets and levels
	if value == repeatOffset:
		return
	
	repeatOffset = value
	
	if p != null:
		_adjust_position_of_copies()

func set_force_refresh(value):
	if p != null:
		_adjust_copies()

func set_hostNode(value):
	if get_node_or_null(value) != null:
		if get_node(value) != self:
			hostNode = value
			h = get_node(value)

func set_apply_copies(value):
	if value:
		if h != null:
			for orig_child in get_children():
				# Ignore hooks
				if orig_child.name.begins_with(_get_hook_name_prefix()):
					continue
				
				# Find the corresponding hook
				var hook = _hooks.get(_get_hook_name(orig_child))
				if hook == null:
					printerr("Corresponding hook not found: ", orig_child.name)
					return
				
				# Index 0 would be the original, so start with 1
				for index in range(1, repeat_count):
					var copy = hook.get_children()[index - 1].duplicate()
					h.add_child(copy)
					copy.set_owner(h.get_owner())
			repeat_count = 1
			_adjust_copies()

func _adjust_copies():
	# Clean up existing hooks
	for child in get_children():
		if child.name.begins_with(_get_hook_name_prefix()):
			remove_child(child)
			child.queue_free()
	
	for orig_child in get_children():
		# Ignore hooks
		if orig_child.name.begins_with(_get_hook_name_prefix()):
			continue
		
		# Create a "hook" for each actual child, to hold the copies in
		var hook = Spatial.new()
		hook.name = _get_hook_name(orig_child)
		add_child(hook)
		_hooks[hook.name] = hook
		
		# Index 0 would be the original, so start with 1
		for index in range(0, repeat_count):
			var _pr = _get_copy_position_rotation(index)
			if index == 0:
				orig_child.transform.origin = _pr[0]
				orig_child.transform = orig_child.transform.looking_at(_pr[1],Vector3.UP)
				if instanceOffset != Vector3.ZERO:
					orig_child.translate(get_transform().basis.xform(instanceOffset))
			else:
				var temp_index = index
				var copy = orig_child.duplicate()
				copy.transform.origin = _pr[0] + instanceOffset
				copy.transform = copy.transform.looking_at(_pr[1],Vector3.UP)
				if instanceOffset != Vector3.ZERO:
					copy.translate(get_transform().basis.xform(instanceOffset))
				hook.add_child(copy)


func _adjust_position_of_copies():	
	for orig_child in get_children():
		# Ignore hooks
		if orig_child.name.begins_with(_get_hook_name_prefix()):
			continue
		
		# Find the corresponding hook
		var hook = _hooks.get(_get_hook_name(orig_child))
		if hook == null:
			printerr("Corresponding hook not found: ", orig_child.name)
			return
		
		# Index 0 would be the original, so start with 1
		for index in range(0, repeat_count):
			var _pr = _get_copy_position_rotation(index)
			if index == 0:
				orig_child.transform.origin = _pr[0]
				orig_child.transform = orig_child.transform.looking_at(_pr[1],Vector3.UP)
				if instanceOffset != Vector3.ZERO:
					orig_child.translate(get_transform().basis.xform(instanceOffset))
			else:
				# Find the copy that should be moved
				var copy = hook.get_children()[index - 1]
				copy.transform.origin = _pr[0]
				copy.transform = copy.transform.looking_at(_pr[1],Vector3.UP)
				if instanceOffset != Vector3.ZERO:
					copy.translate(get_transform().basis.xform(instanceOffset))


func _get_hook_name_prefix():
	return "_array_modifier_hook_" + str(get_instance_id())


func _get_hook_name(child):
	return _get_hook_name_prefix() + "_" + str(child.get_instance_id())

from godot-array-modifier.

RangerDev1 avatar RangerDev1 commented on July 20, 2024

It's also important that I did it quickly, without a doubt maybe need to work better on the variable names, functions and maybe a code cleanup. Oh and of course a cool icon! lol

from godot-array-modifier.

miskatonicstudio avatar miskatonicstudio commented on July 20, 2024

Thanks again! I hope to test your solution this evening, assuming I find some time :)

from godot-array-modifier.

RangerDev1 avatar RangerDev1 commented on July 20, 2024

No problem! Here`s a screen using this solution on my game :D
image

from godot-array-modifier.

miskatonicstudio avatar miskatonicstudio commented on July 20, 2024

The game looks amazing! And I can see so many use cases for "duplicate along path" feature ;)

I've managed to clean up the code a bit (mostly rename some variables) and add a nice example scene. I've also created a new icon, basically the same as ArrayModifier, but with a curve in front. Unfortunately, 16x16 icon doesn't really show a difference :D

One thing that I decided to remove is the "apply" button. I like the feature, but I think all 3 versions (3D, 2D, Path) should receive it together. I'll add it later, since there is already an issue for that (and I saw that you commented there ;) ).

Thanks again for an awesome contribution! 👍

from godot-array-modifier.

miskatonicstudio avatar miskatonicstudio commented on July 20, 2024

Last comment related to #2

from godot-array-modifier.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.