Which method - pros/cons?

Started by Matty, October 11, 2018, 02:17:03

Previous topic - Next topic

Matty

Greetings peoples.....

Most of us are from a blitz background so I'm sure we'll have a bias here....

a 'loadimage' command can be handled a variety of ways...but two that have my interest are this:

loadimage(file) -> returns a value which is the 'handle' of the image

loadimage(id,file) -> passes a value which becomes the 'handle' of the image.

In the first the numeric identifier is generated at runtime.

In the second the numeric identifier *can* be known at either coding/compile time or runtime.

Is there a conventional way to do it - or do both have their uses?

Qube

AGK is similar on this side and I always use :

id = loadimage( "sprite.png" )

loadimage( id, filename ) also has it's use for something super simple like :


for x = 1 to 10
   loadimage( x, "uberSpriteAnimFrame" + str( x ) + ".png" )
next


Then you have a super easy way of displaying those 10 images ( always 1 to 10 ) rather than the first method which would require an array reference. But that is only good for really simple things. Personally I don't like the second way as for more complex uses it'd become a nightmare but I'm sure some find it easier to get to grips with for simple things.

For keeping track of images though I'd always go with the first method as it's more handy to do things like ( over wordy on purpose ) :


titleScreenImage = loadimage( "mySuperTitleScreen.png" )
healthBar = loadimage( "healthBar.png")

much easier to deal with than using image 1, 2, 3 etc.

Also...

for x = 1 to 32
   playerAnim[ x ] = loadimage( x, "uberSpriteAnimFrame" + str( x ) + ".png" )
next

Again, much easier when you have multiple animations etc.


Wow! I think I pressed my own waffle mode button on this response :P
Mac Studio M1 Max ( 10 core CPU - 24 core GPU ), 32GB LPDDR5, 512GB SSD,
Beelink SER7 Mini Gaming PC, Ryzen 7 7840HS 8-Core 16-Thread 5.1GHz Processor, 32G DDR5 RAM 1T PCIe 4.0 SSD
MSI MEG 342C 34" QD-OLED Monitor

Until the next time.

Kryzon

I always felt held back by the Blitz2D design of this.

LoadImage( file ), with this signature, forces you to load it from a local disk file.

What if you want to load an image from the web? Or from an encrypted blob of data in memory?
In BMax (and other game engines) you have this version:

LoadImage( stream )

Now "stream" is a generic object that streams data. It can come from a local disk file (OpenStream), from the web (Create stream, use HTTP GET and download the bytes to fill the stream object), or from memory (OpenStream from a memory buffer), or even inherit from "stream" class and implement your own weird way of getting data, like from a USB device or something. Absolute freedom.

blinkok

Having tried both, i lean towards specifying the handle. That way you can make the handle have meaning. 1000-1999=tiles, 2000-2999=npcs, 3000-3999=bosses etc

If i allow the system to return a handle then i find i'm looking up tables to know what type of sprite it is.

I have asked the developers of agk if they could add some user memory to each sprite so information could be saved/retrieved to help identify what type of sprite you're dealing with.

Steve Elliott

#4
Quote
titleScreenImage = loadimage( "mySuperTitleScreen.png" )
healthBar = loadimage( "healthBar.png")

much easier to deal with than using image 1, 2, 3 etc.

Also...

for x = 1 to 32
   playerAnim[ x ] = loadimage( x, "uberSpriteAnimFrame" + str( x ) + ".png" )
next

Again, much easier when you have multiple animations etc.

It's better to let the system take care of the id like Qube suggests I feel.  Specifying your own values means it's a nightmare to add more when the id numbers have all been taken (and z-order is important for example) because you'd have to manually re-number if you want sprites loaded in certain places.  If you simply return a handle to a variable you're not locking into your game system - a system that could change.

Quote
Now "stream" is a generic object that streams data.

Using streams doesn't seem as user friendly, language wise to me.

Quote
If i allow the system to return a handle then i find i'm looking up tables to know what type of sprite it is.

I have asked the developers of agk if they could add some user memory to each sprite so information could be saved/retrieved to help identify what type of sprite you're dealing with.

What kind of information - a string name?
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Derron

if you need to know what type a sprite is - then best is to store it next to the sprite:

Code (BlitzMax) Select

Type TSprite
  Field group:int
  Field img:TImage

  Method Init:TSprite(imgObj:object, groupKey:int)
     if string(imgObj)
       img = LoadImage(imgObj)
     elseif TImage(imgObj)
       img = TImage(imgObj)
     endif

     group = groupKey
     return self
  End Method
End Type

const GRP_PLAYER:int = 1
local myImg2:TImage = LoadImage("myimg2.png")
local playerAnim:TSprite[]

playerAnim :+ [new TSprite.Init("myimg1.png", GRP_PLAYER)]
playerAnim :+ [new TSprite.Init(myImg2, GRP_PLAYER)]

(code is untested, just written in the forum editor)

Only hurdle to climb is to "one time" code the sprite class - if you hide that away in another file, the "sprite using file" will contain similar LOC than in the "provide id"-approach.


bye
Ron

Qube

@Derron - He's using AGK, not MrMax :)

Quote from: blinkok on October 11, 2018, 20:52:32
Having tried both, i lean towards specifying the handle. That way you can make the handle have meaning. 1000-1999=tiles, 2000-2999=npcs, 3000-3999=bosses etc

If i allow the system to return a handle then i find i'm looking up tables to know what type of sprite it is.

I have asked the developers of agk if they could add some user memory to each sprite so information could be saved/retrieved to help identify what type of sprite you're dealing with.
If you really needed to know the name of the image then I'd do something like


Dim tile[] As Integer
Dim tileName[] As String

for x = 1 to 100
   tile[ x ] = loadimage("tileImage" + str( x ) + ".png" )
   tileName[ x ] = "tileImage" + str( x ) + ".png"
next


Everything is still dynamic and you can also easily know the tiles name.

When I did ExBiEn I needed to know the tile name to do certain actions. Using a similar method above made it a piece of cake.
Mac Studio M1 Max ( 10 core CPU - 24 core GPU ), 32GB LPDDR5, 512GB SSD,
Beelink SER7 Mini Gaming PC, Ryzen 7 7840HS 8-Core 16-Thread 5.1GHz Processor, 32G DDR5 RAM 1T PCIe 4.0 SSD
MSI MEG 342C 34" QD-OLED Monitor

Until the next time.

blinkok

Yes that's an option, using the image name to categorize the sprite.

In fact agk has a function that will return the image associated with a sprite.

Normally i would have a file that defined all the characteristics of a sprite along with the sprite name. With that technique i would be splitting information across the information file and the image name.


STEVIE G

It's got to the handle for me - the other method makes no sense at all.

I always use constants in my code to track what sprites are in an anim-image.  Then if I add or shift anything (which I do all the time) I only have to change the constants. 


Const OID_PANDA_RED = 0
Const OID_WAGON_GREEN = 1
Const OID_PANDA_BLUE = 2
Const OID_WAGON_ORANGE = 3
Const OID_SUPACOP = 4
Const OID_BUS = 5
Const OID_TAXI = 6
Const OID_MEDIC = 7
Const OID_MORGAN = 8
etc...



Steve Elliott

#9
Quote
It's got to the handle for me - the other method makes no sense at all.

I always use constants in my code to track what sprites are in an anim-image.  Then if I add or shift anything (which I do all the time) I only have to change the constants. 

Const OID_PANDA_RED = 0
Const OID_WAGON_GREEN = 1
Const OID_PANDA_BLUE = 2
Const OID_WAGON_ORANGE = 3
Const OID_SUPACOP = 4
Const OID_BUS = 5
Const OID_TAXI = 6
Const OID_MEDIC = 7
Const OID_MORGAN = 8
etc...

This seems a better way to do things because using integers are so much faster than comparing strings.  I'm struggling to see the benefit of using strings.  Any problem with this method Qube?
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Derron

Strings allow a more versatile "wildcard" system.

"player_running1_1"
"player_running1_2"
"player_running1_3"
"player_running1_4"
"player_running2_1"
"player_running2_2"
"player_running2_3"
"player_running2_4"

You could now easily fetch all "player_running" options and randomly choose one of them. With integers you need in advance to know how many "runningX" variants will exist. What happens if you want to have your animations defineable via .ini/.xml/.json... -files? Maintaining "unique IDs" is then a bit more difficult (involves multiple keys - userID, animationID which could collide with others - hence the useRID).

Of course you could use two keys: the animID and the animGroupID (animGroupID would then identify "running"-mechanisms) but things get more and more complicated then.
But with external files you will need to hold a dynamical created lookup table too - which needs to have some "Getters" then (as you do not know in advance what will be available in what amount).

Lookup time for strings will be not as good (except you use "constants" and compiler optimizes stuff) but most often you only use the strings during the initialization phase of an element - or when changing animation ('SetAnimation("running")'). So it isn't as bad as it sounds right at the beginning.

In my DIG-FrameWork (BlitzMax) the "TRegistry" holds everything as "string:value" pairs (of course "value" can be another registry...). Brucey once introduced "TLowerString" with some faster code when it comes to string comparison) - so my strings are now all "TLowerString" but requests to the lookup table are now even faster (and you can pass an object pointer to a function rather than a copy of a string).
If you do not care about lookup times the retrieval of an image is then dead simple:

DrawImage(GetImageFromRegistry("player_running1_1"), x, y)
'or
GetSpriteFromRegistry("player_running1_1").Draw(x,y)


This is very similar to your "register to a specific number/id" approach. Instead of storing the image handle in your objects ("ship.image = loadimage(bla)") you store the "key/id" to the object. If you now replace the image in the registry it will use this one from then on.

The same approach works also well, if you used a numeric ID. But for this constants won't work well as it would create "holes" if you used an ID 1000 and nothing more - exception is to use some kind of "TIntMap" (by Brucey, similar to a dicitonary/BlitzMax-TMap but using just numbers for faster comparison). Using a simple array works if you use dynamically assigned IDs ("id = GetNextID(), LoadImage(id, uri)").


For smaller projects that "const" way of doing might work but as soon as you need to work with externally customizable stuff or want things to become more "dynamic" the simple approach won't work anymore.


bye
Ron

Qube

#11
QuoteIt's got to the handle for me - the other method makes no sense at all.

I always use constants in my code to track what sprites are in an anim-image.  Then if I add or shift anything (which I do all the time) I only have to change the constants. 
But if you did :


OID_PANDA_RED = LoadImage( "animStrip1.png" )
OID_WAGON_GREEN = LoadImage( "animStrip2.png" )
OID_PANDA_BLUE = LoadImage( "animStrip3.png" )
OID_WAGON_ORANGE = LoadImage( "animStrip4.png" )
OID_SUPACOP = LoadImage( "animStrip5.png" )
etc....


Then you'd not have to keep going back to change your code as each is automatically assigned an id number. Overall there is no proper way but only the way that works best for you.

QuoteStrings allow a more versatile "wildcard" system.
And also a lot slower than integers when dealing with a lot of images like in a tile map.

Sometimes for quick and easy coding it's handy to know the exact image name ( string ) without having to map potentially thousands of images and one very easy way was mentioned here. No need to assign id's or create lookup tables.

Damn! still only one page debating this.. Derron, go!.... :P
Mac Studio M1 Max ( 10 core CPU - 24 core GPU ), 32GB LPDDR5, 512GB SSD,
Beelink SER7 Mini Gaming PC, Ryzen 7 7840HS 8-Core 16-Thread 5.1GHz Processor, 32G DDR5 RAM 1T PCIe 4.0 SSD
MSI MEG 342C 34" QD-OLED Monitor

Until the next time.

Steve Elliott

#12
Quote
Damn! still only one page debating this.. Derron, go!.... :P

It's a fascinating topic, thanks guys.  The more opinions the better.
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Derron

@ qube
your way (name-array + id-array) is very similar to a lookup table. Once you need to access via "name" instead of "id" you will have to find the index of the "name" and then use it on the id-array. Using a binary tree (or other hash-thingy-container) it might be faster (depending on how often you use stuff).

As said it depends on what you want to be possible to do. If you had external files (eg. custom user level stuff) they might prefer using "player_running" rather "a number"). So having a lookup possibility (as you proposed too) is to prefer to a strict "const blaID:int = 1" way of doing.


What might be a benefit of "const"-definitions is: if you have a good IDE it offers auto-completition for it while a "registry" would be a runtime-only-thing and allows for spelling mistakes. Think this is why people prefer having constants for groups ("const TILE_WALL:int") rather than using a string. This works well for most static tilemaps (so as long as the user of your tilemap-generator-app cannot extend it and does not have to use "TILE_CUSTOMx" predefined constants).


And you are right that for prototyping "const/globals" will do fine
global playerImage = LoadImage() ...


BTW I am pretty sure that I use that "index-array + name-array" in many situations within TVTower. Especially for specific functionality which is only used in this game rather than the framework (which is used for more apps/games). I use it if I can assure that I only need a limited amount of options. Instead of requesting from 2000 elements I only need to check <5 elements. Time for retrieval is then shorter/faster. All under  the assumption to retrieve by "key" (guid, id, ...) rather than "array index".


@ Steve
You are right - everybody here has it's own/trained opinion about things. And even if others might be right we most likely are not willed to accept their way of doing as the better variant.


bye
Ron

Steve Elliott

#14
Quote
@ Steve
You are right - everybody here has it's own/trained opinion about things. And even if others might be right we most likely are not willed to accept their way of doing as the better variant.

This is an interesting topic for me so the more varied the input the better, we can all learn and discuss options from people with varying ideas that may lead to new ways of doing things.  I disagree about people not willing to accept new ways of doing things, I'm personally open to new ideas   :D

I guess it depends on the complexity of the game, and I'm not talking about the implementation (which might be over-complicating unnecessarily) but simply the mechanics of game a versus game b.
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb