Not drawing multiple images.

BlitzMax Forums/BlitzMax Beginners Area/Not drawing multiple images.

Awesome(Posted 2012) [#1]
So after defeating the nefarious level editor I'm now working on the primary game, I have it pulling the actual level information, everything is going well except for drawning my enemies. If I make it draw a rectangle for each enemy it works fine, but when I try to have it draw the actual "Villain" image it only draws the first one and leaves the rest blank. This tells me that my DrawSprite() is probably the issue, but I can't see the problem.

SuperStrict

Include "openlevel.bmx"

AppTitle="Platform Game"
Const  gwidth:Int = 1280
Const  gheight:Int = 1024

Graphics gwidth, gheight

Global scn_left:Int = 0
Global scn_top:Int = 0
Global Spritelist:TList = CreateList()

For Local x:Int = 0 To mapx-1
	For Local y:Int = 0 To mapy-1
		For Local z:Int = 0 To mapz-1
			If map[x,y,4] > -1 Then Global H:hero = Hero.Create("images/hero.png",(x*tsize)-scn_left,(y*tsize) - scn_top)
			If map[x,y,3] = 0 Then Global E:Enemy = Enemy.Create("images/Villain.png",(x*tsize)-scn_left,(y*tsize) - scn_top)
		Next 
	Next
Next

Repeat
	
	Cls
	
	For Local H:Hero = EachIn Spritelist
		H.UpdateSprites()
	Next 
	
	For Local a:Sprite = EachIn Spritelist
		a.DrawSprites()
	Next 
	
	Flip

Until AppTerminate() Or KeyHit(Key_escape)

Type Sprite
	Field x:Int
	Field y:Int
	Field I:TImage
	
	Method DrawSprites()
		DrawImage I,x,y
	End Method
	
End Type
	
Type Hero Extends Sprite	
	Function Create:Hero(File:String,xstart:Int,ystart:Int)
		Local H:Hero = New Hero
			H.x=xstart
			H.y=ystart
			H.I=LoadImage(file)
			
			ListAddLast Spritelist, H
		Return H
	End Function
	
	Method UpdateSprites()
		If KeyDown(key_left) Then x = x - 5
		If KeyDown(key_right) Then x = x + 5
	End Method
End Type 
	
Type Enemy Extends Sprite
	Function Create:Enemy(File:String,xstart:Int,ystart:Int)
		Local E:Enemy = New Enemy 
			E.x=xstart
			E.y=ystart
			E.I=LoadImage(file)
			
			ListAddLast Spritelist, E
		Return E
	End Function
	
	Method UpdateSprites()
		
	End Method
End Type

End  



therevills(Posted 2012) [#2]
Change your looping and dont create Globals in a loop ;)

Global H:Hero
' Dont need Global E:Enemy

For Local x:Int = 0 To mapx-1
	For Local y:Int = 0 To mapy-1
		For Local z:Int = 0 To mapz-1
			If map[x,y,4] > -1 Then H = Hero.Create("images/hero.png",(x*tsize)-scn_left,(y*tsize) - scn_top)
			If map[x,y,3] = 0 Then Enemy.Create("images/Villain.png",(x*tsize)-scn_left,(y*tsize) - scn_top)
		Next 
	Next
Next


Last edited 2012


Derron(Posted 2012) [#3]
I would replace

	For Local H:Hero = EachIn Spritelist
		H.UpdateSprites()
	Next 
	
	For Local a:Sprite = EachIn Spritelist
		a.DrawSprites()
	Next 


with

	'both types extend from sprite, so we can use that
	'that way we replace a loop of a maybe-big-list with type checks
	'use such style if MORE THAN ONE hero is possible in the list
	For Local a:Sprite = EachIn Spritelist
		if Hero(a) <> null then Hero(a).UpdateSprites()
		a.DrawSprites()
	Next 


or better:

	'we stored hero as global - no need to look for it.
	H.UpdateSprites()
	For Local a:Sprite = EachIn Spritelist
		a.DrawSprites()
	Next 



To check the images/drawimage-part - replace:
Type Sprite
	Field x:Int
	Field y:Int
	Field I:TImage
	
	Method DrawSprites()
		DrawImage I,x,y
	End Method
	
End Type


with

Type Sprite
	Field x:Int
	Field y:Int
	Field I:TImage
	
	Method DrawSprites()
		if I<>null 
			DrawImage(I,x,y)
		else
			DrawRect(x,y,20,20)
			'print "missing sprite image XYZ drawn at x="+x+" y="+y
		endif
	End Method
	
End Type



Hope that helps a bit.

bye
Ron

PS: if you know that enemies share images, make a TMap with Sprites and access them that way and not with LoadImage(url) multiple times.

- create map
- check existance of object by url as key
- insert object (Timage) to map with url as key

You can do the same with other resources in your application.


Awesome(Posted 2012) [#4]
Okay so I did everything suggested (aside from the Tmap, I need to learn more about those) and it's working, almost too well. Now for some reason it's drawing multiple images of the Hero sprite, only one of which actually exists as an object created by the hero type, if I remove H from the Spritelist then it deletes the sprite that I have control over and just leaves a static image that's standing at the starting XY coordinates.

Edit: I tested the sprites for the enemies and they aren't affected by this issue, so it's just an issue with the hero for some reason.

Last edited 2012


therevills(Posted 2012) [#5]
Try this (also I added the TMap stuff that Derron talked about and I added some code so I code actually run (eg mapx, mapy etc):
SuperStrict

AppTitle="Platform Game"
Const WIDTH:Int = 1280
Const HEIGHT:Int = 1024

Graphics WIDTH, HEIGHT

Global scn_left:Int = 0
Global scn_top:Int = 0
Global sprite_list:TList = CreateList()

Global mapx:Int = 20
Global mapy:Int = 20
Global mapz:Int = 20
Global tsize:Int = 30
Global map:Int[,,]

Global h:THero 
Global images:TImageBank = New TImageBank
LoadImages()
PopulateMap()

Local heroCreated:Int = False

For Local x:Int = 0 To mapx - 1
	For Local y:Int = 0 To mapy - 1
		For Local z:Int = 0 To mapz - 1
			If map[x,y,4] > -1 And Not heroCreated Then
				h = THero.Create(images.Get("hero"),(x*tsize)-scn_left,(y*tsize) - scn_top)
				heroCreated = True
			EndIf
			If map[x,y,3] = 0 Then TEnemy.Create(images.Get("Villain"),(x*tsize)-scn_left,(y*tsize) - scn_top)
		Next 
	Next
Next

Repeat
	Cls
	h.UpdateSprite()
	For Local a:TSprite = EachIn sprite_list
		a.DrawSprite()
	Next 
	
	Flip
Until AppTerminate() Or KeyHit(KEY_ESCAPE)

Function LoadImages()
	images.Load("images/hero.png")
	images.Load("images/Villain.png")
EndFunction

Function PopulateMap()
	map = New Int[mapx, mapy, mapz]
	
	For Local x:Int = 0 To mapx - 1
		For Local y:Int = 0 To mapy - 1
			For Local z:Int = 0 To mapz - 1
				map[x, y, z] = Rand(0, 10)
			Next
		Next
	Next
EndFunction

Type TSprite
	Field x:Int
	Field y:Int
	Field image:TImage
	
	Method DrawSprite()
		DrawImage image,x,y
	End Method
End Type
	
Type THero Extends TSprite	
	Function Create:THero(image:TImage, xstart:Int, ystart:Int)
		Local h:THero = New THero
		h.x = xstart
		h.y = ystart
		h.image = image

		sprite_list.AddLast(h)
		Return h
	End Function
	
	Method UpdateSprite()
		If KeyDown(key_left) Then x = x - 5
		If KeyDown(key_right) Then x = x + 5
	End Method
End Type 
	
Type TEnemy Extends TSprite
	Function Create:TEnemy(image:TImage, xstart:Int, ystart:Int)
		Local e:TEnemy = New TEnemy 
		e.x = xstart
		e.y = ystart
		e.image = image
			
		sprite_list.AddLast(e)
		Return e
	End Function
	
	Method UpdateSprite()
		
	End Method
End Type

Type TImageBank Extends TMap
	Method Load(name:String)
		Local i:TImage = LoadImage(name)
		If i = Null Then RuntimeError "Error - Cannot load image "+name
		Insert(StripAll(Upper(name)), i)
	EndMethod
	
	Method Get:TImage(name:String)
		Local i:TImage = TImage(ValueForKey(Upper(name)))
		If i = Null Then RuntimeError "Error - Cannot find image "+name
		Return i
	EndMethod
	
EndType

End  


Last edited 2012

Last edited 2012


Derron(Posted 2012) [#6]
@therevills

[bbcode]
Global h:THero
...
Local heroCreated:Int = False
...
If map[x,y,4] > -1 And Not heroCreated Then
h = THero.Create(images.Get("hero"),(x*tsize)-scn_left,(y*tsize) - scn_top)
heroCreated = True
EndIf
[/bbcode]

could be stripped by...

[bbcode]
Global h:THero = Null
...
If map[x,y,4] > -1 And h = null
h = THero.Create(images.Get("hero"),(x*tsize)-scn_left,(y*tsize) - scn_top)
EndIf
[/bbcode]

Think a "null" check is not that work intensive :D



@awesome
Try to get some code which you can post here for us to test.

Another hint:
instead of calling a type's method "UpdateSprites" you should consider calling it "Update" and "Draw" and the object containing the list
can then have a function (methods if instances) like "UpdateAll".

If you run into problems like "but Hero extends sprite and has to have a method 'draw' too" try something like :

[bbcode]
Type THero
field sprite:TSprite
field name:string="My Hero"
...
Method Draw()
self.sprite.Draw()
End Method
End Type
[/bbcode]

That's needed as you won't be able to extend a class and overwrite the parents methods in Blitzmax (for adding further parameters and so on).

Extend types if they share something - so eg. Renderables but do not extend from attributes they have.

[bbcode]

'object having some values associated but needs some nifty helpers
Type TCoord
field x:float
field y:float
...
Method IsSame(otherTCoord)
...
Method IsSameXY(x:float,y:float)
...
End Type


Type TRenderable
field coord:TCoord
...
'abstract methods are defined in all the ancestors
Method Draw() abstract
...
End Type

Type TSprite extends TRenderable
field image:TImage
...
Method Draw()
DrawImage(self.image, self.coord.x, self.coord,y)
...
End Method
End Type

Type THero
field sprite:TSprite
'here we can store the coord of last cycle
'or before we started some moving action - a backup :D
'but this won't be that easy if extending the things
field lastCoord:TCoord

Method Draw(additionalParameterLikeOverwriteSomething:int = 0)
self.sprite.Draw()
End Method
End Type

'do the same for Type TEnemy
'but this time you can extend different Enemies... TFireElemental extends TEnemy
'then just change sprites, or update-behaviour

Type TLevel
field blocksAndEntityList...
field enemiesList:TList
...
End Type

Type TGame
field hero:THero
field currentLevel:TLevel
...
Method Update()
hero.update()
for local enemy:TEnemy = eachin self.currentLevel.enemiesList
enemy.update()
next
End Method
...
'same for Method Draw()...
End Type

...
[/bbcode]

Ok it's not tested as I wrote it now and I might be run into offtopic or some kind of tutorial for "oop in BlitzMax" ... think I finish my posting here :D


bye
Ron