RenderLists

BlitzMax Forums/BlitzMax Beginners Area/RenderLists

Hi, I was thinking about how to tidy up my code and give me more control (more easily) over what is drawn. I remember from programming the Net Yaroze, that when you wanted to draw an image, you added it to a list which was processed and drawn at the end of the main game loop (as opposed to numerous drawing commands throughout hte code). Does anyone do anything similar using lists in BMax?

Does anyone do anything similar using lists in BMax?
Yup. Cool thing is you can even do Z-ordering by overriding the compare method and just calling myList.sort() - provided you don't have too many objects in the list.

Nice!

Just be aware that TList.sort() is implemented using bubble sort, which has a quadratic time complexity, which can be a significant factor with lots of elements.

I have some example code too, that's pretty messy (but working) that I can send you if you want.

I'd appreciate it if you could

I did the same for my sprite engine, only I created a new Class of my own that inherrited the TList functionality. Then I added my own properties and methods to it.

OOP IS AWESOME!!!

For a fairly tidy main loop, take a look at the source for GridWars (Google for it, you'll find it at yakyak.org). UpdateAll() and RenderAll() are very tidy, enabled by using Lists.

The enemy Types could have been tidier by using an Abstract Type as a parent, but other than that it's a good tutorial :)

Here's some code I played around with when I first got BMax.

Bear in mind that I didn't really have a clue what I was doing as I'd never used an OO language before bmax.

It's pretty clunky but I quite like the idea of having layers.

Strict

Const CLOUD_LAYER_1 = -2
Const CLOUD_LAYER_2 = -1
Const CLOUD_LAYER_3 = 10

Global g_game_clock:Int
Global game:scene = New scene
Global current_player:player

'=== media ===
'data
Incbin "gfx/smallcloud.png"
Incbin "gfx/mediumcloud.png"
Incbin "gfx/largecloud.png"
Incbin "gfx/player.png"
  
'globals
Global small_cloud:TImage
Global medium_cloud:TImage
Global large_cloud:TImage
Global red_plane:TImage

Type sprite
  Field image:TImage
  Field frame_total:Int = 0, current_frame:Int = 0
  Field anim_start_time:Int, anim_interval:Int
  Field pos_x:Float, pos_y:Float
  Field scale_x:Float = 1, scale_y:Float = 1
  Field rotation:Float = 0
  Field alpha:Float = 1
  Field blendmode:Int = ALPHABLEND
  Field visible:Int = True
  Field layer_no = 0
  
  Method show()
    visible = True
  End Method
  
  Method hide()
    visible = False
  End Method
  
  Method update() Abstract
End Type

Type layer
  Field sprites:TList
  Field pos_x:Float = 0, pos_y:Float = 0  
  Field rotation:Float = 0 ' to be continued
  Field alpha:Float = 1
  Field blendmode:Int = ALPHABLEND
  Field visible:Int = True
  Field layer_no = 0
  
  Method add(sprite:sprite)
    If sprites = Null Then sprites = New TList
    
    sprites.addlast(sprite)
  End Method
  
  Method show()
    visible = True
  End Method
  
  Method hide()
    visible = False
  End Method
End Type

Type scene
  Field layers:layer[256]
  
  Method add_sprite(sprite:sprite, layer_no:Int=0)            
    Local layer_index:Int
    
    layer_index = 127 + layer_no
    
    If layers[layer_index] = Null Then layers[layer_index] = New layer
    
    layers[layer_index].add(sprite)
    layers[layer_index].layer_no = layer_no
  End Method
  
  Method which_layer(sprite:sprite)
    Return sprite.layer_no
  End Method
  
  Method get_layer:layer(layer_no:Int)
    Return layers[layer_no + 127]
  End Method
  
  Method update()
    Local this_layer:layer, this_sprite:sprite
    
    For this_layer = EachIn layers
      For this_sprite = EachIn this_layer.sprites
          this_sprite.update
      Next
    Next
  End Method
  
  Method render()
    Local this_layer:layer, this_sprite:sprite
    
    For this_layer = EachIn layers
      If this_layer.visible
        For this_sprite = EachIn this_layer.sprites
          If this_sprite.visible
            SetBlend this_sprite.blendmode
            SetRotation this_sprite.rotation
            SetAlpha this_sprite.alpha * this_layer.alpha
            SetScale this_sprite.scale_x, this_sprite.scale_y
            
            DrawImage this_sprite.image, this_sprite.pos_x + this_layer.pos_x, this_sprite.pos_y + this_layer.pos_y, this_sprite.current_frame
          EndIf
        Next
      EndIf
    Next
  End Method
End Type

'=== Test ===

Type cloud Extends sprite
  Field rotation_speed:Float
  
  Function create(scene:scene, image:TImage, layer_no:Int=0, pos_x:Int=0, pos_y:Int=0, rotation:Float=0, alpha:Float=1)
    Local cloud:cloud = New cloud
    
    cloud.image = image
    cloud.pos_x = pos_x
    cloud.pos_y = pos_y
    cloud.rotation = rotation
    cloud.alpha = alpha
    cloud.rotation_speed = Rnd(180)
    cloud.layer_no = layer_no
    
    scene.add_sprite(cloud, layer_no)
  End Function
  
  Method update()   
    rotation = Cos(rotation_speed + (g_game_clock / 40.0)) * 4
    
    Select layer_no
      Case CLOUD_LAYER_1
        pos_x :- (current_player.x_vel * 0.5)
        pos_y :- (current_player.y_vel * 0.5)
      
      Case CLOUD_LAYER_2
        pos_x :- (current_player.x_vel * 0.75)
        pos_y :- (current_player.y_vel * 0.75)

      Case CLOUD_LAYER_3
        pos_x :- current_player.x_vel
        pos_y :- current_player.y_vel
    End Select
  End Method
End Type

Type weapon
  Field image:TImage, id:Int
  Field pos_x:Int, pos_y:Int, angle:Float, alpha:Float
  Field firing_rate:Int, damage:Int
End Type

Type plane Extends sprite
  Field speed:Float, turn_rate:Float, x_vel:Float, y_vel:Float   
  Field weapon_slot:weapon[]
  
  Method add_weapon(image:TImage, pos_x:Int, pos_y:Int, slot:Int, id:Int) 
    weapon_slot[slot] = New weapon
    weapon_slot[slot].image = image
    weapon_slot[slot].pos_x = pos_x
    weapon_slot[slot].pos_y = pos_y
    weapon_slot[slot].id = id
    weapon_slot[slot].angle = rotation
    weapon_slot[slot].alpha = alpha
  End Method
  
  Method update()
  End Method  
End Type  

Type player Extends plane
  
  Function create:player(scene:scene, image:TImage, layer_no:Int=0, pos_x:Int=0, pos_y:Int=0, rotation:Float=0, alpha:Float=1)
    Local this_player:player = New player
    
    this_player.image = image
    this_player.pos_x = pos_x
    this_player.pos_y = pos_y
    this_player.speed = 2
    this_player.x_vel = Sin(rotation) * this_player.speed
    this_player.y_vel = -Cos(rotation) * this_player.speed
    this_player.turn_rate = 2
    this_player.rotation = rotation
    this_player.scale_x = 1
    this_player.scale_y = 1
    this_player.alpha = alpha
    this_player.frame_total = 1
    this_player.current_frame = 0
    this_player.anim_start_time = g_game_clock
    this_player.anim_interval = 50
'   this_player.weapon_slot = New weapon[2]
  
    scene.add_sprite(this_player, layer_no)
    
    Return this_player
  End Function
  
  Method update()
    If KeyDown(KEY_S)
      rotation :+ turn_rate
      x_vel = Sin(rotation) * speed
      y_vel = -Cos(rotation) * speed
    EndIf
    
    If KeyDown(KEY_A)
      rotation :- turn_rate
      x_vel = Sin(rotation) * speed
      y_vel = -Cos(rotation) * speed
    EndIf
      
    If (g_game_clock - anim_start_time) >= anim_interval
      current_frame :+ 1
      If current_frame > frame_total Then current_frame = 0
      
      anim_start_time = g_game_clock
    EndIf
  End Method
End Type    


Graphics 800, 600, 32, 0

HideMouse
SeedRnd MilliSecs()

load_media()
create_clouds(1500)

Local player1:player = player.create(game, red_plane, 0, 399, 299, 0, 1)

current_player = player1

SetClsColor 0, 30, 150 

Repeat
  g_game_clock = MilliSecs()
  
  Cls
  
  game.update()
  game.render()
  
' FlushMem
  Flip
Until KeyHit(KEY_ESCAPE)

End

Function create_clouds(number:Int)
  Local c:Int, r:Int 

  For c=1 To number
    r = Rand(1, 3)
  
    Select r
      Case 1
        cloud.create(game, small_cloud, CLOUD_LAYER_1, Rand(-3999, 3999), Rand(-2999, 2999), Rand(359), 1)
    
      Case 2
        cloud.create(game, medium_cloud, CLOUD_LAYER_2, Rand(-3999, 3999), Rand(-2999, 2999), Rand(359), 0.98)
      
      Case 3
        cloud.create(game, large_cloud, CLOUD_LAYER_3, Rand(-19, 19) * 200, Rand(-29, 29) * 100, Rand(359), 0.98)
    
    End Select
  Next
End Function

Function load_media()
  AutoMidHandle True
  AutoImageFlags FILTEREDIMAGE
  
  small_cloud:TImage = LoadImage("incbin::gfx/smallcloud.png")
  Assert small_cloud, "Couldn't load ~qgfx/smallcloud.png~q"
  
  medium_cloud:TImage = LoadImage("incbin::gfx/mediumcloud.png")
  Assert medium_cloud, "Couldn't load ~qgfx/mediumcloud.png~q"
  
  large_cloud:TImage = LoadImage("incbin::gfx/largecloud.png")
  Assert large_cloud, "Couldn't load ~qgfx/largecloud.png~q"
  
  red_plane:TImage = LoadAnimImage("incbin::gfx/player.png", 64, 64, 0, 2)
  Assert red_plane, "Couldn't load ~qgfx/player.png~q"
End Function


I created a pretty solid z-ordering render list system in c++ for doing some iso stuff. One thing I did to get a big speed boost was to store things that changed their z/depth/layer separate from things that didn't. Things like terrain tiles, background images, HUDs don't need to be sorted if they're all static.

Of course if nothing changes their z then there's no point to even worry about it.

Sorry I've taken so long to reply, I've been in transit. Unfortunately I left my real source code in Denmark, so here is a small example I created just for you (kinda).

SuperStrict

Graphics Desktop().width , Desktop().height , 32

Type TBall
	Field x:Double , y:Double
	Field x_com:Double , y_com:Double
	Field acc:Double , elast:Double
	Field r:Int, g:Int, b:Int

	Function Create:TBall ()
		Local temp:TBall = New TBall
		temp.x = Rand ( 0 , Desktop().width-16 )
		temp.y = -16
		temp.acc = Rnd (.1 , .3)
		temp.elast = Rnd (.75 , .95)
		temp.x_com = Rand (1,4)
		If Rnd() < .5
			temp.x_com = -temp.x_com
		EndIf
		temp.y_com = 1
		temp.r =Rand(128,255)
		temp.g =Rand(128,255)
		temp.b =Rand(128,255)
		Return temp
	EndFunction
	
	Method Update:Int()		
		If x_com + x <= 0 Or x_com + x => (GraphicsWidth()-16)
			x_com = - x_com
		EndIf
		
		If y_com + y < (GraphicsHeight())
			y_com :+ acc
		Else
			y_com =- y_com
			y_com :* elast
		EndIf
		
		x:+x_com
		y:+y_com

		If y > GraphicsHeight() And Abs(y_com) < 16
			Return True 
		EndIf
		Return False
	EndMethod
	
	Method Draw()
		SetColor r , g , b
		DrawOval Floor(x) , Floor(y) , 16 , 16
	EndMethod
EndType

Local renderList:TList = New TList

HideMouse

While Not KeyHit( KEY_ESCAPE )
	Cls
	For Local i:TBall = EachIn renderList
		If i.update()
			renderList.remove(i)
		EndIf 
		i.draw
	Next
	renderList.addlast(TBall.Create())

	Flip
EndWhile

ShowMouse

End


Fantastic stuff - you are too kind ;-)

Here's just a snippet of some code from one of my programs. I use OOP to create a new version of the TList class. I have more methods than just "Render", but I wanted to just show you the basics...

Type BallGroup Extends TList
     Method Render(WithCollisions:Int = True)
          ForEach Local Ball:BallSprite in Self
               If Ball.Visible Then
                    Ball.Render(WithCollisions)
               End If
          Next
     End Method
End Type

Local Balls:BallGroup = new BallGroup
For Local i:Int = 1 to 10
     Balls.AddLast(new Ball)
Next

While Not KeyHit(KEY_ESCAPE)
     Cls
     Balls.Render(False)
     Flip
Wend


-j9t