How to draw directly onto images
BlitzMax Forums/BlitzMax Beginners Area/How to draw directly onto images
| ||
Hi guys, I've been looking for ways to draw directly onto an image. I'm designing a game where the player has an inventory which can hold up to 60 items. In Blitz3D, it's easy to do, using Setbuffer ImageBuffer(Inventory). Then I draw all the icons of the items onto this empty inventory, put some text over the icons (the quantity of each item in the inventory) and finally use this final image to draw onto the screen. The icons of the items are of size 32x32. When something in the inventory changes, the entire inventory is redrawn once (not every frame). This way, I don't need my game to redraw the entire inventory every frame. It only needs to draw the complete inventory image. I've been searching through the forums, but didn't find any solution yet. I've read that most people would draw the empty inventory to the backbuffer, draw all the icons to the backbuffer too, same as the overlaying text (which represents the quantity of every item) and finally grab the image. But this is sloooooowwww. As it's an inventory which will change frequently during the game (everytime the player picks up an item), I think I cannot afford to grab the image, as it's too slow. How can I solve this, as BMax doesn't use buffers, like Blitz3D. Or is BMax fast enough to draw the empty inventory image, 60 icons (size 32x32), 60 lines of text, and a few rects every frame? I still need to draw other stuff on the screen, so it needs to be fast. |
| ||
The only way you could do it with Max2D is with GrabImage. Draw your stuff, grab the image, clear the screen; or something like that. I think you can draw and grab off the screen's size to get around the clearing part (e.g. start drawing at 800, 600 with an 800, 600-sized window, the user should never see the temporary draw), but I do not recall if there was graphics card issues with that method or not. |
| ||
I didn't test it myself if GrabImage is slow, as I don't have BMax installed yet. I bought BMax years ago and tried it on my previous computer, but went back to B3D while waiting for BMax3D. I just checked my accounts-tab and can download BMax when I get home. Then I'll try this, to see if it's fast enough. If I were to use Xors3D (which has render-to-texture), would it be possible to render the inventory to a texture (as images are actually textures AFAIK)? Would it be faster? |
| ||
Or is BMax fast enough to draw the empty inventory image, 60 icons (size 32x32), 60 lines of text, and a few rects every frame? Yes it is. Even so, as this is the simplest route, you'd do well to only resort to complex solutions if and when they're proved necessary. |
| ||
I've installed BlitzMax and tried some of the samples. Especially the fireworks demo was fast. I'll try to convert my current code to BMax code and see how it performs. This can take a while though, as I still need to learn BMax. I even need time to convert my entire type-structures. Using BMax, it would perhaps be simpler to use all my different itemtypes: potions, weapons, armor, loot, ... Right now, I need to have one "TItem" type structure, which holds all parameters of any itemtype. An HP potion only has a few parameters, while an upgraded weapon uses alot more. Right now, every item holds all parameters, while most parameters aren't used by the item in question (an HP potion doesn't need attack-speed, defense, accuracy, evasion, and alot more). But in Blitz3D, I used one array to hold all kinds of items in the bag, so I could only use one kind of type-structure (which holds all parameters). With BMax, this could become simpler i guess. |
| ||
not could, definitely will if you really unleash the object oriented nature and make a TItem type and then expanded types like TWeapon, ... you can still have a global list in TItem to handle all items in there but you can also have one on each item type on its own etc |
| ||
I've tried converting my code to BMax and it works for now. This is what the code currently draws: - Clear the screen using Cls - The empty player infoscreen - The player's level and HP/FP/SP-bars (drawn over the player infoscreen) - The empty inventory image - The icons of 60 items in the inventory - The quantity of each item is displayed on top of the icons The time it takes to draw all this is only 1.201 (1 and 1/5) milliseconds (I let BMax draw all this 1000 times each frame and time this, so the results was more accurate). So I guess it isn't neccessary to hold a copy of the original inventory (which is empty) and draw all the icons onto this image and finally draw the updated inventory in one go. I've ran into some strange stuff by coding this. Type TVehicle Field x, y Method SetName(n:String) End Method Method GetName:String() End Method End Type Type TCar Extends TVehicle Field Name:String Method SetName(n:String) Name = n End Method Method GetName:String() Return Name End Method End Type car1:TVehicle = New TCar car2:TCar = New TCar 'car1.Name = "car1" 'car2.Name = "car2" car1.SetName("car1") car2.SetName("car2") Print car1.GetName() Print car2.GetName() Take this example. Car1 will hold a TVehicle type, while Car2 holds a TCar type. I find it strange that I can use "car2.Name = blabla" but not car1.Name = blabla" (see the commented lines). For car1, I get the error: Identifier Name not found. But when I use the methods, it works fine. Doesn't car1 see that it's actually a TCar object, which includes the Name-field, even when car1 is of the type TVehicle? When using methods, it sees them correctly (it uses the correct method, the one in TCar). I'm using this structure for my items in my game. It uses TItemBase as the basetype. In the menu for the inventory, there is an array: Items:TItemBase[60]. All other itemtypes are derived from this TItemBase type: TItemWeapon, TItemPotion, TItemArmor, ... When I add a TItemPotion to the array, I cannot access the fields that are specifically for the potions. I only have access to the fields in the TItemBase type. Do I really need to created methods for setting each field, like in the above example? I also tried creating a temporary variable "Item:TPotion", then fill in all fields and then link the item to the array using Items[Slot] = Item. Why doesn't it work directly? |
| ||
when you created the car1 you crated it as Tvehicle Tvehicle doesn't know about the field "name" but it does know about setName and getName because you created abstracts for it. when you created the car2 you created it as Tcar which included the field "name" and therefore accessible directly. to solve your problem just move field "name" to TVehicle. |
| ||
Doesn't car1 see that it's actually a TCar object, which includes the Name-field, even when car1 is of the type TVehicle? No. It can't look forwards in the hierarchy. It can only know about itself and all that it's built of. Where would the world be if every kid had access to dads bank account? :) Type child method Drink3LitresOfColaAndEatAKiloOfHaribos:string () return "go hyper" end method end type Type adult Extends child method Drink3LitresOfColaAndEatAKiloOfHaribos:string () return "vomit" end method method BuyBeer:String () return "oooh!" end method end type Permissions automatically imply the adult can do everything the child can (but not always with the same results) but not vice versa. Your inventory isn't a simple hierarchical problem. Show me some parameters for armor, potion, shield etc and I'll try and help you further. |
| ||
I cannot post those parameters yet, because I don't have them myself yet. I've only started with some potions for now. The fields for every itemtype will be expanded while the game grows larger. I don't even know all the parameters for every item yet. to solve your problem just move field "name" to TVehicle. I can't do this, because then I would have to create one big type-structure with all possible fields in it. Then there is no point to using derived types. I have a TItemBase type-structure that will get extended into TItemWeapon, TItemArmor, TItemShield, TItemPotion, ... The TItemWeapon will have an Attack field, while a potion doesn't have any use for an Attack value. Same as a TItemShield object has a Defense value, while a weapon or potion doesn't have a benefit for these parameters. But the inventory should be able to store ALL types of items, so it uses an array: "Items:TItemBase[60]". Since all items in the game will be derived from TItemBase, they can all be stored in the inventory's array. Some items also have a Quantity parameter, but not all. Since a weapons cannot be stacked, they don't have a use for the Quantity parameter. But potions and loot have the Quantity parameter. So I guess I'll need to do one of these: - create the item first as the proper itemtype, set the fields and then add it to the inventory's array - create methods to Get/Set every parameter for every itemtype, except the basetype parameters |
| ||
Since a weapons cannot be stacked, they don't have a use for the Quantity parameter. How would you deal with bows and arrows? |
| ||
The bow uses the TItemWeapon type-structure, the ammo uses TItemAmmo. The bow is just the weapon. It only defines which type of ammo to use. The weapon itself doesn't hold the ammo. The ammo is something different (is a different TItem). In the equipment window, you will be able to equip the bow in one slot and the ammo in another slot. There are 2 slots present to equip ammo. This way, the player can equip arrows in one slot and buttets in the other. Then the player can swap both types of weapons and the weapon will use the correct ammo-type. When you use a weapon that needs ammo, the game checks both ammo-slots for the correct type. If it finds ammo of the correct type in either slot, 1 is reduced from the correct slot. If the player has equipped arrows and bullets, but the weapon needs rockets, the weapon won't do anything, as the correct type of ammo isn't equipped in either slot. There is a reason why weapons/armors/shields cannot be stacked. If you have a sword without any upgrades, it has a different value for the attack power then the same sword with 2 upgrades. Most MMORPG's won't allow you to stack weapons, it will be the same for my game. |
| ||
I can't do this, because then I would have to create one big type-structure with all possible fields in it. Then there is no point to using derived types. that was just to show you the principle behind derived Types. A way to solve it is to cast it back to what the item really is: like I said move the name field to the base let it be a int constant not a string so that it can be tested more efficiently. once you access it through the base. check to see what type it is by reading the name. then cast it to what ever you need it to be: function (obj:Tbase) select obj.name 'name is an integer case CGUN 'CGUN is value 1 local g:Tgun = Tgun(obj) ' casted back to the original type g.Life = 35 'life is a member of Tgun only . . . case CBOW 'CBOW is value 2 local b:Tbow = Tbow(obj) . . . end select end function the thing is you are trying to mix different types with nothing in common but if you store only common items in a base type you wont have that problem. like say, for example, bullets and arrows will both have speed and maybe life and cause damage so those three variables can be used as fields in the base type. and wont need to be casted back to the original one. but if you are trying to put bullets and potions and shields and create a common base type for all of them, you will have to deal with stuff like what I posted above. in a way it sort of defeat the purpose of derived types or polymorphing which is what you are trying to do. |
| ||
The basetype has some common parameters. It has the most important one: a reference to a TItemType instance. I use one array which holds the data about every sort of item in the game. There are HP potions which restore 100, 250, 500, 1000, 2000, 3000, 4000, 5000 hp when they are used. Then there are FP potions with values of 50, 125, 250, 500, 1000, 1500, 2000. And also SP potions of 100, 200, 400, 800. All these potions are represented by TItemTypePotion. And they exist only once. When the player buys 10 HP potions of value 500 for example, a new TItemPotion is created inside the inventory of the player (if he hasn't any yet). And a reference towards the TItemTypePotion of "HP potion 500" is stored in the TItemPotion instance. Then this TItemPotion instantly knows it's name, description, what the potion does (restore 500 hp), and many more. The basetype of TItemPotion holds the reference to the TItemTypePotion, which holds the name of the item, the icon image, what type the item really is, the HP this potion restores when used, the description of the potion and alot more information. So, there is only one instance (TItemTypePotion) which describes an HP potion of 500, but many TItemPotions can reference this instance. This is done to save memory. There is no need to copy the name, description, ... to every HP potion of 500 in existance. It only needs a reference to it. I'll try to create an image to clarify all this, as it confuses myself too. Right now, I only have the potions, but there will be alot more itemtypes. Then it will be very complex to work with, if I don't have anything on paper as a reference. |
| ||
I've constructed an example of how my type-structure is implemented now. As I said before, I use one array called AItemTypes:TItemType[1000]. So it can store all types of itemtypes in my game: weapons, armor, ammo, potions, ... One file is loaded with all the data you see at the bottom. Every itemtype has an ID. The index of the array where an itemtype is stored is equal to the ID of the item. There can never be 2 different itemtypes with the same ID. Now, when the user buys 10 "HP potion 250", a TItemPotion is created in the player's inventory (which is derived from TItem). The TItem typestructure has one base-field: ItemType:TItemType. So, the TItemPotion holds the quantity (10 in this case), while the ItemType field in the baseclass holds the reference to the TItemTypePotion with ID 2 (the reference to the object AItemTypes[2] is stored). So, the item in the player's inventory holds all information regarding the potion through the reference. ![]() |
| ||
the simplest way "I think" that this can be resolve is to create 5 Arrays, one for each item type base. other than that, I don't see any easy solution to your problem. the item type slot itself will control what kind of array to access. I can't see any other simple way of doing that. |
| ||
Then I'll waste too many slots in each array, as each type of item has it's unique ID. If I have 1000 itemtypes, I'll need 5 arrays with 1000 indexes each. In the potions array, there will only be about 50 types of potion I think. This will waste 950 array-indexes. The same will happen for each array. Then I will also need more arrays than 5, as I didn't include amulets and rings, and alot of other stuff in the example above. I need only one array that holds ALL itemtypes. Then it's easier to find the correct itemtype. I'll find a solution to my problem, it will only take some time. |
| ||
another idea would be to store them all as "object"s in the array and access them as what ever type you need them as long as the program knows where are whatever type of objects in the array. just ideas. Good luck with your project. |
| ||
I can store all kinds of items into the AItemTypes array just fine. It's just that, when the array holds all these items as their base-type, that I cannot access the fields in the derived section directly. I'll need to use methods to retreive them. I've just modified them to load differently. I was using separate functions for each ItemType. Now I only need to create them and the New() method automatically loads the required fields from the opened file (which holds the data for each itemtype). I'll do some more optimising and report back when I have some more difficulties. I use this bmx-file to load all itemtypes (only AutoPotions for now): ' An ItemTypes holds all information about a specific type of item ' During the game, there will be TItem items everywhere. ' These all link to a specific ItemType, so the TItem knows it's name, ID, icon, price, ... ' This variable is used as a reference to the "ItemTypes.txt" file, so every ItemType can access the opened file Global ItemTypesFile ' Predefine the array where all itemtypes are stored (all items will have the reference to one instance in this array to define what the item is and it's properties) Global AItemTypes:TItemTypeBase[1000] ' This declares the base-type for all itemtypes Type TItemTypeBase Field ID:Int ' The ItemID of the itemtype Field Name:String ' The name of the itemtype Field Image:TImage ' The image used To represent the itemtype (32x32 bitmap) Field Stackable:Int ' Defines If this item can be stacked Field Price:Int ' The money that the players gets when he sells the item To an NPC End Type ' This declares the structure for AutoPotion ItemTypes Type TItemTypeAutoPotion Extends TItemTypeBase Field Restore:Int ' The amount of HP/FP/SP that gets restored when used Field PotionType:Int ' Defines the type of autopotion: 1 = HP, 2 = FP, 3 = SP Field UseDelay:Int ' The time in milliseconds before the item can be used again Method New() Local LineFromFile:String, ItemImage:String ' Keep reading While Not Eof(ItemTypesFile) ' Read one line from the file "ItemTypes.txt" LineFromFile:String = ReadLine$(ItemTypesFile) Select True Case Mid$(LineFromFile:String, 1, 10) = "ItemTypeID" ID:Int = Int(Mid$(LineFromFile:String, 14)) Case Mid$(LineFromFile:String, 1, 8) = "ItemName" Name:String = Mid$(LineFromFile:String, 12) Case Mid$(LineFromFile:String, 1, 9) = "ItemImage" Image:TImage = LoadImage(GameDir$ + "Images/Potions/" + Mid$(LineFromFile:String, 13) + ".bmp") If Image:TImage = Null Then RuntimeError "Failed to load autopotion image: " + Mid$(LineFromFile:String, 13) Case Mid$(LineFromFile:String, 1, 9) = "Stackable" If Mid$(LineFromFile:String, 13) = "True" Stackable:Int = True Else Stackable:Int = False EndIf Case Mid$(LineFromFile:String, 1, 5) = "Price" Price:Int = Int(Mid$(LineFromFile:String, 9)) Case Mid$(LineFromFile:String, 1, 7) = "Restore" Restore:Int = Int(Mid$(LineFromFile:String, 11)) Case Mid$(LineFromFile:String, 1, 10) = "PotionType" Select True Case Mid$(LineFromFile:String, 14) = "HP" PotionType:Int = 1 Case Mid$(LineFromFile:String, 14) = "FP" PotionType:Int = 2 Case Mid$(LineFromFile:String, 14) = "SP" PotionType:Int = 3 End Select Case Mid$(LineFromFile:String, 1, 8) = "UseDelay" UseDelay:Int = Int(Mid$(LineFromFile:String, 12)) Case LineFromFile:String = "[/ItemType]" AItemTypes[ID] = Self ' Store this instance in the AItemTypes array at the proper index (the itemtype's ID) Exit ' The end of the ItemType has been found, stop reading End Select Wend End Method End Type Function LoadItemTypes() Local LineFromFile:String ' Open the file "ItemTypes.txt" ItemTypesFile = ReadFile(GameDir$ + "Data\ItemTypes.txt") ' Check If the file has been opened If ItemTypesFile = 0 Then RuntimeError "Unable to read file: ItemTypes.txt" Else ' Read until the und of the file While Not Eof(ItemTypesFile) ' Read one line LineFromFile:String = ReadLine$(ItemTypesFile) ' Check if a new ItemType has been found If Mid$(LineFromFile:String, 1, 9) = "[ItemType" ' Read the MainType, create a new devired instance of the TItemTypeBase depending on the itemtype, and let the new instance use it's New() ' method to load the data about the itemtype automatically Select True ' In case the itemtype is an AutoPotion Case Mid$(LineFromFile:String, 13) = "AutoPotion]" ' Create a new TItemTypeAutoPotion instance, this triggers the New() method in the instance (which loads the rest of the data ' until "[/ItemType]" has been found) NewItemType = New TItemTypeAutoPotion End Select EndIf Wend CloseFile ItemTypesFile EndIf End Function And this is the ItemTypes.txt file: [ItemType = AutoPotion] ItemTypeID = 1 ItemName = HP potion 100 ItemImage = HPPot100 Stackable = True Price = 0 Restore = 100 PotionType = HP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 2 ItemName = HP potion 250 ItemImage = HPPot250 Stackable = True Price = 0 Restore = 250 PotionType = HP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 3 ItemName = HP potion 500 ItemImage = HPPot500 Stackable = True Price = 0 Restore = 500 PotionType = HP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 4 ItemName = HP potion 1000 ItemImage = HPPot1000 Stackable = True Price = 0 Restore = 1000 PotionType = HP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 5 ItemName = HP potion 2000 ItemImage = HPPot2000 Stackable = True Price = 0 Restore = 2000 PotionType = HP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 6 ItemName = HP potion 3000 ItemImage = HPPot3000 Stackable = True Price = 0 Restore = 3000 PotionType = HP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 7 ItemName = HP potion 4000 ItemImage = HPPot4000 Stackable = True Price = 0 Restore = 4000 PotionType = HP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 8 ItemName = HP potion 5000 ItemImage = HPPot5000 Stackable = True Price = 0 Restore = 5000 PotionType = HP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 21 ItemName = FP potion 50 ItemImage = FPPot50 Stackable = True Price = 0 Restore = 50 PotionType = FP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 22 ItemName = FP potion 125 ItemImage = FPPot125 Stackable = True Price = 0 Restore = 125 PotionType = FP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 23 ItemName = FP potion 250 ItemImage = FPPot250 Stackable = True Price = 0 Restore = 250 PotionType = FP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 24 ItemName = FP potion 500 ItemImage = FPPot500 Stackable = True Price = 0 Restore = 500 PotionType = FP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 25 ItemName = FP potion 1000 ItemImage = FPPot1000 Stackable = True Price = 0 Restore = 1000 PotionType = FP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 26 ItemName = FP potion 1500 ItemImage = FPPot1500 Stackable = True Price = 0 Restore = 1500 PotionType = FP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 27 ItemName = FP potion 2000 ItemImage = FPPot2000 Stackable = True Price = 0 Restore = 2000 PotionType = FP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 41 ItemName = SP potion 100 ItemImage = SPPot100 Stackable = True Price = 0 Restore = 100 PotionType = SP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 42 ItemName = SP potion 200 ItemImage = SPPot200 Stackable = True Price = 0 Restore = 200 PotionType = SP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 43 ItemName = SP potion 400 ItemImage = SPPot400 Stackable = True Price = 0 Restore = 400 PotionType = SP UseDelay = 2500 [/ItemType] [ItemType = AutoPotion] ItemTypeID = 44 ItemName = SP potion 800 ItemImage = SPPot800 Stackable = True Price = 0 Restore = 800 PotionType = SP UseDelay = 2500 [/ItemType] |
| ||
I think I won't get many problems using the inventory items. I just added a feature that enables the player to right-click any item in the inventory to equip it. Some potions (HP, FP and SP potions) need to be equipped in the Auto-potion menu before they can be used. It's like RF Online's macro function. In the Auto-potion menu, you'll be able to equip those 3 types of potions and a potion is used automatically when the player's HP/FP/SP drop below a preset value (which the player can choose himself). Right-clicking an item in the inventory equips those 3 types of potions automatically in the AutoPotion menu. If there isn't any potion equipped yet, the item is moved from the inventory to the AutoPotion menu. If there is a potion equipped already, the code determines if the ID is the same. If it's the same, the quantity of the potions in the inventory is added to the quantity of the potions in the autopotion menu. If they aren't the same, they switch places. I use the method Equip() for every itemtype and it works for autopotions already. The Equip method currently isn't present for other itemtypes, because the potions are all i have for now. |
| ||
I've created a worklog for my 3D RPG, so I don't have to report any progress here in the forum. It also has screenshots of the current menu's and some explanation how they work and what they do. http://blitzbasic.com/logs/userlog.php?user=6662&log=1756 |