That would be cool. Ultimately, I realize (and I think we all do!) not everything will be implemented into any engine / framework but common things... like what I would consider boiler plate type of stuff that is just needed purely from a technical perspective... foundational items... these kinds of things should be included right in the api and backend system. Especially when the same kind of fix object to object functionality already exists for the 3D side.
For what it's worth and in case it is helpful to anybody here is the system and demo I made when I went down the rabbit hole the other day investigating this.
I didn't test all scenarios only an initial test of a single centered turret and then a test of 3 turrets which is what the code and animated gif below illustrate.
It
should work for anything but again no guarantees.
This FixSpriteToSprite method works like the native FixObjectToObject method signature in that you only pass in the spriteID that you want to attach and the ID of the sprite you want to attach it to.
The method sorts out how to do that based on the current positioning and angles of the sprites passed in.
Note that for a complete system we'd need to add in a RemoveFixedSprite(spriteID) method which removes that one from the list. Easy enough to do though.
There is a lot of set up code but that doesn't have much at all to do with my FixedSprite system and is just for the demo. Which also illustrates defining waypoints and having an enemy travel across it.
But still this kind of stuff should be built-in and then I could have spent more time working on my actual game instead of playing around with this! LOL!
EDIT: I did some more testing with placement of the turrets and updated the FixSpriteToSprite function accordingly...
SetErrorMode(2)
// set window properties
SetWindowTitle( "FixSpriteToSprite" )
SetWindowSize(1280,720,0)
SetWindowAllowResize( 1 ) // allow the user to resize the window
// set display properties
SetVirtualResolution(1280,720)
SetOrientationAllowed(1,1,1,1 )
SetVSync(1)
SetScissor( 0,0,0,0 )
type TPathNode
x as float
y as float
endtype
type TTurret
sprite as integer
active as integer // if 0 then it has been destroyed
endtype
type TEnemy
sprite as integer
moveTween as integer
turret as TTurret[2] // 3 turrets
targetNodeID // node (waypoint) on the path the enemy is moving to
angle as float
firingTimer as float
firingTurret as integer
endtype
type TPlayer
sprite as integer
xp as float
yp as float
endtype
type TEnemyBullet
sprite as integer
moveTween as integer
active as integer
endtype
global imgSquare as integer
global path as TPathNode[7]
global enemy as TEnemy
global player as TPlayer
global ebullets as TEnemyBullet[14]
imgSquare = CreateImageColor(255,255,255,255)
// define path for enemy to move on.
// This path would have been smoother as a simple cos/sin based motion
// BUT the idea here is the path can be anything desired and thought someone might want to have an
// example of how to do a simple waypoint path system.
path[0].x = 0 + 80
path[0].y = 239
path[1].x = 426
path[1].y = 0 + 80
path[2].x = 853
path[2].y = 0 + 80
path[3].x = 1279 - 80
path[3].y = 239
path[4].x = 1279 - 80
path[4].y = 480
path[5].x = 853
path[5].y = 719 - 80
path[6].x = 426
path[6].y = 719 - 80
path[7].x = 0 + 80
path[7].y = 480
// create enemy base
enemy.sprite = CreateSprite(imgSquare)
SetSpriteDepth(enemy.sprite, 102)
SetSpriteSize(enemy.sprite, 128,128)
SetSpriteOffset(enemy.sprite, 64,64)
SetSpriteColor(enemy.sprite, 160,0,0,255)
// create 3 turrets
i as integer
for i = 0 to enemy.turret.length
enemy.turret[i].sprite = CreateSprite(imgSquare)
SetSpriteDepth(enemy.turret[i].sprite, 100)
SetSpriteSize(enemy.turret[i].sprite, 50,20)
SetSpriteOffset(enemy.turret[i].sprite, 25,10)
SetSpriteColor(enemy.turret[i].sprite, 255,0,0,255)
next i
// position the sprites making up this complex object
SetSpritePositionByOffset(enemy.sprite, path[0].x, path[0].y) // start at node 0 on the path
SetSpritePositionByOffset(enemy.turret[0].sprite, GetSpriteXByOffset(enemy.sprite) - 40, GetSpriteYByOffset(enemy.sprite))
SetSpritePositionByOffset(enemy.turret[1].sprite, GetSpriteXByOffset(enemy.sprite), GetSpriteYByOffset(enemy.sprite))
SetSpritePositionByOffset(enemy.turret[2].sprite, GetSpriteXByOffset(enemy.sprite) + 40, GetSpriteYByOffset(enemy.sprite))
// Fix the turret sprites to the enemy base sprite.
// Doing this adds them to the FixedSprite list which are then updated when FixedSpriteUpdate() is called
FixSpriteToSprite(enemy.turret[0].sprite, enemy.sprite)
FixSpriteToSprite(enemy.turret[1].sprite, enemy.sprite)
FixSpriteToSprite(enemy.turret[2].sprite, enemy.sprite)
// start the enemy moving on the path
enemy.moveTween = CreateTweenSprite(2.0)
enemy.targetNodeID = 1 // move to node 1 on the path
SetTweenSpriteXByOffset(enemy.moveTween, GetSpriteXByOffset(enemy.sprite), path[enemy.targetNodeID].x, TweenLinear())
SetTweenSpriteYByOffset(enemy.moveTween, GetSpriteYByOffset(enemy.sprite), path[enemy.targetNodeID].y, TweenLinear())
PlayTweenSprite(enemy.moveTween, enemy.sprite, 0)
// create player
player.sprite = CreateSprite(imgSquare)
SetSpriteDepth(player.sprite, 50)
SetSpriteSize(player.sprite, 96,96)
SetSpriteOffset(player.sprite, 48,48)
SetSpriteColor(player.sprite, 0,0,255,255)
SetSpritePositionByOffset(player.sprite, 640, 360)
// initialize enemy bullets
for i=0 to ebullets.length
ebullets[i].sprite = CreateSprite(imgSquare)
ebullets[i].moveTween = CreateTweenSprite(0.7)
SetSpriteDepth(ebullets[i].sprite, 101)
SetSpriteSize(ebullets[i].sprite, 16,16)
SetSpriteOffset(ebullets[i].sprite, 8,8)
SetSpriteColor(ebullets[i].sprite, 255,96,64,255)
SetSpriteVisible(ebullets[i].sprite, 0)
ebullets[i].active = 0
next i
enemy.firingTurret = -1
enemy.firingTurret = 300
sleep(500)
sync()
sync()
// *****************************
// DEMO MAIN LOOP
// *****************************
x as integer
y as integer
do
UpdateAllTweens(GetFrameTime())
// this is the enemy moving around the path
// when enemy reaches target node set next node on path as next TweenMove target
if not GetTweenSpritePlaying(enemy.moveTween, enemy.sprite)
inc enemy.targetNodeID, 1
if enemy.targetNodeID > path.length then enemy.targetNodeID = 0
SetTweenSpriteXByOffset(enemy.moveTween, GetSpriteXByOffset(enemy.sprite), path[enemy.targetNodeID].x, TweenLinear())
SetTweenSpriteYByOffset(enemy.moveTween, GetSpriteYByOffset(enemy.sprite), path[enemy.targetNodeID].y, TweenLinear())
PlayTweenSprite(enemy.moveTween, enemy.sprite, 0)
endif
// rotate the enemy base sprite
inc enemy.angle, -1
if enemy.angle >= 360 then dec enemy.angle, 360
SetSpriteAngle(enemy.sprite, enemy.angle)
FixedSpriteUpdate()
// update the turrets to point toward the player
for i=0 to enemy.turret.length
x = GetSpriteXByOffset(player.sprite) - GetSpriteXByOffset(enemy.turret[i].sprite)
y = GetSpriteYByOffset(player.sprite) - GetSpriteYByOffset(enemy.turret[i].sprite)
SetSpriteAngle(enemy.turret[i].sprite, Atan2(y, x))
next i
// update enemy firing control
dec enemy.firingTimer, 1
if enemy.firingTimer < 1
inc enemy.firingTurret, 1
if enemy.firingTurret > enemy.turret.length then enemy.firingTurret = 0
for i=0 to ebullets.length
if ebullets[i].active = 0
x = GetSpriteXByOffset(enemy.turret[enemy.firingTurret].sprite)
y = GetSpriteYByOffset(enemy.turret[enemy.firingTurret].sprite)
SetSpritePositionByOffset(ebullets[i].sprite, x, y)
SetTweenSpriteXByOffset(ebullets[i].moveTween, x, GetSpriteXByOffset(player.sprite), TweenLinear())
SetTweenSpriteYByOffset(ebullets[i].moveTween, y, GetSpriteYByOffset(player.sprite), TweenLinear())
PlayTweenSprite(ebullets[i].moveTween, ebullets[i].sprite, 0)
SetSpriteVisible(ebullets[i].sprite, 1)
ebullets[i].active = 1
enemy.firingTimer = 15
exit
endif
next i
endif
// move bullets
for i=0 to ebullets.length
if ebullets[i].active = 1
if not GetTweenSpritePlaying(ebullets[i].moveTween, ebullets[i].sprite)
SetSpriteVisible(ebullets[i].sprite, 0)
ebullets[i].active = 0
endif
endif
next i
Sync()
if GetRawKeyPressed(27) then exit // exit when Esc keyis pressed
loop
end
// ******************************************
// FixedSpriteToSprite System
// ******************************************
type TFixedSprite
sprite as integer
parentSprite as integer
ao as float
r as float
endtype
global fixedsprites as TFixedSprite[]
function FixSpriteToSprite(spriteID as integer, toSpriteID as integer)
si as integer
i as integer
xo as float
yo as float
i = -1
// see if sprite is already fixed to a sprite
for si=0 to fixedsprites.length
if fixedsprites[si].sprite = spriteID
i = si
exit
endif
next si
// if sprite is not currently fixed to a sprite, create a new entry for it in the array and use that as our index to update
if i < 0
fixedsprites.length = fixedsprites.length + 1
i = fixedsprites.length
endif
fixedsprites[i].sprite = spriteID
fixedsprites[i].parentSprite = toSpriteID
xo = GetSpriteXByOffset(toSpriteID) - GetSpriteXByOffset(spriteID)
yo = GetSpriteYByOffset(toSpriteID) - GetSpriteYByOffset(spriteID)
fixedsprites[i].ao = atan2(yo, xo)
fixedsprites[i].r = abs(xo + yo)
endfunction
function FixedSpriteUpdate()
i as integer
x as float
y as float
a as float
if fixedsprites.length < 0 then exitfunction
for i=0 to fixedsprites.length
x = GetSpriteXByOffset(fixedsprites[i].parentSprite)
y = GetSpriteYByOffset(fixedsprites[i].parentSprite)
a = GetSpriteAngle(fixedsprites[i].parentSprite)
SetSpritePositionByOffset(fixedsprites[i].sprite, x + (cos(a)*fixedsprites[i].r), y + (sin(a)*fixedsprites[i].r))
SetSpriteAngle(fixedsprites[i].sprite, GetSpriteAngle(fixedsprites[i].parentSprite))
next i
endfunction