Hi, I seem to be getting extremely long delays within the Sub GameLoop > Do Until loop. This loop contains only some basic math, if then statements, and two list assignments; I expected this to process relatively quickly, but it is sometimes taking over 11 seconds (not microseconds, but full seconds) just to process one iteration.
I think the only other processes which should be running simultaneously are Sub Sensor_SensorChanged and Timer1_Tick. Sub Timer1_Tick is a bit more complex as it contains the routines to draw the screen, but I see nothing which should cause such a long delay.
Does anyone have any suggestions to improve this?
A brief game explanation:
At start, it should generate a maze in a 10x10 grid which fills the screen horizontally. Tilting the device causes a ball (not yet programmed) which is displayed in the center of the screen, to roll around in the maze. Rather than having the ball move, it remains centered while the background moves about it.
To simulate the ball movement realistically but keep things simple, I am taking into account the force of gravity only, not inertia or friction. To calculate horizontal/vertical movement, I note the position of the ball, iterate over Sub GameLoop, note the time difference and the orientation angle. I base the distance calculation on how far the ball would have fallen "through the screen" and towards the earth in the given time, and then calculate the hypotenuse of the angle that forms relative to the screen to give me screen position, then move the center point of the view to that position.
There is more planned, but this is the basis of what is programmed currently. At present I might get a brief view of the maze for one frame, then it just goes black when it updates, having calculated the position incorrectly due to the delay.
I think the only other processes which should be running simultaneously are Sub Sensor_SensorChanged and Timer1_Tick. Sub Timer1_Tick is a bit more complex as it contains the routines to draw the screen, but I see nothing which should cause such a long delay.
Does anyone have any suggestions to improve this?
A brief game explanation:
At start, it should generate a maze in a 10x10 grid which fills the screen horizontally. Tilting the device causes a ball (not yet programmed) which is displayed in the center of the screen, to roll around in the maze. Rather than having the ball move, it remains centered while the background moves about it.
To simulate the ball movement realistically but keep things simple, I am taking into account the force of gravity only, not inertia or friction. To calculate horizontal/vertical movement, I note the position of the ball, iterate over Sub GameLoop, note the time difference and the orientation angle. I base the distance calculation on how far the ball would have fallen "through the screen" and towards the earth in the given time, and then calculate the hypotenuse of the angle that forms relative to the screen to give me screen position, then move the center point of the view to that position.
There is more planned, but this is the basis of what is programmed currently. At present I might get a brief view of the maze for one frame, then it just goes black when it updates, having calculated the position incorrectly due to the delay.
B4X:
#Region Module Attributes
#FullScreen: False
#IncludeTitle: True
#ApplicationLabel: aMazeBalls
#VersionCode: 1
#VersionName:
#SupportedOrientations: portrait
#CanInstallToExternalStorage: False
#End Region
'Activity module
Sub Process_Globals
'This map maps between PhoneSensors and SensorData objects.
Dim SensorsMap As Map
Type SensorData (Name As String, ThreeValues As Boolean)
Public level = 1 As Int
Public MazeMap(10,10) As List' items in list indicate T/F for presence of wall on current cell 0=N, 1=E, 2=S, 3=W, 4=Visited(T/F)
Public Timer1 As Timer
Public FPS = 1000/60 As Long ' set denominator to desired FPS
Public tiltY, tiltZ As Float
Public Xdpi,Ydpi, Scale As Float
Public TileWidth, ScreenHeight, ScreenWidth As Int
Public LastX = 0, LastY = 0 As Double
Public StartTile, CurrentTile, EndTile As List
End Sub
Sub Globals
Private pnlScreen As Panel
Private cvsScreen As Canvas
End Sub
Sub Activity_Create(FirstTime As Boolean)
Activity.LoadLayout("Main")
If FirstTime Then
SensorsMap.Initialize
Dim ps As PhoneSensors 'This object is only used to access the type constants.
AddSensor(ps.TYPE_ORIENTATION, "ORIENTATION", True)
End If
cvsScreen.Initialize(pnlScreen)
Timer1.Initialize("Timer1",FPS)
Timer1.Enabled = True
'determine the screen size so it can calculate velocity realistically
Dim r As Reflector 'requires reflection library
r.Target = r.GetContext
r.Target = r.RunMethod("getResources")
r.Target = r.RunMethod("getDisplayMetrics")
Xdpi = r.GetField("xdpi")
Ydpi = r.GetField("ydpi")
TileWidth = Floor(GetDeviceLayoutValues.Width/10)
ScreenHeight = GetDeviceLayoutValues.Height
ScreenWidth = GetDeviceLayoutValues.Width
Scale = GetDeviceLayoutValues.Scale
End Sub
Sub AddSensor(SensorType As Int, Name As String, ThreeValues As Boolean) As SensorData
Dim sd As SensorData
sd.Initialize
sd.Name = Name
sd.ThreeValues = ThreeValues
Dim ps As PhoneSensors
ps.Initialize(SensorType)
SensorsMap.Put(ps, sd)'ps seems to be an object not a string, why does that work as a key?
End Sub
Sub Activity_Resume
'Here we start listening for SensorChanged events.
'By checking the return value we knoe if the sensor is supported.
For i = 0 To SensorsMap.Size - 1
Dim ps As PhoneSensors
Dim sd As SensorData
ps = SensorsMap.GetKeyAt(i)
sd = SensorsMap.GetValueAt(i)
If ps.StartListening("Sensor") = False Then
Log(sd.Name & " is not supported.")
End If
Next
GameLoop
End Sub
Sub Activity_Pause (UserClosed As Boolean)
'Stop listening for events.
For i = 0 To SensorsMap.Size - 1
Dim ps As PhoneSensors
ps = SensorsMap.GetKeyAt(i)
ps.StopListening
Next
End Sub
Sub Sensor_SensorChanged (Values() As Float)
tiltY = Values(1)
tiltZ = Values(2)
'Z tilt left up to 90, right to -90
'Y tilt toward me -90, away from me 90
End Sub
Sub GameLoop
CreateMaze
Dim velocX = 0, velocY = 0 As Double
Dim LastUpdateTime = DateTime.Now As Long
Dim LastTiltY = 0, LastTiltZ = 0 As Long
StartTile.Initialize
CurrentTile.Initialize
EndTile.Initialize
StartTile = DefineStartTile 'origin is midpoint of this tile, which at start is midpoint of screen
'keep track of movement of ball relative to origin, then calculate what needs displayed based upon those coordinates
'use precise numbers (not Integers) to track pixel location, then floor for display purposes
EndTile = DefineEndTile(StartTile)
CurrentTile.Add(StartTile.Get(0))
CurrentTile.Add(StartTile.Get(1))
'loop while current location <> EndPoint
Do Until CurrentTile.Get(0) = EndTile.Get(0) And CurrentTile.Get(1) = EndTile.Get(1)
Sleep(30)
'there should be a pause button, touching the screen should start/stop the game. it should be paused at the start of a new level
'if tilt Y or Z is not level, calculate where ball would have moved to since last update and place it there
'for simplification, I am ignoring momentum and friction in calculating ball position
If tiltY <> 0 Or tiltZ <> 0 Then
Dim UpdateInterval, Adjacent=0, Hypotenuse=0, deltaX=0, deltaY=0 As Long
Dim dirX=1, dirY=1 As Int
UpdateInterval = DateTime.Now - LastUpdateTime
LastUpdateTime = DateTime.Now
Dim Ipss = 32.17405 * 12 As Long 'this is inches per second squared, the acceleration of a falling object
If tiltY <> 0 Then
'make sure velocY is still valid, i.e. if it has changed direction reset it to 0
If LastTiltY < 0 And tiltY >= 0 Then velocY = 0
If LastTiltY > 0 And tiltY <= 0 Then velocY = 0
'determine direction of travel
If tiltY >0 Then
dirY = 1
Else
dirY = -1
End If
Adjacent = velocY*(UpdateInterval/1000) + 0.5*Ipss*Power((UpdateInterval/1000),2)'distance ball would have moved toward ground, not across screen, in inches
velocY = velocY + Ipss*(UpdateInterval/1000)
Hypotenuse = Adjacent/CosD(tiltY)'this should be the distance travelled in the vertical direction of the screen, in inches. convert to pixels
deltaY = Hypotenuse * Ydpi / Scale'divide to convert from actual to displayed resolution? should be number of displayed pixels to move from last location
LastTiltY = tiltY
End If
If tiltZ <> 0 Then
'make sure velocX is still valid, i.e. if it has changed direction reset it to 0
If LastTiltZ < 0 And tiltZ >= 0 Then velocX = 0
If LastTiltZ > 0 And tiltZ <= 0 Then velocX = 0
'determine direction of travel
If tiltZ > 0 Then
dirX = -1
Else
dirX = 1
End If
Adjacent = velocX*(UpdateInterval/1000) + 0.5*Ipss*Power((UpdateInterval/1000),2)'distance ball would have moved toward ground, not across screen, in inches
velocX = velocX + Ipss*(UpdateInterval/1000)
Hypotenuse = Adjacent/CosD(tiltZ)'this should be the distance travelled in the vertical direction of the screen, in inches. convert to pixels
deltaX = Hypotenuse * Xdpi / Scale'divide to convert from actual to displayed resolution? should be number of displayed pixels to move from last location
LastTiltZ = tiltZ
End If
' deltaX=0
' deltaY=0
'now update the current position of the ball and determine what tile it is in
Log("deltaX:" & deltaX & " " & "deltaY:" & deltaY & " UpdateInterval:" & UpdateInterval)
LastX = LastX + dirX*deltaX
LastY = LastY + dirY*deltaY
Dim TileShiftX = 0, TileShiftY = 0 As Int
TileShiftX = Floor((LastX+(TileWidth/2))/TileWidth)
TileShiftY = Floor((LastY+(TileWidth/2))/TileWidth)
CurrentTile.Set(0,StartTile.Get(0)+TileShiftX)
CurrentTile.Set(1,StartTile.Get(1)+TileShiftY)
End If
Loop
Log("Level up")
'if current location = EndPoint then increment level and restart GameLoop
level = level + 1
GameLoop
End Sub
Sub CreateMaze
'define the number of rows and columns
If level = 1 Then
'start with a simple 10x10 grid
Dim Height = 10 As Int
Else If level = 2 Then
'now generate a maze big enough to fill the screen vertically, requiring it to scroll horizontally
Dim Height = 15 As Int
Else
'generate a grid of [level 2 height] + (level - 2), square
Dim Height = 15 + level - 2 As Int
End If
Public MazeMap(Height,Height) As List' items in list indicate T/F for presence of wall on current cell 0=N, 1=E, 2=S, 3=W, 4=Visited(T/F)
Dim visited = 1, currentX = 0, currentY = 0, nextX, nextY As Int
Dim nextLoc As String
Dim path As List
path.Initialize
path.Add("0,0")
MazeMap(0,0).Initialize
MazeMap(0,0).AddAll(Array As Boolean(True,True,True,True,True))
Do Until visited = Height * Height
'look up neighboring cells and see which are unvisited
Dim unvisited As List
unvisited.Initialize
For i = -1 To 1
For j = -1 To 1
If i + currentX >= 0 And i + currentX < Height Then
If j + currentY >= 0 And j + currentY < Height Then
If MazeMap(i + currentX, j + currentY).IsInitialized Then
If MazeMap(i + currentX, j + currentY).Get(4) = False Then
unvisited.Add((i + currentX) & "," & (j + currentY))
End If
Else
unvisited.Add((i + currentX) & "," & (j + currentY))
End If
End If
End If
Next
Next
If unvisited.Size > 0 Then
'choose one at random
nextLoc = unvisited.Get(Rnd(0,unvisited.Size))
'make sure there are no walls between current location and nextLoc
Dim commaLoc As Int
commaLoc = nextLoc.IndexOf(",")
nextX = nextLoc.SubString2(0, commaLoc)
nextY = nextLoc.SubString(commaLoc + 1)
If MazeMap(nextX,nextY).IsInitialized = False Then
MazeMap(nextX,nextY).Initialize
MazeMap(nextX,nextY).AddAll(Array As Boolean(True,True,True,True,True))
End If
'presence of wall on current cell 0=N, 1=E, 2=S, 3=W, 4=Visited(T/F)
If nextX = currentX Then
'remaining in same row, is it to the left or right?
If currentY < nextY Then
'remove the top wall of current and lower wall of next
MazeMap(currentX,currentY).Set(0,False)
MazeMap(nextX,nextY).Set(2,False)
Else
'remove the lower wall of current and top wall of next
MazeMap(currentX,currentY).Set(2,False)
MazeMap(nextX,nextY).Set(0,False)
End If
Else
'remaining in same column, is it up or down?
If currentX > nextX Then
'remove the left wall of current and the right wall of next
MazeMap(currentX,currentY).Set(3,False)
MazeMap(nextX,nextY).Set(1,False)
Else
'remove the right wall of current and the left wall of next
MazeMap(currentX,currentY).Set(1,False)
MazeMap(nextX,nextY).Set(3,False)
End If
End If
'set as visited
path.Add(nextX & "," & nextY)
MazeMap(nextX,nextY).Set(4,True)
visited = visited + 1
Else
'There is no where new to go, back up to the previous location and try this again
path.RemoveAt(path.Size - 1)
nextLoc = path.Get(path.Size - 1)
nextX = nextLoc.SubString2(0, commaLoc)
nextY = nextLoc.SubString(commaLoc + 1)
End If
currentX = nextX
currentY = nextY
nextX = 0'error using Null here
nextY = 0
Loop
End Sub
Sub DefineStartTile As List
'choose a random starting point
Dim tempList As List
tempList.Initialize
tempList.Add(Rnd(0,MazeMap.Length))
tempList.Add(Rnd(0,MazeMap.Length))
Return tempList
End Sub
Sub DefineEndTile(x_startpos As List) As List
'determine what quadrant starting point is in (if any) and place endpoint in a different quadrant
Dim x1,x2,mid As Int 'this will define the range to select from
Dim tempList As List
tempList.Initialize
mid = Floor(MazeMap.Length/2)
If x_startpos.Get(0) < mid Then
x1 = mid
x2 = MazeMap.Length -1
Else
x1 = 0
x2 = mid
End If
tempList.Add(Rnd(x1,x2))
tempList.Add(Rnd(0, MazeMap.Length))
Return tempList
End Sub
Sub Timer1_Tick
'refresh the screen with the current data
'LastX and LastY tells where the current view is centered relative to the origin
Log("Timer1_Tick " & DateTime.Now)
Private rect1 As Rect
rect1.Initialize(0dip,0dip,100%x,100%y)
cvsScreen.DrawRect(rect1,Colors.Black,True,1) 'draw a black background
'iterate over entire maze cell by cell to determine what is visible and where it should be displayed
'may wish to put something in here to reduce iteration scope for very large mazes
Log("Timer1_Tick start of nested for loops")
For i = 0 To MazeMap.Length - 1
For j = 0 To MazeMap.Length - 1
'determine where this tile is relative to starting tile and LastX,LastY
Dim DistToStartX, DistToStartY As Int
DistToStartX = (StartTile.Get(0) - i)*TileWidth
DistToStartY = (StartTile.Get(1) - j)*TileWidth
Dim ULTileX,ULTileY As Double 'x & y coordinates of upper left corner of current tile
If CurrentTile.Get(0) <> StartTile.Get(0) Then
ULTileX = 0 - (TileWidth/2) - DistToStartX - LastX'coordinates relative to center of screen
Else
ULTileX = 0 - DistToStartX - LastX
End If
If CurrentTile.Get(1) <> StartTile.Get(1) Then
ULTileY = 0 + (TileWidth/2) + DistToStartY - LastY
Else
ULTileY = 0 + DistToStartY - LastY
End If
'the above may have produced fractional numbers, floor everything so we can use it for pixel display
ULTileX = Floor(ULTileX)
ULTileY = Floor(ULTileY)
Log(ULTileX & " " & ULTileY & " " & LastX & " " & LastY)
'the upper left corner of this tile's coordinates are known; check whether any of this tile is visible
If ULTileX >= (-1*(Floor(ScreenWidth/2))-TileWidth) And ULTileX < Floor(ScreenWidth/2) Then 'if any of the X coordinates are visible
If ULTileY <= (Floor(ScreenHeight/2) + TileWidth) And ULTileY > -1*(Floor(ScreenHeight/2)) Then 'if any of the Y coordinates are visible
Log("Draw tile")
'draw this tile's features
'determine the coordinates of the 4 visible corners of this tile
Dim ULX, ULY, URX, URY, LLX, LLY, LRX, LRY As Int
If ULTileX + Floor(ScreenWidth/2) < 0 Then
ULX = 0'truncate, tile begins to the left of the screen
Else
ULX = ULTileX + Floor(ScreenWidth/2)
End If
If ULTileY - Floor(ScreenHeight/2) > 0 Then
ULY = 0'truncate, tile begins above the screen
Else
ULY = Floor(ScreenHeight/2) - ULTileY
End If
If ULTileX + TileWidth + Floor(ScreenWidth/2) > ScreenWidth Then
URX = ScreenWidth'truncate, tile extends past the right of the screen
Log("used ScreenWidth: " & ScreenWidth)
Else
URX = ULTileX + TileWidth + Floor(ScreenWidth/2)
Log("all visible: " & URX)
End If
If ULTileY - Floor(ScreenHeight/2) > 0 Then
URY = 0'truncate, tile begins above the screen
Else
URY = Floor(ScreenHeight/2) - ULTileY
End If
If ULTileX + Floor(ScreenWidth/2) < 0 Then
LLX = 0 'truncate, tile begins to the left of the screen
Else
LLX = ULTileX + Floor(ScreenWidth/2)
End If
If ULTileY - TileWidth < -Floor(ScreenHeight/2) Then
LLY = ScreenHeight'truncate, tile lies partially below screen
Else
LLY = Floor(ScreenHeight/2) - ULTileY + TileWidth
End If
If ULTileX + TileWidth + Floor(ScreenWidth/2) > ScreenWidth Then
LRX = ScreenWidth'truncate, tile extends past the right of the screen
Else
LRX = ULTileX + TileWidth + Floor(ScreenWidth/2)
End If
If ULTileY - TileWidth < -1*(Floor(ScreenHeight/2)) Then
LRY = ScreenHeight'truncate, tile lies partially below screen
Else
LRY = Floor(ScreenHeight/2) - ULTileY + TileWidth
End If
Log(ULX & " " & ULY & " " & URX & " " & URY & " " & LLX & " " & LLY & " " & LRX & " " & LRY)
'coordinates are defined above, draw the tile
Private Tile As Rect
Tile.Initialize(ULX,ULY,LRX,LRY)
cvsScreen.DrawRect(Tile,Colors.White,True,0)
'now draw any required boundary lines on this tile
'need to test whether these boundaries are supposed to be visible
If MazeMap(i,j).Get(0) Then
'North wall is True
Private North As Rect
North.Initialize(ULX,ULY,URX,URY-1)
cvsScreen.DrawRect(North,Colors.Black,True,0)
End If
If MazeMap(i,j).Get(1) Then
'East wall is True
Private East As Rect
East.Initialize(URX-1,URY,LRX,LRY)
cvsScreen.DrawRect(East,Colors.Black,True,0)
End If
If MazeMap(i,j).Get(2) Then
'South wall is True
Private South As Rect
South.Initialize(LLX,LLY+1,LRX,LRY)
cvsScreen.DrawRect(South,Colors.Black,True,0)
End If
If MazeMap(i,j).Get(3) Then
'West wall is True
Private West As Rect
West.Initialize(ULX,ULY,ULX+1,LLY)
cvsScreen.DrawRect(West,Colors.Black,True,0)
End If
End If
End If
Next
Next
pnlScreen.Invalidate
End Sub