Introduction to TYPE Variables & Linked Lists
BlitzMax Forums/BlitzMax Tutorials/Introduction to TYPE Variables & Linked Lists
| ||
This is really REALLY new for me, but now that I finally understand it, I must share it with the rest of you.![]() Basically it draws colored raindrops, but NOTICE that no arrays are used in this code to keep track of the raindrop positions OR their color NOR are we keeping track of where they've been in a separate array either. No, this program is using a method called LINKED LISTS. In it, you can store multiple kinds of data from a TYPE variable. ![]() A TYPE variable as near as I can tell is a definition class, much like you are defining a variable to be integer, float, string, timage, or pixmap. But it is none of these. Instead a TYPE variable is a bit like a box and can contain multiple kinds of data that can be recalled instantly from a LINKED LIST. And yes, you can even have arrays inside Types for added power and complexity. ![]() Think of a LINKED LIST as a large warehouse for these TYPE boxes. When you run the program, we are essentially taking the warehouse, raising it about 300 feet in the air, removing the roof, and any time you hit SPACE a box (raindrop) is created right at the top of it. With gravity, it falls, but something interesting happens when it hits the ground. ![]() I have coded it above so when it hits the bottom of the screen it is removed. Now read that carefully, it is REMOVED, not zeroed out or changed to a negative value nor are we decreasing a total - in fact if you check, there is no variable recording the total raindrops in the air. And you do not have to keep track of the number of elements in them. Unlike other programs where you 'zero' out a condition, or when you may have to do zero and blank out multiple array values just to get the desired effect it has been deleted. No, instead you can tell your program that the item that is there is no longer needed and you can actually REMOVE IT from the linked list with the REMOVE command. In one command alone. There are many advantages to this over conventional arrays. You can essentially fire and forget - create boxes on the fly when you need them and remove them just as easily, and you don't have to keep track of the number of them either, that is handled automatically in the LINKED LIST and EACHIN. What are some things you could use with LINKED LISTS ? Particles for one, anything that can spawn and die rapidly and varies in the number all the time. Imagine having one ship attack you in a top-view shooter and then when one enemy ship hasn't been hit for a-while it SPAWNS a new enemy ship for you to fight. That would be a nightmare to do with normal arrays, but with TYPES and linked lists, it would be quite simple. So overall BlitzMAX is more powerful than you give it credit for. Let it do the hard work for you to keep track of all your loose variable items and arrays, conducted entirely in a TYPE variable and created, recalled, modified, and removed, from a LINKED LIST. Hope This Helps ! |
| ||
Hi. A Blitzmax type can hold more than just variables, it can also contain functions/methods and has a constructor "New". This is where you should add the code that creates the particle. Also, you could add a method "Draw", this will draw the current particle at its location. To illustrate I've moved around some of your code: The main purpose of structuring like this is manageability; you will certainly lose track of stuff like "where did I set the color of the particle again in my code?" otherwise. Hope this helps. |
| ||
I have added Functions in and Escape key.... This is more cleaner code in main loop(where repeat and until is!) ' Rainbow Rain - an experiment and tutorial in TYPE variables and LINKED LISTS Strict Const speed=10 ' change to 1 for maximum speed Const depth=0 ' change to higher # for more raindrops per cycle SeedRnd MilliSecs() SetGraphicsDriver GLMax2DDriver(),0 ' zero forces front buffer Graphics 768,576 Type particle ' My 1st attempt at using TYPE variables ! Field x ' x-coordinates Field y ' y-coordinates Field color:Int ' TYPE variables can hold more than one type AND value ! Method New() Self.x = Rand(-576, 767) ' x-position of rain Self.y = 0 ' unnecessary it seems as by saying NEW it is already zero Self.color = Rand(0, 5) End Method Method Draw() Select Self.color Case 0 SetColor 255, 0, 255 Case 1 SetColor 255, 0, 0 Case 2 SetColor 255, 128, 0 Case 3 SetColor 255, 255, 0 Case 4 SetColor 0, 255, 0 Case 5 SetColor 0, 0, 255 End Select DrawLine Self.x, Self.y, Self.x + 7, Self.y + 14 ' simple streak Self.x:+1 ' move rain down by 2 and across by 1 Self.y:+2 End Method End Type ' this type has 2-integers and one string for it Global list:TList = CreateList() ' this is called a LINKED LIST Global dot:particle,ilist:particle ' a type of array holding more than one value Function RenderWorld() Cls ' clear display so no streaks are left on screen If KeyDown(32) ' hold down SPACE to add a raindrop For Local i = 0 To depth dot=New particle' we are DEFINING a special type of variable now ListAddFirst list,dot ' add this data to the linked list Next EndIf SetColor 255, 255, 255 ' white (for our rain count) DrawText "Rain: " + list.count(), 0, 0 ' show how many drops are in the air For ilist = EachIn list ' EACHIN will work with non-numeric values, and we're looping only as many raindrops as there are ilist.Draw() If ilist.y>=576 Or ilist.x>=768 Then list.remove ilist ' if out of boundaries, remove from list Next ' and YES this does affect the total count, it's not zeroed out, it's actually REMOVED ! ' powerful business linked lists are Delay speed ' decrease this number to slow down animation effect glFlush ' show our work End Function Repeat ' (* MAIN *) RenderWorld() Until KeyDown(Key_Escape) ' (* END OF MAIN *) |
| ||
Some things to note... "Self" is not required when referring to fields or methods in the Type. I think you are playing with fire choosing to render directly to the front buffer. Each to their own of course, and YMMV. |
| ||
My take on it. Added a bit of gravity. Press Enter to create a gust of wind. :) |
| ||
Hello Gang. Looks like everyone had a shot at my code. :) That's fine. I'm not familiar with the command METHOD yet. One step at a time. I'll try to look into that later today. Brucey, are you saying rendering to the front buffer is dangerous ? Can you explain, please ? |
| ||
@brucy, Self however allows auto complete to kickin without you needing to remember the types methods or fields names. (not in the default ide maybe) |
| ||
I been making Shoot em up based on Link lists and I got problem at the moment. I will post video of it plus the code too :) |
| ||
Can paramaters be passed to a New() method ? local t:Tmytype t = New("stringvalue") TmyType testing here and it doesn't seem to like it. |
| ||
It is not possible. For this you create a custom function/method. Method init:mytype(params) self.x = paramXY Return self End Method Local s:mytype = new mytype.init(paramsToUse) Bye Ron |
| ||
No. New() just returns a new instance of a Type. You will need to write some kind of constructor method/function. There are several typical ways to do it : Type TMyType Field x:int Field y:int ' the create method - useful when extending types. Method Create:TMyType(x:int, y:int) Self.x = x Self.y = y Return Self End Method ' the create function Function CreateFunc:TMyType(x:int, y:int) Local this:TMyType = New TMyType this.x = x this.y = y Return this End Function End Type ' a create function alternative - old style, procedural based. Function CreateMyType(x:int, y:int) ' optionally call one of the above Return New TMyType.Create(x, y) ' or implement a version of CreateFunc() here... End Function |
| ||
Yeah, i did have a constructor written that works nicely but had read in a few places about the New method but hadn't seen an example of it. I really like the methods and functions within a type in Blitzmax makes the code very clear and manageable, after mostly using blitzplus |
| ||
There video of it for people to download it and somethings isn't right thought and here the code of it.... https://www.dropbox.com/s/ubrnhkrw9nme85e/IMG_1308.MOV?dl=0 Graphics 640, 480, 0 Global Background = LoadImage("Data\Back_3.png") Global Centre_Player = LoadImage("Data\Player_1.png") Global Left_Player = LoadImage("Data\Player_3.png") Global Right_Player = LoadImage("Data\Player_2.png") ' Enemys Animations Global Enemy_Anim_Part_1=LoadImage("Data\Ennemy_3_1.png") Global Player_X = 250 Global Player_Y = 400 Global Frames = 0 Global Enemys_Animations=0 Global Y Global X2,Y2 Global SPEED=5 Type Alien Field X, Y, Shoot Method New_E() X2 = Rand(20, 630) ' x-position of Enemys y2 = y2 + 1 End Method Method Draw() Select Enemys_Animations Case 0 DrawImage Enemy_Anim_Part_1,X2,y2 End Select End Method End Type Global list:TList = CreateList() Global dot:Alien,ilist:Alien ' a type of array holding more than one value Function RenderWorld() Cls ' clear display so no streaks are left on screen TileImage Background, 0, Y Y=Y+1 Select Frames Case 1 DrawImage Centre_Player, Player_X, Player_Y Case 2 DrawImage Left_Player, Player_X, Player_Y Case 3 DrawImage Right_Player, Player_X, Player_Y End Select ' Control the Player on the Keyboard !!1 If KeyDown(KEY_LEFT) Frames = 2 Player_X = Player_X - 1 ElseIf KeyDown(KEY_RIGHT) Frames = 3 Player_X = Player_X + 1 Else ' IF No Keys press then go back Static ship ! Frames = 1 EndIf For Local i = 0 To depth dot=New Alien' we are DEFINING a special type of variable now ListAddFirst list,dot ' add this data to the linked list Next For ilist = EachIn list ' EACHIN will work with non-numeric values, and we're looping only as many raindrops as there are ilist.Draw() ' IF Aliens reached bottom of the screen If ilist.y>=480 list.remove ilist ' if out of boundaries, remove from list Else ' IF had been Aliens Remove then Create more Aliens ! ilist.New_E() EndIf Next Delay speed ' decrease this number to slow down animation effect End Function While Not KeyDown(KEY_ESCAPE) RenderWorld() Flip Wend |
| ||
For ilist = EachIn list -> For local ilist:alien = EachIn list (saves to declare the variable before) For Local i = 0 To depth -> how large is "depth" (it is not used anywhere else, Strict/Superstrict helps here) ' IF had been Aliens Remove then Create more Aliens ! ilist.New_E() -> what should that do? you adjust values of the current item/alien ... -> so for each entry in the list: if the y is < 480, set x to random and increase y I would adjust it a bit... what did I change: - splitted UpdateWorld() and RenderWorld() - made it strict - created some dummy gfx for others without media - prepared some "oop" (Update() for each Alien) - redone some logic (refill with new aliens if others died) I left for you to create a class for the player, containing its coordinates, frame, speed ... What did you do wrong: each of your units used the same "X2,Y2" and each time a unit was y < 480, you set new values, which made them "jump around". bye Ron |
| ||
thank you for helping and also good for explain on what I did wrong... I am going to study them and see what I have learn :) Now I going Enemys Animations, Bullets and collisions next on my own and see how goes :-) |
| ||
So instead of removing the aliens when Y > 480, you should set them to "dead" (or alive=false). Then you remove all enemies wo are no longer alive (that "remove :+ [alien]"-portion). This allows you to "kill" the aliens for various reasons: collision, out of screen, timer, ... Instead of "alive = false" you could create multiple attributes: "dying" (eg. while still displaying an "dying" animation...). Bullets should be custom types too (TBullet) so you can easily hold a list/array of them for each unit which is able to shoot (player, aliens?). Good luck bye ron |
| ||
So instead of removing the aliens when Y > 480, you should set them to "dead" (or alive=false). Then you remove all enemies wo are no longer alive (that "remove :+ [alien]"-portion). This allows you to "kill" the aliens for various reasons: collision, out of screen, timer, ... Instead of "alive = false" you could create multiple attributes: "dying" (eg. while still displaying an "dying" animation...). Bullets should be custom types too (TBullet) so you can easily hold a list/array of them for each unit which is able to shoot (player, aliens?). I will take note of that but I going take step by step like sorting the Animations then take it from there. I got the Animations working but I am worry about slowing the game down as I try slow the animations down here the code on what I did Method Update() Enemys_Animations=Enemys_Animations+1 If Enemys_Animations>=6 Enemys_Animations=0 Delay 25 EndIf Move() 'Attack() '... End Method Method Draw() Select Enemys_Animations Case 0 DrawImage Enemy_Anim_Part_1,X,Y Case 1 DrawImage Enemy_Anim_Part_2,X,Y Case 2 DrawImage Enemy_Anim_Part_3,X,Y Case 3 DrawImage Enemy_Anim_Part_4,X,Y Case 4 DrawImage Enemy_Anim_Part_5,X,Y Case 5 DrawImage Enemy_Anim_Part_6,X,Y End Select End Method |
| ||
Yes... Delay is absolutely _wrong_ in that case. So how could we increase the time between animations? a) we could store a timer for each alien ... once it reaches 0, the next animation is set and the timer is reset to the "timer duration". Timer-approach allows to run independently of FPS (speed of your computer) Field lastUpdate:int Field animTimer:int Field animTime:int = 100 'every 100 millisecs? Field animation:int = 0 ... Method Update() UpdateAnimation() 'other stuff we did in Update() 'Move() 'Attack() lastUpdate = Millisecs() 'not error prone, might be negative after 28days uptime End Method Method UpdateAnimation() 'subtract time gone since last call to this instances Update() animTimer :- (Millisecs - lastUpdate) if animTimer < 0 animTimer = animTime animation :+ 1 if animation > 5 then animation = 0 endif End Method b) Instead of storing an timer for each unit, you could calculate the time since the last "UpdateWorld()" call (similar to a), but this time only store that time once). Now you have some kind of "time since last update call" - which allows for a similar approach to a), but this time all units get the same "time since last update" instead of an individual one BTW: instead of delay we now have the information of how many milliseconds have passed since the last update. Now imagine you do no longer say "move +1 each Update", but you declare: my units should move 10 pixels per second. Now you just have to do "y :+ speed * lastUpdate/1000.0". Speed is eg. "10". If last Update was 1 second ago, then the values would be: "y :+ 10 * 1000.0/1000.0" which means, add 10. If only 100 ms passed, then 1.0 would get added. And so on and so forth. BUT ... to enable this kind of of movement, your "Y" must be of type "float" as "int" values only allow discrete steps. (y :+ 0.1 will stay "y" as it is rounded down automatically). Once you set them to "float", it should work. Finally you should be able to remove that "delay 5" from your main loop. If you want to slow down alien-movement you either decrease their individual speeds - or add a general "global speedMod:Float = 1.0" to your alien-type. Then extend "Y :+ speed * lastUpdate" to something like "Y :+ speed * speedMod * lastUpdate". bye Ron |
| ||
I don't think I have good job on Bullets link lists and I just don't know why Bullets isn't working! I hope I havnt made more confusing! Strict Graphics 640, 480, 0 Global Background:TImage = LoadImage("Data\Back_3.png") Global Centre_Player:TImage = LoadImage("Data\Player_1.png") Global Left_Player:TImage = LoadImage("Data\Player_3.png") Global Right_Player:TImage = LoadImage("Data\Player_2.png") ' Enemys Animations Global Enemy_Anim_Part_1:TImage=LoadImage("Data\Ennemy_3_1.png") Global Enemy_Anim_Part_2:TImage=LoadImage("Data\Ennemy_3_2.png") Global Enemy_Anim_Part_3:TImage=LoadImage("Data\Ennemy_3_3.png") Global Enemy_Anim_Part_4:TImage=LoadImage("Data\Ennemy_3_4.png") Global Enemy_Anim_Part_5:TImage=LoadImage("Data\Ennemy_3_5.png") Global Enemy_Anim_Part_6:TImage=LoadImage("Data\Ennemy_3_6.png") Global Player_Bullet_Front:TImage=LoadImage("Data\Bullet_1.Png") Global Player_X:Int = 250 Global Player_Y:Int = 400 Global Frames:Int = 0 Global animations:Int=0 Global BackgroundY:Int Global speed:Int=5 Global animTimer:Int Type Positions Field X:Int, Y:Int 'each alien or Tbullets has its OWN position Field Shoot:Int End Type Type Alien Extends Positions ' Alien Animations Field lastUpdate:Int Field animTimer:Int Field animTime:Int = 100 'every 100 millisecs? Field animations:Int = 0 Global list:TList = CreateList() Global maxAliens:Int = 10 Method New() 'random position for new aliens X = Rand(0, 640 - Enemy_Anim_Part_1.Width) ' x-position of Enemys y = - Enemy_Anim_Part_1.Height - Rand(50,70) 'start outside speed = Rand(2,4) '2-4 End Method Method Move() y :+ speed End Method Method Update() UpdateAnimation() Move() 'Attack() '... lastUpdate = MilliSecs() End Method Method UpdateAnimation() 'subtract time gone since last call to this instances Update() animTimer :- (MilliSecs() - lastUpdate) If animTimer < 0 animTimer = animTime animations :+ 1 If animations > 5 Then animations = 0 EndIf End Method Method Draw() Select animations Case 0 DrawImage Enemy_Anim_Part_1,X,Y Case 1 DrawImage Enemy_Anim_Part_2,X,Y Case 2 DrawImage Enemy_Anim_Part_3,X,Y Case 3 DrawImage Enemy_Anim_Part_4,X,Y Case 4 DrawImage Enemy_Anim_Part_5,X,Y Case 5 DrawImage Enemy_Anim_Part_6,X,Y End Select End Method End Type Global TBullets:TList=New TList Type TBullet Extends Positions Global Bullets:TList = CreateList() ' Position of Player Bullets Method New() ' position for new Bullets X = Player_X ' x-position of Player End Method Method BMove() y :- speed End Method Method BUpdate() BMove() BDraw() 'Attack() End Method Method BDraw() ' Users to press fire the Bullets! If KeyDown(Key_Space) BMOVE() EndIf DrawImage Player_Bullet_Front,Player_X,Y End Method End Type Function UpdateWorld() ' move background backgroundY :+ 1 ' Control the Player on the Keyboard !!1 If KeyDown(KEY_LEFT) Frames = 2 Player_X = Player_X - 1 ElseIf KeyDown(KEY_RIGHT) Frames = 3 Player_X = Player_X + 1 Else ' IF No Keys press then go back Static ship ! Frames = 1 EndIf 'add as many aliens as missing For Local i = 0 Until alien.maxAliens - alien.list.Count() alien.list.AddFirst( New Alien ) Next Local remove:Alien[] 'array holding "to remove entries" For Local entry:Alien = EachIn Alien.list entry.Update() ' if out of boundaries, remove from list If entry.y >= 480 remove :+ [entry] 'add to removal EndIf Next ' using this "remove outside of iterate-over-list" method avoids 'concurrent modification of the list (else: might run into bugs) For Local entry:Alien = EachIn remove Alien.list.Remove(entry) Next End Function Function RenderWorld() Cls ' clear display so no streaks are left on screen TileImage Background, 0, BackgroundY For Local entry:Alien = EachIn Alien.list entry.Draw() Next Select Frames Case 1 DrawImage Centre_Player, Player_X, Player_Y Case 2 DrawImage Left_Player, Player_X, Player_Y Case 3 DrawImage Right_Player, Player_X, Player_Y End Select End Function While Not KeyDown(KEY_ESCAPE) UpdateWorld() RenderWorld() Flip Wend |
| ||
Where in the code do you fire a bullet? whre Do you add a bullet to the list BULLETS and where do you call TBUllet.Draw()? |