Here's an include I put together for a project. It's a node "tree" system that sprites can optionally be attached to. It's not well-tested though, and there are problems with using different x and y scales. It could also be made more efficient, but it's a start. Feel free to use it/modify it.
Demo code:
// ----------------------------------------------------------------------------------
// demo code
// ----------------------------------------------------------------------------------
#include "NodeTree.agc"
SetWindowSize(1280, 720, 0)
SetVirtualResolution(1280, 720)
SetSyncRate(0, 1)
// create a node tree
Tree as tNodeTree
// make nodes
NewNode(Tree, 1)
NewNode(Tree, 2)
NewNode(Tree, 3)
NewNode(Tree, 4)
NewNode(Tree, 5)
NewNode(Tree, 6)
// setup parent structure
SetNodeParent(Tree, 2, 1)
SetNodeParent(Tree, 3, 2)
SetNodeParent(Tree, 4, 1)
SetNodeParent(Tree, 5, 4)
// position nodes (local to parent)
SetNodePosition(Tree, 1, 640, 360)
SetNodePosition(Tree, 2, -100, 0)
SetNodePosition(Tree, 3, -50, -50)
SetNodePosition(Tree, 4, 100, 0)
SetNodePosition(Tree, 5, 50, -50)
SetNodePosition(Tree, 6, 500, 500)
// make some sprites
images as integer[5]
sprites as integer[5]
images[0] = CreateImageColor(255, 0, 0, 255)
images[1] = CreateImageColor(0, 255, 0, 255)
images[2] = CreateImageColor(0, 0, 255, 255)
images[3] = CreateImageColor(255, 0, 255, 255)
images[4] = CreateImageColor(0, 255, 255, 255)
images[5] = CreateImageColor(255,255,255,255)
for i = 0 to 5
ResizeImage(images[i], 32, 32)
sprites[i] = CreateSprite(images[i])
next i
// attach sprites to nodes
for i = 0 to 5
SetNodeSprite(Tree, i+1, sprites[i])
next i
do
// delta time
dt# = GetFrameTime()
// Position root node with mouse
SetNodePosition(Tree, 1, GetPointerX(), GetPointerY())
// rotate root node with mouse buttons
a# = a# + (GetRawMouseRightState() - GetRawMouseLeftState()) * 90.0 * dt#
SetNodeAngle(Tree, 1, a#)
// scale root node with mousewheel
SetNodeScale(Tree, 1, GetNodeScaleX(Tree, 1) + GetRawMouseWheelDelta() * 0.1, GetNodeScaleY(Tree, 1) + GetRawMouseWheelDelta() * 0.1)
// motion for the mid sprites
s# = s# + 100.0 * dt#
SetNodePosition(Tree, 2, GetNodeX(Tree, 2), -50 * sin(s#))
SetNodePosition(Tree, 4, GetNodeX(Tree, 4), 50 * -sin(s#))
// rotate the outer sprites
SetNodeAngle(Tree, 3, GetNodeAngle(Tree, 3) + 180.0 * dt#)
SetNodeAngle(Tree, 5, GetNodeAngle(Tree, 5) + -180.0 * dt#)
// parent/unparent 6th node with spacebar
if GetRawKeyPressed(32)
if GetNodeParent(Tree, 6) = -1
SetNodeParent(Tree, 6, 1)
else
SetNodeParent(Tree, 6, -1)
endif
endif
print("Mouse: Move")
print("Mouse Buttons: Rotate")
print("Mouse Wheel: Scale")
print("Spacebar: Parent/Unparent 6th node")
print(ScreenFPS())
sync()
loop
Include code:
// ----------------------------------------------------------------------------------
// NodeTree Include
// By Hendron
// ----------------------------------------------------------------------------------
type tNodeTree
nodes as tNode[]
endtype
type tNode
id as integer
sprId as integer
x as float
y as float
angle as float
scaleX as float
scaleY as float
parent as integer
children as integer[]
_worldX as float
_worldY as float
_worldAngle as float
_worldScaleX as float
_worldScaleY as float
endtype
function NewNode(tree ref as tNodeTree, id)
if id < 0 then exitfunction
if tree.nodes.find(id) > -1 then exitfunction
inst as tNode
inst.id = id
inst.sprId = -1
inst.x = 0
inst.y = 0
inst.angle = 0
inst.scaleX = 1
inst.scaleY = 1
inst.parent = -1
inst._worldX = 0
inst._worldY = 0
inst._worldAngle = 0
inst._worldScaleX = 1
inst._worldScaleY = 1
tree.nodes.insertSorted(inst)
endfunction
function SetNodeParent(tree ref as tNodeTree, node, parent)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction
if parent < -1 then exitfunction
// reset node transforms to world values
tree.nodes[nodeIdx].x = tree.nodes[nodeIdx]._worldX
tree.nodes[nodeIdx].y = tree.nodes[nodeIdx]._worldY
tree.nodes[nodeIdx].angle = tree.nodes[nodeIdx]._worldAngle
tree.nodes[nodeIdx].scaleX = tree.nodes[nodeIdx]._worldScaleX
tree.nodes[nodeIdx].scaleY = tree.nodes[nodeIdx]._worldScaleY
// remove node from current parent's children
currentParentIdx = tree.nodes[nodeIdx].parent
if currentParentIdx > -1
currentParentIdx = tree.nodes.find(tree.nodes[nodeIdx].parent)
if currentParentIdx > -1
childIdx = tree.nodes[currentParentIdx].children.find(node)
if childIdx > -1
tree.nodes[currentParentIdx].children.remove(childIdx)
endif
endif
endif
// set node parent
parentIdx = tree.nodes.find(parent)
if parentIdx > -1 and tree.nodes[nodeIdx].parent <> parent
tree.nodes[nodeIdx].parent = parent
if tree.nodes[nodeIdx].children.find(node) = -1
tree.nodes[parentIdx].children.insertSorted(node)
endif
// update node's local transforms
tree.nodes[nodeIdx].scaleX = tree.nodes[nodeIdx]._worldScaleX / tree.nodes[parentIdx]._worldScaleX
tree.nodes[nodeIdx].scaleY = tree.nodes[nodeIdx]._worldScaleY / tree.nodes[parentIdx]._worldScaleY
vx# = (tree.nodes[nodeIdx]._worldX - tree.nodes[parentIdx]._worldX)
vy# = (tree.nodes[nodeIdx]._worldY - tree.nodes[parentIdx]._worldY)
vMag# = sqrt(vx# * vx# + vy# * vy#)
vAng# = atan2(vy#, vx#)
diffAng# = vAng# - tree.nodes[parentIdx]._worldAngle
dvx# = cos(diffAng#)
dvy# = sin(diffAng#)
tree.nodes[nodeIdx].angle = tree.nodes[nodeIdx]._worldAngle - tree.nodes[parentIdx]._worldAngle
tree.nodes[nodeIdx].x = (dvx# * vMag#) / (tree.nodes[nodeIdx].scaleX * tree.nodes[parentIdx]._worldScaleX)
tree.nodes[nodeIdx].y = (dvy# * vMag#) / (tree.nodes[nodeIdx].scaleY * tree.nodes[parentIdx]._worldScaleY)
else
tree.nodes[nodeIdx].parent = -1
endif
endfunction
function GetNodeParent(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx > -1
exitfunction tree.nodes[nodeIdx].parent
endif
endfunction -1
function GetNodeX(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx].x
function GetNodeY(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx].y
function GetNodeAngle(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx].angle
function GetNodeScaleX(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx].scaleX
function GetNodeScaleY(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx].scaleY
function GetNodeWorldX(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx]._worldX
function GetNodeWorldY(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx]._worldY
function GetNodeWorldAngle(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx]._worldAngle
function GetNodeWorldScaleX(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx]._worldScaleX
function GetNodeWorldScaleY(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction 0.0
endfunction tree.nodes[nodeIdx]._worldScaleY
function GetNodeSprite(tree ref as tNodeTree, node)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction -1
endfunction tree.nodes[nodeIdx].sprId
function SetNodePosition(tree ref as tNodeTree, node, x as float, y as float)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction
tree.nodes[nodeIdx].x = x
tree.nodes[nodeIdx].y = y
parentIdx = tree.nodes.find(tree.nodes[nodeIdx].parent)
_UpdateNode(tree, nodeIdx, parentIdx)
endfunction
function SetNodeAngle(tree ref as tNodeTree, node, angle as float)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction
tree.nodes[nodeIdx].angle = angle
parentIdx = tree.nodes.find(tree.nodes[nodeIdx].parent)
_UpdateNode(tree, nodeIdx, parentIdx)
endfunction
function SetNodeScale(tree ref as tNodeTree, node, scaleX as float, scaleY as float)
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction
tree.nodes[nodeIdx].scaleX = scaleX
tree.nodes[nodeIdx].scaleY = scaleY
parentIdx = tree.nodes.find(tree.nodes[nodeIdx].parent)
_UpdateNode(tree, nodeIdx, parentIdx)
endfunction
function SetNodeSprite(tree ref as tNodeTree, node, spr)
if spr < -1 then exitfunction
nodeIdx = tree.nodes.find(node)
if nodeIdx = -1 then exitfunction
tree.nodes[nodeIdx].sprId = spr
parentIdx = tree.nodes.find(tree.nodes[nodeIdx].parent)
_UpdateNode(tree, nodeIdx, parentIdx)
endfunction
function _UpdateNode(tree ref as tNodeTree, nodeIdx, parentIdx)
localX# = tree.nodes[nodeIdx].x
localY# = tree.nodes[nodeIdx].y
localAng# = tree.nodes[nodeIdx].angle
localScaleX# = tree.nodes[nodeIdx].scaleX
localScaleY# = tree.nodes[nodeIdx].scaleY
if parentIdx > -1
parentX# = tree.nodes[parentIdx]._worldX
parentY# = tree.nodes[parentIdx]._worldY
parentAng# = tree.nodes[parentIdx]._worldAngle
offsetAng# = atan2(tree.nodes[nodeIdx].y, tree.nodes[nodeIdx].x)
parentScaleX# = tree.nodes[parentIdx]._worldScaleX
parentScaleY# = tree.nodes[parentIdx]._worldScaleY
vMag# = sqrt(localX# * localX# + localY# * localY#)
vx# = cos(parentAng# + offsetAng#) * vMag# * (parentScaleX# * localScaleX#)
vy# = sin(parentAng# + offsetAng#) * vMag# * (parentScaleY# * localScaleY#)
tree.nodes[nodeIdx]._worldX = tree.nodes[parentIdx]._worldX + vx#
tree.nodes[nodeIdx]._worldY = tree.nodes[parentIdx]._worldY + vy#
tree.nodes[nodeIdx]._worldAngle = parentAng# + localAng#
tree.nodes[nodeIdx]._worldScaleX = parentScaleX# * localScaleX#
tree.nodes[nodeIdx]._worldScaleY = parentScaleY# * localScaleY#
else
tree.nodes[nodeIdx]._worldX = tree.nodes[nodeIdx].x
tree.nodes[nodeIdx]._worldY = tree.nodes[nodeIdx].y
tree.nodes[nodeIdx]._worldAngle = tree.nodes[nodeIdx].angle
tree.nodes[nodeIdx]._worldScaleX = tree.nodes[nodeIdx].scaleX
tree.nodes[nodeIdx]._worldScaleY = tree.nodes[nodeIdx].scaleY
endif
if tree.nodes[nodeIdx].sprId > -1
spr = tree.nodes[nodeIdx].sprId
SetSpritePositionByOffset(spr, tree.nodes[nodeIdx]._worldX, tree.nodes[nodeIdx]._worldY)
SetSpriteAngle(spr, tree.nodes[nodeIdx]._worldAngle)
SetSpriteScale(spr, abs(tree.nodes[nodeIdx]._worldScaleX), abs(tree.nodes[nodeIdx]._worldScaleY))
flipH = 0
flipV = 0
if tree.nodes[nodeIdx]._worldScaleX < 0 then flipH = 1
if tree.nodes[nodeIdx]._worldScaleY < 0 then flipV = 1
SetSpriteFlip(spr, flipH, flipV)
endif
for i = 0 to tree.nodes[nodeIdx].children.length
cIdx = tree.nodes.find(tree.nodes[nodeIdx].children[i])
if cIdx > -1
_UpdateNode(tree, cIdx, nodeIdx)
endif
next i
endfunction