Comments (9)
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.
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.
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.
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.
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.
Thanks again! I hope to test your solution this evening, assuming I find some time :)
from godot-array-modifier.
No problem! Here`s a screen using this solution on my game :D
from godot-array-modifier.
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.
Last comment related to #2
from godot-array-modifier.
Related Issues (9)
- Create a 2D version of the ArrayModifier node
- Add unit tests
- Find a way to apply the modifier (the same way it works in Blender) HOT 2
- Create a base class for all modifiers
- Create ArrayModifierPath2D HOT 1
- Undo doesn't work with "Apply"
- Array of CSGboxes disappears when hitting apply while the array is located in CSG shape. HOT 7
- Merge 4.0 branch HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from godot-array-modifier.